@kylecheng3146/agent-ops 0.1.16 → 0.1.18
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 +4 -4
- package/dist/packages/cli/src/args.js +23 -3
- package/dist/packages/cli/src/cli.js +2 -0
- package/dist/packages/cli/src/commands/review.js +4 -1
- package/dist/packages/cli/src/commands/task.js +9 -3
- package/dist/packages/cli/src/wizard.js +5 -5
- package/dist/runtime/src/install/doctor.js +18 -4
- package/dist/runtime/src/install/harness.js +3 -0
- package/dist/runtime/src/review/execute.js +329 -118
- package/dist/runtime/src/review/extract.js +10 -2
- package/dist/runtime/src/review/invocation.js +67 -15
- package/dist/runtime/src/review/probe.js +10 -4
- package/dist/runtime/src/review/render.js +18 -2
- package/dist/runtime/src/review/roles.js +13 -3
- package/dist/runtime/src/review/runner.js +134 -32
- package/dist/runtime/src/schema/validate.js +7 -1
- package/dist/runtime/src/task/render.js +3 -0
- package/dist/runtime/src/task/service.js +18 -2
- package/dist/runtime/src/verify/spawn.js +27 -10
- package/docs/en/guides/configuration.md +14 -5
- package/docs/en/spec/review.md +21 -4
- package/docs/zh-TW/guides/configuration.md +12 -5
- package/docs/zh-TW/spec/review.md +15 -4
- package/package.json +1 -1
- package/schemas/task.schema.json +3 -0
package/README.md
CHANGED
|
@@ -288,10 +288,10 @@ is displayed; a minimal source-fingerprint attestation is persisted after PASS.
|
|
|
288
288
|
|
|
289
289
|
Each attempt uses a fresh temporary cwd, a small allowlisted environment, and a
|
|
290
290
|
target-native read-only mode. Claude additionally uses its complete safe mode;
|
|
291
|
-
Codex ignores user config and persistence, while Agy
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
review target.
|
|
291
|
+
Codex ignores user config and persistence, while Agy runs in sandboxed plan
|
|
292
|
+
mode against a disposable repository clone. Codex and Agy preserve their
|
|
293
|
+
existing login environment, so their context isolation is intentionally weaker
|
|
294
|
+
than Claude's. `opencode` is not a review target.
|
|
295
295
|
|
|
296
296
|
The first valid `PASS` or `FAIL` is final. Attempts that produce no verdict —
|
|
297
297
|
including login failures and unparseable output — advance to the next target
|
|
@@ -63,6 +63,7 @@ export function parseArgs(argv) {
|
|
|
63
63
|
let harness;
|
|
64
64
|
const hookTargets = [];
|
|
65
65
|
let taskId;
|
|
66
|
+
let parentTaskId;
|
|
66
67
|
let targetVersion;
|
|
67
68
|
let title;
|
|
68
69
|
let sessionId;
|
|
@@ -146,6 +147,14 @@ export function parseArgs(argv) {
|
|
|
146
147
|
index += 1;
|
|
147
148
|
break;
|
|
148
149
|
}
|
|
150
|
+
case "--parent": {
|
|
151
|
+
if (parentTaskId !== undefined) {
|
|
152
|
+
duplicate(token);
|
|
153
|
+
}
|
|
154
|
+
parentTaskId = readOptionValue(argv, index, token);
|
|
155
|
+
index += 1;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
149
158
|
case "--target-version": {
|
|
150
159
|
if (targetVersion !== undefined) {
|
|
151
160
|
duplicate(token);
|
|
@@ -276,6 +285,7 @@ export function parseArgs(argv) {
|
|
|
276
285
|
hookTargets.length > 0 ||
|
|
277
286
|
profiles.length > 0 ||
|
|
278
287
|
taskId !== undefined ||
|
|
288
|
+
parentTaskId !== undefined ||
|
|
279
289
|
targetVersion !== undefined ||
|
|
280
290
|
title !== undefined ||
|
|
281
291
|
criteria.length > 0 ||
|
|
@@ -310,6 +320,9 @@ export function parseArgs(argv) {
|
|
|
310
320
|
(hasTaskTargetOptions || hasTaskMutationOptions)) {
|
|
311
321
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Task target options may be used only with task or verify.");
|
|
312
322
|
}
|
|
323
|
+
if (command !== "task" && parentTaskId !== undefined) {
|
|
324
|
+
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--parent may be used only with task create or task status.");
|
|
325
|
+
}
|
|
313
326
|
if (command !== "update" && targetVersion !== undefined) {
|
|
314
327
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--target-version may be used only with update.");
|
|
315
328
|
}
|
|
@@ -342,20 +355,26 @@ export function parseArgs(argv) {
|
|
|
342
355
|
(title !== undefined ||
|
|
343
356
|
criteria.length > 0 ||
|
|
344
357
|
evidence.length > 0 ||
|
|
358
|
+
// --parent filters the listing, so it cannot name a single target.
|
|
359
|
+
(parentTaskId !== undefined &&
|
|
360
|
+
(taskId !== undefined || sessionId !== undefined)) ||
|
|
345
361
|
(taskId !== undefined && sessionId !== undefined))) ||
|
|
346
362
|
(action === "attach" &&
|
|
347
363
|
(title !== undefined ||
|
|
348
364
|
criteria.length > 0 ||
|
|
349
|
-
evidence.length > 0
|
|
365
|
+
evidence.length > 0 ||
|
|
366
|
+
parentTaskId !== undefined)) ||
|
|
350
367
|
(action === "complete" &&
|
|
351
368
|
(title !== undefined ||
|
|
352
369
|
criteria.length > 0 ||
|
|
353
|
-
sessionId !== undefined
|
|
370
|
+
sessionId !== undefined ||
|
|
371
|
+
parentTaskId !== undefined)) ||
|
|
354
372
|
((action === "archive" || action === "export") &&
|
|
355
373
|
(title !== undefined ||
|
|
356
374
|
criteria.length > 0 ||
|
|
357
375
|
evidence.length > 0 ||
|
|
358
|
-
sessionId !== undefined
|
|
376
|
+
sessionId !== undefined ||
|
|
377
|
+
parentTaskId !== undefined));
|
|
359
378
|
if (taskOptionInvalid) {
|
|
360
379
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", `Unsupported option for task ${action}.`);
|
|
361
380
|
}
|
|
@@ -392,6 +411,7 @@ export function parseArgs(argv) {
|
|
|
392
411
|
...(hookTargets.length === 0 ? {} : { hookTargets }),
|
|
393
412
|
profiles,
|
|
394
413
|
...(taskId === undefined ? {} : { taskId }),
|
|
414
|
+
...(parentTaskId === undefined ? {} : { parentTaskId }),
|
|
395
415
|
...(targetVersion === undefined ? {} : { targetVersion }),
|
|
396
416
|
...(title === undefined ? {} : { title }),
|
|
397
417
|
...(reviewTargets.length === 0 ? {} : { reviewTargets }),
|
|
@@ -38,6 +38,8 @@ Options:
|
|
|
38
38
|
--check-auth Doctor only: probe each review target's
|
|
39
39
|
authentication with one real call
|
|
40
40
|
--task <id>
|
|
41
|
+
--parent <task-id> Task create: record a subtask of this
|
|
42
|
+
task; task status: list its subtasks
|
|
41
43
|
--target-version <version> Update target version (offline-capable)
|
|
42
44
|
--title <text>
|
|
43
45
|
--criterion <json> Repeatable
|
|
@@ -358,7 +358,10 @@ export async function runReviewCommand(options) {
|
|
|
358
358
|
});
|
|
359
359
|
}
|
|
360
360
|
try {
|
|
361
|
-
await assertSafeSupportingPaths(options.root,
|
|
361
|
+
await assertSafeSupportingPaths(options.root, [
|
|
362
|
+
...result.report.supportingFilesInspected,
|
|
363
|
+
...(result.adversarial?.report.supportingFilesInspected ?? [])
|
|
364
|
+
]);
|
|
362
365
|
}
|
|
363
366
|
catch (error) {
|
|
364
367
|
if (scopeReason(error) === "unsafe-review-path") {
|
|
@@ -92,16 +92,22 @@ export async function runTaskCommand(options) {
|
|
|
92
92
|
criteria: (options.args.criteria ?? []).map(parseCriterion),
|
|
93
93
|
...(options.policyConfigHash === undefined
|
|
94
94
|
? {}
|
|
95
|
-
: { policyConfigHash: options.policyConfigHash })
|
|
95
|
+
: { policyConfigHash: options.policyConfigHash }),
|
|
96
|
+
...(options.args.parentTaskId === undefined
|
|
97
|
+
? {}
|
|
98
|
+
: { parentTaskId: options.args.parentTaskId })
|
|
96
99
|
});
|
|
97
100
|
return taskEnvelope(action, "TASK_CREATED", `Created task ${record.task.id}.`, record);
|
|
98
101
|
}
|
|
99
102
|
if (action === "status") {
|
|
100
103
|
if (options.args.taskId === undefined && sessionId === undefined) {
|
|
101
|
-
const
|
|
104
|
+
const parentTaskId = options.args.parentTaskId;
|
|
105
|
+
const records = await options.service.list(parentTaskId === undefined ? {} : { parentTaskId });
|
|
102
106
|
return okEnvelope("TASK_LISTED", {
|
|
103
107
|
action,
|
|
104
|
-
message:
|
|
108
|
+
message: parentTaskId === undefined
|
|
109
|
+
? `Listed ${records.length} task(s).`
|
|
110
|
+
: `Listed ${records.length} subtask(s) of ${parentTaskId}.`,
|
|
105
111
|
records,
|
|
106
112
|
text: renderTaskList(records)
|
|
107
113
|
});
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { CliArgumentError } from "./args.js";
|
|
2
|
-
import { DEFAULT_REVIEW_TARGETS } from "../../../runtime/src/review/roles.js";
|
|
2
|
+
import { DEFAULT_REVIEW_TARGETS, REVIEW_TARGET_ORDER } from "../../../runtime/src/review/roles.js";
|
|
3
3
|
import { HARNESS_IDS, resolveHarnessSelection } from "../../../runtime/src/install/harness.js";
|
|
4
4
|
import { selectOption, selectOptions } from "./ui.js";
|
|
5
5
|
const SCOPES = new Set(["project", "user"]);
|
|
6
6
|
const PROFILES = new Set(["advisory", "core", "guardrails", "loop"]);
|
|
7
7
|
const DEFAULT_HARNESS = [];
|
|
8
|
-
const REVIEW_TARGET_SET = new Set(
|
|
8
|
+
const REVIEW_TARGET_SET = new Set(REVIEW_TARGET_ORDER);
|
|
9
9
|
const REVIEW_TARGET_CHOICES = DEFAULT_REVIEW_TARGETS.map((id) => ({
|
|
10
10
|
label: id,
|
|
11
11
|
value: id,
|
|
12
12
|
description: id === "codex"
|
|
13
|
-
? "
|
|
13
|
+
? "Runs read-only from its own credential home."
|
|
14
14
|
: id === "agy"
|
|
15
|
-
? "
|
|
15
|
+
? "Runs in sandboxed plan mode against a disposable clone."
|
|
16
16
|
: "Runs in fresh safe mode with context isolation."
|
|
17
17
|
}));
|
|
18
18
|
function selectReviewTargets(raw) {
|
|
@@ -26,7 +26,7 @@ function selectReviewTargets(raw) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
// Declared order wins: the chain order is the option list, not click order.
|
|
29
|
-
return
|
|
29
|
+
return REVIEW_TARGET_ORDER.filter((target) => values.includes(target));
|
|
30
30
|
}
|
|
31
31
|
function affirmative(raw) {
|
|
32
32
|
return /^(y|yes)$/i.test(raw.trim());
|
|
@@ -370,21 +370,35 @@ async function checkReviewTargets(config, probe, checkAuth) {
|
|
|
370
370
|
return check("review-targets", "PASS", `External review targets: ${targets.join(", ")}. ` +
|
|
371
371
|
"Login state unverified; run: agent-ops doctor --check-auth");
|
|
372
372
|
}
|
|
373
|
+
// Every target is probed before reporting: an ineligible entry is only
|
|
374
|
+
// DEGRADED, so returning at the first one would hide a genuinely broken
|
|
375
|
+
// eligible target behind it and make the verdict depend on ordering.
|
|
376
|
+
const failures = [];
|
|
377
|
+
const degraded = [];
|
|
373
378
|
for (const target of targets) {
|
|
374
379
|
const result = await probe(target, checkAuth);
|
|
375
380
|
if (result === "missing-executable") {
|
|
376
|
-
|
|
381
|
+
failures.push(check("review-targets", "FAIL", `${target} not found.`, undefined, `Install ${target}, or remove "${target}" from reviewRoles[].targets.`));
|
|
382
|
+
continue;
|
|
377
383
|
}
|
|
378
384
|
if (result === "ineligible") {
|
|
379
|
-
|
|
385
|
+
// Not a broken installation: review simply skips this target. A FAIL here
|
|
386
|
+
// would light up every config that merely still names one.
|
|
387
|
+
degraded.push(check("review-targets", "DEGRADED", `${target} has no read-only mode and is skipped when reviewing.`, undefined, `Remove "${target}" from reviewRoles[].targets.`));
|
|
388
|
+
continue;
|
|
380
389
|
}
|
|
381
390
|
if (result === "timeout") {
|
|
382
|
-
|
|
391
|
+
failures.push(check("review-targets", "FAIL", `${target} did not answer in time.`, undefined, "Re-run: agent-ops doctor --check-auth"));
|
|
392
|
+
continue;
|
|
383
393
|
}
|
|
384
394
|
if (checkAuth && result !== "ok") {
|
|
385
|
-
|
|
395
|
+
failures.push(check("review-targets", "FAIL", `${target} is installed but not authenticated, or it rejected the call.`, undefined, `Run: ${target} login`));
|
|
386
396
|
}
|
|
387
397
|
}
|
|
398
|
+
const worst = failures[0] ?? degraded[0];
|
|
399
|
+
if (worst !== undefined) {
|
|
400
|
+
return worst;
|
|
401
|
+
}
|
|
388
402
|
return check("review-targets", "PASS", checkAuth
|
|
389
403
|
? `External review targets authenticated: ${targets.join(", ")}.`
|
|
390
404
|
: `External review targets: ${targets.join(", ")}. ` +
|
|
@@ -246,6 +246,9 @@ export function managedRules(descriptor, context) {
|
|
|
246
246
|
if (context.capabilities.includes("rules")) {
|
|
247
247
|
lines.push("For every change:", "", "1. Define two to five mechanically verifiable acceptance criteria.", "2. Inspect the smallest relevant scope and preserve unrelated changes.", "3. Apply the smallest safe change.", "4. Run evidence-producing verification for every criterion.", "5. Obtain independent review before claiming completion, via", " `agent-ops review --yes` (or the CLI's equivalent invocation). Never call a", " review-target CLI (agy, codex, claude) directly — direct calls skip", " the enforced read-only sandbox flags and can hang or fail on command", " permission prompts.", "", "Treat `.agent-ops/config.json` as verifier authority. Discovery output is", "only a proposal until a user confirms it. Repository commands require an", "exact matching trust record. Confirmed project init/update grants it", "automatically when verification commands are configured.", "");
|
|
248
248
|
}
|
|
249
|
+
if (context.capabilities.includes("task")) {
|
|
250
|
+
lines.push("Split a change that exceeds five acceptance criteria into subtasks:", "`agent-ops task create --parent <task-id>` records one, and", "`agent-ops task status --parent <task-id>` lists them. Each subtask", "carries its own criteria, verification, and independent review;", "completing one never completes its parent.", "");
|
|
251
|
+
}
|
|
249
252
|
if (context.capabilities.includes("lifecycle-summary")) {
|
|
250
253
|
lines.push("Advisory lifecycle summaries and local logs are informational. Advisory", "failures must remain fail-open and cannot become verification evidence.", "");
|
|
251
254
|
}
|