@voybio/ace-swarm 2.4.3 → 2.4.6

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 (144) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +72 -532
  3. package/assets/agent-state/INTERFACE_REGISTRY.md +2 -2
  4. package/assets/agent-state/MODULES/gates/gate-autonomy.json +4 -2
  5. package/assets/agent-state/MODULES/gates/gate-completeness.json +4 -2
  6. package/assets/agent-state/MODULES/gates/gate-operability.json +4 -2
  7. package/assets/agent-state/MODULES/gates/gate-security.json +3 -1
  8. package/assets/agent-state/MODULES/registry.json +1 -4
  9. package/assets/agent-state/MODULES/roles/capability-build.json +1 -2
  10. package/assets/agent-state/MODULES/roles/capability-eval.json +1 -1
  11. package/assets/agent-state/MODULES/roles/capability-qa.json +1 -2
  12. package/assets/agent-state/QUALITY_GATES.md +25 -9
  13. package/assets/agent-state/TEAL_CONFIG.md +2 -11
  14. package/assets/eval-battery/battery.json +85 -0
  15. package/assets/eval-battery/fixtures/edit-target.js +3 -0
  16. package/assets/scripts/render-mcp-configs.sh +12 -0
  17. package/dist/ace-context.d.ts +18 -0
  18. package/dist/ace-context.js +161 -16
  19. package/dist/ace-server-instructions.js +36 -9
  20. package/dist/astgrep-index.d.ts +7 -1
  21. package/dist/astgrep-index.js +66 -39
  22. package/dist/bridge-trace.d.ts +17 -0
  23. package/dist/bridge-trace.js +23 -0
  24. package/dist/cli.d.ts +9 -1
  25. package/dist/cli.js +619 -10
  26. package/dist/discovery-runtime-wrappers.d.ts +16 -1
  27. package/dist/discovery-runtime-wrappers.js +65 -9
  28. package/dist/eval-harness.d.ts +110 -0
  29. package/dist/eval-harness.js +254 -0
  30. package/dist/experience-reflector.d.ts +13 -0
  31. package/dist/experience-reflector.js +62 -0
  32. package/dist/gate-contract.d.ts +63 -0
  33. package/dist/gate-contract.js +178 -0
  34. package/dist/goal-engine.d.ts +155 -0
  35. package/dist/goal-engine.js +795 -0
  36. package/dist/helpers/store-resolution.js +40 -39
  37. package/dist/hermes/session-manager.d.ts +10 -0
  38. package/dist/hermes/session-manager.js +130 -27
  39. package/dist/job-scheduler.d.ts +58 -0
  40. package/dist/job-scheduler.js +202 -2
  41. package/dist/job-worker.d.ts +87 -0
  42. package/dist/job-worker.js +182 -0
  43. package/dist/local-model-policy.d.ts +8 -2
  44. package/dist/local-model-policy.js +31 -17
  45. package/dist/local-model-runtime.d.ts +6 -0
  46. package/dist/local-model-runtime.js +130 -2
  47. package/dist/measured-classing.d.ts +66 -0
  48. package/dist/measured-classing.js +165 -0
  49. package/dist/model-bridge.d.ts +45 -0
  50. package/dist/model-bridge.js +502 -44
  51. package/dist/orchestrator-supervisor.js +1 -1
  52. package/dist/plan-proposal.d.ts +7 -0
  53. package/dist/plan-proposal.js +74 -4
  54. package/dist/playbook-adaptation.d.ts +37 -0
  55. package/dist/playbook-adaptation.js +159 -0
  56. package/dist/playbook-curator.d.ts +16 -0
  57. package/dist/playbook-curator.js +106 -0
  58. package/dist/playbook-llm-reflector.d.ts +26 -0
  59. package/dist/playbook-llm-reflector.js +157 -0
  60. package/dist/public-surface.d.ts +2 -0
  61. package/dist/public-surface.js +26 -14
  62. package/dist/runtime-executor.js +45 -2
  63. package/dist/runtime-tool-specs.d.ts +8 -1
  64. package/dist/runtime-tool-specs.js +19 -1
  65. package/dist/safe-edit.d.ts +20 -0
  66. package/dist/safe-edit.js +355 -78
  67. package/dist/schemas.js +16 -15
  68. package/dist/server.d.ts +11 -2
  69. package/dist/server.js +55 -5
  70. package/dist/shared.d.ts +12 -0
  71. package/dist/shared.js +13 -1
  72. package/dist/source-mutation-coordinator.d.ts +54 -0
  73. package/dist/source-mutation-coordinator.js +259 -0
  74. package/dist/status-events.d.ts +4 -1
  75. package/dist/status-events.js +41 -6
  76. package/dist/store/ace-packed-store.d.ts +28 -9
  77. package/dist/store/ace-packed-store.js +324 -57
  78. package/dist/store/bgzf.d.ts +4 -0
  79. package/dist/store/bgzf.js +94 -0
  80. package/dist/store/bootstrap-store.d.ts +4 -1
  81. package/dist/store/bootstrap-store.js +87 -9
  82. package/dist/store/cache-workspace.d.ts +8 -0
  83. package/dist/store/cache-workspace.js +67 -1
  84. package/dist/store/crc32.d.ts +2 -0
  85. package/dist/store/crc32.js +16 -0
  86. package/dist/store/fsck.d.ts +16 -0
  87. package/dist/store/fsck.js +240 -0
  88. package/dist/store/gate-contract-migration.d.ts +10 -0
  89. package/dist/store/gate-contract-migration.js +413 -0
  90. package/dist/store/materializers/context-snapshot-materializer.d.ts +1 -0
  91. package/dist/store/materializers/context-snapshot-materializer.js +4 -1
  92. package/dist/store/materializers/projection-manager.d.ts +7 -0
  93. package/dist/store/materializers/projection-manager.js +37 -0
  94. package/dist/store/materializers/scheduler-projection-materializer.js +10 -3
  95. package/dist/store/repositories/goal-repository.d.ts +112 -0
  96. package/dist/store/repositories/goal-repository.js +157 -0
  97. package/dist/store/repositories/playbook-repository.d.ts +107 -0
  98. package/dist/store/repositories/playbook-repository.js +314 -0
  99. package/dist/store/store-artifacts.js +44 -2
  100. package/dist/store/store-authority-audit.js +1 -0
  101. package/dist/store/store-cli.d.ts +30 -0
  102. package/dist/store/store-cli.js +242 -0
  103. package/dist/store/store-snapshot.d.ts +12 -0
  104. package/dist/store/store-snapshot.js +74 -11
  105. package/dist/store/types.d.ts +1 -0
  106. package/dist/store/write-coordinator.d.ts +7 -0
  107. package/dist/store/write-coordinator.js +11 -0
  108. package/dist/todo-state.d.ts +21 -0
  109. package/dist/todo-state.js +150 -53
  110. package/dist/tools-agent.js +86 -22
  111. package/dist/tools-discovery.js +5 -3
  112. package/dist/tools-files.js +107 -23
  113. package/dist/tools-framework.js +146 -40
  114. package/dist/tools-goal.d.ts +11 -0
  115. package/dist/tools-goal.js +108 -0
  116. package/dist/tools-handoff.js +7 -11
  117. package/dist/tools-lifecycle.js +2 -0
  118. package/dist/tools-scheduler.js +44 -1
  119. package/dist/tools-skills.js +3 -2
  120. package/dist/tools-todo.js +64 -3
  121. package/dist/tools.js +2 -0
  122. package/dist/tui/chat.js +4 -0
  123. package/dist/tui/index.js +4 -1
  124. package/dist/tui/ollama.d.ts +2 -0
  125. package/dist/tui/openai-compatible.d.ts +14 -0
  126. package/dist/tui/openai-compatible.js +15 -2
  127. package/dist/tui/provider-discovery.d.ts +1 -0
  128. package/dist/tui/provider-discovery.js +2 -2
  129. package/docs/cli-reference.md +125 -0
  130. package/docs/development.md +46 -0
  131. package/docs/evaluation-safety.md +27 -0
  132. package/docs/experience-playbook.md +35 -0
  133. package/docs/hermes.md +129 -0
  134. package/docs/library-api.md +31 -0
  135. package/docs/mcp-tool-reference.md +290 -0
  136. package/docs/provider-security.md +85 -0
  137. package/docs/state-model.md +113 -0
  138. package/docs/troubleshooting.md +95 -0
  139. package/docs/tui-operator-guide.md +75 -0
  140. package/package.json +5 -1
  141. package/scripts/dev/run_hermes_crispr_live.mjs +212 -0
  142. package/assets/agent-state/MODULES/gates/gate-correctness.json +0 -7
  143. package/assets/agent-state/MODULES/gates/gate-evaluation.json +0 -7
  144. package/assets/agent-state/MODULES/gates/gate-typescript-public-surface.json +0 -7
