@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.
- package/README.md +64 -0
- package/dist/packages/cli/src/args.js +214 -10
- package/dist/packages/cli/src/bin.js +77 -17
- package/dist/packages/cli/src/cli.js +42 -10
- package/dist/packages/cli/src/commands/advance.js +173 -0
- package/dist/packages/cli/src/commands/review-show.js +189 -0
- package/dist/packages/cli/src/commands/review.js +201 -34
- package/dist/packages/cli/src/commands/run.js +111 -0
- package/dist/packages/cli/src/commands/task.js +62 -17
- package/dist/packages/cli/src/commands/verify.js +1 -0
- package/dist/packages/cli/src/context.js +63 -1
- package/dist/packages/cli/src/hook-process.js +31 -1
- package/dist/packages/cli/src/owned-run-step.js +65 -0
- package/dist/packages/cli/src/parallel-deps.js +18 -5
- package/dist/packages/cli/src/run-deps.js +314 -0
- package/dist/packages/cli/src/run-entry.js +943 -0
- package/dist/packages/cli/src/run-setup-entry.js +32 -0
- package/dist/packages/cli/src/run-step-entry.js +58 -0
- package/dist/runtime/src/config/explain.js +6 -0
- package/dist/runtime/src/config/hash.js +1 -1
- package/dist/runtime/src/config/merge.js +9 -1
- package/dist/runtime/src/contracts.js +2 -2
- package/dist/runtime/src/hooks/completion-gate.js +8 -2
- package/dist/runtime/src/install/doctor.js +2 -0
- package/dist/runtime/src/install/harness.js +1 -1
- package/dist/runtime/src/install/preauth.js +10 -1
- package/dist/runtime/src/parallel/finish.js +217 -39
- package/dist/runtime/src/parallel/guard.js +5 -1
- package/dist/runtime/src/parallel/integrate.js +416 -0
- package/dist/runtime/src/parallel/manage.js +4 -1
- package/dist/runtime/src/parallel/receipt.js +153 -0
- package/dist/runtime/src/parallel/service.js +45 -3
- package/dist/runtime/src/review/attestation.js +60 -11
- package/dist/runtime/src/review/execute.js +24 -1
- package/dist/runtime/src/review/packet.js +35 -1
- package/dist/runtime/src/review/render.js +71 -0
- package/dist/runtime/src/review/runner.js +12 -1
- package/dist/runtime/src/review/scope.js +18 -1
- package/dist/runtime/src/review/slots.js +35 -10
- package/dist/runtime/src/run/authorization.js +60 -0
- package/dist/runtime/src/run/budget.js +30 -0
- package/dist/runtime/src/run/controls.js +139 -0
- package/dist/runtime/src/run/convergence.js +177 -0
- package/dist/runtime/src/run/hosts/claude.js +180 -0
- package/dist/runtime/src/run/hosts/codex.js +279 -0
- package/dist/runtime/src/run/hosts/index.js +4 -0
- package/dist/runtime/src/run/hosts/protocol.js +254 -0
- package/dist/runtime/src/run/hosts/types.js +1 -0
- package/dist/runtime/src/run/hosts/util.js +136 -0
- package/dist/runtime/src/run/integration.js +204 -0
- package/dist/runtime/src/run/macOS.js +189 -0
- package/dist/runtime/src/run/ownership.js +22 -0
- package/dist/runtime/src/run/planning.js +25 -0
- package/dist/runtime/src/run/policy-transition.js +1076 -0
- package/dist/runtime/src/run/policy.js +176 -0
- package/dist/runtime/src/run/ratchet.js +71 -0
- package/dist/runtime/src/run/scheduler.js +260 -0
- package/dist/runtime/src/run/service.js +425 -0
- package/dist/runtime/src/run/supervisor.js +635 -0
- package/dist/runtime/src/run/transport.js +159 -0
- package/dist/runtime/src/run/types.js +1 -0
- package/dist/runtime/src/run/usage.js +81 -0
- package/dist/runtime/src/run/verification.js +64 -0
- package/dist/runtime/src/run/worker-gate.js +48 -0
- package/dist/runtime/src/schema/acceptance.js +91 -0
- package/dist/runtime/src/schema/validate.js +72 -7
- package/dist/runtime/src/task/completion.js +26 -6
- package/dist/runtime/src/task/contract.js +17 -0
- package/dist/runtime/src/task/pin-finding.js +81 -0
- package/dist/runtime/src/task/render.js +3 -0
- package/dist/runtime/src/task/service.js +361 -10
- package/dist/runtime/src/task/store.js +57 -7
- package/dist/runtime/src/verify/acceptance-coverage.js +85 -0
- package/dist/runtime/src/verify/acceptance-protocol.js +252 -0
- package/dist/runtime/src/verify/acceptance-replay.js +239 -0
- package/dist/runtime/src/verify/adapters/index.js +5 -0
- package/dist/runtime/src/verify/adapters/jest.js +142 -0
- package/dist/runtime/src/verify/adapters/node-reporter.js +20 -0
- package/dist/runtime/src/verify/adapters/node.js +254 -0
- package/dist/runtime/src/verify/adapters/pytest.js +188 -0
- package/dist/runtime/src/verify/adapters/rust.js +84 -0
- package/dist/runtime/src/verify/adapters/shared.js +82 -0
- package/dist/runtime/src/verify/adapters/vitest.js +218 -0
- package/dist/runtime/src/verify/evidence.js +5 -2
- package/dist/runtime/src/verify/service.js +24 -8
- package/dist/runtime/src/verify/source-fingerprint.js +1 -1
- package/dist/runtime/src/verify/spawn.js +13 -2
- package/docs/en/guides/configuration.md +10 -0
- package/docs/en/spec/acceptance-and-evidence.md +58 -0
- package/docs/en/spec/delegation.md +4 -4
- package/docs/en/spec/loop-engineering.md +49 -0
- package/docs/en/spec/review.md +24 -2
- package/docs/zh-TW/guides/configuration.md +9 -0
- package/docs/zh-TW/spec/acceptance-and-evidence.md +31 -1
- package/docs/zh-TW/spec/delegation.md +4 -4
- package/docs/zh-TW/spec/loop-engineering.md +22 -0
- package/docs/zh-TW/spec/review.md +24 -2
- package/package.json +1 -1
- package/schemas/config.schema.json +221 -19
- package/schemas/evidence.schema.json +129 -6
- package/schemas/task.schema.json +268 -6
- package/templates/acceptance/agent_ops_acceptance.rs +36 -0
- 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" &&
|
|
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 {
|
|
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
|
-
|
|
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:
|
|
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:
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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") {
|