@voybio/ace-swarm 0.2.5 → 2.4.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 (115) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/README.md +20 -13
  3. package/assets/agent-state/EVIDENCE_LOG.md +1 -1
  4. package/assets/agent-state/MODULES/roles/capability-framework.json +41 -0
  5. package/assets/agent-state/MODULES/roles/capability-git.json +33 -0
  6. package/assets/agent-state/MODULES/roles/capability-safety.json +37 -0
  7. package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +21 -0
  8. package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +43 -0
  9. package/assets/agent-state/MODULES/schemas/WORKSPACE_SESSION_REGISTRY.schema.json +11 -0
  10. package/assets/agent-state/STATUS.md +2 -2
  11. package/assets/scripts/ace-hook-dispatch.mjs +70 -6
  12. package/assets/scripts/render-mcp-configs.sh +19 -5
  13. package/dist/ace-context.js +22 -1
  14. package/dist/ace-server-instructions.js +3 -3
  15. package/dist/ace-state-resolver.js +5 -3
  16. package/dist/astgrep-index.d.ts +9 -1
  17. package/dist/astgrep-index.js +14 -3
  18. package/dist/cli.js +27 -20
  19. package/dist/handoff-registry.js +5 -5
  20. package/dist/helpers/artifacts.d.ts +19 -0
  21. package/dist/helpers/artifacts.js +152 -0
  22. package/dist/helpers/bootstrap.d.ts +24 -0
  23. package/dist/helpers/bootstrap.js +894 -0
  24. package/dist/helpers/constants.d.ts +53 -0
  25. package/dist/helpers/constants.js +288 -0
  26. package/dist/helpers/drift.d.ts +13 -0
  27. package/dist/helpers/drift.js +45 -0
  28. package/dist/helpers/path-utils.d.ts +17 -0
  29. package/dist/helpers/path-utils.js +104 -0
  30. package/dist/helpers/store-resolution.d.ts +19 -0
  31. package/dist/helpers/store-resolution.js +301 -0
  32. package/dist/helpers/workspace-root.d.ts +3 -0
  33. package/dist/helpers/workspace-root.js +80 -0
  34. package/dist/helpers.d.ts +8 -125
  35. package/dist/helpers.js +8 -1768
  36. package/dist/job-scheduler.js +3 -3
  37. package/dist/local-model-runtime.js +12 -1
  38. package/dist/model-bridge.d.ts +7 -0
  39. package/dist/model-bridge.js +75 -5
  40. package/dist/orchestrator-supervisor.d.ts +14 -0
  41. package/dist/orchestrator-supervisor.js +72 -1
  42. package/dist/run-ledger.js +3 -3
  43. package/dist/runtime-command.d.ts +8 -0
  44. package/dist/runtime-command.js +38 -6
  45. package/dist/runtime-executor.d.ts +14 -0
  46. package/dist/runtime-executor.js +669 -171
  47. package/dist/runtime-profile.d.ts +32 -0
  48. package/dist/runtime-profile.js +89 -13
  49. package/dist/runtime-tool-specs.d.ts +21 -0
  50. package/dist/runtime-tool-specs.js +78 -3
  51. package/dist/safe-edit.d.ts +7 -0
  52. package/dist/safe-edit.js +163 -37
  53. package/dist/schemas.js +19 -0
  54. package/dist/shared.d.ts +2 -2
  55. package/dist/status-events.js +9 -6
  56. package/dist/store/ace-packed-store.d.ts +3 -2
  57. package/dist/store/ace-packed-store.js +188 -110
  58. package/dist/store/bootstrap-store.d.ts +1 -1
  59. package/dist/store/bootstrap-store.js +94 -81
  60. package/dist/store/cache-workspace.js +11 -5
  61. package/dist/store/materializers/context-snapshot-materializer.js +6 -2
  62. package/dist/store/materializers/hook-context-materializer.d.ts +6 -9
  63. package/dist/store/materializers/hook-context-materializer.js +11 -21
  64. package/dist/store/materializers/host-file-materializer.js +6 -0
  65. package/dist/store/materializers/projection-manager.d.ts +0 -1
  66. package/dist/store/materializers/projection-manager.js +5 -13
  67. package/dist/store/materializers/scheduler-projection-materializer.js +1 -1
  68. package/dist/store/materializers/vericify-projector.d.ts +7 -7
  69. package/dist/store/materializers/vericify-projector.js +11 -11
  70. package/dist/store/repositories/local-model-runtime-repository.d.ts +120 -3
  71. package/dist/store/repositories/local-model-runtime-repository.js +242 -6
  72. package/dist/store/skills-install.d.ts +4 -0
  73. package/dist/store/skills-install.js +21 -12
  74. package/dist/store/state-reader.d.ts +2 -0
  75. package/dist/store/state-reader.js +20 -0
  76. package/dist/store/store-artifacts.d.ts +7 -0
  77. package/dist/store/store-artifacts.js +27 -1
  78. package/dist/store/store-authority-audit.d.ts +18 -1
  79. package/dist/store/store-authority-audit.js +115 -5
  80. package/dist/store/store-snapshot.d.ts +3 -0
  81. package/dist/store/store-snapshot.js +22 -2
  82. package/dist/store/workspace-store-paths.d.ts +39 -0
  83. package/dist/store/workspace-store-paths.js +94 -0
  84. package/dist/store/write-coordinator.d.ts +65 -0
  85. package/dist/store/write-coordinator.js +386 -0
  86. package/dist/todo-state.js +5 -5
  87. package/dist/tools-agent.js +268 -14
  88. package/dist/tools-discovery.js +1 -1
  89. package/dist/tools-files.d.ts +7 -0
  90. package/dist/tools-files.js +299 -10
  91. package/dist/tools-framework.js +25 -5
  92. package/dist/tools-handoff.js +2 -2
  93. package/dist/tools-lifecycle.js +4 -4
  94. package/dist/tools-memory.js +6 -6
  95. package/dist/tools-todo.js +2 -2
  96. package/dist/tracker-adapters.d.ts +1 -1
  97. package/dist/tracker-adapters.js +13 -18
  98. package/dist/tracker-sync.js +5 -3
  99. package/dist/tui/agent-runner.js +3 -1
  100. package/dist/tui/chat.js +103 -7
  101. package/dist/tui/dashboard.d.ts +1 -0
  102. package/dist/tui/dashboard.js +43 -0
  103. package/dist/tui/layout.d.ts +20 -0
  104. package/dist/tui/layout.js +31 -1
  105. package/dist/tui/local-model-contract.d.ts +6 -2
  106. package/dist/tui/local-model-contract.js +16 -3
  107. package/dist/vericify-bridge.d.ts +5 -0
  108. package/dist/vericify-bridge.js +27 -3
  109. package/dist/workspace-manager.d.ts +30 -3
  110. package/dist/workspace-manager.js +257 -27
  111. package/package.json +1 -2
  112. package/dist/internal-tool-runtime.d.ts +0 -21
  113. package/dist/internal-tool-runtime.js +0 -136
  114. package/dist/store/workspace-snapshot.d.ts +0 -26
  115. package/dist/store/workspace-snapshot.js +0 -107
