@kylecheng3146/agent-ops 0.5.2 → 0.6.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 (103) hide show
  1. package/README.md +64 -0
  2. package/dist/packages/cli/src/args.js +214 -10
  3. package/dist/packages/cli/src/bin.js +77 -17
  4. package/dist/packages/cli/src/cli.js +42 -10
  5. package/dist/packages/cli/src/commands/advance.js +173 -0
  6. package/dist/packages/cli/src/commands/review-show.js +189 -0
  7. package/dist/packages/cli/src/commands/review.js +201 -34
  8. package/dist/packages/cli/src/commands/run.js +111 -0
  9. package/dist/packages/cli/src/commands/task.js +62 -17
  10. package/dist/packages/cli/src/commands/verify.js +1 -0
  11. package/dist/packages/cli/src/context.js +63 -1
  12. package/dist/packages/cli/src/hook-process.js +31 -1
  13. package/dist/packages/cli/src/owned-run-step.js +65 -0
  14. package/dist/packages/cli/src/parallel-deps.js +18 -5
  15. package/dist/packages/cli/src/run-deps.js +314 -0
  16. package/dist/packages/cli/src/run-entry.js +943 -0
  17. package/dist/packages/cli/src/run-setup-entry.js +32 -0
  18. package/dist/packages/cli/src/run-step-entry.js +58 -0
  19. package/dist/runtime/src/config/explain.js +6 -0
  20. package/dist/runtime/src/config/hash.js +1 -1
  21. package/dist/runtime/src/config/merge.js +9 -1
  22. package/dist/runtime/src/contracts.js +2 -2
  23. package/dist/runtime/src/hooks/completion-gate.js +8 -2
  24. package/dist/runtime/src/install/doctor.js +2 -0
  25. package/dist/runtime/src/install/harness.js +1 -1
  26. package/dist/runtime/src/install/preauth.js +10 -1
  27. package/dist/runtime/src/parallel/finish.js +217 -39
  28. package/dist/runtime/src/parallel/guard.js +5 -1
  29. package/dist/runtime/src/parallel/integrate.js +416 -0
  30. package/dist/runtime/src/parallel/manage.js +4 -1
  31. package/dist/runtime/src/parallel/receipt.js +153 -0
  32. package/dist/runtime/src/parallel/service.js +45 -3
  33. package/dist/runtime/src/review/attestation.js +60 -11
  34. package/dist/runtime/src/review/execute.js +24 -1
  35. package/dist/runtime/src/review/packet.js +35 -1
  36. package/dist/runtime/src/review/render.js +71 -0
  37. package/dist/runtime/src/review/runner.js +12 -1
  38. package/dist/runtime/src/review/scope.js +18 -1
  39. package/dist/runtime/src/review/slots.js +35 -10
  40. package/dist/runtime/src/run/authorization.js +60 -0
  41. package/dist/runtime/src/run/budget.js +30 -0
  42. package/dist/runtime/src/run/controls.js +139 -0
  43. package/dist/runtime/src/run/convergence.js +177 -0
  44. package/dist/runtime/src/run/hosts/claude.js +180 -0
  45. package/dist/runtime/src/run/hosts/codex.js +279 -0
  46. package/dist/runtime/src/run/hosts/index.js +4 -0
  47. package/dist/runtime/src/run/hosts/protocol.js +254 -0
  48. package/dist/runtime/src/run/hosts/types.js +1 -0
  49. package/dist/runtime/src/run/hosts/util.js +136 -0
  50. package/dist/runtime/src/run/integration.js +204 -0
  51. package/dist/runtime/src/run/macOS.js +189 -0
  52. package/dist/runtime/src/run/ownership.js +22 -0
  53. package/dist/runtime/src/run/planning.js +25 -0
  54. package/dist/runtime/src/run/policy-transition.js +1076 -0
  55. package/dist/runtime/src/run/policy.js +176 -0
  56. package/dist/runtime/src/run/ratchet.js +71 -0
  57. package/dist/runtime/src/run/scheduler.js +260 -0
  58. package/dist/runtime/src/run/service.js +425 -0
  59. package/dist/runtime/src/run/supervisor.js +635 -0
  60. package/dist/runtime/src/run/transport.js +159 -0
  61. package/dist/runtime/src/run/types.js +1 -0
  62. package/dist/runtime/src/run/usage.js +81 -0
  63. package/dist/runtime/src/run/verification.js +64 -0
  64. package/dist/runtime/src/run/worker-gate.js +48 -0
  65. package/dist/runtime/src/schema/acceptance.js +91 -0
  66. package/dist/runtime/src/schema/validate.js +72 -7
  67. package/dist/runtime/src/task/completion.js +26 -6
  68. package/dist/runtime/src/task/contract.js +17 -0
  69. package/dist/runtime/src/task/pin-finding.js +81 -0
  70. package/dist/runtime/src/task/render.js +3 -0
  71. package/dist/runtime/src/task/service.js +361 -10
  72. package/dist/runtime/src/task/store.js +57 -7
  73. package/dist/runtime/src/verify/acceptance-coverage.js +85 -0
  74. package/dist/runtime/src/verify/acceptance-protocol.js +252 -0
  75. package/dist/runtime/src/verify/acceptance-replay.js +239 -0
  76. package/dist/runtime/src/verify/adapters/index.js +5 -0
  77. package/dist/runtime/src/verify/adapters/jest.js +142 -0
  78. package/dist/runtime/src/verify/adapters/node-reporter.js +20 -0
  79. package/dist/runtime/src/verify/adapters/node.js +254 -0
  80. package/dist/runtime/src/verify/adapters/pytest.js +188 -0
  81. package/dist/runtime/src/verify/adapters/rust.js +84 -0
  82. package/dist/runtime/src/verify/adapters/shared.js +82 -0
  83. package/dist/runtime/src/verify/adapters/vitest.js +218 -0
  84. package/dist/runtime/src/verify/evidence.js +5 -2
  85. package/dist/runtime/src/verify/service.js +24 -8
  86. package/dist/runtime/src/verify/source-fingerprint.js +1 -1
  87. package/dist/runtime/src/verify/spawn.js +13 -2
  88. package/docs/en/guides/configuration.md +10 -0
  89. package/docs/en/spec/acceptance-and-evidence.md +58 -0
  90. package/docs/en/spec/delegation.md +4 -4
  91. package/docs/en/spec/loop-engineering.md +49 -0
  92. package/docs/en/spec/review.md +24 -2
  93. package/docs/zh-TW/guides/configuration.md +9 -0
  94. package/docs/zh-TW/spec/acceptance-and-evidence.md +31 -1
  95. package/docs/zh-TW/spec/delegation.md +4 -4
  96. package/docs/zh-TW/spec/loop-engineering.md +22 -0
  97. package/docs/zh-TW/spec/review.md +24 -2
  98. package/package.json +1 -1
  99. package/schemas/config.schema.json +221 -19
  100. package/schemas/evidence.schema.json +129 -6
  101. package/schemas/task.schema.json +268 -6
  102. package/templates/acceptance/agent_ops_acceptance.rs +36 -0
  103. package/templates/acceptance/agent_ops_pytest.py +46 -0
