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