@kylecheng3146/agent-ops 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/packages/cli/src/args.js +8 -43
  2. package/dist/packages/cli/src/bin.js +28 -29
  3. package/dist/packages/cli/src/cli.js +12 -14
  4. package/dist/packages/cli/src/codex-loop-process.js +0 -6
  5. package/dist/packages/cli/src/commands/review.js +5 -10
  6. package/dist/packages/cli/src/commands/run.js +3 -1
  7. package/dist/packages/cli/src/commands/task.js +6 -50
  8. package/dist/packages/cli/src/commands/verify.js +7 -3
  9. package/dist/packages/cli/src/default-base.js +26 -0
  10. package/dist/packages/cli/src/hook-process.js +19 -83
  11. package/dist/packages/cli/src/run-entry.js +66 -21
  12. package/dist/runtime/src/adapters/agy/config.js +2 -3
  13. package/dist/runtime/src/adapters/claude/config.js +2 -2
  14. package/dist/runtime/src/adapters/codex/config.js +2 -5
  15. package/dist/runtime/src/hooks/completion-gate.js +0 -15
  16. package/dist/runtime/src/install/harness.js +3 -3
  17. package/dist/runtime/src/install/plan.js +3 -3
  18. package/dist/runtime/src/install/profiles.js +0 -2
  19. package/dist/runtime/src/office/snapshot.js +4 -1
  20. package/dist/runtime/src/parallel/manage.js +0 -3
  21. package/dist/runtime/src/review/execute.js +68 -4
  22. package/dist/runtime/src/run/verification.js +2 -2
  23. package/dist/runtime/src/schema/validate.js +10 -5
  24. package/dist/runtime/src/task/completion.js +4 -42
  25. package/dist/runtime/src/task/contract.js +11 -0
  26. package/dist/runtime/src/task/service.js +34 -57
  27. package/dist/runtime/src/task/store.js +3 -7
  28. package/dist/runtime/src/verify/acceptance-coverage.js +2 -3
  29. package/dist/runtime/src/verify/acceptance-protocol.js +4 -3
  30. package/dist/runtime/src/verify/acceptance-replay.js +4 -7
  31. package/dist/runtime/src/verify/service.js +6 -23
  32. package/dist/runtime/src/verify/source-fingerprint.js +17 -4
  33. package/docs/en/guides/task-debug.md +39 -133
  34. package/docs/en/spec/acceptance-and-evidence.md +31 -23
  35. package/docs/en/spec/harness-adapters.md +9 -11
  36. package/docs/en/spec/loop-engineering.md +11 -5
  37. package/docs/zh-TW/guides/task-debug.md +27 -73
  38. package/docs/zh-TW/spec/acceptance-and-evidence.md +18 -18
  39. package/docs/zh-TW/spec/harness-adapters.md +7 -8
  40. package/docs/zh-TW/spec/loop-engineering.md +9 -4
  41. package/package.json +1 -1
  42. package/schemas/config.schema.json +1 -21
  43. package/dist/packages/cli/src/debug-intake.js +0 -245
  44. package/dist/runtime/src/task/debug-intake.js +0 -15
  45. package/dist/runtime/src/task/debug-state.js +0 -135
  46. package/dist/runtime/src/task/debug-user-event.js +0 -160
  47. package/dist/runtime/src/task/debug.js +0 -632