package/README.md CHANGED
@@ -12,6 +12,54 @@ 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
+ ## Executable acceptance and native goal runs (0.6.0)
16
+
17
+ Criteria can use `behavioral`, `invariant` or `review-only` acceptance contracts.
18
+ Behavioral proof requires each designated red check to fail by assertion on its
19
+ immutable baseline and every required check to pass on the candidate. Invariants
20
+ can pass on both versions. Review-only requirements still need two fresh reviews
21
+ against the original goal and an explicit `reviewOnlyReason` in the acceptance
22
+ contract. Legacy criteria keep their existing verifier behavior.
23
+
24
+ Configure and trust `verification.acceptanceRunners` in the repository. Tasks bind
25
+ stable check IDs and explicit committed test/fixture/helper materials; they cannot
26
+ override runner executables or arguments. Each version installs and builds its own
27
+ declared dependencies. Fixture errors, skipped/retried checks, incomplete output and
28
+ timeouts produce UNKNOWN. Review examines the checks themselves and every fallback.
29
+ `task revise`, `task replan` and `task pin-finding` preserve goal and evidence history;
30
+ `task status` and `doctor` distinguish mechanical coverage from review-only coverage.
31
+
32
+ On macOS, a trusted clean main checkout can start an isolated native goal run:
33
+
34
+ ```sh
35
+ agent-ops run "Implement the requested behavior" --host codex --time-budget 60m --jobs 2
36
+ agent-ops run status <run-id>
37
+ agent-ops run logs <run-id>
38
+ agent-ops run respond <run-id> --question-id <id> --answer "Clarification"
39
+ agent-ops run stop <run-id>
40
+ agent-ops run resume <run-id>
41
+ ```
42
+
43
+ `--host claude` uses Claude Code; `--wait` waits for a terminal or input state.
44
+ The coordinator counts toward the two writer slots. Native processes write only
45
+ under current leases in separate worktrees. Active time counts once across parallel
46
+ workers and continues through proof and integration; resume retains earlier usage
47
+ epochs. Missing token or cost counters remain unknown. Completion requires current
48
+ verification, two fresh reviews, completed tasks and a receipt bound to the target.
49
+ Setup runs in the background under registered process groups. Stop cancels native
50
+ writers, local verification and setup; interrupted setup requires explicit resume.
51
+ The coordinator can assess additional non-dangerous fixed commands through a
52
+ policy request. This authorization is bound to the run, runtime, lease and budget;
53
+ it creates no permanent repository trust. Policy transitions fence writers and
54
+ journal delivery imports and contract synchronization before starting new leases.
55
+ Native authorization decisions retain redacted command scope and rationale;
56
+ missing provider decisions remain unknown. Repair compares actual check outcomes
57
+ and outstanding finding pins rather than source fingerprints.
58
+
59
+ Native lifecycle reachability and framework probe conditions are recorded in
60
+ [the capability probe](docs/harness/native-goal-probe.md); these observations do not
61
+ establish external adoption or a reduction in review cost.
62
+
15
63
  ## Quick start from npm
