Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"build:plugin": "bun build src/source/v1.js --outfile=src/index.js --target=bun --format=esm --external=@opencode-ai/plugin/tool",
"build:plugin:npm": "npm run build:plugin",
"prepack": "node --check src/index.js",
"check": "node --check src/source/v1.js && node --check src/source/core/args.js && node --check src/source/core/state.js && node --check src/source/core/jobs.js && node --check src/source/core/process.js && node --check src/source/opencode/sdk.js && node --check src/source/opencode/session-context.js && node --check src/source/opencode/command-router.js && node --check src/source/opencode/goal-commands.js && node --check src/source/opencode/loop-commands.js && node --check src/source/opencode/loop-registration.js && node --check src/source/runtime/session-activity.js && node --check src/source/runtime/session-status.js && node --check src/source/runtime/compaction.js && node --check src/source/runtime/action-dispatch.js && node --check src/source/runtime/run-finalization.js && node --check src/source/runtime/run-admission.js && node --check src/source/runtime/executor.js && node --check src/source/runtime/scheduler.js && node --check src/source/runtime/goal-prompt.js && node --check src/source/runtime/goal-report.js && node --check src/source/runtime/goal-runtime.js && node --check src/source/runtime/goal-policy.js && node --check src/source/runtime/goal-steering.js && node --check src/source/runtime/job-workspace.js && node --check src/source/opencode2/prompt-runtime.js && node --check src/source/opencode2/diagnostics.js && node --check src/source/opencode2/logging.js && node --check src/source/legacy-v1.js && node --check src/index.js && node --check scripts/install-node.mjs && node --check scripts/install-with-goals.mjs && node --check scripts/loopd.mjs && node --check scripts/install-test.mjs && node --check scripts/goal-companion-test.mjs && node --check scripts/loopd-test.mjs && node --check scripts/smoke-test.mjs && node --check scripts/host-adapter-contract-test.mjs && node --check scripts/command-router-test.mjs && node --check scripts/goal-command-handlers-test.mjs && node --check scripts/loop-command-handlers-test.mjs && node --check scripts/loop-registration-test.mjs && node --check scripts/session-activity-test.mjs && node --check scripts/session-status-test.mjs && node --check scripts/compaction-runtime-test.mjs && node --check scripts/executor-runtime-test.mjs && node --check scripts/scheduler-runtime-test.mjs && node --check scripts/goal-runtime-test.mjs && node --check scripts/goal-policy-test.mjs && node --check scripts/goal-steering-test.mjs && node --check scripts/job-workspace-test.mjs && node --check scripts/v2-prompt-runtime-test.mjs && node --check scripts/v2-prompt-interval-test.mjs && node --check scripts/v2-command-runtime-test.mjs && node --check scripts/v2-command-adapter-test.mjs && node --check scripts/v2-diagnostics-test.mjs && node --check scripts/v2-logging-test.mjs && node --check scripts/comprehensive-watchdog.mjs && node --check scripts/comprehensive-test.mjs && node --check scripts/host-loop-canary.mjs && node --check scripts/host-goal-steering-canary.mjs && node --check scripts/publish-workflow-test.mjs",
"check": "node --check src/source/v1.js && node --check src/source/core/args.js && node --check src/source/core/state.js && node --check src/source/core/jobs.js && node --check src/source/core/process.js && node --check src/source/opencode/sdk.js && node --check src/source/opencode/session-context.js && node --check src/source/opencode/command-router.js && node --check src/source/opencode/goal-commands.js && node --check src/source/opencode/loop-commands.js && node --check src/source/opencode/loop-registration.js && node --check src/source/runtime/session-activity.js && node --check src/source/runtime/session-status.js && node --check src/source/runtime/compaction.js && node --check src/source/runtime/action-dispatch.js && node --check src/source/runtime/run-finalization.js && node --check src/source/runtime/run-admission.js && node --check src/source/runtime/executor.js && node --check src/source/runtime/scheduler.js && node --check src/source/runtime/goal-prompt.js && node --check src/source/runtime/goal-report.js && node --check src/source/runtime/goal-evidence.js && node --check src/source/runtime/goal-runtime.js && node --check src/source/runtime/goal-policy.js && node --check src/source/runtime/goal-steering.js && node --check src/source/runtime/job-workspace.js && node --check src/source/opencode2/prompt-runtime.js && node --check src/source/opencode2/diagnostics.js && node --check src/source/opencode2/logging.js && node --check src/source/legacy-v1.js && node --check src/index.js && node --check scripts/install-node.mjs && node --check scripts/install-with-goals.mjs && node --check scripts/loopd.mjs && node --check scripts/install-test.mjs && node --check scripts/goal-companion-test.mjs && node --check scripts/loopd-test.mjs && node --check scripts/smoke-test.mjs && node --check scripts/host-adapter-contract-test.mjs && node --check scripts/command-router-test.mjs && node --check scripts/goal-command-handlers-test.mjs && node --check scripts/loop-command-handlers-test.mjs && node --check scripts/loop-registration-test.mjs && node --check scripts/session-activity-test.mjs && node --check scripts/session-status-test.mjs && node --check scripts/compaction-runtime-test.mjs && node --check scripts/executor-runtime-test.mjs && node --check scripts/scheduler-runtime-test.mjs && node --check scripts/goal-runtime-test.mjs && node --check scripts/goal-policy-test.mjs && node --check scripts/goal-steering-test.mjs && node --check scripts/job-workspace-test.mjs && node --check scripts/v2-prompt-runtime-test.mjs && node --check scripts/v2-prompt-interval-test.mjs && node --check scripts/v2-command-runtime-test.mjs && node --check scripts/v2-command-adapter-test.mjs && node --check scripts/v2-diagnostics-test.mjs && node --check scripts/v2-logging-test.mjs && node --check scripts/comprehensive-watchdog.mjs && node --check scripts/comprehensive-test.mjs && node --check scripts/host-loop-canary.mjs && node --check scripts/host-goal-steering-canary.mjs && node --check scripts/publish-workflow-test.mjs",
"test": "node scripts/publish-workflow-test.mjs && node scripts/command-router-test.mjs && node scripts/goal-command-handlers-test.mjs && node scripts/loop-command-handlers-test.mjs && node scripts/loop-registration-test.mjs && node scripts/session-activity-test.mjs && node scripts/session-status-test.mjs && node scripts/compaction-runtime-test.mjs && node scripts/action-dispatch-test.mjs && node scripts/run-finalization-test.mjs && node scripts/run-admission-test.mjs && node scripts/executor-runtime-test.mjs && node scripts/scheduler-runtime-test.mjs && node scripts/goal-runtime-test.mjs && node scripts/goal-policy-test.mjs && node scripts/goal-steering-test.mjs && node scripts/job-workspace-test.mjs && node scripts/v2-prompt-runtime-test.mjs && node scripts/v2-prompt-interval-test.mjs && node scripts/v2-command-runtime-test.mjs && node scripts/v2-command-adapter-test.mjs && node scripts/v2-diagnostics-test.mjs && node scripts/v2-logging-test.mjs && node scripts/install-test.mjs && node scripts/goal-companion-test.mjs && node scripts/loopd-test.mjs && node scripts/smoke-test.mjs && node scripts/host-adapter-contract-test.mjs && node scripts/comprehensive-watchdog.mjs",
"canary:host": "node scripts/host-loop-canary.mjs && node scripts/host-goal-steering-canary.mjs",
"install:global": "node scripts/install-with-goals.mjs",
Expand Down
39 changes: 39 additions & 0 deletions src/source/runtime/goal-evidence.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export function hasConcreteGoalEvidence(value) {
const text = String(value || "").trim()
if (text.length < 24) return false
const normalized = text.toLowerCase().replace(/\s+/g, " ")
const weak = new Set(["done", "complete", "completed", "ok", "looks good", "n/a", "none", "no evidence", "no evidence provided", "goal completed", "marked complete"])
if (weak.has(normalized) || normalized.startsWith("marked complete by /loop-goal-done")) return false
return /(\b(npm|pnpm|yarn|bun|node|pytest|cargo|dotnet|go test|tsc|typecheck|test|tests|lint|build|check|checks|exit\s*\d|passed|verified|changed|updated|created|fixed|file|files|diff|commit)\b|[`\\/][\w./:-]+)/i.test(text) || text.length >= 80
}

export function goalChecksPassed(job) {
return Array.isArray(job?.lastGoalChecks) && job.lastGoalChecks.length > 0 && job.lastGoalChecks.every((item) => Number(item?.code) === 0)
}

export function goalRequiresPassingChecks(job) {
return job?.goalRequireChecksPass !== false && Array.isArray(job?.goalChecks) && job.goalChecks.length > 0
}

export function goalProgressSnapshot(job) {
return {
status: job?.goalStatus || "",
progressCount: Array.isArray(job?.goalProgress) ? job.goalProgress.length : 0,
evidence: String(job?.goalEvidence || ""),
checksPassedAt: Number(job?.goalChecksPassedAt || 0),
lastGoalCheckAt: Number(job?.lastGoalCheckAt || 0),
lastVerifyAt: Number(job?.lastVerifyAt || 0),
lastVerifyCode: Number.isFinite(Number(job?.lastVerifyCode)) ? Number(job.lastVerifyCode) : undefined,
}
}

export function goalMadeMeaningfulProgress(beforeJob, afterJob) {
const before = goalProgressSnapshot(beforeJob || {})
const after = goalProgressSnapshot(afterJob || {})
if (["completed", "blocked"].includes(after.status) && after.status !== before.status) return true
if (after.progressCount > before.progressCount) return true
if (after.evidence !== before.evidence && hasConcreteGoalEvidence(after.evidence)) return true
if (after.checksPassedAt > before.checksPassedAt || goalChecksPassed(afterJob) && after.lastGoalCheckAt > before.lastGoalCheckAt) return true
if (after.lastVerifyAt > before.lastVerifyAt && after.lastVerifyCode === 0) return true
return false
}
2 changes: 1 addition & 1 deletion src/source/runtime/goal-policy.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DEFAULT_GOAL_MAX_NO_PROGRESS, now as defaultNow } from "../core/args.js"
import { isGoalJob } from "../core/jobs.js"
import { appendLoopLog as defaultAppendLoopLog } from "../core/process.js"
import { goalMadeMeaningfulProgress } from "./goal-runtime.js"
import { goalMadeMeaningfulProgress } from "./goal-evidence.js"

function requireFunction(value, name) {
if (typeof value !== "function") throw new TypeError(`createGoalExecutionPolicy requires ${name}`)
Expand Down
42 changes: 2 additions & 40 deletions src/source/runtime/goal-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { matchJob, isGoalJob } from "../core/jobs.js"
import { readState, writeState } from "../core/state.js"
import { appendLoopLog } from "../core/process.js"
import { writeGoalReport } from "./goal-report.js"
import { hasConcreteGoalEvidence, goalChecksPassed, goalRequiresPassingChecks } from "./goal-evidence.js"

export { buildGoalPrompt } from "./goal-prompt.js"
export { goalReportPath, goalReportText, writeGoalReport } from "./goal-report.js"
export { hasConcreteGoalEvidence, goalChecksPassed, goalRequiresPassingChecks, goalProgressSnapshot, goalMadeMeaningfulProgress } from "./goal-evidence.js"

export function pickGoalJob(state, target = "") {
const goals = (state.jobs || []).filter(isGoalJob)
Expand All @@ -21,46 +23,6 @@ export function parseGoalToolText(args, fields) {
return result
}

export function hasConcreteGoalEvidence(value) {
const text = String(value || "").trim()
if (text.length < 24) return false
const normalized = text.toLowerCase().replace(/\s+/g, " ")
const weak = new Set(["done", "complete", "completed", "ok", "looks good", "n/a", "none", "no evidence", "no evidence provided", "goal completed", "marked complete"])
if (weak.has(normalized) || normalized.startsWith("marked complete by /loop-goal-done")) return false
return /(\b(npm|pnpm|yarn|bun|node|pytest|cargo|dotnet|go test|tsc|typecheck|test|tests|lint|build|check|checks|exit\s*\d|passed|verified|changed|updated|created|fixed|file|files|diff|commit)\b|[`\\/][\w./:-]+)/i.test(text) || text.length >= 80
}

