@kylecheng3146/agent-ops 0.1.7 → 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.
Files changed (33) hide show
  1. package/README.md +9 -4
  2. package/dist/packages/cli/src/args.js +15 -0
  3. package/dist/packages/cli/src/bin.js +36 -22
  4. package/dist/packages/cli/src/cli.js +1 -0
  5. package/dist/packages/cli/src/commands/review.js +286 -29
  6. package/dist/packages/cli/src/commands/task.js +4 -1
  7. package/dist/packages/cli/src/commands/verify.js +13 -1
  8. package/dist/packages/cli/src/wizard.js +3 -3
  9. package/dist/runtime/src/contracts.js +1 -1
  10. package/dist/runtime/src/review/execute.js +143 -83
  11. package/dist/runtime/src/review/extract.js +20 -22
  12. package/dist/runtime/src/review/invocation.js +66 -2
  13. package/dist/runtime/src/review/packet.js +42 -5
  14. package/dist/runtime/src/review/probe.js +50 -26
  15. package/dist/runtime/src/review/render.js +62 -0
  16. package/dist/runtime/src/review/report.js +183 -0
  17. package/dist/runtime/src/review/runner.js +85 -33
  18. package/dist/runtime/src/review/scope.js +123 -0
  19. package/dist/runtime/src/schema/validate.js +18 -0
  20. package/dist/runtime/src/task/service.js +6 -1
  21. package/dist/runtime/src/task/store.js +16 -4
  22. package/dist/runtime/src/verify/change-surface.js +38 -2
  23. package/dist/runtime/src/verify/command-executor.js +4 -1
  24. package/dist/runtime/src/verify/evidence.js +36 -0
  25. package/dist/runtime/src/verify/scope.js +1 -2
  26. package/dist/runtime/src/verify/service.js +66 -9
  27. package/dist/runtime/src/verify/source-fingerprint.js +49 -0
  28. package/dist/runtime/src/verify/spawn.js +9 -3
  29. package/docs/en/guides/configuration.md +17 -9
  30. package/docs/zh-TW/guides/configuration.md +15 -8
  31. package/package.json +1 -1
  32. package/schemas/evidence.schema.json +16 -1
  33. package/schemas/review-report.schema.json +48 -0
package/README.md CHANGED
@@ -265,10 +265,15 @@ the project configuration defines those workflows.
265
265
 
266
266
  `agent-ops review` can hand the review to another agent CLI, so the work is not
267
267
  judged by the agent that produced it. Enable it during `agent-ops init` (the
268
- default is off) and pick an ordered fallback chain of targets: `codex`, `agy`
269
- (Antigravity), and `claude`. Each is launched with its own read-only flag, and a
270
- target without one is skipped rather than run unsandboxed — which is why
271
- `opencode` is not a review target despite being a supported harness.
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.
272
277
 
273
278
  The first target that actually runs produces the verdict. A `FAIL` is final:
274
279
  the chain never retries elsewhere after a real verdict. `--yes` is still