@@ -1,18 +1,66 @@
1
1
  /**
2
2
  * File operation tool registrations + new safe-edit and diff tools.
3
3
  */
4
- import { readdirSync } from "node:fs";
5
- import { isAbsolute, relative } from "node:path";
4
+ import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
6
6
  import { z } from "zod";
7
- import { ACE_TASKS_ROOT_REL, normalizePathForValidation, safeRead, safeWrite, wsPath, WORKSPACE_ROOT, } from "./helpers.js";
8
- import { looksLikeSwarmHandoffPath } from "./shared.js";
7
+ import { ACE_TASKS_ROOT_REL, normalizePathForValidation, safeRead, safeWriteAsync, safeWrite, resolveStoreFallbackKeysForPath, wsPath, WORKSPACE_ROOT, } from "./helpers.js";
8
+ import { isInside, looksLikeSwarmHandoffPath, normalizeRelPath } from "./shared.js";
9
9
  import { validateAgentStateHandoffPayload, validateArtifactManifestPayload, validateProvenanceLogContent, validateStatusEventsNdjsonContent, validateSwarmHandoffPayload, validateTealConfigContent, validateRuntimeExecutorSessionRegistryPayload, validateRuntimeToolSpecRegistryPayload, validateTrackerSnapshotPayload, validateVericifyBridgeSnapshotPayload, validateVericifyProcessPostLogPayload, validateWorkspaceSessionRegistryPayload, lintHandoffPayload, } from "./schemas.js";
10
10
  import { syncTodoStateSafe } from "./todo-state.js";
11
- import { validateRuntimeProfileContent } from "./runtime-profile.js";
12
11
  import { shouldAutoRefreshKanbanForPath, refreshKanbanArtifacts, } from "./kanban.js";
13
12
  import { appendRunLedgerEntrySafe } from "./run-ledger.js";
14
13
  import { appendStatusEventSafe } from "./status-events.js";
15
- import { safeEditFile, diffContents } from "./safe-edit.js";
14
+ import { safeEditFile, diffContents, applyPatch } from "./safe-edit.js";
15
+ import { readRuntimeProfile, resolveEffectiveSurgicalReadBudget, validateRuntimeProfileContent, } from "./runtime-profile.js";
16
+ import { withLocalModelRuntimeRepository } from "./store/repositories/local-model-runtime-repository.js";
17
+ export function planAstgrepRewriteTargets(files) {
18
+ const affected = [...new Set(files.filter(Boolean))].sort((a, b) => a.localeCompare(b));
19
+ if (affected.length > 1) {
20
+ return {
21
+ ok: false,
22
+ affected_files: affected,
23
+ error: `astgrep_rewrite refuses multi-file rewrites (${affected.length} files): ${affected.slice(0, 5).join(", ")}`,
24
+ };
25
+ }
26
+ return {
27
+ ok: true,
28
+ affected_files: affected,
29
+ target_file: affected[0],
30
+ };
31
+ }
32
+ async function appendAstgrepRewriteTransition(sessionId, to, reason, evidenceRefs) {
33
+ if (!sessionId)
34
+ return;
35
+ await withLocalModelRuntimeRepository(WORKSPACE_ROOT, async (repo) => {
36
+ await repo.appendTransitionRecord({
37
+ subject_kind: "plan_step",
38
+ subject_id: `${sessionId}/astgrep_rewrite`,
39
+ from: "staged",
40
+ to,
41
+ reason,
42
+ reason_code: to === "promoted" ? "surgical_rewrite" : "surgical_rewrite_failed",
43
+ evidence_refs: evidenceRefs,
44
+ });
45
+ }).catch(() => undefined);
46
+ }
47
+ function astgrepFileToWorkspaceRel(file) {
48
+ const abs = isAbsolute(file) ? file : resolve(WORKSPACE_ROOT, file);
49
+ if (!isInside(WORKSPACE_ROOT, abs))
50
+ return undefined;
51
+ return normalizeRelPath(relative(WORKSPACE_ROOT, abs));
52
+ }
53
+ async function resolveReadFileLinesBudget() {
54
+ let modelClass;
55
+ const statuses = await withLocalModelRuntimeRepository(WORKSPACE_ROOT, async (repo) => repo.listRuntimeStatuses()).catch(() => undefined);
56
+ modelClass = statuses?.[0]?.model_class;
57
+ const profile = readRuntimeProfile();
58
+ const resolved = resolveEffectiveSurgicalReadBudget(profile, modelClass);
59
+ return {
60
+ ...resolved,
61
+ source: modelClass ? "runtime-status" : "runtime-profile",
62
+ };
63
+ }
16
64
  export function registerFileTools(server) {
17
65
  // ── Append-only artifact protection ───────────────────────────────
18
66
  // These files must only grow; overwrites from the generic write tool
@@ -26,12 +74,12 @@ export function registerFileTools(server) {
26
74
  "agent-state/PROVENANCE_LOG.md",
27
75
  ]);