export function goalChecksPassed(job) {
return Array.isArray(job?.lastGoalChecks) && job.lastGoalChecks.length > 0 && job.lastGoalChecks.every((item) => Number(item?.code) === 0)
}

export function goalRequiresPassingChecks(job) {
return job?.goalRequireChecksPass !== false && Array.isArray(job?.goalChecks) && job.goalChecks.length > 0
}

export function goalProgressSnapshot(job) {
return {
status: job?.goalStatus || "",
progressCount: Array.isArray(job?.goalProgress) ? job.goalProgress.length : 0,
evidence: String(job?.goalEvidence || ""),
checksPassedAt: Number(job?.goalChecksPassedAt || 0),
lastGoalCheckAt: Number(job?.lastGoalCheckAt || 0),
lastVerifyAt: Number(job?.lastVerifyAt || 0),
lastVerifyCode: Number.isFinite(Number(job?.lastVerifyCode)) ? Number(job.lastVerifyCode) : undefined,
}
}

export function goalMadeMeaningfulProgress(beforeJob, afterJob) {
const before = goalProgressSnapshot(beforeJob || {})
const after = goalProgressSnapshot(afterJob || {})
if (["completed", "blocked"].includes(after.status) && after.status !== before.status) return true
if (after.progressCount > before.progressCount) return true
if (after.evidence !== before.evidence && hasConcreteGoalEvidence(after.evidence)) return true
if (after.checksPassedAt > before.checksPassedAt || goalChecksPassed(afterJob) && after.lastGoalCheckAt > before.lastGoalCheckAt) return true
if (after.lastVerifyAt > before.lastVerifyAt && after.lastVerifyCode === 0) return true
return false
}

export async function rejectGoalCompletion(directory, sessionID, state, job, reason) {
job.goalCompletionRejectedAt = now()
job.goalCompletionRejectedReason = reason
Expand Down