@@ -3,10 +3,10 @@
3
3
  */
4
4
  import { createHash, randomUUID } from "node:crypto";
5
5
  import { spawnSync } from "node:child_process";
6
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
7
7
  import { dirname, isAbsolute, relative, resolve } from "node:path";
8
8
  import { z } from "zod";
9
- import { ACE_TASKS_ROOT_REL, normalizePathForValidation, safeRead, safeWriteAsync, safeWrite, resolveWorkspaceWritePath, resolveStoreFallbackKeysForPath, wsPath, WORKSPACE_ROOT, } from "./helpers.js";
9
+ import { ACE_TASKS_ROOT_REL, normalizePathForValidation, isReadError, safeRead, safeWriteAsync, safeWrite, resolveWorkspaceWritePath, resolveStoreFallbackKeysForPath, wsPath, WORKSPACE_ROOT, } from "./helpers.js";
10
10
  import { isInside, looksLikeSwarmHandoffPath, normalizeRelPath } from "./shared.js";
11
11
  import { validateAgentStateHandoffPayload, validateArtifactManifestPayload, validateProvenanceLogContent, validateStatusEventsNdjsonContent, validateSwarmHandoffPayload, validateTealConfigContent, validateRuntimeExecutorSessionRegistryPayload, validateRuntimeToolSpecRegistryPayload, validateTrackerSnapshotPayload, validateVericifyBridgeSnapshotPayload, validateVericifyProcessPostLogPayload, validateWorkspaceSessionRegistryPayload, lintHandoffPayload, } from "./schemas.js";
12
12
  import { syncTodoStateSafe } from "./todo-state.js";
@@ -14,9 +14,10 @@ import { shouldAutoRefreshKanbanForPath, refreshKanbanArtifacts, } from "./kanba
14
14
  import { appendRunLedgerEntrySafe } from "./run-ledger.js";
15
15
  import { appendStatusEventSafe } from "./status-events.js";
16
16
  import { safeEditFile, diffContents, applyPatch } from "./safe-edit.js";
17
- import { detectAstgrepCommand, locateAstgrepMatches, runAstgrepQuery, } from "./astgrep-index.js";
17
+ import { detectAstgrepCommand, flattenMetaVariables, locateAstgrepMatches, runAstgrepQuery, } from "./astgrep-index.js";
18
18
  import { readRuntimeProfile, resolveEffectiveSurgicalReadBudget, validateRuntimeProfileContent, } from "./runtime-profile.js";
19
19
  import { withLocalModelRuntimeRepository } from "./store/repositories/local-model-runtime-repository.js";
20
+ import { listStoreKeysSync } from "./store/store-snapshot.js";
20
21
  function reasonCodeFromError(error) {
21
22
  return typeof error === "object" && error !== null && "reason_code" in error
22
23
  ? String(error.reason_code)
@@ -129,13 +130,22 @@ async function storeLocateArtifact(input, result) {
129
130
  });
130
131
  }