@@ -66,6 +66,7 @@ export function parseArgs(argv) {
66
66
  let targetVersion;
67
67
  let title;
68
68
  let sessionId;
69
+ let base;
69
70
  const profiles = [];
70
71
  const reviewTargets = [];
71
72
  const criteria = [];
@@ -179,6 +180,14 @@ export function parseArgs(argv) {
179
180
  index += 1;
180
181
  break;
181
182
  }
183
+ case "--base": {
184
+ if (base !== undefined) {
185
+ duplicate(token);
186
+ }
187
+ base = readOptionValue(argv, index, token);
188
+ index += 1;
189
+ break;
190
+ }
182
191
  case "--check-auth":
183
192
  if (checkAuth) {
184
193
  duplicate(token);
@@ -273,6 +282,7 @@ export function parseArgs(argv) {
273
282
  evidence.length > 0 ||
274
283
  reviewTargets.length > 0 ||
275
284
  sessionId !== undefined ||
285
+ base !== undefined ||
276
286
  checkAuth ||
277
287
  dryRun ||
278
288
  yes) {
@@ -303,6 +313,9 @@ export function parseArgs(argv) {
303
313
  if (command !== "update" && targetVersion !== undefined) {
304
314
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--target-version may be used only with update.");
305
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
+ }
306
319
  if (hookTargets.length > 0 &&
307
320
  command !== "init" &&
308
321
  command !== "update") {
@@ -355,6 +368,7 @@ export function parseArgs(argv) {
355
368
  evidence.length > 0 ||
356
369
  dryRun ||
357
370
  yes ||
371
+ base !== undefined ||
358
372
  (taskId !== undefined && sessionId !== undefined))) {
359
373
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Verify accepts only scope, task or session, and json options.");
360
374
  }
@@ -379,6 +393,7 @@ export function parseArgs(argv) {
379
393
  ...(criteria.length === 0 ? {} : { criteria }),
380
394
  ...(evidence.length === 0 ? {} : { evidence }),
381
395
  ...(sessionId === undefined ? {} : { sessionId }),
396
+ ...(base === undefined ? {} : { base }),
382
397
  ...(checkAuth ? { checkAuth } : {}),
383
398
  dryRun,
384
399
  json,
@@ -70,6 +70,29 @@ async function installedManifest(root) {
70
70
  async function installedHarness(root) {
71
71
  return (await installedManifest(root))?.harness ?? [...HARNESS_IDS];
72
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
+ }
73
96
  async function confirmInit(plan) {
74
97
  writeBanner({
75
98
  isTTY: process.stdout.isTTY === true,
@@ -179,9 +202,13 @@ else {
179
202
  const taskService = new TaskService(new FileTaskStore(join(root, ".agent-ops", "tasks", "state.json"), root));
180
203
  if (args.command === "task") {
181
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;
182
208
  return await runTaskCommand({
183
209
  args,
184
210
  service: taskService,
211
+ ...(policyConfigHash === undefined ? {} : { policyConfigHash }),
185
212
  ...(sessionId === undefined ? {} : { sessionId })
186
213
  });
187
214
  }
@@ -199,6 +226,12 @@ else {
199
226
  ...(reviewConfig.reviewRoles === undefined
200
227
  ? {}
201
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),
202
235
  execute: createReviewExecutor({
203
236
  targets: reviewRole?.targets ?? [],
204
237
  cwd: root,
@@ -233,31 +266,12 @@ else {
233
266
  root,
234
267
  scope: args.scope === "user" ? "user" : "project",
235
268
  config,
236
- gitRunner: {
237
- run: async (gitArgs) => {
238
- try {
239
- return {
240
- exitCode: 0,
241
- stdout: execFileSync("git", [...gitArgs], {
242
- cwd: root,
243
- encoding: "buffer",
244
- stdio: ["ignore", "pipe", "ignore"]
245
- })
246
- };
247
- }
248
- catch (error) {
249
- const failure = error;
250
- return {
251
- exitCode: failure.status ?? 1,
252
- stdout: failure.stdout ?? new Uint8Array()
253
- };
254
- }
255
- }
256
- },
269
+ gitRunner: gitRunner(root),
257
270
  processRunner: new NodeVerificationProcessRunner(),
258
271
  taskService,
259
272
  evidenceStore: new FileEvidenceStore(root, root),
260
- trusted: trustStatus === "TRUSTED"
273
+ trusted: trustStatus === "TRUSTED",
274
+ ...(args.base === undefined ? {} : { base: args.base })
261
275
  })
262
276
  });
263
277
  }
@@ -43,6 +43,7 @@ Options:
43
43
  --criterion <json> Repeatable
44
44
  --evidence <criterion-id=reference> Repeatable
45
45
  --session <id>
46
+ --base <git-ref> Verify/review a clean committed range
46
47
  --dry-run
47
48
  --json
48
49
  --yes
@@ -1,6 +1,12 @@
1
1
  import { buildReviewPacket } from "../../../../runtime/src/review/packet.js";
2
2
  import { runIndependentReview } from "../../../../runtime/src/review/runner.js";
3
+ import { renderReviewResult } from "../../../../runtime/src/review/render.js";
3
4
  import { resolveReviewRole } from "../../../../runtime/src/review/roles.js";
5
+ import { AgentOpsError } from "../../../../runtime/src/fs/paths.js";
6
+ import { assertSafeSupportingPaths, isReviewerPolicyPath, resolveReviewScope, reviewScopeSignature } from "../../../../runtime/src/review/scope.js";
7
+ import { calculateSourceFingerprint } from "../../../../runtime/src/verify/source-fingerprint.js";
8
+ import { calculateConfigHash, isPassingVerificationEvidence } from "../../../../runtime/src/verify/evidence.js";
9
+ import { validateEvidence } from "../../../../runtime/src/schema/validate.js";
4
10
  import { okEnvelope } from "../output.js";
5
11
  /**
6
12
  * Review runs against one target. Argument parsing already rejects a
@@ -53,10 +59,129 @@ async function taskContext(options) {
53
59
  }
54
60
  return {
55
61
  taskId: record.task.id,
62
+ title: record.task.title,
56
63
  active: record.status === "active",
64
+ policyConfigHash: record.policyConfigHash,
65
+ evidence: record.evidence,
66
+ failureFingerprint: record.failureFingerprint,
57
67
  criteria
58
68
  };
59
69
  }
70
+ function newestEvidence(values) {
71
+ return [...values].sort((left, right) => {
72
+ const leftTime = Date.parse(left.evidence.finishedAt);
73
+ const rightTime = Date.parse(right.evidence.finishedAt);
74
+ return rightTime - leftTime || left.reference.localeCompare(right.reference);
75
+ })[0];
76
+ }
77
+ async function currentEvidence(options, context, criterionId, commandId, configHash, sourceFingerprint) {
78
+ const current = [];
79
+ let hasReference = false;
80
+ let unreadable = false;
81
+ let stale = false;
82
+ for (const reference of context.evidence[criterionId] ?? []) {
83
+ if (reference.startsWith("review:")) {
84
+ continue;
85
+ }
86
+ hasReference = true;
87
+ let stored;
88
+ try {
89
+ stored = await options.evidenceStore?.load(reference) ?? null;
90
+ }
91
+ catch {
92
+ unreadable = true;
93
+ continue;
94
+ }
95
+ const validation = validateEvidence(stored);
96
+ if (!validation.ok) {
97
+ unreadable = true;
98
+ continue;
99
+ }
100
+ const evidence = validation.value;
101
+ if (evidence.schemaVersion !== 2 ||
102
+ evidence.taskId !== context.taskId ||
103
+ evidence.criterionId !== criterionId ||
104
+ evidence.commandId !== commandId ||
105
+ evidence.configHash !== configHash ||
106
+ evidence.sourceFingerprint !== sourceFingerprint) {
107
+ stale = true;
108
+ continue;
109
+ }
110
+ current.push({ reference, evidence });
111
+ }
112
+ return { current, hasReference, unreadable, stale };
113
+ }
114
+ async function preflightReview(options, context, scope) {
115
+ if (options.config === undefined ||
116
+ options.evidenceStore === undefined ||
117
+ options.root === undefined ||
118
+ options.gitRunner === undefined ||
119
+ context.failureFingerprint !== null) {
120
+ return { ok: false, reason: "stale-verification" };
121
+ }
122
+ const configHash = calculateConfigHash(options.config);
123
+ const sourceFingerprint = await calculateSourceFingerprint(options.root, scope, options.gitRunner);
124
+ const commands = [];
125
+ for (const criterion of context.criteria) {
126
+ for (const commandId of criterion.verifierIds ?? []) {
127
+ const command = options.config.verification.commands.find((candidate) => candidate.id === commandId);
128
+ if (command === undefined) {
129
+ return { ok: false, reason: "missing-verification-evidence" };
130
+ }
131
+ const found = await currentEvidence(options, context, criterion.id, commandId, configHash, sourceFingerprint);
132
+ const selected = newestEvidence(found.current);
133
+ if (command.required !== true) {
134
+ if (selected !== undefined) {
135
+ commands.push({
136
+ criterionId: criterion.id,
137
+ commandId,
138
+ required: false,
139
+ status: selected.evidence.status,
140
+ evidenceReference: selected.reference
141
+ });
142
+ }
143
+ continue;
144
+ }
145
+ if (selected === undefined) {
146
+ return {
147
+ ok: false,
148
+ reason: found.unreadable
149
+ ? "unreadable-verification-evidence"
150
+ : found.stale
151
+ ? "stale-verification"
152
+ : found.hasReference
153
+ ? "missing-verification-evidence"
154
+ : "missing-verification-evidence"
155
+ };
156
+ }
157
+ if (!isPassingVerificationEvidence(command, selected.evidence)) {
158
+ return { ok: false, reason: "verification-not-passed" };
159
+ }
160
+ commands.push({
161
+ criterionId: criterion.id,
162
+ commandId,
163
+ required: true,
164
+ status: "PASS",
165
+ evidenceReference: selected.reference
166
+ });
167
+ }
168
+ }
169
+ return {
170
+ ok: true,
171
+ summary: { status: "PASS", sourceFingerprint, commands }
172
+ };
173
+ }
174
+ function scopeReason(error) {
175
+ if (!(error instanceof AgentOpsError)) {
176
+ return undefined;
177
+ }
178
+ return {
179
+ REVIEW_UNSAFE_PATH: "unsafe-review-path",
180
+ REVIEW_NO_CHANGE_SURFACE: "no-change-surface",
181
+ REVIEW_DIRTY_WORKTREE: "dirty-worktree",
182
+ REVIEW_INVALID_BASE: "invalid-base"
183
+ }[error.code];
184
+ }
60
185
  function notRunEnvelope(result) {
61
186
  const message = "Independent review was not run.";
62
187
  return {
@@ -65,11 +190,28 @@ function notRunEnvelope(result) {
65
190
  data: {
66
191
  message,
67
192
  result,
68
- text: [message, `Reason: ${result.reason ?? "unknown"}.`, ""].join("\n")
193
+ text: renderReviewResult(result)
69
194
  },
70
195
  errors: [{ code: "REVIEW_NOT_RUN", message }]
71
196
  };
72
197
  }
198
+ function sourceChangedResult(result) {
199
+ return {
200
+ status: "NOT_RUN",
201
+ reason: "source-changed-during-review",
202
+ harness: result.harness,
203
+ model: result.model,
204
+ effort: result.effort,
205
+ prompt: result.prompt,
206
+ ...(result.scope === undefined ? {} : { scope: result.scope }),
207
+ ...(result.independence === undefined
208
+ ? {}
209
+ : { independence: result.independence }),
210
+ ...(result.verification === undefined
211
+ ? {}
212
+ : { verification: result.verification })
213
+ };
214
+ }
73
215
  export async function runReviewCommand(options) {
74
216
  const role = resolveReviewRole(options.role ?? "independent-review", options.roles ?? []);
75
217
  const selectedHarness = harness(options.args.harness);
@@ -82,7 +224,7 @@ export async function runReviewCommand(options) {
82
224
  requirement: separator < 0 ? value : value.slice(separator + 1)
83
225
  };
84
226
  });
85
- if (options.tasks !== undefined && context === undefined) {
227
+ if (context === undefined) {
86
228
  return notRunEnvelope({
87
229
  status: "NOT_RUN",
88
230
  reason: "no-task-context",
@@ -92,20 +234,105 @@ export async function runReviewCommand(options) {
92
234
  prompt: ""
93
235
  });
94
236
  }
95
- const criteria = context?.criteria !== undefined
96
- ? [...context.criteria]
97
- : (options.args.criteria ?? []).map((id) => ({ id, description: id }));
237
+ let scope;
238
+ let verification;
239
+ if (options.root !== undefined && options.gitRunner !== undefined) {
240
+ try {
241
+ scope = await resolveReviewScope({
242
+ root: options.root,
243
+ runner: options.gitRunner,
244
+ ...(options.args.base === undefined ? {} : { base: options.args.base })
245
+ });
246
+ }
247
+ catch (error) {
248
+ const reason = scopeReason(error);
249
+ if (reason !== undefined) {
250
+ return notRunEnvelope({
251
+ status: "NOT_RUN",
252
+ reason,
253
+ harness: target,
254
+ model: role?.model ?? options.model ?? "configured",
255
+ effort: role?.effort ?? options.effort ?? "configured",
256
+ prompt: ""
257
+ });
258
+ }
259
+ throw error;
260
+ }
261
+ if (scope.changedFiles.some(isReviewerPolicyPath)) {
262
+ return notRunEnvelope({
263
+ status: "NOT_RUN",
264
+ reason: "reviewer-policy-changed",
265
+ harness: target,
266
+ model: role?.model ?? options.model ?? "configured",
267
+ effort: role?.effort ?? options.effort ?? "configured",
268
+ prompt: "",
269
+ scope
270
+ });
271
+ }
272
+ if (options.policyConfigHash !== undefined) {
273
+ if (context.policyConfigHash === null) {
274
+ return notRunEnvelope({
275
+ status: "NOT_RUN", reason: "reviewer-policy-baseline-missing",
276
+ harness: target, model: role?.model ?? options.model ?? "configured",
277
+ effort: role?.effort ?? options.effort ?? "configured", prompt: "", scope
278
+ });
279
+ }
280
+ if (context.policyConfigHash !== options.policyConfigHash) {
281
+ return notRunEnvelope({
282
+ status: "NOT_RUN", reason: "reviewer-policy-changed",
283
+ harness: target, model: role?.model ?? options.model ?? "configured",
284
+ effort: role?.effort ?? options.effort ?? "configured", prompt: "", scope
285
+ });
286
+ }
287
+ }
288
+ const preflight = await preflightReview(options, context, scope);
289
+ if (!preflight.ok) {
290
+ return notRunEnvelope({
291
+ status: "NOT_RUN", reason: preflight.reason,
292
+ harness: target, model: role?.model ?? options.model ?? "configured",
293
+ effort: role?.effort ?? options.effort ?? "configured", prompt: "", scope
294
+ });
295
+ }
296
+ verification = preflight.summary;
297
+ }
298
+ const criteria = [...context.criteria];
299
+ let packet;
300
+ try {
301
+ packet = buildReviewPacket({
302
+ request: context.title,
303
+ criteria,
304
+ artifactRefs: scope?.changedFiles ?? [],
305
+ evidenceRequirements
306
+ });
307
+ }
308
+ catch (error) {
309
+ if (error instanceof AgentOpsError) {
310
+ const reason = error.code === "REVIEW_SENSITIVE_INPUT"
311
+ ? "sensitive-review-input"
312
+ : error.code === "REVIEW_SCOPE_TOO_LARGE"
313
+ ? "scope-too-large"
314
+ : undefined;
315
+ if (reason !== undefined) {
316
+ return notRunEnvelope({
317
+ status: "NOT_RUN",
318
+ reason,
319
+ harness: target,
320
+ model: role?.model ?? options.model ?? "configured",
321
+ effort: role?.effort ?? options.effort ?? "configured",
322
+ prompt: ""
323
+ });
324
+ }
325
+ }
326
+ throw error;
327
+ }
98
328
  const result = await runIndependentReview({
99
329
  invocation: {
100
330
  harness: target,
101
331
  model: role?.model ?? options.model ?? "configured",
102
332
  effort: role?.effort ?? options.effort ?? "configured",
103
- packet: buildReviewPacket({
104
- request: "Review the requested implementation.",
105
- criteria,
106
- artifactRefs: [],
107
- evidenceRequirements
108
- })
333
+ packet,
334
+ ...(scope === undefined ? {} : { scope }),
335
+ ...(verification === undefined ? {} : { verification })
109
336
  },
110
337
  authorized: options.authorized,
111
338
  execute: options.execute ?? (async () => ({
@@ -113,15 +340,60 @@ export async function runReviewCommand(options) {
113
340
  reason: "missing-cli"
114
341
  }))
115
342
  });
343
+ if (scope !== undefined && result.report !== undefined && options.root !== undefined) {
344
+ try {
345
+ if (result.report.changedFilesInspected.length !== scope.changedFiles.length ||
346
+ result.report.changedFilesInspected.some((path) => !scope?.changedFiles.includes(path))) {
347
+ return notRunEnvelope({
348
+ ...result,
349
+ status: "NOT_RUN",
350
+ reason: "incomplete-scope"
351
+ });
352
+ }
353
+ try {
354
+ await assertSafeSupportingPaths(options.root, result.report.supportingFilesInspected);
355
+ }
356
+ catch (error) {
357
+ if (scopeReason(error) === "unsafe-review-path") {
358
+ return notRunEnvelope({
359
+ ...result,
360
+ status: "NOT_RUN",
361
+ reason: "unsafe-review-path"
362
+ });
363
+ }
364
+ throw error;
365
+ }
366
+ const postflight = await resolveReviewScope({
367
+ root: options.root,
368
+ runner: options.gitRunner,
369
+ ...(options.args.base === undefined ? {} : { base: options.args.base })
370
+ });
371
+ const currentHash = options.currentPolicyConfigHash === undefined
372
+ ? options.policyConfigHash
373
+ : await options.currentPolicyConfigHash();
374
+ const postflightFingerprint = verification === undefined
375
+ ? undefined
376
+ : await calculateSourceFingerprint(options.root, postflight, options.gitRunner);
377
+ if (reviewScopeSignature(scope) !== reviewScopeSignature(postflight) ||
378
+ (options.policyConfigHash !== undefined && currentHash !== options.policyConfigHash) ||
379
+ (verification !== undefined &&
380
+ postflightFingerprint !== verification.sourceFingerprint)) {
381
+ return notRunEnvelope(sourceChangedResult(result));
382
+ }
383
+ }
384
+ catch {
385
+ return notRunEnvelope(sourceChangedResult(result));
386
+ }
387
+ }
116
388
  // Evidence is only appended while the task is active: a completed record
117
389
  // must stay exactly as it was verified.
118
390
  if (options.tasks !== undefined &&
119
- context !== undefined &&
391
+ result.status === "PASS" &&
120
392
  context.active &&
121
393
  result.results !== undefined) {
122
394
  await options.tasks.recordEvidence(context.taskId, Object.fromEntries(result.results.map((item) => [
123
395
  item.criterionId,
124
- item.evidence.map((reference) => `review:${target}:${reference}`)
396
+ item.evidence.map((reference) => `review:${result.harness}:${reference}`)
125
397
  ])));
126
398
  }
127
399
  const message = result.status === "PASS"
@@ -132,22 +404,7 @@ export async function runReviewCommand(options) {
132
404
  const data = {
133
405
  message,
134
406
  result,
135
- text: [
136
- message,
137
- `Status: ${result.status}`,
138
- `Harness: ${result.harness}; model: ${result.model}; effort: ${result.effort}.`,
139
- ...(result.reason === undefined ? [] : [`Reason: ${result.reason}.`]),
140
- ...(result.status === "NOT_RUN"
141
- ? [
142
- "Run: agent-ops doctor --check-auth to verify target authentication."
143
- ]
144
- : []),
145
- ...(result.results === undefined
146
- ? []
147
- : result.results.map((item) => `${item.criterionId}: ${item.status} [${item.evidence.join(", ")}]`)),
148
- result.prompt,
149
- ""
150
- ].join("\n")
407
+ text: renderReviewResult(result)
151
408
  };
152
409
  if (result.status === "PASS") {
153
410
  return okEnvelope("REVIEW_RESULT", data);
@@ -89,7 +89,10 @@ export async function runTaskCommand(options) {
89
89
  }
90
90
  const record = await options.service.create({
91
91
  title: options.args.title,
92
- criteria: (options.args.criteria ?? []).map(parseCriterion)
92
+ criteria: (options.args.criteria ?? []).map(parseCriterion),
93
+ ...(options.policyConfigHash === undefined
94
+ ? {}
95
+ : { policyConfigHash: options.policyConfigHash })
93
96
  });
94
97
  return taskEnvelope(action, "TASK_CREATED", `Created task ${record.task.id}.`, record);
95
98
  }
@@ -41,7 +41,19 @@ function publicReport(report) {
41
41
  testCount: result.testCount,
42
42
  evidenceReferences: result.evidenceReferences.map(redactSecrets)
43
43
  })),
44
- signal: report.signal
44
+ signal: report.signal,
45
+ reviewScope: report.reviewScope.mode === "base"
46
+ ? {
47
+ mode: "base",
48
+ baseRef: redactSecrets(report.reviewScope.baseRef),
49
+ resolvedBase: report.reviewScope.resolvedBase,
50
+ changedFiles: report.reviewScope.changedFiles.map(redactSecrets)
51
+ }
52
+ : {
53
+ mode: "worktree",
54
+ changedFiles: report.reviewScope.changedFiles.map(redactSecrets)
55
+ },
56
+ sourceFingerprint: report.sourceFingerprint
45
57
  };
46
58
  }
47
59
  function formatResult(result) {
@@ -10,10 +10,10 @@ const REVIEW_TARGET_CHOICES = DEFAULT_REVIEW_TARGETS.map((id) => ({
10
10
  label: id,
11
11
  value: id,
12
12
  description: id === "codex"
13
- ? "Runs with -s read-only; stdout is the bare final message."
13
+ ? "Retained for future isolation support; currently not auto-run."
14
14
  : id === "agy"
15
- ? "Antigravity CLI; runs with --sandbox --mode plan."
16
- : "Runs with --permission-mode plan; tried last when it is the host."
15
+ ? "Retained for future isolation support; currently not auto-run."
16
+ : "Runs in fresh safe mode with context isolation."
17
17
  }));
18
18
  function selectReviewTargets(raw) {
19
19
  const values = raw
@@ -1,6 +1,6 @@
1
1
  export const CONFIG_SCHEMA_VERSION = 2;
2
2
  export const TASK_SCHEMA_VERSION = 1;
3
- export const EVIDENCE_SCHEMA_VERSION = 1;
3
+ export const EVIDENCE_SCHEMA_VERSION = 2;
4
4
  /** @deprecated Use the document-specific schema version constants. */
5
5
  export const SCHEMA_VERSION = CONFIG_SCHEMA_VERSION;
6
6
  /**