@@ -66,6 +66,10 @@ function parseHookTarget(option, value) {
66
66
  export function parseArgs(argv) {
67
67
  if (argv[0] === "run")
68
68
  return parseRunArgs(argv.slice(1));
69
+ if (argv[0] === "task" && argv[1] === "debug")
70
+ throw new CliArgumentError("TASK_DEBUG_REMOVED", "task debug was removed in 0.11.0. A DEBUG task is task create --debug with a behavioral " +
71
+ "criterion whose redCheckIds fail at the baseline and pass on the candidate; then verify, review and finish as usual. " +
72
+ "Declare an escape origin, if known, with task revise --escape-of.");
69
73
  let command;
70
74
  let action;
71
75
  let scope;
@@ -78,10 +82,6 @@ export function parseArgs(argv) {
78
82
  let intent;
79
83
  let taskGoal;
80
84
  let debug = false;
81
- let debugAction;
82
- let payloadFile;
83
- let commandId;
84
- let eventId;
85
85
  let sessionId;
86
86
  let base;
87
87
  let parentBase;
@@ -233,30 +233,6 @@ export function parseArgs(argv) {
233
233
  duplicate(token);
234
234
  debug = true;
235
235
  break;
236
- case "--debug-action": {
237
- if (debugAction !== undefined)
238
- duplicate(token);
239
- const value = readOptionValue(argv, index++, token);
240
- if (!["status", "hypothesis", "strategy", "investigate", "checkpoint", "resume", "extend", "origin"].includes(value))
241
- invalidValue(token, value);
242
- debugAction = value;
243
- break;
244
- }
245
- case "--payload-file":
246
- if (payloadFile !== undefined)
247
- duplicate(token);
248
- payloadFile = readOptionValue(argv, index++, token);
249
- break;
250
- case "--command-id":
251
- if (commandId !== undefined)
252
- duplicate(token);
253
- commandId = readOptionValue(argv, index++, token);
254
- break;
255
- case "--event-id":
256
- if (eventId !== undefined)
257
- duplicate(token);
258
- eventId = readOptionValue(argv, index++, token);
259
- break;
260
236
  case "--tree": {
261
237
  if (tree)
262
238
  duplicate(token);
@@ -530,7 +506,7 @@ export function parseArgs(argv) {
530
506
  "complete",
531
507
  "create",
532
508
  "export",
533
- "status", "revise", "replan", "pin-finding", "debug"
509
+ "status", "revise", "replan", "pin-finding"
534
510
  ].includes(token)) {
535
511
  action = token;
536
512
  break;
@@ -674,8 +650,8 @@ export function parseArgs(argv) {
674
650
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--target-version may be used only with update.");
675
651
  }
676
652
  if (base !== undefined && command !== "verify" && command !== "review" &&
677
- command !== "batch" && !(command === "task" && (action === "complete" || action === "debug"))) {
678
- throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--base may be used only with verify, review, batch, task complete or task debug.");
653
+ command !== "batch" && !(command === "task" && action === "complete")) {
654
+ throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--base may be used only with verify, review, batch or task complete.");
679
655
  }
680
656
  if ((parentBase !== undefined || width !== undefined) && command !== "batch") {
681
657
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", `${parentBase !== undefined ? "--parent-base" : "--width"} may be used only with batch.`);
@@ -714,13 +690,6 @@ export function parseArgs(argv) {
714
690
  const revisionAction = command === "task" && ["revise", "replan", "pin-finding"].includes(action ?? "");
715
691
  if ((taskGoal !== undefined || debug) && !(command === "task" && action === "create"))
716
692
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--goal and --debug apply only to task create.");
717
- if ((debugAction !== undefined || commandId !== undefined || eventId !== undefined || payloadFile !== undefined) && !(command === "task" && action === "debug"))
718
- throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Debug operation options apply only to task debug.");
719
- if (command === "task" && action === "debug" && (taskId === undefined || debugAction === undefined || title !== undefined || intent !== undefined ||
720
- parentTaskId !== undefined || criteria.length > 0 || evidence.length > 0 ||
721
- ((debugAction === "hypothesis" || debugAction === "strategy" || debugAction === "origin") !== (payloadFile !== undefined)) ||
722
- ((debugAction === "investigate") !== (commandId !== undefined)) || ((debugAction === "extend") !== (eventId !== undefined))))
723
- throw new CliArgumentError("CLI_MISSING_VALUE", "Task debug requires --task, --debug-action and the operation's exact payload/command/event options.");
724
693
  if ((criterionFiles.length > 0 && !(command === "task" && (action === "create" || action === "pin-finding"))) ||
725
694
  (criteriaFile !== undefined && !(command === "task" && action === "revise")) ||
726
695
  (surfaceFile !== undefined && !(command === "task" && (action === "create" || action === "revise"))) ||
@@ -739,7 +708,7 @@ export function parseArgs(argv) {
739
708
  if (command === "escapes" && argv.some(token => token !== "escapes" && token !== "--json"))
740
709
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "escapes accepts only --json; it is a read-only report.");
741
710
  if (revisionAction && (taskId === undefined || evidence.length > 0 || title !== undefined || intent !== undefined || parentTaskId !== undefined || sessionId !== undefined || criteria.length > 0 || base !== undefined ||
742
- (action === "revise" && (expectedContract === undefined || (criteriaFile === undefined && surfaceFile === undefined && escapeOf === undefined) || reason === undefined)) ||
711
+ (action === "revise" && (expectedContract === undefined || (criteriaFile === undefined && surfaceFile === undefined && escapeOf === undefined && baseline === undefined) || reason === undefined)) ||
743
712
  (action === "replan" && (expectedTreeContract === undefined || planFile === undefined || reason === undefined)) ||
744
713
  (action === "pin-finding" && (expectedContract === undefined || findingReference === undefined || criterionFiles.length !== 1))))
745
714
  throw new CliArgumentError("CLI_MISSING_VALUE", "Acceptance revision requires task, expected contract, payload and reason.");
@@ -902,10 +871,6 @@ export function parseArgs(argv) {
902
871
  ...(intent === undefined ? {} : { intent }),
903
872
  ...(taskGoal === undefined ? {} : { goal: taskGoal }),
904
873
  ...(debug ? { debug: true } : {}),
905
- ...(debugAction === undefined ? {} : { debugAction }),
906
- ...(payloadFile === undefined ? {} : { payloadFile }),
907
- ...(commandId === undefined ? {} : { commandId }),
908
- ...(eventId === undefined ? {} : { eventId }),
909
874
  ...(reviewTargets.length === 0 ? {} : { reviewTargets }),
910
875
  ...(completionGate === undefined ? {} : { completionGate }),
911
876
  ...(office === undefined ? {} : { office }),
@@ -47,9 +47,11 @@ import { memoizePreflight } from "../../../runtime/src/review/batch.js";
47
47
  import { createReviewExecutor, ReviewInterruptedError } from "../../../runtime/src/review/execute.js";
48
48
  import { probeReviewTargetDetailed } from "../../../runtime/src/review/probe.js";
49
49
  import { resolveReviewScope } from "../../../runtime/src/review/scope.js";
50
+ import { runSurfaceGate } from "../../../runtime/src/surface/discovery.js";
50
51
  import { resolveReviewRole } from "../../../runtime/src/review/roles.js";
51
52
  import { runTrustCommand } from "./commands/trust.js";
52
53
  import { runVerifyCommand } from "./commands/verify.js";
54
+ import { defaultBase } from "./default-base.js";
53
55
  import { runAdvanceCommand } from "./commands/advance.js";
54
56
  import { runReviewShowCommand } from "./commands/review-show.js";
55
57
  import { runEscapesCommand } from "./commands/escapes.js";
@@ -255,6 +257,16 @@ else {
255
257
  const root = args.scope === "user"
256
258
  ? process.env.AGENT_OPS_HOME ?? homedir()
257
259
  : process.cwd();
260
+ // Committed work is judged as a range by default; see defaultBase.
261
+ if ((args.command === "verify" || (args.command === "review" && args.action !== "show")) && args.base === undefined && args.scope !== "user") {
262
+ const taskId = args.taskId;
263
+ const base = await defaultBase(root, gitRunner(root), async () => taskId === undefined ? undefined :
264
+ (await new TaskService(new FileTaskStore(join(root, ".agent-ops", "tasks", "state.json"), root)).status({ taskId })).createdSourceCommit)
265
+ // Only a default: any failure leaves the command's own scope handling and errors in charge.
266
+ .catch(() => undefined);
267
+ if (base !== undefined)
268
+ args = { ...args, base };
269
+ }
258
270
  const isTTY = !args.json &&
259
271
  process.stdin.isTTY === true &&
260
272
  process.stdout.isTTY === true;
@@ -495,7 +507,6 @@ else {
495
507
  return await runTaskCommand({
496
508
  args,
497
509
  service: taskService,
498
- ...(args.action !== "debug" ? {} : { trusted: await (async () => { const binding = await plannedTrustBinding(root, (await loadEffectiveConfig(root, "project")).config); return binding !== null && (await trustStore().status(binding)).status === "TRUSTED"; })() }),
499
510
  ...(policyConfigHash === undefined ? {} : { policyConfigHash }),
500
511
  ...(sessionId === undefined ? {} : { sessionId }),
501
512
  ...(fromMain
@@ -553,41 +564,16 @@ else {
553
564
  reviewActivity = undefined;
554
565
  }
555
566
  });
556
- const debugRecord = reviewArgs.taskId === undefined ? undefined : await taskService.status({ taskId: reviewArgs.taskId });
557
- const inDebug = (executor) => debugRecord?.task.debug === undefined ? executor : async (request) => await taskService.debugService().review(debugRecord.task.id, () => executor(request));
558
567
  const inSlot = (executor) => slotDir === undefined
559
- ? inDebug(executor)
568
+ ? executor
560
569
  : async (request) => await withReviewSlot({
561
570
  dir: slotDir,
562
571
  signal,
563
572
  holder: { root, ...(reviewArgs.taskId === undefined ? {} : { taskId: reviewArgs.taskId }) },
564
- ...(debugRecord?.task.debug === undefined || reviewSessionId === undefined
565
- ? {}
566
- : (() => {
567
- let paused = false;
568
- const debug = taskService.debugService();
569
- return {
570
- onWaitStart: async () => {
571
- if (paused)
572
- return;
573
- const current = await debug.root(debugRecord.task.id).catch(() => null);
574
- if (current?.debug.foreground?.sessionId !== reviewSessionId)
575
- return;
576
- await debug.foregroundStop(debugRecord.task.id, reviewSessionId);
577
- paused = true;
578
- },
579
- onWaitEnd: async () => {
580
- if (!paused)
581
- return;
582
- await debug.foregroundStart(debugRecord.task.id, reviewSessionId);
583
- paused = false;
584
- }
585
- };
586
- })()),
587
573
  onWait: (line) => {
588
574
  process.stderr.write(`${progressPrefix}${line}\n`);
589
575
  }
590
- }, async () => await inDebug(executor)(request));
576
+ }, async () => await executor(request));
591
577
  return {
592
578
  args: reviewArgs,
593
579
  authorized: reviewArgs.yes,
@@ -607,7 +593,6 @@ else {
607
593
  config: reviewConfig,
608
594
  evidenceStore: new FileEvidenceStore(root, root),
609
595
  execute: inActivity(inSlot(createReviewExecutor({
610
- ...(debugRecord?.task.debug === undefined ? {} : { remainingBudgetMs: () => taskService.debugService().remaining(debugRecord.task.id) }),
611
596
  runner: registeredRunProofRunner(),
612
597
  targets: configuredReviewTargets,
613
598
  cwd: root,
@@ -749,6 +734,20 @@ else {
749
734
  return await runVerifyCommand({
750
735
  args,
751
736
  taskService,
737
+ surface: async (taskId) => {
738
+ const record = await taskService.status({ taskId });
739
+ // A subtask's surface is its parent's; review checks the tree, so verify stays quiet here.
740
+ if (record.task.parentTaskId !== undefined)
741
+ return [];
742
+ const scope = await resolveReviewScope({ root, runner: gitRunner(root), ...(args.base === undefined ? {} : { base: args.base }) });
743
+ const gate = await runSurfaceGate({ root, taskId, surface: record.task.surface, required: record.task.surfaceRequired === true,
744
+ ...(scope.mode === "base" ? { base: scope.resolvedBase } : {}), changedFiles: scope.changedFiles, configHash: calculateConfigHash(config) });
745
+ if (gate.status === "PASS")
746
+ return ["Surface: PASS"];
747
+ const paths = gate.details.flatMap(d => typeof d === "object" && d !== null && "path" in d ? [String(d.path)] : []);
748
+ return [`Surface (advisory; review would stop with ${gate.code}): ${gate.message}`,
749
+ ...paths.slice(0, 20).map(path => ` - ${path}`), ...(paths.length > 20 ? [` ... ${paths.length - 20} more`] : [])];
750
+ },
752
751
  activity: { commonDir: await officeCommonDir(root), root, ...(verifySessionId === undefined ? {} : { sessionId: verifySessionId }) },
753
752
  service: { verify: async (taskId) => {
754
753
  const run = await runPolicyContext(root);
@@ -25,7 +25,7 @@ Commands:
25
25
  doctor Diagnose an installation
26
26
  update Update managed artifacts
27
27
  uninstall Remove managed artifacts
28
- task <create|status|attach|revise|replan|pin-finding|debug|advance|complete|archive|export>
28
+ task <create|status|attach|revise|replan|pin-finding|advance|complete|archive|export>
29
29
  Manage independent task acceptance state
30
30
  verify Run configured verification
31
31
  review Run an independent review or show recorded reports
@@ -105,7 +105,7 @@ The URL carries an access token; do not share it.
105
105
  agent-ops run respond <run-id> --question-id <id> <--answer <text>|--answer-file <path>> [--json]
106
106
 
107
107
  New runs have no total time cap. Manual resume upgrades legacy finite runs and keeps accounting.
108
- Command/review timeouts, provider limits, DEBUG budgets and stop controls remain effective.
108
+ Command/review timeouts, provider limits and stop controls remain effective.
109
109
  Native goal state is observational. Completion requires current verification,
110
110
  two fresh reviews, task completion and a target-bound integration receipt.
111
111
  --retry-approval explicitly retries an intact Codex approval-review usage-limit
@@ -188,7 +188,7 @@ Options:
188
188
  --json
189
189
  --yes
190
190
  `,
191
- task: `Usage: agent-ops task <create|status|attach|revise|replan|pin-finding|debug|advance|complete|archive|export> [options]
191
+ task: `Usage: agent-ops task <create|status|attach|revise|replan|pin-finding|advance|complete|archive|export> [options]
192
192
 
193
193
  Manage independent task acceptance state. Task commands accept none of
194
194
  --harness, --profile or --dry-run. Only advance accepts --yes.
@@ -196,16 +196,12 @@ Manage independent task acceptance state. Task commands accept none of
196
196
  Options:
197
197
  --title <text> create
198
198
  --goal <text> create: immutable original issue/goal
199
- --debug create: task-level investigation tag
200
- --debug-action <status|hypothesis|strategy|investigate|checkpoint|resume|extend|origin>
201
- --payload-file <path> debug hypothesis/strategy/origin: bounded JSON
202
- --command-id <id> debug investigate: trusted configured command
203
- --event-id <native-event-id|latest> debug extend: authentic human continue event
199
+ --debug create: bug fix; needs --goal and a behavioral criterion with redCheckIds
204
200
  --intent <text> create: intended behavior and constraints before editing
205
201
  --criterion <json> create, repeatable, two to five total
206
202
  --criterion-file <path> create: one criterion object, repeatable; pin-finding: exactly one
207
203
  --criteria-file <path> revise: replacement criterion array
208
- --surface-file <path> create (required unless --debug), revise: EVIDENCE-SURFACE-001
204
+ --surface-file <path> create (required), revise: EVIDENCE-SURFACE-001
209
205
  audit JSON, or {"covers":[surface-id]} under a surfaced parent
210
206
  --escape-of <commit> create, revise: the commit this bug escaped from (Escape Loop)
211
207
  --escape-label <label> with --escape-of: requirement|surface|scenario|criteria|verify|review|flagged|out-of-model
@@ -216,12 +212,12 @@ Options:
216
212
  --expected-contract <hash> revise, pin-finding: current task contract from status
217
213
  --expected-tree-contract <hash> replan: current tree contract from status
218
214
  --reason <text> required for revise/replan; optional for pin-finding
219
- --baseline <git-ref> create/revise: baseline for new mechanical requirements
215
+ --baseline <git-ref> create: baseline for typed criteria; revise: move every typed baseline
220
216
  --parent <task-id> create: record a subtask; status: list subtasks
221
217
  --task <id> status, attach, revise, replan, pin-finding, complete, archive, export, advance
222
- --session <id> create, attach, status, advance, debug
218
+ --session <id> create, attach, status, advance
223
219
  --evidence <criterion-id=reference> complete, repeatable
224
- --base <git-ref> complete, debug: a clean committed range
220
+ --base <git-ref> complete: a clean committed range
225
221
  --yes advance: authorize the two review sessions
226
222
  --json
227
223
 
@@ -233,7 +229,8 @@ Each legacy criterion needs at least one verifierIds entry naming a verification
233
229
  command id configured in .agent-ops/config.json. Typed criteria additionally
234
230
  accept an acceptance object: mode (behavioral, invariant or review-only),
235
231
  bindings and optional baselineCommit/reviewOnlyReason. Mechanical bindings name
236
- configured acceptance runner IDs, checkIds, committed materials and optional
232
+ configured acceptance runner IDs, checkIds (the checks this criterion needs;
233
+ other runner output is ignored), committed materials and optional
237
234
  redCheckIds; tasks cannot supply commands. Creation freezes the baseline.
238
235
  Review-only example (mandatory repository policy commands still run):
239
236
 
@@ -241,7 +238,8 @@ Review-only example (mandatory repository policy commands still run):
241
238
 
242
239
  Pin a saved finding with task pin-finding --task <id> <report-digest:index>
243
240
  --expected-contract <hash> --criterion-file <path>. Revise/replan preserve the
244
- original goal and immutable existing baselines; obtain new verify and review.
241
+ original goal and existing baselines unless revise --baseline moves them all.
242
+ A revise that keeps the criteria keeps verification; every revise needs review.
245
243
  `,
246
244
  verify: `Usage: agent-ops verify [options]
247
245
 
@@ -1,6 +1,5 @@
1
1
  import { PROJECT_LOOP_EVENTS, resolveProjectLoopRoot, runProjectLoop } from "../../../runtime/src/hooks/codex-loop.js";
2
2
  import { officeEnabled, officeLoopPhaseHint } from "./office-entry.js";
3
- import { routeDebugPrompt } from "./debug-intake.js";
4
3
  const MAX_LOOP_INPUT_BYTES = 64 * 1024;
5
4
  function isLoopHarness(value) {
6
5
  return value === "claude" || value === "codex";
@@ -74,11 +73,6 @@ export async function runLoopProcess(argv, io, dependencies = {}) {
74
73
  ? {}
75
74
  : { telemetryMaxBytes: dependencies.telemetryMaxBytes })
76
75
  });
77
- if (event === "UserPromptSubmit" && managedRoot !== null && result.exitCode === 0 && result.stdout === "" && result.stderr === "") {
78
- const context = await routeDebugPrompt(managedRoot, input, harness).catch(error => `DEBUG intake unavailable: ${error instanceof Error ? error.message : "preserve current state"}`);
79
- if (context !== null)
80
- io.writeStdout(harness === "claude" ? JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: context } }) : context + "\n");
81
- }
82
76
  if (result.stdout.length > 0) {
83
77
  io.writeStdout(result.stdout);
84
78
  }
@@ -4,7 +4,7 @@ import { renderReviewResult } from "../../../../runtime/src/review/render.js";
4
4
  import { reviewReportResults } from "../../../../runtime/src/review/report.js";
5
5
  import { findPriorFailingFindings, findReviewAttestation, invalidateReviewAttestation, readReviewReportArtifact, reviewReportDigest, saveReviewAttestation, saveReviewReportArtifact } from "../../../../runtime/src/review/attestation.js";
6
6
  import { detectHostTarget, planReviewTargets, resolveReviewRole } from "../../../../runtime/src/review/roles.js";
7
- import { taskContractHash, treeContractHash, goalHash } from "../../../../runtime/src/task/contract.js";
7
+ import { evidenceMatchesContract, taskContractHash, treeContractHash, goalHash } from "../../../../runtime/src/task/contract.js";
8
8
  import { acceptanceCoverage, coverageDigest } from "../../../../runtime/src/verify/acceptance-coverage.js";
9
9
  import { resolveGitCommit } from "../../../../runtime/src/verify/change-surface.js";
10
10
  import { sha256 } from "../../../../runtime/src/fs/hash.js";
@@ -15,7 +15,6 @@ import { assertSafeSupportingPaths, isReviewerPolicyPath, resolveReviewScope, re
15
15
  import { calculateSourceFingerprint } from "../../../../runtime/src/verify/source-fingerprint.js";
16
16
  import { calculateConfigHash, isPassingVerificationEvidence } from "../../../../runtime/src/verify/evidence.js";
17
17
  import { validateEvidence } from "../../../../runtime/src/schema/validate.js";
18
- import { loadDebugProof, debugProofDigest } from "../../../../runtime/src/task/debug-state.js";
19
18
  import { logSurfaceResidualRisks, runSurfaceGate } from "../../../../runtime/src/surface/discovery.js";
20
19
  import { escapeReviewData } from "../../../../runtime/src/escape/link.js";
21
20
  import { logGateCatch } from "../../../../runtime/src/logging/local-log.js";
@@ -187,7 +186,7 @@ async function currentEvidence(options, context, criterionId, commandId, configH
187
186
  evidence.sourceFingerprint !== sourceFingerprint ||
188
187
  (context.records.some(r => r.task.id === (owner?.taskId ?? context.taskId) &&
189
188
  (r.task.goal !== undefined || (r.task.contractRevision ?? 0) > 0 || r.task.criteria.some(c => c.acceptance !== undefined))) &&
190
- evidence.taskContractHash !== taskContractHash(context.records.find(r => r.task.id === (owner?.taskId ?? context.taskId)).task))) {
189
+ !evidenceMatchesContract(evidence.taskContractHash, context.records.find(r => r.task.id === (owner?.taskId ?? context.taskId)).task))) {
191
190
  stale = true;
192
191
  continue;
193
192
  }
@@ -343,7 +342,7 @@ async function reviewEscapes(options, context, scope) {
343
342
  const origin = record.task.origin;
344
343
  if (origin === undefined)
345
344
  continue;
346
- const base = record.debug?.baselineCommit ?? record.createdSourceCommit ?? (scope?.mode === "base" ? scope.resolvedBase : undefined);
345
+ const base = record.createdSourceCommit ?? (scope?.mode === "base" ? scope.resolvedBase : undefined);
347
346
  try {
348
347
  if (base === undefined)
349
348
  throw new Error("no base");
@@ -770,7 +769,6 @@ export async function runReviewCommand(options) {
770
769
  }
771
770
  context.coverage = rows;
772
771
  context.binding = {
773
- ...(context.records.every(r => r.task.debug === undefined) || options.root === undefined ? {} : { debugProofs: Object.fromEntries(await Promise.all(context.records.filter(r => r.task.debug !== undefined).map(async (r) => [r.task.id, debugProofDigest(await loadDebugProof(options.root, r.task.debug.rootTaskId))]))) }),
774
772
  taskContractHash: taskContractHash(context.records.find(r => r.task.id === context.taskId).task),
775
773
  taskContracts: Object.fromEntries(context.records.map(r => [r.task.id, taskContractHash(r.task)])),
776
774
  ...(context.treeTasks === undefined ? {} : { treeContractHash: treeContractHash(context.records.map(r => r.task)) }),
@@ -821,8 +819,7 @@ export async function runReviewCommand(options) {
821
819
  executionPolicy: options.config,
822
820
  ...(surfacePacket === undefined ? {} : { surface: surfacePacket }),
823
821
  artifacts: contractArtifacts.map(({ path, digest }) => ({ path, digest })),
824
- tasks: await Promise.all(context.records.map(async (r) => ({ task: r.task, revisions: r.revisions ?? [],
825
- ...(r.task.debug === undefined ? {} : { debugProof: await loadDebugProof(options.root, r.task.debug.rootTaskId) }) }))),
822
+ tasks: context.records.map(r => ({ task: r.task, revisions: r.revisions ?? [] })),
826
823
  superseded: context.historyRecords ?? [] }));
827
824
  const digest = sha256(content);
828
825
  const path = ".agent-ops/tasks/review-contracts/" + digest + ".json";
@@ -926,9 +923,7 @@ export async function runReviewCommand(options) {
926
923
  await options.tasks.treeContract(context.taskId) !== context.binding.treeContractHash) ||
927
924
  (context !== undefined && options.tasks !== undefined &&
928
925
  (await Promise.all(context.records.map(async (r) => taskContractHash((await options.tasks.status({ taskId: r.task.id })).task))))
929
- .some((hash, i) => hash !== taskContractHash(context.records[i].task))) ||
930
- (context?.binding?.debugProofs !== undefined && options.root !== undefined &&
931
- (await Promise.all(context.records.filter(r => r.task.debug !== undefined).map(async (r) => debugProofDigest(await loadDebugProof(options.root, r.task.debug.rootTaskId)) !== context.binding.debugProofs[r.task.id]))).some(Boolean))) {
926
+ .some((hash, i) => hash !== taskContractHash(context.records[i].task)))) {
932
927
  const changed = await persistReviewEvidence(options, sourceChangedResult(result), sourceFingerprint, context);
933
928
  return notRunEnvelope(changed);
934
929
  }
@@ -47,7 +47,9 @@ function data(action, result) {
47
47
  };
48
48
  }
49
49
  function stateData(action, state, message) {
50
- return { action, runId: state.runId, state, recovery: describeRunRecovery(state), message, text: textFor(action, state, message) };
50
+ const { events, goal: _goal, ...brief } = state;
51
+ return { action, runId: state.runId, state: { ...brief, eventCount: events.length }, recovery: describeRunRecovery(state), message,
52
+ text: textFor(action, state, message) };
51
53
  }
52
54
  async function goalFromArgs(args, root) {
53
55
  if (args.goal !== undefined && args.goalFile !== undefined)
@@ -47,10 +47,10 @@ async function withEscape(envelope, escape, service) {
47
47
  ...facts.map(fact => `- ${fact.path}: ${fact.surface}`),
48
48
  ...(proof === undefined ? [] : [proof.status === "proven"
49
49
  ? `Proof: rule ${proof.rule} replays at the escaped commit; backtest ${proof.backtest.fires}/${proof.backtest.commits} commits (${proof.backtest.rate}).`
50
- : proof.status === "unproven" ? `Proof: ${proof.code} - ${proof.reason} Finish refuses this until a tracked rule proves it.`
50
+ : proof.status === "unproven" ? `Proof: ${proof.code} - ${proof.reason} Advisory: finish does not wait for it; add a tracked rule to prove it.`
51
51
  : proof.status === "lesson" ? `Lesson (unproven, does not block): ${proof.reason}`
52
- : proof.check === null ? "Proof: ESCAPE_RULE_UNPROVEN - name the red-to-green check with --escape-promote <runnerId:checkId>."
53
- : `Proof: promoted check ${proof.check} must be a proven redCheckId of this task. Config proposal (not written):\n${JSON.stringify(proposal, null, 2)}`]),
52
+ : proof.check === null ? "Proof: ESCAPE_RULE_UNPROVEN (advisory) - name the red-to-green check with --escape-promote <runnerId:checkId>."
53
+ : `Proof: promoted check ${proof.check} should be a proven redCheckId of this task (advisory). Config proposal (not written):\n${JSON.stringify(proposal, null, 2)}`]),
54
54
  `Ledger record: ${escape.path} (commit it with the fix).`,
55
55
  escape.blame.length === 0 ? "Blame hints: none (the fix only adds lines; an omission has no author)."
56
56
  : `Blame hints (a hint only): ${escape.blame.map(hint => `${hint.commit.slice(0, 12)} (${hint.lines} line${hint.lines === 1 ? "" : "s"})`).join(", ")}`
@@ -98,7 +98,7 @@ function taskAction(args) {
98
98
  "complete",
99
99
  "create",
100
100
  "export",
101
- "status", "revise", "replan", "pin-finding", "debug"
101
+ "status", "revise", "replan", "pin-finding"
102
102
  ].includes(args.action)) {
103
103
  throw new AgentOpsError("TASK_ACTION_INVALID", "A supported task action is required.");
104
104
  }
@@ -110,7 +110,7 @@ function taskEnvelope(action, code, message, record) {
110
110
  message,
111
111
  record,
112
112
  contractHash: taskContractHash(record.task),
113
- text: renderTaskMarkdown(record) + "\nContract: " + taskContractHash(record.task) + (record.debug === undefined ? "" : "\nDEBUG: " + JSON.stringify(record.debug))
113
+ text: renderTaskMarkdown(record) + "\nContract: " + taskContractHash(record.task)
114
114
  });
115
115
  }
116
116
  function renderTaskList(records) {
@@ -129,8 +129,6 @@ export async function runTaskCommand(options) {
129
129
  if (options.args.title === undefined) {
130
130
  throw new AgentOpsError("TASK_TITLE_REQUIRED", "Task creation requires --title.");
131
131
  }
132
- if (options.args.debug)
133
- await options.service.assertDebugBaseline();
134
132
  const worktree = sessionId === undefined || options.sessionWorktree === undefined
135
133
  ? undefined
136
134
  : await options.sessionWorktree(sessionId);
@@ -156,12 +154,6 @@ export async function runTaskCommand(options) {
156
154
  requireSurface: true
157
155
  });
158
156
  const escape = origin === undefined ? undefined : await (worktree?.service ?? options.service).declareEscape(record.task.id);
159
- // An explicit CLI DEBUG intake may be the only start signal on hosts
160
- // without a prompt hook (notably agy). The native session owns the clock;
161
- // foregroundStart is idempotent for a repeated hook or command.
162
- if (options.args.debug && sessionId !== undefined) {
163
- await (worktree?.service ?? options.service).debugService().foregroundStart(record.task.id, sessionId);
164
- }
165
157
  const created = await withEscape(taskEnvelope(action, "TASK_CREATED", sessionId === undefined
166
158
  ? `Created task ${record.task.id}.`
167
159
  : record.task.parentTaskId === undefined
@@ -187,42 +179,6 @@ export async function runTaskCommand(options) {
187
179
  }
188
180
  };
189
181
  }
190
- if (action === "debug") {
191
- const id = requireTaskId(options.args), service = options.service.debugService();
192
- if (options.args.debugAction === "origin") {
193
- const declared = await service.origin(id, await jsonFile(options.args.payloadFile));
194
- return await withEscape(taskEnvelope(action, "TASK_DEBUG_ORIGIN_DECLARED", "Origin bound into the contract; re-verify, re-record the confirmed hypothesis, then review.", declared.record), declared.escape, options.service);
195
- }
196
- switch (options.args.debugAction) {
197
- case "hypothesis":
198
- await service.hypothesis(id, await jsonFile(options.args.payloadFile));
199
- break;
200
- case "strategy":
201
- await service.strategy(id, await jsonFile(options.args.payloadFile));
202
- break;
203
- case "investigate":
204
- await service.investigate(id, options.args.commandId, options.trusted === true);
205
- break;
206
- case "checkpoint":
207
- if (sessionId === undefined)
208
- throw new AgentOpsError("TASK_SESSION_REQUIRED", "Debug checkpoint requires a session.");
209
- await service.checkpoint(id, sessionId);
210
- break;
211
- case "resume":
212
- if (sessionId === undefined)
213
- throw new AgentOpsError("TASK_SESSION_REQUIRED", "Debug resume requires a session.");
214
- await service.resume(id, sessionId);
215
- break;
216
- case "extend":
217
- if (sessionId === undefined)
218
- throw new AgentOpsError("TASK_SESSION_REQUIRED", "Debug extension requires a native session.");
219
- await service.extend(id, sessionId, options.args.eventId);
220
- break;
221
- case "status": break;
222
- default: throw new AgentOpsError("TASK_DEBUG_ACTION_INVALID", "A supported debug operation is required.");
223
- }
224
- return taskEnvelope(action, "TASK_DEBUG_UPDATED", "Original issue debug state preserved.", await service.root(id));
225
- }
226
182
  if (action === "revise") {
227
183
  const payload = options.args.criteriaFile === undefined ? undefined : await jsonFile(options.args.criteriaFile);
228
184
  if (payload !== undefined && !Array.isArray(payload))
@@ -235,7 +191,7 @@ export async function runTaskCommand(options) {
235
191
  ...(origin === undefined ? {} : { origin }),
236
192
  reason: options.args.reason, ...(options.args.baseline === undefined ? {} : { baseline: options.args.baseline })
237
193
  });
238
- return await withEscape(taskEnvelope(action, "TASK_REVISED", "Contract revised; obtain new verification and fresh review.", record), origin === undefined ? undefined : await options.service.declareEscape(record.task.id), options.service);
194
+ return await withEscape(taskEnvelope(action, "TASK_REVISED", "Contract revised; obtain a fresh review, and new verification if the criteria or configuration changed.", record), origin === undefined ? undefined : await options.service.declareEscape(record.task.id), options.service);
239
195
  }
240
196
  if (action === "replan") {
241
197
  const payload = await jsonFile(options.args.planFile);
@@ -96,12 +96,16 @@ export async function runVerifyCommand(options) {
96
96
  try {
97
97
  const taskId = await resolveTaskId(options), activity = options.activity;
98
98
  const report = publicReport(await withActivity(activity?.commonDir, { kind: "verify", root: activity?.root ?? "", taskId, ...(activity?.sessionId === undefined ? {} : { sessionId: activity.sessionId }) }, async () => await options.service.verify(taskId)));
99
- const text = formatReport(report);
99
+ // A broken surface check must not hide the verification result.
100
+ const surface = options.surface === undefined ? [] : await options.surface(taskId).catch(() => []);
101
+ const text = formatReport(report) + surface.map(line => `${safe(line)}\n`).join("");
102
+ const advisory = surface.length === 0 ? {} : { surface };
100
103
  if (report.status === "PASS") {
101
104
  return okEnvelope("VERIFICATION_PASSED", {
102
105
  message: "Required verification passed.",
103
106
  report,
104
- text
107
+ text,
108
+ ...advisory
105
109
  });
106
110
  }
107
111
  const code = report.status === "FAIL"
@@ -113,7 +117,7 @@ export async function runVerifyCommand(options) {
113
117
  return {
114
118
  code,
115
119
  status: "error",
116
- data: { message, report, text },
120
+ data: { message, report, text, ...advisory },
117
121
  errors: [{ code, message }]
118
122
  };
119
123
  }
@@ -0,0 +1,26 @@
1
+ import { readWorktreeRecord } from "../../../runtime/src/parallel/service.js";
2
+ import { collectChangeSurface, resolveGitCommit } from "../../../runtime/src/verify/change-surface.js";
3
+ /**
4
+ * The base verify and review use when `--base` is absent, so committed work is
5
+ * judged as a range without the agent having to copy it. A dirty tree keeps the
6
+ * working-tree mode; a session worktree compares against its merge-base with the
7
+ * target branch (which follows a finish rebase); elsewhere the task's creation
8
+ * commit counts when it is an ancestor of HEAD.
9
+ */
10
+ export async function defaultBase(root, runner, createdSourceCommit) {
11
+ if ((await collectChangeSurface(runner)).paths.length > 0)
12
+ return undefined;
13
+ const head = await resolveGitCommit(runner, "HEAD");
14
+ const record = await readWorktreeRecord(root);
15
+ let base;
16
+ if (record !== null) {
17
+ const merge = await runner.run(["merge-base", "HEAD", record.targetBranch]);
18
+ base = merge.exitCode === 0 ? Buffer.from(merge.stdout).toString("utf8").trim() : record.base;
19
+ }
20
+ else {
21
+ const created = await createdSourceCommit().catch(() => undefined);
22
+ if (created !== undefined && (await runner.run(["merge-base", "--is-ancestor", created, "HEAD"])).exitCode === 0)
23
+ base = created;
24
+ }
25
+ return base === undefined || base === "" || base === head ? undefined : base;
26
+ }