131
132
  function listStructuralEditArtifacts(prefix) {
133
+ const relPaths = new Set();
134
+ const storePrefix = `state/artifacts/${STRUCTURAL_EDIT_ARTIFACTS_REL}/${prefix}-`;
135
+ for (const key of listStoreKeysSync(WORKSPACE_ROOT, storePrefix)) {
136
+ if (key.endsWith(".json")) {
137
+ relPaths.add(key.slice("state/artifacts/".length));
138
+ }
139
+ }
132
140
  const dir = wsPath(STRUCTURAL_EDIT_ARTIFACTS_REL);
133
- if (!existsSync(dir))
134
- return [];
135
- return readdirSync(dir)
136
- .filter((name) => name.startsWith(`${prefix}-`) && name.endsWith(".json"))
137
- .sort((a, b) => b.localeCompare(a))
138
- .map((name) => normalizeRelPath(relative(WORKSPACE_ROOT, resolve(dir, name))));
141
+ if (existsSync(dir)) {
142
+ for (const name of readdirSync(dir)) {
143
+ if (name.startsWith(`${prefix}-`) && name.endsWith(".json")) {
144
+ relPaths.add(normalizeRelPath(relative(WORKSPACE_ROOT, resolve(dir, name))));
145
+ }
146
+ }
147
+ }
148
+ return [...relPaths].sort((a, b) => b.localeCompare(a));
139
149
  }
140
150
  function findStoredMatch(matchId) {
141
151
  for (const relPath of listStructuralEditArtifacts("locate")) {
@@ -206,8 +216,12 @@ async function compileStructuralEditPlan(input) {
206
216
  },
207
217
  validation_command: input.validationCommand,
208
218
  test_command: input.testCommand,
219
+ allow_multi_match: input.allowMultiMatch,
209
220
  });
210
221
  }
222
+ function renderStructuralRewrite(template, captures) {
223
+ return template.replace(/\$([A-Z][A-Z0-9_]*)/g, (full, name) => captures[name] ?? full);
224
+ }
211
225
  function previewDiffText(diffText) {
212
226
  const lines = diffText.trimEnd().split("\n");
213
227
  const limited = lines.slice(0, 80).join("\n");
@@ -378,6 +392,24 @@ async function buildStructuralPreview(plan) {
378
392
  }),
379
393
  };
380
394
  }
395
+ if (targetMatches.length > 1 && !plan.allow_multi_match) {
396
+ return {
397
+ artifact: await finalize({
398
+ ok: false,
399
+ reason_code: "ambiguous_multi_match_in_file",
400
+ error: `Plan ${plan.plan_id} resolves to ${targetMatches.length} matches in ${plan.target.file}. Pass allow_multi_match=true at compile time to opt in, or narrow the pattern.`,
401
+ target_file: plan.target.file,
402
+ expected_file_hash: plan.target.file_hash,
403
+ current_file_hash: currentFileHash,
404
+ matched_count: targetMatches.length,
405
+ affected_file_count: affectedFiles.length,
406
+ changed_ranges: [],
407
+ diff_summary: "",
408
+ diff_preview: "",
409
+ promotable: false,
410
+ }),
411
+ };
412
+ }
381
413
  const astgrepCmd = detectAstgrepCommand();