28
76
  // ── Core file operations ──────────────────────────────────────────
29
- server.tool("read_workspace_file", "Read any workspace file by relative path", {
77
+ server.tool("read_workspace_file", "Read any workspace file by relative path. Prefer outline_file for discovery, read_file_lines for known regions, astgrep_query for symbol lookup. For small_local runs, this is the fallback after surgical reads are insufficient. Cost: heavy for large files.", {
30
78
  path: z.string().describe("Relative path from workspace root"),
31
79
  }, async ({ path }) => ({
32
80
  content: [{ type: "text", text: safeRead(path) }],
33
81
  }));
34
- server.tool("write_workspace_file", "Write or update a workspace file", {
82
+ server.tool("write_workspace_file", "Write or update a workspace file. Prefer apply_patch for targeted edits, astgrep_rewrite for structural rewrites. Cost: heavy.", {
35
83
  path: z.string().describe("Relative path from workspace root"),
36
84
  content: z.string().describe("File content"),
37
85
  }, async ({ path, content }) => {
@@ -475,8 +523,10 @@ export function registerFileTools(server) {
475
523
  const synced = await syncTodoStateSafe(content);
476
524
  todoStateSuffix = `\nTODO state synced: ${synced.path}`;
477
525
  }
526
+ const storeFallbackKeys = resolveStoreFallbackKeysForPath(normalizedPath);
527
+ const useAsyncStoreAdmission = storeFallbackKeys.length > 0;
478
528
  // Keep the file as a materialized projection after the canonical store update.
479
- const abs = safeWrite(path, content);
529
+ const abs = useAsyncStoreAdmission ? await safeWriteAsync(path, content) : safeWrite(path, content);
480
530
  let kanbanSuffix = "";
481
531
  const shouldRefreshKanban = shouldAutoRefreshKanbanForPath(normalizedPath);
482
532
  if (shouldRefreshKanban) {
@@ -544,7 +594,7 @@ export function registerFileTools(server) {
544
594
  }
545
595
  });
546
596
  // ── Safe-edit (new P0 tool) ───────────────────────────────────────
547
- server.tool("safe_edit_file", "Edit a file using copy→validate→swap pattern. Automatically rolls back if validation or tests fail.", {
597
+ server.tool("safe_edit_file", "Edit a file using copy→validate→swap pattern. Automatically rolls back if validation or tests fail. Prefer apply_patch for small targeted edits. Cost: heavy for large files.", {
548
598
  path: z.string().describe("Workspace-relative file path to edit"),
549
599
  content: z.string().describe("New file content"),
550
600
  validation_command: z
@@ -676,5 +726,244 @@ export function registerFileTools(server) {
676
726
  ],
677
727
  };
678
728
  });
729
+ // ── Surgical read / query / patch tools ──────────────────────────
730
+ server.tool("read_file_lines", "Read a specific line range from a workspace file. Prefer this over read_workspace_file for large files. Cost: moderate.", {
731
+ path: z.string().describe("Workspace-relative file path"),
732
+ start_line: z.number().int().min(1).describe("First line to read (1-indexed)"),
733
+ end_line: z.number().int().min(1).describe("Last line to read (inclusive)"),
734
+ symbol_anchor: z.string().optional().describe("Optional symbol name hint for context (informational only)"),
735
+ }, async ({ path: relPath, start_line, end_line, symbol_anchor }) => {
736
+ const budget = await resolveReadFileLinesBudget();
737
+ const requestedLines = end_line - start_line + 1;
738
+ const budgetText = budget.read_file_lines_max_lines === null
739
+ ? "unbounded"
740
+ : String(budget.read_file_lines_max_lines);
741
+ const headerLines = [
742
+ `# Budget: model_class=${budget.model_class}; read_file_lines_max_lines=${budgetText}`,
743
+ `# Budget source: ${budget.source}`,
744
+ ];
745
+ if (budget.read_file_lines_max_lines !== null &&
746
+ requestedLines > budget.read_file_lines_max_lines) {
747
+ return {
748
+ content: [
749
+ {
750
+ type: "text",
751
+ text: [
752
+ ...headerLines,
753
+ `Error: requested range (${requestedLines} lines) exceeds ${budget.model_class} budget of ${budgetText} lines.`,
754
+ "Use outline_file for discovery or astgrep_query for symbol lookup.",
755
+ ].join("\n"),
756
+ },
757
+ ],
758
+ isError: true,
759
+ };
760
+ }
761
+ const content = safeRead(relPath);
762
+ const lines = content.split("\n");
763
+ const slice = lines.slice(start_line - 1, end_line);
764
+ const result = slice.map((l, i) => `${start_line + i}: ${l}`).join("\n");
765
+ return {
766
+ content: [
767
+ {
768
+ type: "text",
769
+ text: [
770
+ ...headerLines,
771
+ `# ${relPath} (lines ${start_line}–${end_line})`,
772
+ symbol_anchor ? `# Symbol context: ${symbol_anchor}` : "",
773
+ "",
774
+ result,
775
+ ]
776
+ .filter(Boolean)
777
+ .join("\n"),
778
+ },
779
+ ],
780
+ };
781
+ });
782
+ server.tool("outline_file", "Return symbol signatures and top-level structure of a file without bodies. Use for discovery before reading specific regions. Cost: cheap.", {
783
+ path: z.string().describe("Workspace-relative file path"),
784
+ }, async ({ path: relPath }) => {
785
+ const content = safeRead(relPath);
786
+ const lines = content.split("\n");
787
+ const outlinePatterns = [
788
+ /^export\s+(async\s+)?function\s+\w+/,
789
+ /^export\s+(class|interface|type|enum|const|abstract)\s+\w+/,
790
+ /^(export\s+)?default\s+/,
791
+ /^\s+(async\s+)?(\w+\s+)?\w+\s*\([^)]*\)\s*[:{]/,
792
+ /^(function|class|interface|type|enum|const|let|var)\s+\w+/,
793
+ ];
794
+ const outline = [];
795
+ lines.forEach((line, idx) => {
796
+ if (outlinePatterns.some(p => p.test(line))) {
797
+ outline.push(`${idx + 1}: ${line.trimEnd()}`);
798
+ }
799
+ });
800
+ return {
801
+ content: [{
802
+ type: "text",
803
+ text: [
804
+ `# Outline: ${relPath} (${lines.length} lines total)`,
805
+ "",
806
+ outline.length > 0 ? outline.join("\n") : "(no symbols found)",
807
+ ].join("\n"),
808
+ }],
809
+ };
810
+ });
811
+ server.tool("astgrep_query", "Search for a structural pattern in workspace files using ast-grep. Returns matching ranges without reading whole files. Cost: cheap.", {
812
+ pattern: z.string().describe("ast-grep pattern, e.g. 'export function $NAME($$$)'"),
813
+ lang: z.string().describe("Language: ts, py, rust, go, js"),
814
+ scope: z.string().optional().describe("Workspace-relative directory to search (default: src)"),
815
+ max_results: z.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),
816
+ }, async ({ pattern, lang, scope, max_results }) => {
817
+ const { runAstgrepQuery } = await import("./astgrep-index.js");
818
+ const root = wsPath(scope ?? "src");
819
+ const matches = runAstgrepQuery(pattern, lang, [root]);
820
+ const limited = matches.slice(0, max_results ?? 50);
821
+ const lines = limited.map((m) => `${m.file}:${m.line}: ${m.text}`);
822
+ return {
823
+ content: [{
824
+ type: "text",
825
+ text: [
826
+ `# astgrep_query: ${pattern} (${lang})`,
827
+ `# Found ${matches.length} matches, showing ${limited.length}`,
828
+ "",
829
+ ...lines,
830
+ ].join("\n"),
831
+ }],
832
+ };
833
+ });
834
+ server.tool("astgrep_rewrite", "Apply a structural rewrite to one workspace file using ast-grep pattern + replacement. Multi-file rewrites are refused; staged through safe_edit_file. Emits transition-compatible result details. Cost: heavy.", {
835
+ pattern: z.string().describe("ast-grep pattern to match"),
836
+ rewrite: z.string().describe("Replacement template (use $NAME etc. from pattern)"),
837
+ lang: z.string().describe("Language: ts, py, rust, go, js"),
838
+ scope: z.string().optional().describe("Workspace-relative directory (default: src)"),
839
+ confirm_multi_file: z.boolean().optional().describe("Deprecated; multi-file rewrites are always refused"),
840
+ validation_command: z.string().optional().describe("Shell command to validate the staged rewrite before promotion"),
841
+ test_command: z.string().optional().describe("Shell command to test the staged rewrite before promotion"),
842
+ session_id: z.string().optional().describe("Optional runtime session id for transition-record emission"),
843
+ }, async ({ pattern, rewrite, lang, scope, validation_command, test_command, session_id }) => {
844
+ const { runAstgrepQuery } = await import("./astgrep-index.js");
845
+ const root = wsPath(scope ?? "src");
846
+ if (!isInside(WORKSPACE_ROOT, root)) {
847
+ return {
848
+ content: [{ type: "text", text: `astgrep_rewrite failed: scope escapes workspace root (${scope})` }],
849
+ isError: true,
850
+ };
851
+ }
852
+ const matches = runAstgrepQuery(pattern, lang, [root]);
853
+ const affectedFiles = [...new Set(matches.map(m => m.file))];
854
+ const plan = planAstgrepRewriteTargets(affectedFiles);
855
+ if (!plan.ok) {
856
+ await appendAstgrepRewriteTransition(session_id, "refused", plan.error ?? "astgrep_rewrite refused the requested rewrite", plan.affected_files);
857
+ return {
858
+ content: [{
859
+ type: "text",
860
+ text: plan.error ?? "astgrep_rewrite refused the requested rewrite",
861
+ }],
862
+ isError: true,
863
+ };
864
+ }
865
+ if (matches.length === 0) {
866
+ return {
867
+ content: [{ type: "text", text: "No matches found for pattern. No files modified." }],
868
+ };
869
+ }
870
+ const targetRel = astgrepFileToWorkspaceRel(plan.target_file ?? "");
871
+ if (!targetRel) {
872
+ return {
873
+ content: [{ type: "text", text: `astgrep_rewrite failed: matched path escapes workspace root (${plan.target_file ?? ""})` }],
874
+ isError: true,
875
+ };
876
+ }
877
+ const originalContent = safeRead(targetRel);
878
+ if (originalContent.startsWith("[FILE NOT FOUND]") || originalContent.startsWith("[ACCESS DENIED]")) {
879
+ return {
880
+ content: [{ type: "text", text: `astgrep_rewrite failed: cannot read ${targetRel}\n${originalContent}` }],
881
+ isError: true,
882
+ };
883
+ }
884
+ const stagingDir = wsPath(".ace-staging", `astgrep-rewrite-${Date.now()}`);
885
+ const stagedFile = resolve(stagingDir, targetRel);
886
+ mkdirSync(dirname(stagedFile), { recursive: true });
887
+ writeFileSync(stagedFile, originalContent, "utf-8");
888
+ const { spawnSync } = await import("node:child_process");
889
+ const result = spawnSync("ast-grep", ["--pattern", pattern, "--rewrite", rewrite, "--lang", lang, stagedFile, "--update-all"], { encoding: "utf8", cwd: WORKSPACE_ROOT });
890
+ if (result.status !== 0) {
891
+ await appendAstgrepRewriteTransition(session_id, "failed", `astgrep_rewrite failed in staging: ${result.stderr || result.stdout || "unknown error"}`, [targetRel]);
892
+ return {
893
+ content: [{ type: "text", text: `astgrep rewrite failed:\n${result.stderr}` }],
894
+ isError: true,
895
+ };
896
+ }
897
+ const rewrittenContent = readFileSync(stagedFile, "utf-8");
898
+ const safeResult = safeEditFile({
899
+ path: targetRel,
900
+ content: rewrittenContent,
901
+ validation_command,
902
+ test_command,
903
+ });
904
+ if (!safeResult.ok) {
905
+ await appendAstgrepRewriteTransition(session_id, "failed", `astgrep_rewrite failed before promotion for ${targetRel}: ${safeResult.error ?? "unknown error"}`, [targetRel]);
906
+ return {
907
+ content: [{
908
+ type: "text",
909
+ text: [
910
+ `astgrep_rewrite failed before promotion`,
911
+ `Path: ${targetRel}`,
912
+ `Error: ${safeResult.error ?? "unknown error"}`,
913
+ safeResult.validation_passed !== undefined ? `Validation: ${safeResult.validation_passed ? "passed" : "FAILED"}` : "Validation: not requested",
914
+ safeResult.validation_output ? `Validation output:\n${safeResult.validation_output}` : "",
915
+ safeResult.test_passed !== undefined ? `Tests: ${safeResult.test_passed ? "passed" : "FAILED"}` : "Tests: not requested",
916
+ safeResult.test_output ? `Test output:\n${safeResult.test_output}` : "",
917
+ `Staging: ${safeResult.staging_path || stagingDir}`,
918
+ ].filter(Boolean).join("\n"),
919
+ }],
920
+ isError: true,
921
+ };
922
+ }
923
+ await appendAstgrepRewriteTransition(session_id, "promoted", `astgrep_rewrite promoted staged rewrite for ${targetRel}`, [targetRel]);
924
+ return {
925
+ content: [{
926
+ type: "text",
927
+ text: [
928
+ `# astgrep_rewrite completed`,
929
+ `Pattern: ${pattern}`,
930
+ `Rewrite: ${rewrite}`,
931
+ `Files affected: ${affectedFiles.length}`,
932
+ `Path: ${targetRel}`,
933
+ `Hash: ${safeResult.original_hash} → ${safeResult.new_hash}`,
934
+ safeResult.validation_passed !== undefined ? `Validation: ${safeResult.validation_passed ? "passed" : "failed"}` : "Validation: not requested",
935
+ safeResult.test_passed !== undefined ? `Tests: ${safeResult.test_passed ? "passed" : "failed"}` : "Tests: not requested",
936
+ `Staging: ${safeResult.staging_path}`,
937
+ result.stdout ? `\nOutput:\n${result.stdout}` : "",
938
+ ].filter(Boolean).join("\n"),
939
+ }],
940
+ };
941
+ });
942
+ 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.", {
943
+ path: z.string().describe("Workspace-relative file path to patch"),
944
+ patch: z.string().describe("Unified diff patch (output of diff -u or similar)"),
945
+ validation_command: z.string().optional().describe("Shell command to validate after applying (e.g. 'npx tsc --noEmit')"),
946
+ test_command: z.string().optional().describe("Shell command to run tests after applying"),
947
+ }, async ({ path: relPath, patch, validation_command, test_command }) => {
948
+ const result = applyPatch({ path: relPath, patch, validation_command, test_command });
949
+ if (!result.ok) {
950
+ return {
951
+ content: [{ type: "text", text: `apply_patch failed: ${result.error ?? "unknown error"}\n${result.validation_output ?? ""}` }],
952
+ isError: true,
953
+ };
954
+ }
955
+ return {
956
+ content: [{
957
+ type: "text",
958
+ text: [
959
+ `# apply_patch: ${relPath}`,
960
+ `Status: success`,
961
+ result.validation_passed !== undefined ? `Validation: ${result.validation_passed ? "passed" : "failed"}` : "",
962
+ result.test_passed !== undefined ? `Tests: ${result.test_passed ? "passed" : "failed"}` : "",
963
+ `Staging: ${result.staging_path}`,
964
+ ].filter(Boolean).join("\n"),
965
+ }],
966
+ };
967
+ });
679
968
  }
680
969
  //# sourceMappingURL=tools-files.js.map
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { z } from "zod";
6
6
  import { bootstrapStoreWorkspace } from "./store/bootstrap-store.js";
7
- import { ACE_ROOT_REL, ACE_TASKS_ROOT_REL, ALL_MCP_CLIENTS, ALL_LLM_PROVIDERS, ALL_AGENTS, COMPOSABLE_AGENTS, SWARM_AGENTS, SWARM_SUBAGENT_MAP, WORKSPACE_ROOT, classifyPathSource, detectAssetDrift, getAllMcpServerConfigSnippets, getAgentInstructionPath, getAgentManifestPath, getKernelArtifactPath, getMcpClientInstallHint, getMcpServerConfigSnippet, getTaskArtifactPath, isSwarmRole, listAvailableSkills, normalizePathForValidation, resolveWorkspaceRoot, safeRead, safeWrite, withFileLock, wsPath, } from "./helpers.js";
7
+ import { ACE_ROOT_REL, ACE_TASKS_ROOT_REL, ALL_MCP_CLIENTS, ALL_LLM_PROVIDERS, ALL_AGENTS, COMPOSABLE_AGENTS, SWARM_AGENTS, SWARM_SUBAGENT_MAP, WORKSPACE_ROOT, classifyPathSource, detectAssetDrift, getAllMcpServerConfigSnippets, getAgentInstructionPath, getAgentManifestPath, getKernelArtifactPath, getMcpClientInstallHint, getMcpServerConfigSnippet, getTaskArtifactPath, isSwarmRole, listAvailableSkills, normalizePathForValidation, resolveWorkspaceRoot, safeRead, safeWriteAsync, withFileLock, wsPath, } from "./helpers.js";
8
8
  import { getRoleTitle, MCP_CLIENT_ENUM, scoreDomains, } from "./shared.js";
9
9
  import { defaultModelForProvider, } from "./tui/provider-discovery.js";
10
10
  import { refreshAstgrepIndex } from "./astgrep-index.js";
@@ -21,6 +21,20 @@ import { auditStoreAuthority, writeStoreAuthorityAuditReport, } from "./store/st
21
21
  import { PROVENANCE_CRITICAL_EVENT_TYPES, validateArtifactManifestPayload, validateProvenanceLogContent, validateTealConfigContent, } from "./schemas.js";
22
22
  import { readAceTaskContractAssessment } from "./ace-autonomy.js";
23
23
  import { listStoreKeysSync, readStoreBlobSync } from "./store/store-snapshot.js";
24
+ function executionRoleForDomain(domain) {
25
+ switch (domain) {
26
+ case "venture":
27
+ return "vos";
28
+ case "ux":
29
+ return "ui";
30
+ case "engineering":
31
+ return "coders";
32
+ case "research":
33
+ return "research";
34
+ default:
35
+ return "orchestrator";
36
+ }
37
+ }
24
38
  function getArtifactManifestEntries(payload) {
25
39
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
26
40
  return [];
@@ -287,7 +301,7 @@ async function appendEvidenceLogEntrySafe(review) {
287
301
  const timestamp = new Date().toISOString();
288
302
  const anchor = `#ts:${timestamp}`;
289
303
  const evidenceRef = `agent-state/EVIDENCE_LOG.md${anchor}`;
290
- return withFileLock("agent-state/EVIDENCE_LOG.md", () => {
304
+ return withFileLock("agent-state/EVIDENCE_LOG.md", async () => {
291
305
  const existing = safeRead("agent-state/EVIDENCE_LOG.md");
292
306
  const seed = existing.startsWith("[FILE NOT FOUND]") || existing.startsWith("[ACCESS DENIED]")
293
307
  ? "# EVIDENCE LOG\n\nAppend-only validation evidence.\n\n## Entries\n"
@@ -319,7 +333,7 @@ async function appendEvidenceLogEntrySafe(review) {
319
333
  })
320
334
  : [" - none"]),
321
335
  ];
322
- const path = safeWrite("agent-state/EVIDENCE_LOG.md", `${seed}${lines.join("\n")}\n`);
336
+ const path = await safeWriteAsync("agent-state/EVIDENCE_LOG.md", `${seed}${lines.join("\n")}\n`);
323
337
  return { path, evidenceRef };
324
338
  });
325
339
  }
@@ -534,6 +548,12 @@ export function registerFrameworkTools(server) {
534
548
  recommendedSubagents.add("memory");
535
549
  }
536
550
  return {
551
+ structuredContent: {
552
+ detected_domain: detected,
553
+ suggested_execution_role: executionRoleForDomain(detected),
554
+ default_entry_agent: "orchestrator",
555
+ task_contract_ok: taskContract.ok,
556
+ },
537
557
  content: [
538
558
  {
539
559
  type: "text",
@@ -635,7 +655,7 @@ export function registerFrameworkTools(server) {
635
655
  model: resolvedLlmModel ?? undefined,
636
656
  baseUrl: resolvedLlmBaseUrl ?? undefined,
637
657
  });
638
- const astIndex = refreshAstgrepIndex({
658
+ const astIndex = await refreshAstgrepIndex({
639
659
  scope: ".",
640
660
  append_evidence: true,
641
661
  emit_event: true,
@@ -662,7 +682,7 @@ export function registerFrameworkTools(server) {
662
682
  category: "major_update",
663
683
  message: "Bootstrapped ACE workspace state",
664
684
  artifacts: [
665
- `${ACE_ROOT_REL}/ace-state.ace`,
685
+ storeResult.storePath,
666
686
  `${ACE_TASKS_ROOT_REL}/todo.md`,
667
687
  normalizePathForValidation(delta.index_path),
668
688
  "agent-state/AST_GREP_INDEX.json",
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { z } from "zod";
6
- import { ACE_TASKS_ROOT_REL, normalizePathForValidation, readTaskArtifact, safeWrite, wsPath, WORKSPACE_ROOT, } from "./helpers.js";
6
+ import { ACE_TASKS_ROOT_REL, normalizePathForValidation, readTaskArtifact, safeWriteAsync, wsPath, WORKSPACE_ROOT, } from "./helpers.js";
7
7
  import { ROLE_ENUM, HANDOFF_VALIDATION_MODE, HANDOFF_LIFECYCLE_STATUS_ENUM, } from "./shared.js";
8
8
  import { validateHandoffPayload, validateSwarmHandoffPayload, lintHandoffPayload, } from "./schemas.js";
9
9
  import { acknowledgeHandoffSafe, getHandoffRegistryPath, readHandoffRegistry, registerHandoffSafe, } from "./handoff-registry.js";
@@ -247,7 +247,7 @@ export function registerHandoffTools(server) {
247
247
  .replace("T", "-")
248
248
  .slice(0, 15);
249
249
  const filePath = wsPath("tasks", `SWARM_HANDOFF.${from}_to_${to}_${timeStr}_${shortId}.json`);
250
- safeWrite(filePath, JSON.stringify(handoff, null, 2));
250
+ await safeWriteAsync(filePath, JSON.stringify(handoff, null, 2));
251
251
  const registry = await registerHandoffSafe({
252
252
  handoff_id: handoff.handoff_id,
253
253
  from: handoff.router.from,
@@ -2,8 +2,8 @@
2
2
  * Lifecycle tool registrations: status events, circuit breaker, and kanban.
3
3
  */
4
4
  import { z } from "zod";
5
- import { safeRead, safeWrite } from "./helpers.js";
6
- import { appendStatusEventSafe, getStatusEventsPath, readStatusEvents, } from "./status-events.js";
5
+ import { safeRead, safeWriteAsync } from "./helpers.js";
6
+ import { appendStatusEventSafe, getStatusEventsPath, readStatusEvents } from "./status-events.js";
7
7
  import { refreshKanbanArtifacts } from "./kanban.js";
8
8
  export function registerLifecycleTools(server) {
9
9
  server.tool("emit_status_event", "Append a schema-valid lifecycle event into agent-state/STATUS_EVENTS.ndjson", {
@@ -119,7 +119,7 @@ export function registerLifecycleTools(server) {
119
119
  ? "# STATUS\n\n"
120
120
  : current.trimEnd();
121
121
  const next = `${seed}\n- [${timestamp}] CIRCUIT_BREAKER_OPENED: ${reason}${owner ? ` (owner: ${owner})` : ""}\n`;
122
- const statusPath = safeWrite("agent-state/STATUS.md", next);
122
+ const statusPath = await safeWriteAsync("agent-state/STATUS.md", next);
123
123
  const event = await appendStatusEventSafe({
124
124
  source_module: "capability-ops",
125
125
  event_type: "CIRCUIT_BREAKER_OPENED",
@@ -158,7 +158,7 @@ export function registerLifecycleTools(server) {
158
158
  ? "# STATUS\n\n"
159
159
  : current.trimEnd();
160
160
  const next = `${seed}\n- [${timestamp}] CIRCUIT_BREAKER_CLOSED: ${reason}\n`;
161
- const statusPath = safeWrite("agent-state/STATUS.md", next);
161
+ const statusPath = await safeWriteAsync("agent-state/STATUS.md", next);
162
162
  const event = await appendStatusEventSafe({
163
163
  source_module: "capability-ops",
164
164
  event_type: "CIRCUIT_BREAKER_CLOSED",
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { existsSync } from "node:fs";
5
5
  import { z } from "zod";
6
- import { resolveWorkspaceRoot, safeRead, safeWrite, wsPath } from "./helpers.js";
6
+ import { resolveWorkspaceRoot, safeRead, safeWriteAsync, wsPath } from "./helpers.js";
7
7
  import { isReadError } from "./shared.js";
8
8
  import { appendRunLedgerEntrySafe } from "./run-ledger.js";
9
9
  import { appendStatusEventSafe } from "./status-events.js";
@@ -13,7 +13,7 @@ import { openStore } from "./store/ace-packed-store.js";
13
13
  import { ProjectionManager } from "./store/materializers/projection-manager.js";
14
14
  import { ContextSnapshotRepository } from "./store/repositories/context-snapshot-repository.js";
15
15
  import { getWorkspaceStorePath, storeExistsSync, toVirtualStorePath, } from "./store/store-snapshot.js";
16
- import { withStoreWriteQueue } from "./store/write-queue.js";
16
+ import { withStoreWriteCoordinator } from "./store/write-coordinator.js";
17
17
  const CONTEXT_STORE_REL = "agent-state/context-snapshots";
18
18
  export function registerMemoryTools(server) {
19
19
  server.tool("context_snapshot", "Capture a named context snapshot for later retrieval (decisions, progress, state)", {
@@ -41,7 +41,7 @@ export function registerMemoryTools(server) {
41
41
  let path = wsPath(CONTEXT_STORE_REL, `${name}.json`);
42
42
  if (storeExistsSync(root) && existsSync(getWorkspaceStorePath(root))) {
43
43
  const storePath = getWorkspaceStorePath(root);
44
- const saved = await withStoreWriteQueue(storePath, async () => {
44
+ const saved = await withStoreWriteCoordinator(storePath, async () => {
45
45
  const store = await openStore(storePath);
46
46
  try {
47
47
  const repo = new ContextSnapshotRepository(store);
@@ -62,7 +62,7 @@ export function registerMemoryTools(server) {
62
62
  finally {
63
63
  await store.close();
64
64
  }
65
- });
65
+ }, { operation_label: "contextSnapshotSave" });
66
66
  path = toVirtualStorePath(storePath, `state/memory/context_snapshots/${saved.file}`);
67
67
  }
68
68
  else {
@@ -82,7 +82,7 @@ export function registerMemoryTools(server) {
82
82
  .slice(0, 48);
83
83
  const filename = `${slug}.json`;
84
84
  path = wsPath(CONTEXT_STORE_REL, filename);
85
- safeWrite(path, JSON.stringify(snapshot, null, 2));
85
+ await safeWriteAsync(path, JSON.stringify(snapshot, null, 2));
86
86
  const indexPath = wsPath(CONTEXT_STORE_REL, "index.json");
87
87
  const indexRaw = safeRead(indexPath);
88
88
  let index = { snapshots: [] };
@@ -97,7 +97,7 @@ export function registerMemoryTools(server) {
97
97
  index.snapshots = index.snapshots.filter((s) => s.name !== name);
98
98
  index.snapshots.push({ name, file: filename, timestamp, summary });
99
99
  index.snapshots.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
100
- safeWrite(indexPath, JSON.stringify(index, null, 2));
100
+ await safeWriteAsync(indexPath, JSON.stringify(index, null, 2));
101
101
  }
102
102
  const ledger = await appendRunLedgerEntrySafe({
103
103
  tool: "context_snapshot",
@@ -2,7 +2,7 @@
2
2
  * TODO, task management, and run-ledger tool registrations.
3
3
  */
4
4
  import { z } from "zod";
5
- import { ACE_TASKS_ROOT_REL, readTaskArtifact, resolveWritableTaskPath, safeWrite, } from "./helpers.js";
5
+ import { ACE_TASKS_ROOT_REL, readTaskArtifact, resolveWritableTaskPath, safeWriteAsync, } from "./helpers.js";
6
6
  import { TODO_STATUS_ENUM } from "./shared.js";
7
7
  import { getTodoStatePath, readTodoState, syncTodoStateSafe, transitionTodoNodeSafe, } from "./todo-state.js";
8
8
  import { appendRunLedgerEntrySafe, getRunLedgerPath, readRunLedger, } from "./run-ledger.js";
@@ -33,7 +33,7 @@ export function registerTodoTools(server) {
33
33
  }, async ({ new_content, node_id, status, note }) => {
34
34
  if (typeof new_content === "string") {
35
35
  const synced = await syncTodoStateSafe(new_content);
36
- const path = safeWrite(resolveWritableTaskPath("todo"), new_content);
36
+ const path = await safeWriteAsync(resolveWritableTaskPath("todo"), new_content);
37
37
  const runLedger = await appendRunLedgerEntrySafe({
38
38
  tool: "update_todo",
39
39
  category: "major_update",
@@ -67,7 +67,7 @@ export declare function validateTrackerSnapshotContent(raw: string): ValidationR
67
67
  export declare function loadTrackerSnapshot(explicitPath?: string): TrackerSnapshotResult;
68
68
  export declare function readTrackerSnapshot(): TrackerSnapshot;
69
69
  export declare function getTrackerSnapshotPath(): string;
70
- export declare function writeTrackerSnapshot(snapshot: TrackerSnapshot): string;
70
+ export declare function writeTrackerSnapshot(snapshot: TrackerSnapshot): Promise<string>;
71
71
  export declare function listTrackerAdapterKinds(): string[];
72
72
  export declare function getTrackerAdapter(kind?: string): IssueTrackerAdapter;
73
73
  export declare function buildDefaultTrackerSnapshot(kind?: TrackerAdapterKind): TrackerSnapshot;