16
64
 
17
65
  Requires Node.js `>=22.14.0`. Install the published CLI globally:
@@ -347,6 +395,22 @@ agent-ops doctor --check-auth # one real print call per configured target
347
395
 
348
396
  ### Batch review
349
397
 
398
+ For writing subagents in separate worktrees, record each child's complete
399
+ modification intent at task creation (`--intent`), then commit and run its
400
+ local verifier. The coordinator runs `agent-ops task advance --task <parent-id>
401
+ --session <session-id> --yes` from the main checkout through the same trusted
402
+ outer host runner used for review. Advance integrates the committed children,
403
+ verifies every parent and child criterion on the integrated candidate, and
404
+ runs one two-round tree review. Only `NOT_RUN / scope-too-large` falls back to
405
+ per-task two-round reviews; FAIL requires a fix and a fresh final gate. A
406
+ passing gate saves the complete local receipt in `.git/agent-ops/receipts/`
407
+ before finish. A moved target gets at most one automatic rebase and repeat.
408
+ Run `agent-ops review show --task <parent-id>` from the main checkout to read
409
+ both full recorded reports for the latest candidate without invoking a reviewer.
410
+ `task advance` prints a short summary of nonblocking findings and residual risks.
411
+
412
+ `batch` remains the per-task review command for tasks already in one worktree.
413
+
350
414
  A change split into subtasks needs one verify and one review per subtask.
351
415
  `agent-ops batch --parent <task-id> --yes` runs them together for the parent's
352
416
  active subtasks and the parent itself, instead of one `review` at a time:
@@ -11,7 +11,8 @@ export const COMMAND_NAMES = [
11
11
  "review",
12
12
  "batch",
13
13
  "allow-stop",
14
- "worktree"
14
+ "worktree",
15
+ "run"
15
16
  ];
16
17
  const COMMAND_SET = new Set(COMMAND_NAMES);
17
18
  const SCOPES = new Set(["project", "user"]);
@@ -61,6 +62,8 @@ function parseHookTarget(option, value) {
61
62
  return { harness: harness, surfaceId };
62
63
  }
