@kylecheng3146/agent-ops 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +64 -0
  2. package/dist/packages/cli/src/args.js +214 -10
  3. package/dist/packages/cli/src/bin.js +77 -17
  4. package/dist/packages/cli/src/cli.js +42 -10
  5. package/dist/packages/cli/src/commands/advance.js +173 -0
  6. package/dist/packages/cli/src/commands/review-show.js +189 -0
  7. package/dist/packages/cli/src/commands/review.js +201 -34
  8. package/dist/packages/cli/src/commands/run.js +111 -0
  9. package/dist/packages/cli/src/commands/task.js +62 -17
  10. package/dist/packages/cli/src/commands/verify.js +1 -0
  11. package/dist/packages/cli/src/context.js +63 -1
  12. package/dist/packages/cli/src/hook-process.js +31 -1
  13. package/dist/packages/cli/src/owned-run-step.js +65 -0
  14. package/dist/packages/cli/src/parallel-deps.js +18 -5
  15. package/dist/packages/cli/src/run-deps.js +314 -0
  16. package/dist/packages/cli/src/run-entry.js +943 -0
  17. package/dist/packages/cli/src/run-setup-entry.js +32 -0
  18. package/dist/packages/cli/src/run-step-entry.js +58 -0
  19. package/dist/runtime/src/config/explain.js +6 -0
  20. package/dist/runtime/src/config/hash.js +1 -1
  21. package/dist/runtime/src/config/merge.js +9 -1
  22. package/dist/runtime/src/contracts.js +2 -2
  23. package/dist/runtime/src/hooks/completion-gate.js +8 -2
  24. package/dist/runtime/src/install/doctor.js +2 -0
  25. package/dist/runtime/src/install/harness.js +1 -1
  26. package/dist/runtime/src/install/preauth.js +10 -1
  27. package/dist/runtime/src/parallel/finish.js +217 -39
  28. package/dist/runtime/src/parallel/guard.js +5 -1
  29. package/dist/runtime/src/parallel/integrate.js +416 -0
  30. package/dist/runtime/src/parallel/manage.js +4 -1
  31. package/dist/runtime/src/parallel/receipt.js +153 -0
  32. package/dist/runtime/src/parallel/service.js +45 -3
  33. package/dist/runtime/src/review/attestation.js +60 -11
  34. package/dist/runtime/src/review/execute.js +24 -1
  35. package/dist/runtime/src/review/packet.js +35 -1
  36. package/dist/runtime/src/review/render.js +71 -0
  37. package/dist/runtime/src/review/runner.js +12 -1
  38. package/dist/runtime/src/review/scope.js +18 -1
  39. package/dist/runtime/src/review/slots.js +35 -10
  40. package/dist/runtime/src/run/authorization.js +60 -0
  41. package/dist/runtime/src/run/budget.js +30 -0
  42. package/dist/runtime/src/run/controls.js +139 -0
  43. package/dist/runtime/src/run/convergence.js +177 -0
  44. package/dist/runtime/src/run/hosts/claude.js +180 -0
  45. package/dist/runtime/src/run/hosts/codex.js +279 -0
  46. package/dist/runtime/src/run/hosts/index.js +4 -0
  47. package/dist/runtime/src/run/hosts/protocol.js +254 -0
  48. package/dist/runtime/src/run/hosts/types.js +1 -0
  49. package/dist/runtime/src/run/hosts/util.js +136 -0
  50. package/dist/runtime/src/run/integration.js +204 -0
  51. package/dist/runtime/src/run/macOS.js +189 -0
  52. package/dist/runtime/src/run/ownership.js +22 -0
  53. package/dist/runtime/src/run/planning.js +25 -0
  54. package/dist/runtime/src/run/policy-transition.js +1076 -0
  55. package/dist/runtime/src/run/policy.js +176 -0
  56. package/dist/runtime/src/run/ratchet.js +71 -0
  57. package/dist/runtime/src/run/scheduler.js +260 -0
  58. package/dist/runtime/src/run/service.js +425 -0
  59. package/dist/runtime/src/run/supervisor.js +635 -0
  60. package/dist/runtime/src/run/transport.js +159 -0
  61. package/dist/runtime/src/run/types.js +1 -0
  62. package/dist/runtime/src/run/usage.js +81 -0
  63. package/dist/runtime/src/run/verification.js +64 -0
  64. package/dist/runtime/src/run/worker-gate.js +48 -0
  65. package/dist/runtime/src/schema/acceptance.js +91 -0
  66. package/dist/runtime/src/schema/validate.js +72 -7
  67. package/dist/runtime/src/task/completion.js +26 -6
  68. package/dist/runtime/src/task/contract.js +17 -0
  69. package/dist/runtime/src/task/pin-finding.js +81 -0
  70. package/dist/runtime/src/task/render.js +3 -0
  71. package/dist/runtime/src/task/service.js +361 -10
  72. package/dist/runtime/src/task/store.js +57 -7
  73. package/dist/runtime/src/verify/acceptance-coverage.js +85 -0
  74. package/dist/runtime/src/verify/acceptance-protocol.js +252 -0
  75. package/dist/runtime/src/verify/acceptance-replay.js +239 -0
  76. package/dist/runtime/src/verify/adapters/index.js +5 -0
  77. package/dist/runtime/src/verify/adapters/jest.js +142 -0
  78. package/dist/runtime/src/verify/adapters/node-reporter.js +20 -0
  79. package/dist/runtime/src/verify/adapters/node.js +254 -0
  80. package/dist/runtime/src/verify/adapters/pytest.js +188 -0
  81. package/dist/runtime/src/verify/adapters/rust.js +84 -0
  82. package/dist/runtime/src/verify/adapters/shared.js +82 -0
  83. package/dist/runtime/src/verify/adapters/vitest.js +218 -0
  84. package/dist/runtime/src/verify/evidence.js +5 -2
  85. package/dist/runtime/src/verify/service.js +24 -8
  86. package/dist/runtime/src/verify/source-fingerprint.js +1 -1
  87. package/dist/runtime/src/verify/spawn.js +13 -2
  88. package/docs/en/guides/configuration.md +10 -0
  89. package/docs/en/spec/acceptance-and-evidence.md +58 -0
  90. package/docs/en/spec/delegation.md +4 -4
  91. package/docs/en/spec/loop-engineering.md +49 -0
  92. package/docs/en/spec/review.md +24 -2
  93. package/docs/zh-TW/guides/configuration.md +9 -0
  94. package/docs/zh-TW/spec/acceptance-and-evidence.md +31 -1
  95. package/docs/zh-TW/spec/delegation.md +4 -4
  96. package/docs/zh-TW/spec/loop-engineering.md +22 -0
  97. package/docs/zh-TW/spec/review.md +24 -2
  98. package/package.json +1 -1
  99. package/schemas/config.schema.json +221 -19
  100. package/schemas/evidence.schema.json +129 -6
  101. package/schemas/task.schema.json +268 -6
  102. package/templates/acceptance/agent_ops_acceptance.rs +36 -0
  103. package/templates/acceptance/agent_ops_pytest.py +46 -0
