@kylecheng3146/agent-ops 0.10.2 → 0.11.1

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 (56) hide show
  1. package/README.md +13 -0
  2. package/dist/packages/cli/src/args.js +18 -44
  3. package/dist/packages/cli/src/bin.js +37 -29
  4. package/dist/packages/cli/src/cli.js +16 -14
  5. package/dist/packages/cli/src/codex-loop-process.js +0 -6
  6. package/dist/packages/cli/src/commands/review.js +5 -10
  7. package/dist/packages/cli/src/commands/run.js +3 -1
  8. package/dist/packages/cli/src/commands/task.js +27 -54
  9. package/dist/packages/cli/src/commands/verify.js +7 -3
  10. package/dist/packages/cli/src/criteria-review.js +63 -0
  11. package/dist/packages/cli/src/default-base.js +26 -0
  12. package/dist/packages/cli/src/hook-process.js +19 -83
  13. package/dist/packages/cli/src/owned-run-step.js +1 -1
  14. package/dist/packages/cli/src/run-entry.js +203 -115
  15. package/dist/runtime/src/adapters/agy/config.js +2 -3
  16. package/dist/runtime/src/adapters/claude/config.js +2 -2
  17. package/dist/runtime/src/adapters/codex/config.js +2 -5
  18. package/dist/runtime/src/hooks/completion-gate.js +0 -15
  19. package/dist/runtime/src/install/harness.js +4 -4
  20. package/dist/runtime/src/install/plan.js +3 -3
  21. package/dist/runtime/src/install/profiles.js +0 -2
  22. package/dist/runtime/src/logging/local-log.js +12 -0
  23. package/dist/runtime/src/office/snapshot.js +4 -1
  24. package/dist/runtime/src/parallel/manage.js +0 -3
  25. package/dist/runtime/src/review/criteria.js +252 -0
  26. package/dist/runtime/src/review/execute.js +68 -4
  27. package/dist/runtime/src/review/invocation.js +3 -3
  28. package/dist/runtime/src/run/verification.js +2 -2
  29. package/dist/runtime/src/schema/validate.js +10 -5
  30. package/dist/runtime/src/task/completion.js +4 -42
  31. package/dist/runtime/src/task/contract.js +11 -0
  32. package/dist/runtime/src/task/service.js +34 -57
  33. package/dist/runtime/src/task/store.js +3 -7
  34. package/dist/runtime/src/verify/acceptance-coverage.js +2 -3
  35. package/dist/runtime/src/verify/acceptance-protocol.js +4 -3
  36. package/dist/runtime/src/verify/acceptance-replay.js +4 -7
  37. package/dist/runtime/src/verify/service.js +6 -23
  38. package/dist/runtime/src/verify/source-fingerprint.js +17 -4
  39. package/dist/runtime/src/verify/spawn.js +3 -2
  40. package/docs/en/guides/task-debug.md +39 -133
  41. package/docs/en/spec/acceptance-and-evidence.md +31 -23
  42. package/docs/en/spec/harness-adapters.md +9 -11
  43. package/docs/en/spec/loop-engineering.md +11 -5
  44. package/docs/en/spec/review.md +16 -0
  45. package/docs/zh-TW/guides/task-debug.md +27 -73
  46. package/docs/zh-TW/spec/acceptance-and-evidence.md +18 -18
  47. package/docs/zh-TW/spec/harness-adapters.md +7 -8
  48. package/docs/zh-TW/spec/loop-engineering.md +9 -4
  49. package/docs/zh-TW/spec/review.md +17 -1
  50. package/package.json +1 -1
  51. package/schemas/config.schema.json +1 -21
  52. package/dist/packages/cli/src/debug-intake.js +0 -245
  53. package/dist/runtime/src/task/debug-intake.js +0 -15
  54. package/dist/runtime/src/task/debug-state.js +0 -135
  55. package/dist/runtime/src/task/debug-user-event.js +0 -160
  56. package/dist/runtime/src/task/debug.js +0 -632
package/README.md CHANGED
@@ -12,6 +12,19 @@ pre-1.0 interface.
12
12
  The CLI is published as `@kylecheng3146/agent-ops` as a pre-1.0 interface;
13
13
  command behavior may change before 1.0.
14
14
 