63
64
  export function parseArgs(argv) {
65
+ if (argv[0] === "run")
66
+ return parseRunArgs(argv.slice(1));
64
67
  let command;
65
68
  let action;
66
69
  let scope;
@@ -70,6 +73,7 @@ export function parseArgs(argv) {
70
73
  let parentTaskId;
71
74
  let targetVersion;
72
75
  let title;
76
+ let intent;
73
77
  let sessionId;
74
78
  let base;
75
79
  let parentBase;
@@ -77,6 +81,14 @@ export function parseArgs(argv) {
77
81
  const profiles = [];
78
82
  const reviewTargets = [];
79
83
  const criteria = [];
84
+ const criterionFiles = [];
85
+ let criteriaFile;
86
+ let planFile;
87
+ let expectedContract;
88
+ let expectedTreeContract;
89
+ let reason;
90
+ let baseline;
91
+ let findingReference;
80
92
  const evidence = [];
81
93
  let checkAuth = false;
82
94
  const checkAuthTargets = [];
@@ -85,6 +97,7 @@ export function parseArgs(argv) {
85
97
  let json = false;
86
98
  let yes = false;
87
99
  let rerun = false;
100
+ let tree = false;
88
101
  let helpSeen = false;
89
102
  let versionSeen = false;
90
103
  let worktreeName;
@@ -187,6 +200,52 @@ export function parseArgs(argv) {
187
200
  index += 1;
188
201
  break;
189
202
  }
203
+ case "--intent": {
204
+ if (intent !== undefined)
205
+ duplicate(token);
206
+ intent = readOptionValue(argv, index, token);
207
+ index += 1;
208
+ break;
209
+ }
210
+ case "--tree": {
211
+ if (tree)
212
+ duplicate(token);
213
+ tree = true;
214
+ break;
215
+ }
216
+ case "--criterion-file":
217
+ criterionFiles.push(readOptionValue(argv, index++, token));
218
+ break;
219
+ case "--criteria-file":
220
+ if (criteriaFile !== undefined)
221
+ duplicate(token);
222
+ criteriaFile = readOptionValue(argv, index++, token);
223
+ break;
224
+ case "--plan-file":
225
+ if (planFile !== undefined)
226
+ duplicate(token);
227
+ planFile = readOptionValue(argv, index++, token);
228
+ break;
229
+ case "--expected-contract":
230
+ if (expectedContract !== undefined)
231
+ duplicate(token);
232
+ expectedContract = readOptionValue(argv, index++, token);
233
+ break;
234
+ case "--expected-tree-contract":
235
+ if (expectedTreeContract !== undefined)
236
+ duplicate(token);
237
+ expectedTreeContract = readOptionValue(argv, index++, token);
238
+ break;
239
+ case "--reason":
240
+ if (reason !== undefined)
241
+ duplicate(token);
242
+ reason = readOptionValue(argv, index++, token);
243
+ break;
244
+ case "--baseline":
245
+ if (baseline !== undefined)
246
+ duplicate(token);
247
+ baseline = readOptionValue(argv, index++, token);
248
+ break;
190
249
  case "--criterion": {
191
250
  criteria.push(readOptionValue(argv, index, token));
192
251
  index += 1;
@@ -365,16 +424,21 @@ export function parseArgs(argv) {
365
424
  if (command === "task" &&
366
425
  action === undefined &&
367
426
  [
427
+ "advance",
368
428
  "archive",
369
429
  "attach",
370
430
  "complete",
371
431
  "create",
372
432
  "export",
373
- "status"
433
+ "status", "revise", "replan", "pin-finding"
374
434
  ].includes(token)) {
375
435
  action = token;
376
436
  break;
377
437
  }
438
+ if (command === "review" && action === undefined && token === "show") {
439
+ action = "show";
440
+ break;
441
+ }
378
442
  if (command === "worktree" &&
379
443
  action === undefined &&
380
444
  WORKTREE_ACTIONS.includes(token)) {
@@ -389,6 +453,14 @@ export function parseArgs(argv) {
389
453
  worktreeName = token;
390
454
  break;
391
455
  }
456
+ if (command === "task" && ["revise", "replan", "pin-finding"].includes(action ?? "") && taskId === undefined) {
457
+ taskId = token;
458
+ break;
459
+ }
460
+ if (command === "task" && action === "pin-finding" && findingReference === undefined) {
461
+ findingReference = token;
462
+ break;
463
+ }
392
464
  if (command !== undefined) {
393
465
  throw new CliArgumentError("CLI_UNEXPECTED_ARGUMENT", `Unexpected argument: ${token}`);
394
466
  }
@@ -433,6 +505,7 @@ export function parseArgs(argv) {
433
505
  parentTaskId !== undefined ||
434
506
  targetVersion !== undefined ||
435
507
  title !== undefined ||
508
+ intent !== undefined ||
436
509
  criteria.length > 0 ||
437
510
  evidence.length > 0 ||
438
511
  reviewTargets.length > 0 ||
@@ -446,6 +519,7 @@ export function parseArgs(argv) {
446
519
  dryRun ||
447
520
  yes ||
448
521
  rerun ||
522
+ tree ||
449
523
  worktree !== undefined ||
450
524
  worktreeFrom !== undefined ||
451
525
  worktreeMessage !== undefined ||
@@ -475,11 +549,12 @@ export function parseArgs(argv) {
475
549
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--message may be used only with worktree commit.", "--message");
476
550
  }
477
551
  if (command === "task" && action === undefined) {
478
- throw new CliArgumentError("CLI_ACTION_REQUIRED", "The task command requires one of: create, status, attach, complete, archive, export.");
552
+ throw new CliArgumentError("CLI_ACTION_REQUIRED", "The task command requires one of: create, status, attach, complete, archive, export, advance.");
479
553
  }
480
554
  const hasTaskTargetOptions = taskId !== undefined ||
481
555
  sessionId !== undefined;
482
556
  const hasTaskMutationOptions = title !== undefined ||
557
+ intent !== undefined ||
483
558
  criteria.length > 0 ||
484
559
  evidence.length > 0;
485
560
  if (command !== "task" &&
@@ -528,11 +603,25 @@ export function parseArgs(argv) {
528
603
  if (worktree !== undefined && command !== "init" && command !== "update") {
529
604
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--worktree may be used only with init or update.");
530
605
  }
606
+ const revisionAction = command === "task" && ["revise", "replan", "pin-finding"].includes(action ?? "");
607
+ if ((criterionFiles.length > 0 && !(command === "task" && (action === "create" || action === "pin-finding"))) ||
608
+ (criteriaFile !== undefined && !(command === "task" && action === "revise")) ||
609
+ (planFile !== undefined && !(command === "task" && action === "replan")) ||
610
+ (expectedContract !== undefined && !(command === "task" && (action === "revise" || action === "pin-finding"))) ||
611
+ (expectedTreeContract !== undefined && !(command === "task" && action === "replan")) ||
612
+ (reason !== undefined && !revisionAction) ||
613
+ (baseline !== undefined && !(command === "task" && (action === "create" || action === "revise"))))
614
+ throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Acceptance options are not supported by this command.");
615
+ if (revisionAction && (taskId === undefined || evidence.length > 0 || title !== undefined || intent !== undefined || parentTaskId !== undefined || sessionId !== undefined || criteria.length > 0 || base !== undefined ||
616
+ (action === "revise" && (expectedContract === undefined || criteriaFile === undefined || reason === undefined)) ||
617
+ (action === "replan" && (expectedTreeContract === undefined || planFile === undefined || reason === undefined)) ||
618
+ (action === "pin-finding" && (expectedContract === undefined || findingReference === undefined || criterionFiles.length !== 1))))
619
+ throw new CliArgumentError("CLI_MISSING_VALUE", "Acceptance revision requires task, expected contract, payload and reason.");
531
620
  if (command === "task") {
532
621
  if (harness !== undefined ||
533
622
  profiles.length > 0 ||
534
623
  dryRun ||
535
- yes ||
624
+ (yes && action !== "advance") ||
536
625
  rerun) {
537
626
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Task commands do not accept harness, profile, dry-run, or yes options.");
538
627
  }
@@ -540,7 +629,7 @@ export function parseArgs(argv) {
540
629
  (taskId !== undefined ||
541
630
  evidence.length > 0)) ||
542
631
  (action === "status" &&
543
- (title !== undefined ||
632
+ (title !== undefined || intent !== undefined ||
544
633
  criteria.length > 0 ||
545
634
  evidence.length > 0 ||
546
635
  // --parent filters the listing, so it cannot name a single target.
@@ -548,21 +637,25 @@ export function parseArgs(argv) {
548
637
  (taskId !== undefined || sessionId !== undefined)) ||
549
638
  (taskId !== undefined && sessionId !== undefined))) ||
550
639
  (action === "attach" &&
551
- (title !== undefined ||
640
+ (title !== undefined || intent !== undefined ||
552
641
  criteria.length > 0 ||
553
642
  evidence.length > 0 ||
554
643
  parentTaskId !== undefined)) ||
555
644
  (action === "complete" &&
556
- (title !== undefined ||
645
+ (title !== undefined || intent !== undefined ||
557
646
  criteria.length > 0 ||
558
647
  sessionId !== undefined ||
559
648
  parentTaskId !== undefined)) ||
560
649
  ((action === "archive" || action === "export") &&
561
- (title !== undefined ||
650
+ (title !== undefined || intent !== undefined ||
562
651
  criteria.length > 0 ||
563
652
  evidence.length > 0 ||
564
653
  sessionId !== undefined ||
565
- parentTaskId !== undefined));
654
+ parentTaskId !== undefined)) ||
655
+ (action === "advance" &&
656
+ (taskId === undefined || title !== undefined || intent !== undefined ||
657
+ criteria.length > 0 || evidence.length > 0 || parentTaskId !== undefined ||
658
+ !yes));
566
659
  if (taskOptionInvalid) {
567
660
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", `Unsupported option for task ${action}.`);
568
661
  }
@@ -585,7 +678,19 @@ export function parseArgs(argv) {
585
678
  if (command === "review" && taskId === undefined) {
586
679
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Review requires --task for the complete task-bound review.");
587
680
  }
588
- if (command === "review" && !yes) {
681
+ if (command === "review" && action === "show" &&
682
+ (yes || rerun || tree || base !== undefined || output !== undefined ||
683
+ scope !== undefined || harness !== undefined || profiles.length > 0 ||
684
+ criteria.length > 0 || evidence.length > 0)) {
685
+ throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "review show accepts only --task and optional --json; it never runs a reviewer.");
686
+ }
687
+ if (tree && command !== "review") {
688
+ throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--tree may be used only with review.");
689
+ }
690
+ if (intent !== undefined && !(command === "task" && action === "create")) {
691
+ throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--intent may be used only with task create.");
692
+ }
693
+ if (command === "review" && action !== "show" && !yes) {
589
694
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Review requires --yes to authorize both reviewer sessions.");
590
695
  }
591
696
  if (command === "review" && harness !== undefined) {
@@ -668,9 +773,18 @@ export function parseArgs(argv) {
668
773
  ...(parentTaskId === undefined ? {} : { parentTaskId }),
669
774
  ...(targetVersion === undefined ? {} : { targetVersion }),
670
775
  ...(title === undefined ? {} : { title }),
776
+ ...(intent === undefined ? {} : { intent }),
671
777
  ...(reviewTargets.length === 0 ? {} : { reviewTargets }),
672
778
  ...(completionGate === undefined ? {} : { completionGate }),
673
779
  ...(criteria.length === 0 ? {} : { criteria }),
780
+ ...(criterionFiles.length === 0 ? {} : { criterionFiles }),
781
+ ...(criteriaFile === undefined ? {} : { criteriaFile }),
782
+ ...(planFile === undefined ? {} : { planFile }),
783
+ ...(expectedContract === undefined ? {} : { expectedContract }),
784
+ ...(expectedTreeContract === undefined ? {} : { expectedTreeContract }),
785
+ ...(reason === undefined ? {} : { reason }),
786
+ ...(baseline === undefined ? {} : { baseline }),
787
+ ...(findingReference === undefined ? {} : { findingReference }),
674
788
  ...(evidence.length === 0 ? {} : { evidence }),
675
789
  ...(sessionId === undefined ? {} : { sessionId }),
676
790
  ...(base === undefined ? {} : { base }),
@@ -683,9 +797,99 @@ export function parseArgs(argv) {
683
797
  ...(checkAuth ? { checkAuth } : {}),
684
798
  ...(checkAuthTargets.length === 0 ? {} : { checkAuthTargets }),
685
799
  rerun,
800
+ ...(tree ? { tree } : {}),
686
801
  dryRun,
687
802
  json,
688
803
  yes,
689
804
  ...(worktree === undefined ? {} : { worktree })
690
805
  };
691
806
  }
807
+ function parseRunArgs(argv) {
808
+ const actions = ["start", "status", "logs", "resume", "stop", "respond"];
809
+ if (argv.includes("--help") || argv.includes("-h"))
810
+ return { command: "help", helpTopic: "run", profiles: [], dryRun: false, json: false, yes: false, rerun: false };
811
+ const action = actions.includes(argv[0]) ? argv[0] : "start";
812
+ const args = { command: "run", action, runAction: action, profiles: [], dryRun: false, json: false, yes: false, rerun: false };
813
+ const seen = new Set();
814
+ let positional;
815
+ for (let i = actions.includes(argv[0]) ? 1 : 0; i < argv.length; i++) {
816
+ const token = argv[i];
817
+ if (!token.startsWith("-")) {
818
+ if (positional !== undefined)
819
+ throw new CliArgumentError("CLI_UNEXPECTED_ARGUMENT", "Quote the goal as one argument; control actions take one run ID.");
820
+ positional = token;
821
+ continue;
822
+ }
823
+ if (seen.has(token))
824
+ duplicate(token);
825
+ seen.add(token);
826
+ if (token === "--json") {
827
+ args.json = true;
828
+ continue;
829
+ }
830
+ if (token === "--wait") {
831
+ args.wait = true;
832
+ continue;
833
+ }
834
+ if (!["--host", "--goal-file", "--time-budget", "--jobs", "--question-id", "--answer", "--answer-file"].includes(token))
835
+ throw new CliArgumentError("CLI_UNKNOWN_OPTION", `Unknown run option: ${token}`, token);
836
+ const value = readOptionValue(argv, i++, token);
837
+ switch (token) {
838
+ case "--host":
839
+ if (value !== "claude" && value !== "codex")
840
+ invalidValue(token, value);
841
+ args.host = value;
842
+ break;
843
+ case "--goal-file":
844
+ args.goalFile = value;
845
+ break;
846
+ case "--question-id":
847
+ args.questionId = value;
848
+ break;
849
+ case "--answer":
850
+ args.answer = value;
851
+ break;
852
+ case "--answer-file":
853
+ args.answerFile = value;
854
+ break;
855
+ case "--jobs":
856
+ if (value !== "1" && value !== "2")
857
+ invalidValue(token, value);
858
+ args.jobs = Number(value);
859
+ break;
860
+ case "--time-budget": {
861
+ const match = value.match(/^([1-9][0-9]*)(s|m|h)$/u);
862
+ if (match === null)
863
+ invalidValue(token, value);
864
+ const ms = Number(match[1]) * ({ s: 1000, m: 60000, h: 3600000 }[match[2]]);
865
+ if (!Number.isSafeInteger(ms) || ms > 86400000)
866
+ invalidValue(token, value);
867
+ args.timeBudgetMs = ms;
868
+ break;
869
+ }
870
+ }
871
+ }
872
+ if (action === "start") {
873
+ args.goal = positional;
874
+ if ((args.goal === undefined) === (args.goalFile === undefined))
875
+ throw new CliArgumentError("RUN_GOAL_REQUIRED", "Provide exactly one quoted goal or --goal-file.");
876
+ if (args.host === undefined)
877
+ throw new CliArgumentError("RUN_HOST_REQUIRED", "Choose --host claude or --host codex.");
878
+ if (args.questionId !== undefined || args.answer !== undefined || args.answerFile !== undefined)
879
+ throw new CliArgumentError("CLI_INVALID_OPTION", "Answer options require run respond.");
880
+ }
881
+ else {
882
+ if (positional === undefined || !/^[a-z][a-z0-9-]{7,63}$/u.test(positional))
883
+ throw new CliArgumentError("RUN_ID_REQUIRED", "Control actions require a valid run ID.");
884
+ args.runId = positional;
885
+ if (args.host !== undefined || args.goalFile !== undefined || args.jobs !== undefined || args.timeBudgetMs !== undefined || args.wait)
886
+ throw new CliArgumentError("CLI_INVALID_OPTION", "Start options are not accepted by run control actions.");
887
+ if (action === "respond") {
888
+ if (args.questionId === undefined || (args.answer === undefined) === (args.answerFile === undefined))
889
+ throw new CliArgumentError("RUN_ANSWER_REQUIRED", "Respond requires --question-id and exactly one --answer or --answer-file.");
890
+ }
891
+ else if (args.questionId !== undefined || args.answer !== undefined || args.answerFile !== undefined)
892
+ throw new CliArgumentError("CLI_INVALID_OPTION", "Answer options require run respond.");
893
+ }
894
+ return args;
895
+ }
@@ -1,4 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ import { productionRunContext } from "./run-deps.js";
3
+ import { AgentOpsError } from "../../../runtime/src/fs/paths.js";
4
+ import { recordRunVerification } from "../../../runtime/src/run/verification.js";
5
+ import { runOwnedLocalProof } from "./owned-run-step.js";
6
+ import { runRunCommand } from "./commands/run.js";
2
7
  import { readFile, readdir, stat } from "node:fs/promises";
3
8
  import { randomUUID } from "node:crypto";
4
9
  import { homedir } from "node:os";
@@ -17,10 +22,10 @@ import { calculateConfigHash } from "../../../runtime/src/config/hash.js";
17
22
  import { FileEvidenceStore } from "../../../runtime/src/verify/evidence.js";
18
23
  import { calculateSourceFingerprint } from "../../../runtime/src/verify/source-fingerprint.js";
19
24
  import { VerificationService } from "../../../runtime/src/verify/service.js";
20
- import { NodeVerificationProcessRunner } from "../../../runtime/src/verify/spawn.js";
25
+ import { registeredRunProofRunner } from "../../../runtime/src/verify/spawn.js";
21
26
  import { COMMAND_NAMES, parseArgs } from "./args.js";
22
27
  import { runCli } from "./cli.js";
23
- import { loadEffectiveConfig, repositoryTrust, repositoryTrustBinding } from "./context.js";
28
+ import { loadEffectiveConfig, runPolicyContext, repositoryTrust, repositoryTrustBinding } from "./context.js";
24
29
  import { runHookProcess } from "./hook-process.js";
25
30
  import { gitRunner, trustStore, worktreeDependencies } from "./parallel-deps.js";
26
31
  import { selectYesNo, writeBanner } from "./ui.js";
@@ -42,6 +47,8 @@ import { resolveReviewScope } from "../../../runtime/src/review/scope.js";
42
47
  import { resolveReviewRole } from "../../../runtime/src/review/roles.js";
43
48
  import { runTrustCommand } from "./commands/trust.js";
44
49
  import { runVerifyCommand } from "./commands/verify.js";
50
+ import { runAdvanceCommand } from "./commands/advance.js";
51
+ import { runReviewShowCommand } from "./commands/review-show.js";
45
52
  import { runAllowStopCommand } from "./commands/allow-stop.js";
46
53
  import { CompletionGateService } from "../../../runtime/src/hooks/completion-gate.js";
47
54
  import { formatUpdatePlan, runUpdateCommand } from "./commands/update.js";
@@ -163,7 +170,22 @@ function runAgy(args, options = {}) {
163
170
  ...(process.platform === "win32" ? { shell: true } : {})
164
171
  });
165
172
  }
166
- if (argv[0] === "agy-run") {
173
+ let ownedProofExit = null;
174
+ try {
175
+ ownedProofExit = await runOwnedLocalProof(argv);
176
+ }
177
+ catch (cause) {
178
+ const code = cause instanceof AgentOpsError ? cause.code : "RUN_LOCAL_PROOF_FAILED";
179
+ const message = cause instanceof AgentOpsError ? cause.message : "Local proof could not be registered safely.";
180
+ if (argv.includes("--json"))
181
+ process.stdout.write(JSON.stringify(errorEnvelope(code, message)) + "\n");
182
+ else
183
+ process.stderr.write(message + "\n");
184
+ ownedProofExit = 2;
185
+ }
186
+ if (ownedProofExit !== null)
187
+ process.exitCode = ownedProofExit;
188
+ else if (argv[0] === "agy-run") {
167
189
  try {
168
190
  const root = process.cwd();
169
191
  const config = (await loadEffectiveConfig(root, "project")).config;
@@ -222,6 +244,13 @@ else {
222
244
  const isTTY = !args.json &&
223
245
  process.stdin.isTTY === true &&
224
246
  process.stdout.isTTY === true;
247
+ if ((["init", "update"].includes(args.command) || (args.command === "trust" && args.action !== "status")) &&
248
+ (process.env.AGENT_OPS_RUN_ID !== undefined || await runPolicyContext(process.cwd()) !== null))
249
+ throw new AgentOpsError("RUN_POLICY_COORDINATOR_REQUIRED", "Run workers must request scoped policy review from the coordinator; permanent trust and host rules cannot be changed by the run.");
250
+ if (args.command === "run") {
251
+ const context = await productionRunContext(root);
252
+ return await runRunCommand({ args, ...context, root: context.mainRoot });
253
+ }
225
254
  if (args.command === "init") {
226
255
  const store = trustStore();
227
256
  return await runInitCommand({
@@ -265,6 +294,17 @@ else {
265
294
  root,
266
295
  toolkitVersion: CLI_VERSION,
267
296
  probes: {
297
+ acceptanceCoverage: async () => {
298
+ const service = new TaskService(new FileTaskStore(join(root, ".agent-ops/tasks/state.json"), root), { completion: { root, gitRunner: gitRunner(root), loadConfig: async () => config } });
299
+ const records = (await service.list()).filter(r => r.status !== "archived");
300
+ const rows = await Promise.all(records.map(r => service.coverage(r.task.id)));
301
+ const criteria = rows.flatMap(row => row.rows);
302
+ const mechanical = criteria.filter(c => c.mode === "behavioral" || c.mode === "invariant");
303
+ return { status: "PASS", message: criteria.length + " criteria; " +
304
+ criteria.filter(c => c.mode === "legacy").length + " legacy; " + mechanical.filter(c => c.status === "proven").length + "/" + mechanical.length +
305
+ " mechanical proven; " + criteria.filter(c => c.mode === "review-only").length + " review-only; originally mechanical " + rows.reduce((n, r) => n + r.originalMechanical, 0) +
306
+ "; undischarged " + rows.reduce((n, r) => n + r.rows.filter(c => c.status !== "proven" && !r.reviewed).length, 0) };
307
+ },
268
308
  ...(doctorManifest?.harness.includes("agy") === true
269
309
  ? {
270
310
  agyRuntime: () => {
@@ -402,6 +442,14 @@ else {
402
442
  const sessionId = args.sessionId ?? (actsAsSession
403
443
  ? await resolveCommandSessionId(root)
404
444
  : sessionIdFromEnvironment() ?? await readRecordedSessionId(root));
445
+ if (args.action === "advance") {
446
+ return await runAdvanceCommand({
447
+ cwd: root,
448
+ sessionId: sessionId ?? await resolveCommandSessionId(root),
449
+ parentTaskId: args.taskId,
450
+ deps: worktreeDependencies()
451
+ });
452
+ }
405
453
  const createConfig = args.action === "create"
406
454
  ? (await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project")).config
407
455
  : undefined;
@@ -431,7 +479,8 @@ else {
431
479
  return {
432
480
  path: record.path,
433
481
  base: record.base,
434
- service: new TaskService(new FileTaskStore(join(record.path, ".agent-ops", "tasks", "state.json"), record.path))
482
+ service: new TaskService(new FileTaskStore(join(record.path, ".agent-ops", "tasks", "state.json"), record.path), { completion: { root: record.path, gitRunner: gitRunner(record.path),
483
+ loadConfig: async () => (await loadEffectiveConfig(record.path, "project")).config } })
435
484
  };
436
485
  }
437
486
  }
@@ -491,6 +540,7 @@ else {
491
540
  config: reviewConfig,
492
541
  evidenceStore: new FileEvidenceStore(root, root),
493
542
  execute: inSlot(createReviewExecutor({
543
+ runner: registeredRunProofRunner(),
494
544
  targets: configuredReviewTargets,
495
545
  cwd: root,
496
546
  ...(reviewRole?.model === undefined
@@ -503,10 +553,11 @@ else {
503
553
  ? {}
504
554
  : { timeoutMs: reviewRole.timeoutMs }),
505
555
  preflightTarget,
506
- verifySourceFingerprint: async (expected) => {
556
+ verifySourceFingerprint: async (expected, scope) => {
507
557
  const currentScope = await resolveReviewScope({
508
558
  root,
509
559
  runner: reviewGit,
560
+ ...(scope?.mode === "base" && scope.noChange === true ? { noChangePaths: scope.changedFiles } : {}),
510
561
  ...(reviewArgs.base === undefined ? {} : { base: reviewArgs.base })
511
562
  });
512
563
  return await calculateSourceFingerprint(root, currentScope, reviewGit) === expected;
@@ -519,6 +570,9 @@ else {
519
570
  };
520
571
  };
521
572
  if (args.command === "review") {
573
+ if (args.action === "show") {
574
+ return await runReviewShowCommand({ cwd: root, taskId: args.taskId, deps: worktreeDependencies() });
575
+ }
522
576
  const controller = new AbortController();
523
577
  let interruptedBy;
524
578
  const interrupt = (signal) => {
@@ -576,7 +630,7 @@ else {
576
630
  scope: batchScope,
577
631
  config: batchConfig,
578
632
  gitRunner: batchGit,
579
- processRunner: new NodeVerificationProcessRunner(),
633
+ processRunner: registeredRunProofRunner(),
580
634
  taskService,
581
635
  evidenceStore: new FileEvidenceStore(root, root),
582
636
  trusted,
@@ -620,17 +674,23 @@ else {
620
674
  return await runVerifyCommand({
621
675
  args,
622
676
  taskService,
623
- service: new VerificationService({
624
- root,
625
- scope: args.scope === "user" ? "user" : "project",
626
- config,
627
- gitRunner: gitRunner(root),
628
- processRunner: new NodeVerificationProcessRunner(),
629
- taskService,
630
- evidenceStore: new FileEvidenceStore(root, root),
631
- trusted: trustStatus === "TRUSTED",
632
- ...(args.base === undefined ? {} : { base: args.base })
633
- })
677
+ service: { verify: async (taskId) => {
678
+ const run = await runPolicyContext(root);
679
+ const report = await new VerificationService({
680
+ root,
681
+ scope: args.scope === "user" ? "user" : "project",
682
+ config,
683
+ gitRunner: gitRunner(root),
684
+ processRunner: registeredRunProofRunner(),
685
+ taskService,
686
+ evidenceStore: new FileEvidenceStore(root, root),
687
+ trusted: trustStatus === "TRUSTED",
688
+ ...(args.base === undefined ? {} : { base: args.base })
689
+ }).verify(taskId);
690
+ if (run !== null)
691
+ await recordRunVerification(run.repository, run.state, run.worker?.workerId ?? run.state.coordinatorId, await taskService.status({ taskId }), report);
692
+ return report;
693
+ } }
634
694
  });
635
695
  }
636
696
  if (args.command === "trust") {