@@ -2,8 +2,24 @@ import { calculateConfigHash } from "../config/hash.js";
2
2
  import { findReviewAttestation } from "../review/attestation.js";
3
3
  import { validateEvidence, validateTaskAgainstConfig } from "../schema/validate.js";
4
4
  import { isPassingVerificationEvidence } from "../verify/evidence.js";
5
+ import { taskContractHash } from "./contract.js";
6
+ import { acceptanceCoverage } from "../verify/acceptance-coverage.js";
5
7
  export async function checkTaskCompletionEvidence(stored, options) {
6
- const { root, config, sourceFingerprint, evidenceStore } = options;
8
+ const problem = await checkTaskVerificationEvidence(stored, options);
9
+ if (problem !== null)
10
+ return problem;
11
+ const attestation = await findReviewAttestation(options.root, options.sourceFingerprint, stored.task.id);
12
+ if (attestation === null || attestation.taskId !== stored.task.id ||
13
+ ((stored.task.criteria.some(c => c.acceptance !== undefined) || (stored.task.contractRevision ?? 0) > 0 || stored.task.goal !== undefined) &&
14
+ (attestation.schemaVersion !== 3 || attestation.taskContracts?.[stored.task.id] !== taskContractHash(stored.task))) ||
15
+ (attestation.tree !== undefined && stored.task.criteria.some((criterion) => !attestation.tree.criterionIds.includes(`${stored.task.id}:${criterion.id}`)))) {
16
+ return { status: "FAIL", code: "REVIEW_REQUIRED", remedy: `Run agent-ops review --task ${stored.task.id} --yes for the whole task and current source.` };
17
+ }
18
+ return null;
19
+ }
20
+ /** The same mechanical proof check used by final completion and child delivery. */
21
+ export async function checkTaskVerificationEvidence(stored, options) {
22
+ const { config, sourceFingerprint, evidenceStore } = options;
7
23
  if (stored.failureFingerprint !== null) {
8
24
  return { status: "FAIL", code: "VERIFICATION_FAILED", remedy: "Resolve the latest verification failure before completing the task." };
9
25
  }
@@ -12,8 +28,9 @@ export async function checkTaskCompletionEvidence(stored, options) {
12
28
  return { status: "FAIL", code: "TASK_STALE", remedy: "Recreate the task against the current config, then verify and review it." };
13
29
  }
14
30
  for (const criterion of stored.task.criteria) {
15
- const commands = config.verification.commands.filter(({ id, required }) => criterion.verifierIds.includes(id) && required);
16
- if (commands.length === 0) {
31
+ const commands = config.verification.commands.filter(({ id, required }) => required &&
32
+ (criterion.acceptance !== undefined || criterion.verifierIds.includes(id)));
33
+ if (commands.length === 0 && criterion.acceptance === undefined) {
17
34
  return { status: "FAIL", code: "REQUIRED_VERIFIER_MISSING", remedy: "Configure a required verifier for every criterion, then recreate, verify and review the task." };
18
35
  }
19
36
  for (const command of commands) {
@@ -30,6 +47,7 @@ export async function checkTaskCompletionEvidence(stored, options) {
30
47
  if (evidence.taskId === stored.task.id && evidence.criterionId === criterion.id &&
31
48
  evidence.commandId === command.id && evidence.configHash === configHash &&
32
49
  evidence.sourceFingerprint === sourceFingerprint &&
50
+ ((stored.task.goal === undefined && (stored.task.contractRevision ?? 0) === 0 && stored.task.criteria.every(c => c.acceptance === undefined)) || evidence.taskContractHash === taskContractHash(stored.task)) &&
33
51
  (latest === undefined || Date.parse(evidence.finishedAt) > Date.parse(latest.finishedAt) ||
34
52
  (Date.parse(evidence.finishedAt) === Date.parse(latest.finishedAt) && !isPassingVerificationEvidence(command, evidence)))) {
35
53
  latest = evidence;
@@ -40,9 +58,9 @@ export async function checkTaskCompletionEvidence(stored, options) {
40
58
  }
41
59
  }
42
60
  }
43
- const attestation = await findReviewAttestation(root, sourceFingerprint, stored.task.id);
44
- if (attestation === null || attestation.taskId !== stored.task.id) {
45
- return { status: "FAIL", code: "REVIEW_REQUIRED", remedy: `Run agent-ops review --task ${stored.task.id} --yes for the whole task and current source.` };
61
+ const coverage = await acceptanceCoverage(stored, config, sourceFingerprint, evidenceStore);
62
+ if (coverage.some(row => (row.mode === "behavioral" || row.mode === "invariant") && row.status !== "proven")) {
63
+ return { status: "FAIL", code: "ACCEPTANCE_EVIDENCE_REQUIRED", remedy: "Run current paired acceptance checks; UNKNOWN and non-discriminating checks do not prove a criterion." };
46
64
  }
47
65
  return null;
48
66
  }
@@ -53,6 +71,8 @@ export function findIncompleteSubtask(records, taskId) {
53
71
  while (previousSize !== descendants.size) {
54
72
  previousSize = descendants.size;
55
73
  for (const record of records) {
74
+ if (record.supersededBy !== undefined)
75
+ continue;
56
76
  if (record.task.parentTaskId !== undefined && descendants.has(record.task.parentTaskId)) {
57
77
  descendants.add(record.task.id);
58
78
  if (record.status === "active" || record.completedAt === null || record.failureFingerprint !== null)
@@ -0,0 +1,17 @@
1
+ import { canonicalJson } from "../config/hash.js";
2
+ import { sha256 } from "../fs/hash.js";
3
+ export function goalHash(task) {
4
+ return sha256(task.goal ?? task.intent ?? task.title);
5
+ }
6
+ export function criterionContractHash(criterion) {
7
+ return sha256(canonicalJson(criterion));
8
+ }
9
+ export function taskContractHash(task) {
10
+ return sha256(canonicalJson({ goalHash: goalHash(task), title: task.title,
11
+ intent: task.intent ?? null, revision: task.contractRevision ?? 0,
12
+ criteria: task.criteria.map(criterionContractHash) }));
13
+ }
14
+ export function treeContractHash(tasks) {
15
+ return sha256(canonicalJson(tasks.map(task => ({ id: task.id, hash: taskContractHash(task) }))
16
+ .sort((a, b) => a.id.localeCompare(b.id))));
17
+ }
@@ -0,0 +1,81 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { AgentOpsError } from "../fs/paths.js";
4
+ import { sha256 } from "../fs/hash.js";
5
+ import { readPrivateFile } from "../security/permissions.js";
6
+ import { reviewReportDigest, REVIEW_ATTESTATION_DIRECTORY, REVIEW_REPORT_ARTIFACT_SUFFIX } from "../review/attestation.js";
7
+ import { validateReviewReport } from "../review/report.js";
8
+ import { validateCriterion } from "../schema/validate.js";
9
+ import { commitIdentity } from "../schema/acceptance.js";
10
+ import { goalHash, taskContractHash } from "./contract.js";
11
+ export async function loadFindingPin(root, record, reference, input, records = [record]) {
12
+ const checked = validateCriterion(input.finding !== undefined && input.acceptance !== undefined
13
+ ? { ...input, acceptance: { ...input.acceptance, baselineCommit: input.finding.candidateCommit } } : input, "$.criterion");
14
+ if (!checked.ok)
15
+ throw new AgentOpsError("TASK_PIN_CRITERION_INVALID", "Pin requires a valid acceptance criterion.");
16
+ const match = reference.match(/^([a-f0-9]{64}):(0|[1-9][0-9]{0,2})$/u);
17
+ if (match === null)
18
+ throw new AgentOpsError("TASK_FINDING_REFERENCE_INVALID", "Finding reference must be report digest:finding index.");
19
+ const digest = match[1];
20
+ const index = Number(match[2]);
21
+ const directory = join(root, ...REVIEW_ATTESTATION_DIRECTORY.split("/"));
22
+ let names;
23
+ try {
24
+ names = await readdir(directory);
25
+ }
26
+ catch {
27
+ names = [];
28
+ }
29
+ const hashes = new Set([taskContractHash(record.task), ...(record.revisions ?? []).flatMap(r => [r.previousHash, r.currentHash])]);
30
+ for (const name of names.filter(n => /^[a-f0-9]{64}(?:\.[a-z][a-z0-9-]{0,127})?\.reports\.json$/u.test(n)).sort()) {
31
+ const source = await readPrivateFile(join(directory, name), root);
32
+ if (source === null || Buffer.byteLength(source) > 512 * 1024)
33
+ continue;
34
+ let artifact;
35
+ try {
36
+ artifact = JSON.parse(source);
37
+ }
38
+ catch {
39
+ continue;
40
+ }
41
+ if (typeof artifact !== "object" || artifact === null || Array.isArray(artifact))
42
+ continue;
43
+ if (artifact.tree !== undefined && (typeof artifact.tree !== "object" || artifact.tree === null ||
44
+ !Array.isArray(artifact.tree.taskIds) || typeof artifact.tree.rootTaskId !== "string"))
45
+ continue;
46
+ if (artifact.schemaVersion !== 2 || artifact.status !== "FAIL" || !commitIdentity(artifact.candidateCommit) ||
47
+ artifact.taskContracts?.[record.task.id] === undefined || !hashes.has(artifact.taskContracts[record.task.id]) ||
48
+ name !== artifact.sourceFingerprint + (artifact.taskId === undefined ? "" : "." + artifact.taskId) + REVIEW_REPORT_ARTIFACT_SUFFIX)
49
+ continue;
50
+ const owner = artifact.tree === undefined ? record : records.find(r => r.task.id === artifact.tree.rootTaskId);
51
+ if (owner === undefined || artifact.goalHash !== goalHash(owner.task) ||
52
+ (artifact.tree === undefined ? artifact.taskId !== record.task.id : !artifact.tree.taskIds.includes(record.task.id)))
53
+ continue;
54
+ for (const report of [artifact.report, artifact.adversarial?.report]) {
55
+ if (typeof report !== "object" || report === null || !Array.isArray(report.results) ||
56
+ report.results.some(r => typeof r !== "object" || r === null || typeof r.criterionId !== "string"))
57
+ continue;
58
+ const valid = validateReviewReport(report, report.results.map(r => r.criterionId));
59
+ if (!valid.ok || reviewReportDigest(valid.value) !== digest || index >= valid.value.findings.length)
60
+ continue;
61
+ const finding = valid.value.findings[index];
62
+ if (input.acceptance === undefined || input.acceptance.mode === "invariant")
63
+ throw new AgentOpsError("TASK_PIN_ACCEPTANCE_REQUIRED", "Pinned findings require behavioral red checks or an explicit review-only contract.");
64
+ const pinId = sha256(digest + ":" + index);
65
+ const requestedPin = input.finding?.pinId;
66
+ const existing = record.task.criteria.find(c => c.finding?.pinId === (requestedPin ?? pinId));
67
+ if (requestedPin !== undefined && existing === undefined)
68
+ throw new AgentOpsError("TASK_PIN_UNKNOWN", "Explicit recurrence must name an existing pin ID.");
69
+ if (existing !== undefined && requestedPin === undefined) {
70
+ if (input.id !== existing.id)
71
+ throw new AgentOpsError("TASK_PIN_EXISTS", "This finding already has a criterion; name its pin ID to record recurrence.");
72
+ return structuredClone(existing);
73
+ }
74
+ return { ...structuredClone(input), id: existing?.id ?? input.id,
75
+ acceptance: { ...input.acceptance, baselineCommit: existing?.acceptance?.baselineCommit ?? artifact.candidateCommit },
76
+ finding: existing?.finding ?? { pinId, reportDigest: digest, findingIndex: index,
77
+ candidateCommit: artifact.candidateCommit, criterionIds: [...finding.criterionIds] } };
78
+ }
79
+ }
80
+ throw new AgentOpsError("TASK_FINDING_NOT_FOUND", "No saved failed report binds this finding to the task's goal, contract and candidate commit.");
81
+ }
@@ -26,6 +26,9 @@ export function renderTaskMarkdown(record) {
26
26
  ...(record.archivedAt === null
27
27
  ? []
28
28
  : [`Archived: ${record.archivedAt}`]),
29
+ ...(record.task.intent === undefined
30
+ ? []
31
+ : ["", "## Modification intent", "", safeTaskText(record.task.intent)]),
29
32
  "",
30
33
  "## Acceptance criteria",
31
34
  ""