382
414
  if (!astgrepCmd) {
383
415
  return {
@@ -403,7 +435,7 @@ async function buildStructuralPreview(plan) {
403
435
  mkdirSync(dirname(stagedOriginal), { recursive: true });
404
436
  writeFileSync(stagedOriginal, currentContent, "utf-8");
405
437
  writeFileSync(stagedRewrite, currentContent, "utf-8");
406
- const rewriteResult = spawnSync(astgrepCmd, ["--pattern", plan.locator.pattern, "--rewrite", plan.rewrite.rewrite_template, "--lang", plan.locator.lang, stagedRewrite, "--update-all"], { encoding: "utf8", cwd: WORKSPACE_ROOT });
438
+ const rewriteResult = spawnSync(astgrepCmd, ["--pattern", plan.locator.pattern, "--rewrite", plan.rewrite.rewrite_template, "--lang", plan.locator.lang, "--json", stagedRewrite], { encoding: "utf8", cwd: WORKSPACE_ROOT });
407
439
  if (rewriteResult.status !== 0) {
408
440
  return {
409
441
  artifact: await finalize({
@@ -423,7 +455,46 @@ async function buildStructuralPreview(plan) {
423
455
  }),
424
456
  };
425
457
  }
426
- const rewrittenContent = readFileSync(stagedRewrite, "utf-8");
458
+ let rawMatches = [];
459
+ try {
460
+ rawMatches = JSON.parse(rewriteResult.stdout || "[]");
461
+ }
462
+ catch {
463
+ rawMatches = [];
464
+ }
465
+ const selectedBytes = plan.target.selected_range?.byteOffset;
466
+ const targeted = rawMatches.find((match) => selectedBytes
467
+ && match.range?.byteOffset?.start === selectedBytes.start
468
+ && match.range?.byteOffset?.end === selectedBytes.end);
469
+ const replacement = targeted && (typeof targeted.replacement === "string"
470
+ ? targeted.replacement
471
+ : renderStructuralRewrite(plan.rewrite.rewrite_template, flattenMetaVariables(targeted.metaVariables)));
472
+ if (!selectedBytes || !targeted || typeof replacement !== "string") {
473
+ return {
474
+ artifact: await finalize({
475
+ ok: false,
476
+ reason_code: "target_byte_range_drift",
477
+ error: `No match at selected byte range [${selectedBytes?.start},${selectedBytes?.end}] for plan ${plan.plan_id}`,
478
+ target_file: plan.target.file,
479
+ expected_file_hash: plan.target.file_hash,
480
+ current_file_hash: currentFileHash,
481
+ matched_count: rawMatches.length,
482
+ affected_file_count: affectedFiles.length,
483
+ changed_ranges: [],
484
+ diff_summary: "",
485
+ diff_preview: "",
486
+ promotable: false,
487
+ staging_path: stagingDir,
488
+ }),
489
+ };
490
+ }
491
+ const originalBuffer = Buffer.from(currentContent, "utf8");
492
+ const rewrittenContent = Buffer.concat([
493
+ originalBuffer.subarray(0, selectedBytes.start ?? 0),
494
+ Buffer.from(replacement, "utf8"),
495
+ originalBuffer.subarray(selectedBytes.end ?? 0),
496
+ ]).toString("utf8");
497
+ writeFileSync(stagedRewrite, rewrittenContent, "utf8");
427
498
  const rewriteHash = contentSha256(rewrittenContent);
428
499
  const diff = diffContents(currentContent, rewrittenContent);
429
500
  const diffProcess = spawnSync("diff", ["-u", "--label", `a/${plan.target.file}`, "--label", `b/${plan.target.file}`, stagedOriginal, stagedRewrite], { encoding: "utf8", cwd: WORKSPACE_ROOT });
@@ -432,6 +503,7 @@ async function buildStructuralPreview(plan) {
432
503
  : diffProcess.status === 0
433
504
  ? ""
434
505
  : diff.diff_summary;
506
+ const hasUnifiedDiff = diffProcess.status === 1;
435
507
  return {
436
508
  artifact: await finalize({
437
509
  ok: true,
@@ -444,7 +516,7 @@ async function buildStructuralPreview(plan) {
444
516
  changed_ranges: parseChangedRanges(diffText),
445
517
  diff_summary: diff.diff_summary,
446
518
  diff_preview: diffText ? previewDiffText(diffText) : diff.diff_summary,
447
- promotable: diff.has_diff,
519
+ promotable: hasUnifiedDiff,
448
520
  staging_path: stagingDir,
449
521
  }),
450
522
  rewritten_content: rewrittenContent,
@@ -1073,10 +1145,15 @@ export function registerFileTools(server) {
1073
1145
  .boolean()
1074
1146
  .optional()
1075
1147
  .describe("Keep .ace-backup copy (default: true)"),
1076
- }, async ({ path, content, validation_command, test_command, backup }) => {
1148
+ expected_base_hash: z
1149
+ .string()
1150
+ .optional()
1151
+ .describe("Optional source precondition checked immediately before promotion; prefer full sha256:<hex>"),
1152
+ }, async ({ path, content, validation_command, test_command, backup, expected_base_hash }) => {
1077
1153
  const result = safeEditFile({
1078
1154
  path,
1079
1155
  content,
1156
+ expected_base_hash,
1080
1157
  validation_command,
1081
1158
  test_command,
1082
1159
  backup,
@@ -1093,6 +1170,9 @@ export function registerFileTools(server) {
1093
1170
  original_hash: result.original_hash,
1094
1171
  new_hash: result.new_hash,
1095
1172
  restored: result.restored,
1173
+ reason_code: result.reason_code,
1174
+ expected_hash: result.expected_hash,
1175
+ observed_hash: result.observed_hash,
1096
1176
  validation_passed: result.validation_passed,
1097
1177
  test_passed: result.test_passed,
1098
1178
  },
@@ -1154,19 +1234,19 @@ export function registerFileTools(server) {
1154
1234
  };
1155
1235
  });
1156
1236
  // ── Diff (new P2 tool) ────────────────────────────────────────────
1157
- server.tool("diff_files", "Compute a line-based diff between two file contents or file paths", {
1237
+ server.tool("diff_files", "Produce a sequence-aware unified diff between two contents or two workspace paths", {
1158
1238
  original: z.string().describe("Original content or workspace-relative file path"),
1159
1239
  modified: z.string().describe("Modified content or workspace-relative file path"),
1160
- is_path: z
1161
- .boolean()
1162
- .optional()
1163
- .describe("If true, treat original/modified as file paths and read their contents"),
1164
- }, async ({ original, modified, is_path }) => {
1240
+ mode: z.enum(["content", "path"]).default("content").describe("Explicitly interpret both inputs as literal content or workspace-relative paths"),
1241
+ }, async ({ original, modified, mode }) => {
1165
1242
  let origContent = original;
1166
1243
  let modContent = modified;
1167
- if (is_path) {
1244
+ if (mode === "path") {
1168
1245
  origContent = safeRead(original);
1169
1246
  modContent = safeRead(modified);
1247
+ if (isReadError(origContent) || isReadError(modContent)) {
1248
+ throw new Error(`Unable to read diff path: ${isReadError(origContent) ? origContent : modContent}`);
1249
+ }
1170
1250
  }
1171
1251
  const diff = diffContents(origContent, modContent);
1172
1252
  return {
@@ -1344,9 +1424,10 @@ export function registerFileTools(server) {
1344
1424
  max_results: z.number().int().min(1).max(200).optional().describe("Max locator matches to keep (default: 50)"),
1345
1425
  desired_change: z.string().describe("Plain-language edit intent; used as rewrite_template when no explicit template is provided"),
1346
1426
  rewrite_template: z.string().optional().describe("Explicit ast-grep rewrite template; preferred when provided"),
1427
+ allow_multi_match: z.boolean().optional().describe("Opt into selecting and rewriting only the chosen match when multiple matches exist in the target file"),
1347
1428
  validation_command: z.string().optional().describe("Shell command to validate before promotion"),
1348
1429
  test_command: z.string().optional().describe("Shell command to run before promotion"),
1349
- }, async ({ match_id, pattern, lang, scope, symbol_hint, max_results, desired_change, rewrite_template, validation_command, test_command, }) => {
1430
+ }, async ({ match_id, pattern, lang, scope, symbol_hint, max_results, desired_change, rewrite_template, allow_multi_match, validation_command, test_command, }) => {
1350
1431
  if (!match_id && (!pattern || !lang)) {
1351
1432
  return jsonResponse({
1352
1433
  ok: false,
@@ -1455,6 +1536,7 @@ export function registerFileTools(server) {
1455
1536
  selectedMatch,
1456
1537
  desiredChange: desired_change,
1457
1538
  rewriteTemplate: rewrite_template,
1539
+ allowMultiMatch: allow_multi_match,
1458
1540
  validationCommand: validation_command,
1459
1541
  testCommand: test_command,
1460
1542
  }).catch((error) => {
@@ -1597,6 +1679,7 @@ export function registerFileTools(server) {
1597
1679
  const safeResult = safeEditFile({
1598
1680
  path: compiledPlan.target.file,
1599
1681
  content: preview.rewritten_content,
1682
+ expected_base_hash: compiledPlan.target.file_hash,
1600
1683
  validation_command: validation_command ?? compiledPlan.validation_command,
1601
1684
  test_command: test_command ?? compiledPlan.test_command,
1602
1685
  });
@@ -1644,10 +1727,11 @@ export function registerFileTools(server) {
1644
1727
  server.tool("apply_patch", "Apply a unified diff patch to a workspace file. Applies the patch to staged content, runs optional validation/tests, and promotes to the target only on success. Cost: moderate.", {
1645
1728
  path: z.string().describe("Workspace-relative file path to patch"),
1646
1729
  patch: z.string().describe("Unified diff patch (output of diff -u or similar)"),
1730
+ expected_base_hash: z.string().optional().describe("Optional source precondition checked immediately before promotion; prefer full sha256:<hex>"),
1647
1731
  validation_command: z.string().optional().describe("Shell command to validate after applying (e.g. 'npx tsc --noEmit')"),
1648
1732
  test_command: z.string().optional().describe("Shell command to run tests after applying"),
1649
- }, async ({ path: relPath, patch, validation_command, test_command }) => {
1650
- const result = applyPatch({ path: relPath, patch, validation_command, test_command });
1733
+ }, async ({ path: relPath, patch, expected_base_hash, validation_command, test_command }) => {
1734
+ const result = applyPatch({ path: relPath, patch, expected_base_hash, validation_command, test_command });
1651
1735
  if (!result.ok) {
1652
1736
  return {
1653
1737
  content: [{ type: "text", text: `apply_patch failed: ${result.error ?? "unknown error"}\n${result.validation_output ?? ""}` }],
@@ -18,6 +18,9 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
18
18
  import { resolve } from "node:path";
19
19
  import { auditPublicSurface } from "./public-surface.js";
20
20
  import { auditStoreAuthority, writeStoreAuthorityAuditReport, } from "./store/store-authority-audit.js";
21
+ import { openStore } from "./store/ace-packed-store.js";
22
+ import { getWorkspaceStorePath } from "./store/store-snapshot.js";
23
+ import { parseGateManifest, resolveGateSelection, } from "./gate-contract.js";
21
24
  import { PROVENANCE_CRITICAL_EVENT_TYPES, validateArtifactManifestPayload, validateProvenanceLogContent, validateTealConfigContent, } from "./schemas.js";
22
25
  import { readAceTaskContractAssessment } from "./ace-autonomy.js";
23
26
  import { listStoreKeysSync, readStoreBlobSync } from "./store/store-snapshot.js";
@@ -54,28 +57,6 @@ function getArtifactManifestEntries(payload) {
54
57
  }
55
58
  return [];
56
59
  }
57
- function parseGateManifest(raw, sourceRef) {
58
- try {
59
- const gate = JSON.parse(raw);
60
- const id = typeof gate.id === "string" ? gate.id.trim() : "";
61
- if (!id)
62
- return undefined;
63
- const type = gate.type === "executable" || gate.type === "artifact_scan" || gate.type === "manual_review"
64
- ? gate.type
65
- : "manual_review";
66
- return {
67
- id,
68
- type,
69
- invariant: typeof gate.invariant === "string" ? gate.invariant : "",
70
- command: typeof gate.command === "string" ? gate.command : "",
71
- evidence_requirement: typeof gate.evidence_requirement === "string" ? gate.evidence_requirement : "",
72
- source_ref: sourceRef,
73
- };
74
- }
75
- catch {
76
- return undefined;
77
- }
78
- }
79
60
  function parseJsonForValidation(raw) {
80
61
  return parseJsonLikeText(raw);
81
62
  }
@@ -130,10 +111,14 @@ function hasArtifactEvidence(reference) {
130
111
  const normalized = reference.trim();
131
112
  if (!normalized)
132
113
  return false;
133
- const candidates = [wsPath("agent-state", normalized), wsPath(normalized)];
134
- if (candidates.some((candidate) => existsSync(candidate)))
114
+ const relativeCandidates = [
115
+ normalized,
116
+ normalized.startsWith("agent-state/") ? normalized : `agent-state/${normalized}`,
117
+ ];
118
+ const absoluteCandidates = relativeCandidates.map((candidate) => wsPath(candidate));
119
+ if (absoluteCandidates.some((candidate) => existsSync(candidate)))
135
120
  return true;
136
- return candidates.some((candidate) => {
121
+ return relativeCandidates.some((candidate) => {
137
122
  const content = safeRead(candidate);
138
123
  return !content.startsWith("[FILE NOT FOUND]") && !content.startsWith("[ACCESS DENIED]");
139
124
  });
@@ -1216,6 +1201,27 @@ export function registerFrameworkTools(server) {
1216
1201
  ok: isGitRepo(),
1217
1202
  detail: isGitRepo() ? "initialized" : "NOT a git repo",
1218
1203
  });
1204
+ // Check: malformed store events were not silently dropped.
1205
+ const storePath = getWorkspaceStorePath(resolveWorkspaceRoot());
1206
+ if (existsSync(storePath)) {
1207
+ try {
1208
+ const store = await openStore(storePath, { readOnly: true });
1209
+ const skipped = store.skippedMalformedEventCount;
1210
+ await store.close();
1211
+ checks.push({
1212
+ name: "acepack:skipped-malformed-events",
1213
+ ok: skipped === 0,
1214
+ detail: skipped === 0 ? "0 skipped malformed events" : `${skipped} skipped malformed events`,
1215
+ });
1216
+ }
1217
+ catch (error) {
1218
+ checks.push({
1219
+ name: "acepack:skipped-malformed-events",
1220
+ ok: false,
1221
+ detail: `store validation failed: ${error instanceof Error ? error.message : String(error)}`,
1222
+ });
1223
+ }
1224
+ }
1219
1225
  // ── Deep checks (P2 hardening) ─────────────────────────────────
1220
1226
  // Check: module registry role coverage vs event emitters
1221
1227
  const registryRaw = safeRead("agent-state/MODULES/registry.json");
@@ -1385,7 +1391,9 @@ export function registerFrameworkTools(server) {
1385
1391
  result.ok
1386
1392
  ? `✅ Public surface audit passed (${result.summary.tools} tools, ${result.summary.resources} resources, ${result.summary.prompts} prompts)`
1387
1393
  : `❌ Public surface audit failed (${result.failures.length} failure${result.failures.length === 1 ? "" : "s"})`,
1388
- `Report: ${result.report_path}`,
1394
+ `Artifact: ${result.artifact_path}`,
1395
+ `Read: ace read ${result.artifact_path}`,
1396
+ ...(result.written_path ? [`Store path: ${result.written_path}`] : []),
1389
1397
  `Events: ${result.summary.emitted_events}`,
1390
1398
  ...(result.failures.slice(0, 12).map((entry) => `- ${entry}`)),
1391
1399
  ...(result.warnings.slice(0, 12).map((entry) => `- warning: ${entry}`)),
@@ -1406,8 +1414,9 @@ export function registerFrameworkTools(server) {
1406
1414
  }, async ({ write_artifact, artifact_path }) => {
1407
1415
  const result = auditStoreAuthority();
1408
1416
  const reportPath = artifact_path ?? result.report_path;
1417
+ let writtenPath;
1409
1418
  if (write_artifact !== false) {
1410
- writeStoreAuthorityAuditReport({
1419
+ writtenPath = writeStoreAuthorityAuditReport({
1411
1420
  ...result,
1412
1421
  report_path: reportPath,
1413
1422
  }, reportPath);
@@ -1418,7 +1427,9 @@ export function registerFrameworkTools(server) {
1418
1427
  type: "text",
1419
1428
  text: [
1420
1429
  `✅ Store authority audit completed (${result.total_safe_write_sites} safeWrite users)`,
1421
- `Report: ${reportPath}`,
1430
+ `Artifact: ${reportPath}`,
1431
+ `Read: ace read ${reportPath}`,
1432
+ ...(writtenPath ? [`Store path: ${writtenPath}`] : []),
1422
1433
  `Bypassing ACEPACK: ${result.bypassing_sites.length}`,
1423
1434
  `Runtime files without fallback keys: ${result.runtime_files_without_store_fallback_keys.length}`,
1424
1435
  `Non-regenerable projections: ${result.non_regenerable_projections.length}`,
@@ -1436,7 +1447,7 @@ export function registerFrameworkTools(server) {
1436
1447
  gate_ids: z
1437
1448
  .array(z.string())
1438
1449
  .optional()
1439
- .describe("Specific gate IDs to run (e.g. ['gate-correctness']). Omit to run all registered gates."),
1450
+ .describe("Specific gate IDs to run (e.g. ['gate-correctness']). Omit to run the active stage's stage-activated workspace gates."),
1440
1451
  review_mode: z
1441
1452
  .enum(["skeptic_adversarial"])
1442
1453
  .optional()
@@ -1449,25 +1460,102 @@ export function registerFrameworkTools(server) {
1449
1460
  const gatesDir = wsPath("agent-state", "MODULES", "gates");
1450
1461
  const resolved = resolveGateManifests(gatesDir);
1451
1462
  const allGates = resolved.gates;
1452
- const gateEvidenceRef = resolved.source === "store" ? "knowledge/gates/*" : "agent-state/MODULES/gates/";
1453
- // Filter to requested gates (or run all)
1454
- const targets = gate_ids
1455
- ? allGates.filter((g) => gate_ids.includes(g.id))
1456
- : allGates;
1463
+ const manifestEvidenceRef = resolved.source === "store" ? "knowledge/gates/*" : "agent-state/MODULES/gates/";
1464
+ const selection = resolveGateSelection({
1465
+ gates: allGates,
1466
+ manifestSource: resolved.source,
1467
+ statusRaw: safeRead("agent-state/STATUS.md"),
1468
+ tealRaw: safeRead("agent-state/TEAL_CONFIG.md"),
1469
+ gateIds: gate_ids,
1470
+ });
1471
+ const targets = selection.selected;
1457
1472
  if (targets.length === 0) {
1458
- const noGateMessage = allGates.length === 0
1459
- ? "❌ No gate manifests found in agent-state/MODULES/gates/ or ace-state.ace knowledge/gates/*."
1460
- : `❌ No matching gates found. Available: ${allGates.map((g) => g.id).join(", ")}`;
1473
+ if (allGates.length === 0) {
1474
+ return {
1475
+ content: [
1476
+ {
1477
+ type: "text",
1478
+ text: "❌ No gate manifests found in agent-state/MODULES/gates/ or ace-state.ace knowledge/gates/*.",
1479
+ },
1480
+ ],
1481
+ };
1482
+ }
1483
+ if (selection.selection_mode === "explicit") {
1484
+ const noGateMessage = selection.missing_explicit_gate_ids.length > 0
1485
+ ? `❌ No matching gates found for: ${selection.missing_explicit_gate_ids.join(", ")}. Available: ${allGates.map((g) => g.id).join(", ")}`
1486
+ : `❌ No matching gates found. Available: ${allGates.map((g) => g.id).join(", ")}`;
1487
+ return {
1488
+ content: [
1489
+ {
1490
+ type: "text",
1491
+ text: noGateMessage,
1492
+ },
1493
+ ],
1494
+ };
1495
+ }
1496
+ const message = selection.note ??
1497
+ "No stage-activated workspace gates were selected for the active ACE stage. Use explicit gate_ids when needed.";
1498
+ await appendStatusEventSafe({
1499
+ source_module: "capability-framework",
1500
+ event_type: "GATES_EXECUTED",
1501
+ status: "pass",
1502
+ summary: message,
1503
+ payload: {
1504
+ gates_run: [],
1505
+ gate_results: [],
1506
+ passed: 0,
1507
+ failed: 0,
1508
+ selection_mode: selection.selection_mode,
1509
+ selected_gate_ids: [],
1510
+ skipped_gate_ids: selection.skipped_gate_ids,
1511
+ gate_manifest_source: resolved.source,
1512
+ active_role: selection.active_role ?? null,
1513
+ active_module: selection.active_module ?? null,
1514
+ active_pipeline: selection.active_pipeline ?? null,
1515
+ selection_note: message,
1516
+ evidence_ref: manifestEvidenceRef,
1517
+ },
1518
+ objective_id: "gate-execution",
1519
+ });
1520
+ const ledger = await appendRunLedgerEntrySafe({
1521
+ tool: "execute_gates",
1522
+ category: "info",
1523
+ message,
1524
+ artifacts: [],
1525
+ metadata: {
1526
+ passed: 0,
1527
+ failed: 0,
1528
+ selection_mode: selection.selection_mode,
1529
+ selected_gate_ids: [],
1530
+ skipped_gate_ids: selection.skipped_gate_ids,
1531
+ gate_manifest_source: resolved.source,
1532
+ active_role: selection.active_role ?? null,
1533
+ active_module: selection.active_module ?? null,
1534
+ active_pipeline: selection.active_pipeline ?? null,
1535
+ selection_note: message,
1536
+ },
1537
+ });
1461
1538
  return {
1462
1539
  content: [
1463
1540
  {
1464
1541
  type: "text",
1465
- text: noGateMessage,
1542
+ text: [
1543
+ `ℹ️ ${message}`,
1544
+ `Selection mode: ${selection.selection_mode}`,
1545
+ `Active role/module: ${selection.active_role ?? "(unknown)"} / ${selection.active_module ?? "(unknown)"}`,
1546
+ `Active pipeline: ${selection.active_pipeline ?? "(unknown)"}`,
1547
+ `Selected gates: none`,
1548
+ `Skipped gates: ${selection.skipped_gate_ids.length > 0 ? selection.skipped_gate_ids.join(", ") : "none"}`,
1549
+ `Gate manifest source: ${resolved.source === "store"
1550
+ ? "ace-state.ace (knowledge/gates/*)"
1551
+ : "agent-state/MODULES/gates/"}`,
1552
+ `Run ledger: ${ledger.path} (${ledger.entry.id})`,
1553
+ ].join("\n"),
1466
1554
  },
1467
1555
  ],
1468
1556
  };
1469
1557
  }
1470
- const gateArtifactRefs = targets.map((gate) => gate.source_ref ?? gateEvidenceRef);
1558
+ const gateArtifactRefs = targets.map((gate) => gate.source_ref ?? manifestEvidenceRef);
1471
1559
  const results = evaluateGateTargets(targets);
1472
1560
  const passed = results.filter((r) => r.ok).length;
1473
1561
  const failed = results.filter((r) => !r.ok).length;
@@ -1495,7 +1583,13 @@ export function registerFrameworkTools(server) {
1495
1583
  })),
1496
1584
  passed,
1497
1585
  failed,
1498
- evidence_ref: evidence?.evidenceRef ?? gateEvidenceRef,
1586
+ evidence_ref: evidence?.evidenceRef ?? manifestEvidenceRef,
1587
+ selection_mode: selection.selection_mode,
1588
+ active_role: selection.active_role ?? null,
1589
+ active_module: selection.active_module ?? null,
1590
+ active_pipeline: selection.active_pipeline ?? null,
1591
+ selected_gate_ids: results.map((r) => r.id),
1592
+ skipped_gate_ids: selection.skipped_gate_ids,
1499
1593
  gate_manifest_source: resolved.source,
1500
1594
  ...(review
1501
1595
  ? {
@@ -1530,6 +1624,13 @@ export function registerFrameworkTools(server) {
1530
1624
  passed,
1531
1625
  failed,
1532
1626
  gate_ids: results.map((r) => r.id),
1627
+ selection_mode: selection.selection_mode,
1628
+ active_role: selection.active_role ?? null,
1629
+ active_module: selection.active_module ?? null,
1630
+ active_pipeline: selection.active_pipeline ?? null,
1631
+ selected_gate_ids: results.map((r) => r.id),
1632
+ skipped_gate_ids: selection.skipped_gate_ids,
1633
+ missing_explicit_gate_ids: selection.missing_explicit_gate_ids,
1533
1634
  gate_manifest_source: resolved.source,
1534
1635
  ...(review
1535
1636
  ? {
@@ -1559,6 +1660,11 @@ export function registerFrameworkTools(server) {
1559
1660
  `Gate manifest source: ${resolved.source === "store"
1560
1661
  ? "ace-state.ace (knowledge/gates/*)"
1561
1662
  : "agent-state/MODULES/gates/"}`,
1663
+ `Selection mode: ${selection.selection_mode}`,
1664
+ `Active role/module: ${selection.active_role ?? "(unknown)"} / ${selection.active_module ?? "(unknown)"}`,
1665
+ `Active pipeline: ${selection.active_pipeline ?? "(unknown)"}`,
1666
+ `Selected gates: ${results.map((r) => r.id).join(", ")}`,
1667
+ `Skipped gates: ${selection.skipped_gate_ids.length > 0 ? selection.skipped_gate_ids.join(", ") : "none"}`,
1562
1668
  `Run ledger: ${ledger.path} (${ledger.entry.id})`,
1563
1669
  ...(evidence ? [`Evidence: ${evidence.path} (${evidence.evidenceRef})`] : []),
1564
1670
  "",
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ACE Goal Engine tool registrations (WS-F).
3
+ *
4
+ * These let a premium host delegate a long-running objective to ACE and poll
5
+ * it. `start_goal`/`resume_goal` never block the MCP server for the life of the
6
+ * goal: they compile/prepare, then hand execution to a detached `ace goal run`
7
+ * child and return immediately. The host polls `get_goal_status`.
8
+ */
9
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ export declare function registerGoalTools(server: McpServer): void;
11
+ //# sourceMappingURL=tools-goal.d.ts.map
@@ -0,0 +1,108 @@
1
+ /**
2
+ * ACE Goal Engine tool registrations (WS-F).
3
+ *
4
+ * These let a premium host delegate a long-running objective to ACE and poll
5
+ * it. `start_goal`/`resume_goal` never block the MCP server for the life of the
6
+ * goal: they compile/prepare, then hand execution to a detached `ace goal run`
7
+ * child and return immediately. The host polls `get_goal_status`.
8
+ */
9
+ import { spawn } from "node:child_process";
10
+ import { fileURLToPath } from "node:url";
11
+ import { z } from "zod";
12
+ import { getGoalStatus, listGoals, startGoal, stopGoal, } from "./goal-engine.js";
13
+ function asJsonBlock(value) {
14
+ return `\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\``;
15
+ }
16
+ function spawnDetachedRunner(goalId, verb) {
17
+ try {
18
+ // dist/cli.js is a sibling of this compiled module.
19
+ const cliPath = fileURLToPath(new URL("./cli.js", import.meta.url));
20
+ const child = spawn(process.execPath, [cliPath, "goal", verb, goalId], {
21
+ detached: true,
22
+ stdio: "ignore",
23
+ });
24
+ child.unref();
25
+ return child.pid;
26
+ }
27
+ catch {
28
+ return undefined;
29
+ }
30
+ }
31
+ export function registerGoalTools(server) {
32
+ server.tool("start_goal", "Compile and launch a long-running autonomous goal (ACE plans it, executes step-by-step under evidence-gated completion, and can never declare success by prose). Returns immediately; poll with get_goal_status.", {
33
+ objective: z.string().min(1).describe("What the goal should accomplish"),
34
+ budget_hours: z.number().positive().max(24).optional().describe("Wall-clock budget (default 4h)"),
35
+ max_replans: z.number().int().min(0).max(20).optional().describe("Max plan-level replans (default 3)"),
36
+ plan_only: z.boolean().optional().describe("Compile + validate the plan but do not execute"),
37
+ detach: z.boolean().optional().describe("Run in a detached background child (default true when not plan_only)"),
38
+ }, async ({ objective, budget_hours, max_replans, plan_only, detach }) => {
39
+ const started = await startGoal(objective, { budget_hours, max_replans, plan_only });
40
+ let runnerPid;
41
+ if (started.ok && !plan_only && detach !== false) {
42
+ runnerPid = spawnDetachedRunner(started.goal.goal_id, "run");
43
+ }
44
+ return {
45
+ content: [
46
+ {
47
+ type: "text",
48
+ text: [
49
+ started.ok ? "✅ Goal started" : "❌ Goal not started",
50
+ `goal_id: ${started.goal.goal_id}`,
51
+ `status: ${started.goal.status}`,
52
+ started.reason_code ? `reason: ${started.reason_code}` : "",
53
+ runnerPid ? `detached runner pid: ${runnerPid}` : "",
54
+ asJsonBlock({ ...started, runner_pid: runnerPid }),
55
+ ]
56
+ .filter(Boolean)
57
+ .join("\n"),
58
+ },
59
+ ],
60
+ };
61
+ });
62
+ server.tool("get_goal_status", "Get a goal's status (tick count, current step, last heartbeat, escalations, plan status), or list all goals when no goal_id is given.", {
63
+ goal_id: z.string().optional().describe("Goal id; omit to list all goals"),
64
+ }, async ({ goal_id }) => {
65
+ if (!goal_id) {
66
+ return {
67
+ content: [{ type: "text", text: asJsonBlock({ goals: listGoals() }) }],
68
+ };
69
+ }
70
+ const view = getGoalStatus(goal_id);
71
+ if (!view) {
72
+ return { content: [{ type: "text", text: `❌ Unknown goal: ${goal_id}` }] };
73
+ }
74
+ return { content: [{ type: "text", text: asJsonBlock(view) }] };
75
+ });
76
+ server.tool("stop_goal", "Request that a running goal stop at its next tick boundary.", { goal_id: z.string().describe("Goal id to stop") }, async ({ goal_id }) => {
77
+ try {
78
+ const goal = stopGoal(goal_id);
79
+ return {
80
+ content: [{ type: "text", text: `🛑 Stop requested for ${goal.goal_id} (status ${goal.status}).` }],
81
+ };
82
+ }
83
+ catch (err) {
84
+ return {
85
+ content: [{ type: "text", text: `❌ ${err instanceof Error ? err.message : String(err)}` }],
86
+ };
87
+ }
88
+ });
89
+ server.tool("resume_goal", "Resume a blocked/stopped goal: reset any step left running mid-crash and continue execution in a detached runner.", { goal_id: z.string().describe("Goal id to resume") }, async ({ goal_id }) => {
90
+ const view = getGoalStatus(goal_id);
91
+ if (!view) {
92
+ return { content: [{ type: "text", text: `❌ Unknown goal: ${goal_id}` }] };
93
+ }
94
+ const runnerPid = spawnDetachedRunner(goal_id, "resume");
95
+ return {
96
+ content: [
97
+ {
98
+ type: "text",
99
+ text: [
100
+ `▶️ Resuming ${goal_id}`,
101
+ runnerPid ? `detached runner pid: ${runnerPid}` : "runner spawn failed; run `ace goal resume` manually",
102
+ ].join("\n"),
103
+ },
104
+ ],
105
+ };
106
+ });
107
+ }
108
+ //# sourceMappingURL=tools-goal.js.map