@kylecheng3146/agent-ops 0.1.6 → 0.1.8
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 +27 -0
- package/dist/packages/cli/src/args.js +47 -0
- package/dist/packages/cli/src/bin.js +74 -25
- package/dist/packages/cli/src/cli.js +13 -1
- package/dist/packages/cli/src/commands/init.js +4 -1
- package/dist/packages/cli/src/commands/review.js +371 -27
- package/dist/packages/cli/src/commands/task.js +4 -1
- package/dist/packages/cli/src/commands/verify.js +13 -1
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/cli/src/wizard.js +62 -3
- package/dist/runtime/src/config/merge.js +17 -2
- package/dist/runtime/src/contracts.js +1 -1
- package/dist/runtime/src/install/doctor.js +42 -1
- package/dist/runtime/src/install/plan.js +11 -5
- package/dist/runtime/src/review/execute.js +180 -0
- package/dist/runtime/src/review/extract.js +69 -0
- package/dist/runtime/src/review/invocation.js +116 -0
- package/dist/runtime/src/review/packet.js +42 -5
- package/dist/runtime/src/review/probe.js +72 -0
- package/dist/runtime/src/review/render.js +62 -0
- package/dist/runtime/src/review/report.js +183 -0
- package/dist/runtime/src/review/result.js +2 -2
- package/dist/runtime/src/review/roles.js +35 -0
- package/dist/runtime/src/review/runner.js +98 -12
- package/dist/runtime/src/review/scope.js +123 -0
- package/dist/runtime/src/schema/validate.js +80 -0
- package/dist/runtime/src/task/service.js +46 -1
- package/dist/runtime/src/task/store.js +16 -4
- package/dist/runtime/src/verify/change-surface.js +38 -2
- package/dist/runtime/src/verify/command-executor.js +4 -1
- package/dist/runtime/src/verify/evidence.js +36 -0
- package/dist/runtime/src/verify/scope.js +1 -2
- package/dist/runtime/src/verify/service.js +66 -9
- package/dist/runtime/src/verify/source-fingerprint.js +49 -0
- package/dist/runtime/src/verify/spawn.js +9 -3
- package/docs/en/guides/configuration.md +68 -0
- package/docs/en/spec/review.md +37 -4
- package/docs/zh-TW/guides/configuration.md +60 -0
- package/docs/zh-TW/spec/review.md +33 -3
- package/package.json +1 -1
- package/schemas/config.schema.json +29 -0
- package/schemas/evidence.schema.json +16 -1
- package/schemas/review-report.schema.json +48 -0
package/README.md
CHANGED
|
@@ -261,6 +261,33 @@ For a full command reference, run `agent-ops --help`. The `task`, `verify`, and
|
|
|
261
261
|
`review` commands support acceptance tracking and independent verification when
|
|
262
262
|
the project configuration defines those workflows.
|
|
263
263
|
|
|
264
|
+
### External review
|
|
265
|
+
|
|
266
|
+
`agent-ops review` can hand the review to another agent CLI, so the work is not
|
|
267
|
+
judged by the agent that produced it. Enable it during `agent-ops init` (the
|
|
268
|
+
default is off). Reviews require an attached task, current required
|
|
269
|
+
verification evidence, and a deterministic worktree (or `--base`) scope. The
|
|
270
|
+
native-schema detailed report is displayed but not persisted.
|
|
271
|
+
|
|
272
|
+
Each attempt uses a fresh temporary cwd, a small allowlisted environment, and a
|
|
273
|
+
target-native read-only/context-isolation mode. Currently only Claude safe mode
|
|
274
|
+
meets the full isolation contract; configured Codex and Agy entries return
|
|
275
|
+
`capability-unavailable` rather than run with a weaker boundary. `opencode` is
|
|
276
|
+
not a review target.
|
|
277
|
+
|
|
278
|
+
The first target that actually runs produces the verdict. A `FAIL` is final:
|
|
279
|
+
the chain never retries elsewhere after a real verdict. `--yes` is still
|
|
280
|
+
required for every run, since each run spends another provider's quota.
|
|
281
|
+
|
|
282
|
+
Authentication is diagnosed, never guessed:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
agent-ops doctor # presence only: no tokens, no network
|
|
286
|
+
agent-ops doctor --check-auth # one real print call per configured target
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
See [Configuration](docs/en/guides/configuration.md) for the full contract.
|
|
290
|
+
|
|
264
291
|
## Project principles
|
|
265
292
|
|
|
266
293
|
- Define verifiable success before making changes.
|
|
@@ -13,6 +13,8 @@ export const COMMAND_NAMES = [
|
|
|
13
13
|
const COMMAND_SET = new Set(COMMAND_NAMES);
|
|
14
14
|
const SCOPES = new Set(["project", "user"]);
|
|
15
15
|
const PROFILES = new Set(["advisory", "core", "guardrails", "loop"]);
|
|
16
|
+
// opencode is absent: it has no read-only flag, so it cannot review.
|
|
17
|
+
const REVIEW_TARGETS = new Set(["agy", "claude", "codex"]);
|
|
16
18
|
export class CliArgumentError extends Error {
|
|
17
19
|
code;
|
|
18
20
|
option;
|
|
@@ -64,9 +66,12 @@ export function parseArgs(argv) {
|
|
|
64
66
|
let targetVersion;
|
|
65
67
|
let title;
|
|
66
68
|
let sessionId;
|
|
69
|
+
let base;
|
|
67
70
|
const profiles = [];
|
|
71
|
+
const reviewTargets = [];
|
|
68
72
|
const criteria = [];
|
|
69
73
|
const evidence = [];
|
|
74
|
+
let checkAuth = false;
|
|
70
75
|
let dryRun = false;
|
|
71
76
|
let json = false;
|
|
72
77
|
let yes = false;
|
|
@@ -121,6 +126,18 @@ export function parseArgs(argv) {
|
|
|
121
126
|
index += 1;
|
|
122
127
|
break;
|
|
123
128
|
}
|
|
129
|
+
case "--review-target": {
|
|
130
|
+
const value = readOptionValue(argv, index, token);
|
|
131
|
+
if (!REVIEW_TARGETS.has(value)) {
|
|
132
|
+
invalidValue(token, value);
|
|
133
|
+
}
|
|
134
|
+
if (reviewTargets.includes(value)) {
|
|
135
|
+
duplicate(`${token} ${value}`);
|
|
136
|
+
}
|
|
137
|
+
reviewTargets.push(value);
|
|
138
|
+
index += 1;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
124
141
|
case "--task": {
|
|
125
142
|
if (taskId !== undefined) {
|
|
126
143
|
duplicate(token);
|
|
@@ -163,6 +180,20 @@ export function parseArgs(argv) {
|
|
|
163
180
|
index += 1;
|
|
164
181
|
break;
|
|
165
182
|
}
|
|
183
|
+
case "--base": {
|
|
184
|
+
if (base !== undefined) {
|
|
185
|
+
duplicate(token);
|
|
186
|
+
}
|
|
187
|
+
base = readOptionValue(argv, index, token);
|
|
188
|
+
index += 1;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case "--check-auth":
|
|
192
|
+
if (checkAuth) {
|
|
193
|
+
duplicate(token);
|
|
194
|
+
}
|
|
195
|
+
checkAuth = true;
|
|
196
|
+
break;
|
|
166
197
|
case "--dry-run":
|
|
167
198
|
if (dryRun) {
|
|
168
199
|
duplicate(token);
|
|
@@ -249,7 +280,10 @@ export function parseArgs(argv) {
|
|
|
249
280
|
title !== undefined ||
|
|
250
281
|
criteria.length > 0 ||
|
|
251
282
|
evidence.length > 0 ||
|
|
283
|
+
reviewTargets.length > 0 ||
|
|
252
284
|
sessionId !== undefined ||
|
|
285
|
+
base !== undefined ||
|
|
286
|
+
checkAuth ||
|
|
253
287
|
dryRun ||
|
|
254
288
|
yes) {
|
|
255
289
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Only --json may be combined with global help or version.");
|
|
@@ -279,11 +313,20 @@ export function parseArgs(argv) {
|
|
|
279
313
|
if (command !== "update" && targetVersion !== undefined) {
|
|
280
314
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--target-version may be used only with update.");
|
|
281
315
|
}
|
|
316
|
+
if (base !== undefined && command !== "verify" && command !== "review") {
|
|
317
|
+
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--base may be used only with verify or review.");
|
|
318
|
+
}
|
|
282
319
|
if (hookTargets.length > 0 &&
|
|
283
320
|
command !== "init" &&
|
|
284
321
|
command !== "update") {
|
|
285
322
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--hook-target may be used only with init or update.");
|
|
286
323
|
}
|
|
324
|
+
if (checkAuth && command !== "doctor") {
|
|
325
|
+
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--check-auth may be used only with doctor.");
|
|
326
|
+
}
|
|
327
|
+
if (reviewTargets.length > 0 && command !== "init") {
|
|
328
|
+
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--review-target may be used only with init.");
|
|
329
|
+
}
|
|
287
330
|
if (command === "task") {
|
|
288
331
|
if (harness !== undefined ||
|
|
289
332
|
profiles.length > 0 ||
|
|
@@ -325,6 +368,7 @@ export function parseArgs(argv) {
|
|
|
325
368
|
evidence.length > 0 ||
|
|
326
369
|
dryRun ||
|
|
327
370
|
yes ||
|
|
371
|
+
base !== undefined ||
|
|
328
372
|
(taskId !== undefined && sessionId !== undefined))) {
|
|
329
373
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Verify accepts only scope, task or session, and json options.");
|
|
330
374
|
}
|
|
@@ -345,9 +389,12 @@ export function parseArgs(argv) {
|
|
|
345
389
|
...(taskId === undefined ? {} : { taskId }),
|
|
346
390
|
...(targetVersion === undefined ? {} : { targetVersion }),
|
|
347
391
|
...(title === undefined ? {} : { title }),
|
|
392
|
+
...(reviewTargets.length === 0 ? {} : { reviewTargets }),
|
|
348
393
|
...(criteria.length === 0 ? {} : { criteria }),
|
|
349
394
|
...(evidence.length === 0 ? {} : { evidence }),
|
|
350
395
|
...(sessionId === undefined ? {} : { sessionId }),
|
|
396
|
+
...(base === undefined ? {} : { base }),
|
|
397
|
+
...(checkAuth ? { checkAuth } : {}),
|
|
351
398
|
dryRun,
|
|
352
399
|
json,
|
|
353
400
|
yes
|
|
@@ -29,6 +29,9 @@ import { formatInstallPlan, runInitCommand } from "./commands/init.js";
|
|
|
29
29
|
import { formatUninstallPlan, runUninstallCommand } from "./commands/uninstall.js";
|
|
30
30
|
import { runTaskCommand } from "./commands/task.js";
|
|
31
31
|
import { runReviewCommand } from "./commands/review.js";
|
|
32
|
+
import { createReviewExecutor } from "../../../runtime/src/review/execute.js";
|
|
33
|
+
import { probeReviewTarget } from "../../../runtime/src/review/probe.js";
|
|
34
|
+
import { resolveReviewRole } from "../../../runtime/src/review/roles.js";
|
|
32
35
|
import { runTrustCommand } from "./commands/trust.js";
|
|
33
36
|
import { runVerifyCommand } from "./commands/verify.js";
|
|
34
37
|
import { formatUpdatePlan, runUpdateCommand } from "./commands/update.js";
|
|
@@ -67,6 +70,29 @@ async function installedManifest(root) {
|
|
|
67
70
|
async function installedHarness(root) {
|
|
68
71
|
return (await installedManifest(root))?.harness ?? [...HARNESS_IDS];
|
|
69
72
|
}
|
|
73
|
+
function gitRunner(root) {
|
|
74
|
+
return {
|
|
75
|
+
run: async (gitArgs) => {
|
|
76
|
+
try {
|
|
77
|
+
return {
|
|
78
|
+
exitCode: 0,
|
|
79
|
+
stdout: execFileSync("git", [...gitArgs], {
|
|
80
|
+
cwd: root,
|
|
81
|
+
encoding: "buffer",
|
|
82
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
83
|
+
})
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const failure = error;
|
|
88
|
+
return {
|
|
89
|
+
exitCode: failure.status ?? 1,
|
|
90
|
+
stdout: failure.stdout ?? new Uint8Array()
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
70
96
|
async function confirmInit(plan) {
|
|
71
97
|
writeBanner({
|
|
72
98
|
isTTY: process.stdout.isTTY === true,
|
|
@@ -139,8 +165,12 @@ else {
|
|
|
139
165
|
sources: await hookSources(root, args.scope === "user" ? "user" : "project")
|
|
140
166
|
}),
|
|
141
167
|
repositoryTrust: async () => repositoryTrustStatus(await repositoryTrust(root, config, CLI_VERSION)),
|
|
142
|
-
smokeAvailability: () => smokeAvailabilityStatus(config)
|
|
143
|
-
|
|
168
|
+
smokeAvailability: () => smokeAvailabilityStatus(config),
|
|
169
|
+
reviewTarget: async (target, deep) => await probeReviewTarget(target, { cwd: root, deep })
|
|
170
|
+
},
|
|
171
|
+
...(args.checkAuth === true
|
|
172
|
+
? { checkReviewTargetAuth: true }
|
|
173
|
+
: {})
|
|
144
174
|
});
|
|
145
175
|
}
|
|
146
176
|
if (args.command === "uninstall") {
|
|
@@ -172,16 +202,54 @@ else {
|
|
|
172
202
|
const taskService = new TaskService(new FileTaskStore(join(root, ".agent-ops", "tasks", "state.json"), root));
|
|
173
203
|
if (args.command === "task") {
|
|
174
204
|
const sessionId = process.env.AGENT_OPS_SESSION_ID;
|
|
205
|
+
const policyConfigHash = args.action === "create"
|
|
206
|
+
? calculateConfigHash((await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project")).config)
|
|
207
|
+
: undefined;
|
|
175
208
|
return await runTaskCommand({
|
|
176
209
|
args,
|
|
177
210
|
service: taskService,
|
|
211
|
+
...(policyConfigHash === undefined ? {} : { policyConfigHash }),
|
|
178
212
|
...(sessionId === undefined ? {} : { sessionId })
|
|
179
213
|
});
|
|
180
214
|
}
|
|
181
215
|
if (args.command === "review") {
|
|
216
|
+
const reviewSessionId = process.env.AGENT_OPS_SESSION_ID;
|
|
217
|
+
const reviewConfig = (await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project")).config;
|
|
218
|
+
const reviewRole = resolveReviewRole("independent-review", reviewConfig.reviewRoles ?? []);
|
|
182
219
|
return await runReviewCommand({
|
|
183
220
|
args,
|
|
184
|
-
authorized: args.yes
|
|
221
|
+
authorized: args.yes,
|
|
222
|
+
tasks: taskService,
|
|
223
|
+
...(reviewSessionId === undefined
|
|
224
|
+
? {}
|
|
225
|
+
: { sessionId: reviewSessionId }),
|
|
226
|
+
...(reviewConfig.reviewRoles === undefined
|
|
227
|
+
? {}
|
|
228
|
+
: { roles: reviewConfig.reviewRoles }),
|
|
229
|
+
root,
|
|
230
|
+
gitRunner: gitRunner(root),
|
|
231
|
+
policyConfigHash: calculateConfigHash(reviewConfig),
|
|
232
|
+
currentPolicyConfigHash: async () => calculateConfigHash((await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project")).config),
|
|
233
|
+
config: reviewConfig,
|
|
234
|
+
evidenceStore: new FileEvidenceStore(root, root),
|
|
235
|
+
execute: createReviewExecutor({
|
|
236
|
+
targets: reviewRole?.targets ?? [],
|
|
237
|
+
cwd: root,
|
|
238
|
+
...(reviewRole?.model === undefined
|
|
239
|
+
? {}
|
|
240
|
+
: { model: reviewRole.model }),
|
|
241
|
+
...(reviewRole?.effort === undefined
|
|
242
|
+
? {}
|
|
243
|
+
: { effort: reviewRole.effort }),
|
|
244
|
+
...(reviewRole?.timeoutMs === undefined
|
|
245
|
+
? {}
|
|
246
|
+
: { timeoutMs: reviewRole.timeoutMs }),
|
|
247
|
+
onProgress: (line) => {
|
|
248
|
+
if (!args.json) {
|
|
249
|
+
process.stderr.write(`${line}\n`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
})
|
|
185
253
|
});
|
|
186
254
|
}
|
|
187
255
|
if (args.command === "config") {
|
|
@@ -198,31 +266,12 @@ else {
|
|
|
198
266
|
root,
|
|
199
267
|
scope: args.scope === "user" ? "user" : "project",
|
|
200
268
|
config,
|
|
201
|
-
gitRunner:
|
|
202
|
-
run: async (gitArgs) => {
|
|
203
|
-
try {
|
|
204
|
-
return {
|
|
205
|
-
exitCode: 0,
|
|
206
|
-
stdout: execFileSync("git", [...gitArgs], {
|
|
207
|
-
cwd: root,
|
|
208
|
-
encoding: "buffer",
|
|
209
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
210
|
-
})
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
catch (error) {
|
|
214
|
-
const failure = error;
|
|
215
|
-
return {
|
|
216
|
-
exitCode: failure.status ?? 1,
|
|
217
|
-
stdout: failure.stdout ?? new Uint8Array()
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
},
|
|
269
|
+
gitRunner: gitRunner(root),
|
|
222
270
|
processRunner: new NodeVerificationProcessRunner(),
|
|
223
271
|
taskService,
|
|
224
272
|
evidenceStore: new FileEvidenceStore(root, root),
|
|
225
|
-
trusted: trustStatus === "TRUSTED"
|
|
273
|
+
trusted: trustStatus === "TRUSTED",
|
|
274
|
+
...(args.base === undefined ? {} : { base: args.base })
|
|
226
275
|
})
|
|
227
276
|
});
|
|
228
277
|
}
|
|
@@ -2,6 +2,7 @@ import { CliArgumentError, parseArgs } from "./args.js";
|
|
|
2
2
|
import { AgentOpsError } from "../../../runtime/src/fs/paths.js";
|
|
3
3
|
import { errorEnvelope, okEnvelope, writeEnvelope } from "./output.js";
|
|
4
4
|
import { completeInitChoices } from "./wizard.js";
|
|
5
|
+
import { probeReviewTarget } from "../../../runtime/src/review/probe.js";
|
|
5
6
|
import { BANNER } from "./ui.js";
|
|
6
7
|
export function renderWelcome(color) {
|
|
7
8
|
const cyan = color ? "\u001b[36m" : "";
|
|
@@ -32,12 +33,17 @@ Options:
|
|
|
32
33
|
--harness <all|both|claude|codex|opencode|comma-separated> Init/update
|
|
33
34
|
--hook-target <harness=surface-id> Repeatable advanced init/update option
|
|
34
35
|
--profile <core|advisory|guardrails|loop> Repeatable
|
|
36
|
+
--review-target <codex|agy|claude> Repeatable init option; external review
|
|
37
|
+
targets in fallback-chain order
|
|
38
|
+
--check-auth Doctor only: probe each review target's
|
|
39
|
+
authentication with one real call
|
|
35
40
|
--task <id>
|
|
36
41
|
--target-version <version> Update target version (offline-capable)
|
|
37
42
|
--title <text>
|
|
38
43
|
--criterion <json> Repeatable
|
|
39
44
|
--evidence <criterion-id=reference> Repeatable
|
|
40
45
|
--session <id>
|
|
46
|
+
--base <git-ref> Verify/review a clean committed range
|
|
41
47
|
--dry-run
|
|
42
48
|
--json
|
|
43
49
|
--yes
|
|
@@ -76,7 +82,13 @@ export async function runCli(argv, io, services) {
|
|
|
76
82
|
}
|
|
77
83
|
try {
|
|
78
84
|
if (args.command === "init") {
|
|
79
|
-
args = await completeInitChoices(args, args.json ? { ...io, isTTY: false } : io
|
|
85
|
+
args = await completeInitChoices(args, args.json ? { ...io, isTTY: false } : io, {
|
|
86
|
+
probeReviewTarget: async (target) => (await probeReviewTarget(target, {
|
|
87
|
+
cwd: process.cwd(),
|
|
88
|
+
deep: true
|
|
89
|
+
})) === "ok",
|
|
90
|
+
warn: (message) => io.writeStderr(`${message}\n`)
|
|
91
|
+
});
|
|
80
92
|
}
|
|
81
93
|
const execute = args.command === "help" || args.command === "version"
|
|
82
94
|
? services.execute
|
|
@@ -63,7 +63,10 @@ export async function runInitCommand(options) {
|
|
|
63
63
|
: { hookRuntimePath: options.hookRuntimePath }),
|
|
64
64
|
...((options.hookTargets ?? args.hookTargets) === undefined
|
|
65
65
|
? {}
|
|
66
|
-
: { hookTargets: options.hookTargets ?? args.hookTargets })
|
|
66
|
+
: { hookTargets: options.hookTargets ?? args.hookTargets }),
|
|
67
|
+
...(args.reviewTargets === undefined
|
|
68
|
+
? {}
|
|
69
|
+
: { reviewTargets: args.reviewTargets })
|
|
67
70
|
});
|
|
68
71
|
if (args.dryRun) {
|
|
69
72
|
return okEnvelope("INIT_PLAN_READY", {
|