15
+ ## Criteria advisory before implementation
16
+
17
+ Every new task, including DEBUG and native run parent/child tasks, waits for one
18
+ independent advisory review of its goal, criteria and assigned surface before
19
+ implementation. Read the suggestions, decide which to adopt, and record reasons
20
+ through `task revise`. Revisions do not automatically re-review.
21
+
22
+ The advisory has no time/token budget; explicit cancellation stops work. Genuine
23
+ execution errors report NOT_RUN and allow work. Detailed reports remain private;
24
+ `task status` shows their snapshot and whether they are historical. Explicitly
25
+ rerun with `agent-ops review --criteria --task <id> --rerun --yes`.
26
+ Final verification, two-round review and finish remain required.
27
+
15
28
  ## Executable acceptance and native goal runs (0.6.0)
16
29
 
17
30
  Criteria can use `behavioral`, `invariant` or `review-only` acceptance contracts.
@@ -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;
@@ -113,6 +113,7 @@ export function parseArgs(argv) {
113
113
  let json = false;
114
114
  let yes = false;
115
115
  let rerun = false;
116
+ let criteriaReview = false;
116
117
  let tree = false;
117
118
  let helpSeen = false;
118
119
  let versionSeen = false;
@@ -233,30 +234,6 @@ export function parseArgs(argv) {
233
234
  duplicate(token);
234
235
  debug = true;
235
236
  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
237
  case "--tree": {
261
238
  if (tree)
262
239
  duplicate(token);
@@ -485,6 +462,11 @@ export function parseArgs(argv) {
485
462
  index += 1;
486
463
  break;
487
464
  }
465
+ case "--criteria":
466
+ if (criteriaReview)
467
+ throw new CliArgumentError("CLI_DUPLICATE_OPTION", "--criteria may appear only once.");
468
+ criteriaReview = true;
469
+ break;
488
470
  case "--rerun":
489
471
  if (rerun) {
490
472
  duplicate(token);
@@ -530,7 +512,7 @@ export function parseArgs(argv) {
530
512
  "complete",
531
513
  "create",
532
514
  "export",
533
- "status", "revise", "replan", "pin-finding", "debug"
515
+ "status", "revise", "replan", "pin-finding"
534
516
  ].includes(token)) {
535
517
  action = token;
536
518
  break;
@@ -674,8 +656,8 @@ export function parseArgs(argv) {
674
656
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--target-version may be used only with update.");
675
657
  }
676
658
  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.");
659
+ command !== "batch" && !(command === "task" && action === "complete")) {
660
+ throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--base may be used only with verify, review, batch or task complete.");
679
661
  }
680
662
  if ((parentBase !== undefined || width !== undefined) && command !== "batch") {
681
663
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", `${parentBase !== undefined ? "--parent-base" : "--width"} may be used only with batch.`);
@@ -711,16 +693,11 @@ export function parseArgs(argv) {
711
693
  if (worktree !== undefined && command !== "init" && command !== "update") {
712
694
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--worktree may be used only with init or update.");
713
695
  }
696
+ if (criteriaReview && (command !== "review" || action !== undefined || tree || base !== undefined || output !== undefined))
697
+ throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--criteria requires review --task <id> --yes; omit show, tree, base and output.");
714
698
  const revisionAction = command === "task" && ["revise", "replan", "pin-finding"].includes(action ?? "");
715
699
  if ((taskGoal !== undefined || debug) && !(command === "task" && action === "create"))
716
700
  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
701
  if ((criterionFiles.length > 0 && !(command === "task" && (action === "create" || action === "pin-finding"))) ||
725
702
  (criteriaFile !== undefined && !(command === "task" && action === "revise")) ||
726
703
  (surfaceFile !== undefined && !(command === "task" && (action === "create" || action === "revise"))) ||
@@ -739,7 +716,7 @@ export function parseArgs(argv) {
739
716
  if (command === "escapes" && argv.some(token => token !== "escapes" && token !== "--json"))
740
717
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "escapes accepts only --json; it is a read-only report.");
741
718
  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)) ||
719
+ (action === "revise" && (expectedContract === undefined || (criteriaFile === undefined && surfaceFile === undefined && escapeOf === undefined && baseline === undefined) || reason === undefined)) ||
743
720
  (action === "replan" && (expectedTreeContract === undefined || planFile === undefined || reason === undefined)) ||
744
721
  (action === "pin-finding" && (expectedContract === undefined || findingReference === undefined || criterionFiles.length !== 1))))
745
722
  throw new CliArgumentError("CLI_MISSING_VALUE", "Acceptance revision requires task, expected contract, payload and reason.");
@@ -817,7 +794,7 @@ export function parseArgs(argv) {
817
794
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--intent may be used only with task create.");
818
795
  }
819
796
  if (command === "review" && action !== "show" && !yes) {
820
- throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Review requires --yes to authorize both reviewer sessions.");
797
+ throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", criteriaReview ? "Criteria advisory requires --yes to authorize one reviewer session." : "Review requires --yes to authorize both reviewer sessions.");
821
798
  }
822
799
  if (command === "review" && harness !== undefined) {
823
800
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Complete review uses the configured reviewer pair; omit --harness.");
@@ -902,11 +879,8 @@ export function parseArgs(argv) {
902
879
  ...(intent === undefined ? {} : { intent }),
903
880
  ...(taskGoal === undefined ? {} : { goal: taskGoal }),
904
881
  ...(debug ? { debug: true } : {}),
905
- ...(debugAction === undefined ? {} : { debugAction }),
906
- ...(payloadFile === undefined ? {} : { payloadFile }),
907
- ...(commandId === undefined ? {} : { commandId }),
908
- ...(eventId === undefined ? {} : { eventId }),
909
882
  ...(reviewTargets.length === 0 ? {} : { reviewTargets }),
883
+ ...(criteriaReview ? { criteriaReview: true } : {}),
910
884
  ...(completionGate === undefined ? {} : { completionGate }),
911
885
  ...(office === undefined ? {} : { office }),
912
886
  ...(autoRun === undefined ? {} : { autoRun }),
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { reviewCreatedCriteria, criteriaReviewStatus } from "./criteria-review.js";
3
+ import { renderCriteriaReview } from "../../../runtime/src/review/criteria.js";
2
4
  import { advancePhaseObserver, productionRunContext } from "./run-deps.js";
3
5
  import { AgentOpsError } from "../../../runtime/src/fs/paths.js";
4
6
  import { recordRunVerification } from "../../../runtime/src/run/verification.js";
@@ -47,9 +49,11 @@ import { memoizePreflight } from "../../../runtime/src/review/batch.js";
47
49
  import { createReviewExecutor, ReviewInterruptedError } from "../../../runtime/src/review/execute.js";
48
50
  import { probeReviewTargetDetailed } from "../../../runtime/src/review/probe.js";
49
51
  import { resolveReviewScope } from "../../../runtime/src/review/scope.js";
52
+ import { runSurfaceGate } from "../../../runtime/src/surface/discovery.js";
50
53
  import { resolveReviewRole } from "../../../runtime/src/review/roles.js";
51
54
  import { runTrustCommand } from "./commands/trust.js";
52
55
  import { runVerifyCommand } from "./commands/verify.js";
56
+ import { defaultBase } from "./default-base.js";
53
57
  import { runAdvanceCommand } from "./commands/advance.js";
54
58
  import { runReviewShowCommand } from "./commands/review-show.js";
55
59
  import { runEscapesCommand } from "./commands/escapes.js";
@@ -255,6 +259,16 @@ else {
255
259
  const root = args.scope === "user"
256
260
  ? process.env.AGENT_OPS_HOME ?? homedir()
257
261
  : process.cwd();
262
+ // Committed work is judged as a range by default; see defaultBase.
263
+ if ((args.command === "verify" || (args.command === "review" && args.action !== "show" && !args.criteriaReview)) && args.base === undefined && args.scope !== "user") {
264
+ const taskId = args.taskId;
265
+ const base = await defaultBase(root, gitRunner(root), async () => taskId === undefined ? undefined :
266
+ (await new TaskService(new FileTaskStore(join(root, ".agent-ops", "tasks", "state.json"), root)).status({ taskId })).createdSourceCommit)
267
+ // Only a default: any failure leaves the command's own scope handling and errors in charge.
268
+ .catch(() => undefined);
269
+ if (base !== undefined)
270
+ args = { ...args, base };
271
+ }
258
272
  const isTTY = !args.json &&
259
273
  process.stdin.isTTY === true &&
260
274
  process.stdout.isTTY === true;
@@ -495,7 +509,8 @@ else {
495
509
  return await runTaskCommand({
496
510
  args,
497
511
  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"; })() }),
512
+ onTaskCreated: async (record, createdRoot) => await reviewCreatedCriteria(createdRoot ?? root, record.task),
513
+ criteriaStatus: async (record) => await criteriaReviewStatus(root, record.task),
499
514
  ...(policyConfigHash === undefined ? {} : { policyConfigHash }),
500
515
  ...(sessionId === undefined ? {} : { sessionId }),
501
516
  ...(fromMain
@@ -516,6 +531,11 @@ else {
516
531
  : {})
517
532
  });
518
533
  }
534
+ if (args.command === "review" && args.criteriaReview) {
535
+ const record = await taskService.status({ taskId: args.taskId });
536
+ const criteriaReview = await reviewCreatedCriteria(root, record.task, args.rerun);
537
+ return { status: "ok", code: "CRITERIA_REVIEW_ADVISORY", data: { criteriaReview, text: renderCriteriaReview(criteriaReview) }, errors: [] };
538
+ }
519
539
  const probeTarget = async (target, budget) => await probeReviewTargetDetailed(target, {
520
540
  cwd: root,
521
541
  deep: true,
@@ -553,41 +573,16 @@ else {
553
573
  reviewActivity = undefined;
554
574
  }
555
575
  });
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
576
  const inSlot = (executor) => slotDir === undefined
559
- ? inDebug(executor)
577
+ ? executor
560
578
  : async (request) => await withReviewSlot({
561
579
  dir: slotDir,
562
580
  signal,
563
581
  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
582
  onWait: (line) => {
588
583
  process.stderr.write(`${progressPrefix}${line}\n`);
589
584
  }
590
- }, async () => await inDebug(executor)(request));
585
+ }, async () => await executor(request));
591
586
  return {
592
587
  args: reviewArgs,
593
588
  authorized: reviewArgs.yes,
@@ -607,7 +602,6 @@ else {
607
602
  config: reviewConfig,
608
603
  evidenceStore: new FileEvidenceStore(root, root),
609
604
  execute: inActivity(inSlot(createReviewExecutor({
610
- ...(debugRecord?.task.debug === undefined ? {} : { remainingBudgetMs: () => taskService.debugService().remaining(debugRecord.task.id) }),
611
605
  runner: registeredRunProofRunner(),
612
606
  targets: configuredReviewTargets,
613
607
  cwd: root,
@@ -749,6 +743,20 @@ else {
749
743
  return await runVerifyCommand({
750
744
  args,
751
745
  taskService,
746
+ surface: async (taskId) => {
747
+ const record = await taskService.status({ taskId });
748
+ // A subtask's surface is its parent's; review checks the tree, so verify stays quiet here.
749
+ if (record.task.parentTaskId !== undefined)
750
+ return [];
751
+ const scope = await resolveReviewScope({ root, runner: gitRunner(root), ...(args.base === undefined ? {} : { base: args.base }) });
752
+ const gate = await runSurfaceGate({ root, taskId, surface: record.task.surface, required: record.task.surfaceRequired === true,
753
+ ...(scope.mode === "base" ? { base: scope.resolvedBase } : {}), changedFiles: scope.changedFiles, configHash: calculateConfigHash(config) });
754
+ if (gate.status === "PASS")
755
+ return ["Surface: PASS"];
756
+ const paths = gate.details.flatMap(d => typeof d === "object" && d !== null && "path" in d ? [String(d.path)] : []);
757
+ return [`Surface (advisory; review would stop with ${gate.code}): ${gate.message}`,
758
+ ...paths.slice(0, 20).map(path => ` - ${path}`), ...(paths.length > 20 ? [` ... ${paths.length - 20} more`] : [])];
759
+ },
752
760
  activity: { commonDir: await officeCommonDir(root), root, ...(verifySessionId === undefined ? {} : { sessionId: verifySessionId }) },
753
761
  service: { verify: async (taskId) => {
754
762
  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
@@ -59,6 +59,7 @@ Options:
59
59
  --width <n> Batch only: reviews allowed at once (default 2)
60
60
  --target-version <version> Update target version (offline-capable)
61
61
  --title <text>
62
+ --criteria Review only: one pre-edit advisory, no time budget
62
63
  --criterion <json> Repeatable
63
64
  --evidence <criterion-id=reference> Repeatable
64
65
  --session <id>
@@ -105,7 +106,7 @@ The URL carries an access token; do not share it.
105
106
  agent-ops run respond <run-id> --question-id <id> <--answer <text>|--answer-file <path>> [--json]
106
107
 
107
108
  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.
109
+ Command/review timeouts, provider limits and stop controls remain effective.
109
110
  Native goal state is observational. Completion requires current verification,
110
111
  two fresh reviews, task completion and a target-bound integration receipt.
111
112
  --retry-approval explicitly retries an intact Codex approval-review usage-limit
@@ -188,7 +189,7 @@ Options:
188
189
  --json
189
190
  --yes
190
191
  `,
191
- task: `Usage: agent-ops task <create|status|attach|revise|replan|pin-finding|debug|advance|complete|archive|export> [options]
192
+ task: `Usage: agent-ops task <create|status|attach|revise|replan|pin-finding|advance|complete|archive|export> [options]
192
193
 
193
194
  Manage independent task acceptance state. Task commands accept none of
194
195
  --harness, --profile or --dry-run. Only advance accepts --yes.
@@ -196,16 +197,12 @@ Manage independent task acceptance state. Task commands accept none of
196
197
  Options:
197
198
  --title <text> create
198
199
  --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
200
+ --debug create: bug fix; needs --goal and a behavioral criterion with redCheckIds
204
201
  --intent <text> create: intended behavior and constraints before editing
205
202
  --criterion <json> create, repeatable, two to five total
206
203
  --criterion-file <path> create: one criterion object, repeatable; pin-finding: exactly one
207
204
  --criteria-file <path> revise: replacement criterion array
208
- --surface-file <path> create (required unless --debug), revise: EVIDENCE-SURFACE-001
205
+ --surface-file <path> create (required), revise: EVIDENCE-SURFACE-001
209
206
  audit JSON, or {"covers":[surface-id]} under a surfaced parent
210
207
  --escape-of <commit> create, revise: the commit this bug escaped from (Escape Loop)
211
208
  --escape-label <label> with --escape-of: requirement|surface|scenario|criteria|verify|review|flagged|out-of-model
@@ -216,12 +213,12 @@ Options:
216
213
  --expected-contract <hash> revise, pin-finding: current task contract from status
217
214
  --expected-tree-contract <hash> replan: current tree contract from status
218
215
  --reason <text> required for revise/replan; optional for pin-finding
219
- --baseline <git-ref> create/revise: baseline for new mechanical requirements
216
+ --baseline <git-ref> create: baseline for typed criteria; revise: move every typed baseline
220
217
  --parent <task-id> create: record a subtask; status: list subtasks
221
218
  --task <id> status, attach, revise, replan, pin-finding, complete, archive, export, advance
222
- --session <id> create, attach, status, advance, debug
219
+ --session <id> create, attach, status, advance
223
220
  --evidence <criterion-id=reference> complete, repeatable
224
- --base <git-ref> complete, debug: a clean committed range
221
+ --base <git-ref> complete: a clean committed range
225
222
  --yes advance: authorize the two review sessions
226
223
  --json
227
224
 
@@ -233,7 +230,8 @@ Each legacy criterion needs at least one verifierIds entry naming a verification
233
230
  command id configured in .agent-ops/config.json. Typed criteria additionally
234
231
  accept an acceptance object: mode (behavioral, invariant or review-only),
235
232
  bindings and optional baselineCommit/reviewOnlyReason. Mechanical bindings name
236
- configured acceptance runner IDs, checkIds, committed materials and optional
233
+ configured acceptance runner IDs, checkIds (the checks this criterion needs;
234
+ other runner output is ignored), committed materials and optional
237
235
  redCheckIds; tasks cannot supply commands. Creation freezes the baseline.
238
236
  Review-only example (mandatory repository policy commands still run):
239
237
 
@@ -241,7 +239,8 @@ Review-only example (mandatory repository policy commands still run):
241
239
 
242
240
  Pin a saved finding with task pin-finding --task <id> <report-digest:index>
243
241
  --expected-contract <hash> --criterion-file <path>. Revise/replan preserve the
244
- original goal and immutable existing baselines; obtain new verify and review.
242
+ original goal and existing baselines unless revise --baseline moves them all.
243
+ A revise that keeps the criteria keeps verification; every revise needs review.
245
244
  `,
246
245
  verify: `Usage: agent-ops verify [options]
247
246
 
@@ -254,11 +253,14 @@ Options:
254
253
  --json
255
254
  `,
256
255
  review: `Usage: agent-ops review --task <id> --yes [options]
256
+ agent-ops review --criteria --task <id> --yes [--rerun]
257
257
  agent-ops review show --task <id> [--json]
258
258
 
259
259
  Run the complete task-bound review: one necessary reviewer followed by one
260
260
  fresh adversarial reviewer from the configured pair. Show reads recorded
261
261
  reports without running a reviewer, including from the main checkout.
262
+ Criteria mode runs/reads one pre-edit advisory, with no time/token budget;
263
+ only --rerun starts another attempt. It never replaces final proof.
262
264
 
263
265
  Options:
264
266
  --task <id> Required task whose original criteria are reviewed
@@ -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)