@voybio/ace-swarm 0.2.5 → 2.4.1

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 +19 -1
  2. package/README.md +21 -13
  3. package/assets/.agents/ACE/agent-qa/instructions.md +11 -0
  4. package/assets/agent-state/EVIDENCE_LOG.md +1 -1
  5. package/assets/agent-state/MODULES/roles/capability-framework.json +41 -0
  6. package/assets/agent-state/MODULES/roles/capability-git.json +33 -0
  7. package/assets/agent-state/MODULES/roles/capability-safety.json +37 -0
  8. package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +21 -0
  9. package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +43 -0
  10. package/assets/agent-state/MODULES/schemas/RUNTIME_TOOL_SPEC_REGISTRY.schema.json +43 -0
  11. package/assets/agent-state/MODULES/schemas/WORKSPACE_SESSION_REGISTRY.schema.json +11 -0
  12. package/assets/agent-state/STATUS.md +2 -2
  13. package/assets/agent-state/runtime-tool-specs.json +70 -2
  14. package/assets/instructions/ACE_Coder.instructions.md +13 -0
  15. package/assets/instructions/ACE_UI.instructions.md +11 -0
  16. package/assets/scripts/ace-hook-dispatch.mjs +70 -6
  17. package/assets/scripts/render-mcp-configs.sh +19 -5
  18. package/dist/ace-context.js +91 -11
  19. package/dist/ace-internal-tools.d.ts +3 -1
  20. package/dist/ace-internal-tools.js +10 -2
  21. package/dist/ace-server-instructions.js +3 -3
  22. package/dist/ace-state-resolver.js +5 -3
  23. package/dist/agent-runtime/role-adapters.d.ts +18 -1
  24. package/dist/agent-runtime/role-adapters.js +49 -5
  25. package/dist/astgrep-index.d.ts +57 -1
  26. package/dist/astgrep-index.js +140 -4
  27. package/dist/cli.js +232 -35
  28. package/dist/discovery-runtime-wrappers.d.ts +108 -0
  29. package/dist/discovery-runtime-wrappers.js +615 -0
  30. package/dist/handoff-registry.js +5 -5
  31. package/dist/helpers/artifacts.d.ts +19 -0
  32. package/dist/helpers/artifacts.js +152 -0
  33. package/dist/helpers/bootstrap.d.ts +24 -0
  34. package/dist/helpers/bootstrap.js +894 -0
  35. package/dist/helpers/constants.d.ts +53 -0
  36. package/dist/helpers/constants.js +295 -0
  37. package/dist/helpers/drift.d.ts +13 -0
  38. package/dist/helpers/drift.js +45 -0
  39. package/dist/helpers/path-utils.d.ts +24 -0
  40. package/dist/helpers/path-utils.js +123 -0
  41. package/dist/helpers/store-resolution.d.ts +19 -0
  42. package/dist/helpers/store-resolution.js +305 -0
  43. package/dist/helpers/workspace-root.d.ts +3 -0
  44. package/dist/helpers/workspace-root.js +80 -0
  45. package/dist/helpers.d.ts +8 -125
  46. package/dist/helpers.js +8 -1768
  47. package/dist/job-scheduler.js +33 -7
  48. package/dist/json-sanitizer.d.ts +16 -0
  49. package/dist/json-sanitizer.js +26 -0
  50. package/dist/local-model-policy.d.ts +27 -0
  51. package/dist/local-model-policy.js +84 -0
  52. package/dist/local-model-runtime.d.ts +6 -0
  53. package/dist/local-model-runtime.js +33 -21
  54. package/dist/model-bridge.d.ts +13 -1
  55. package/dist/model-bridge.js +410 -23
  56. package/dist/orchestrator-supervisor.d.ts +56 -0
  57. package/dist/orchestrator-supervisor.js +179 -1
  58. package/dist/plan-proposal.d.ts +115 -0
  59. package/dist/plan-proposal.js +1073 -0
  60. package/dist/run-ledger.js +3 -3
  61. package/dist/runtime-command.d.ts +8 -0
  62. package/dist/runtime-command.js +38 -6
  63. package/dist/runtime-executor.d.ts +20 -1
  64. package/dist/runtime-executor.js +737 -172
  65. package/dist/runtime-profile.d.ts +32 -0
  66. package/dist/runtime-profile.js +89 -13
  67. package/dist/runtime-tool-specs.d.ts +39 -0
  68. package/dist/runtime-tool-specs.js +144 -28
  69. package/dist/safe-edit.d.ts +7 -0
  70. package/dist/safe-edit.js +163 -37
  71. package/dist/schemas.js +48 -1
  72. package/dist/server.js +51 -0
  73. package/dist/shared.d.ts +3 -2
  74. package/dist/shared.js +2 -0
  75. package/dist/status-events.js +9 -6
  76. package/dist/store/ace-packed-store.d.ts +3 -2
  77. package/dist/store/ace-packed-store.js +188 -110
  78. package/dist/store/bootstrap-store.d.ts +2 -1
  79. package/dist/store/bootstrap-store.js +102 -83
  80. package/dist/store/cache-workspace.js +11 -5
  81. package/dist/store/materializers/context-snapshot-materializer.js +6 -2
  82. package/dist/store/materializers/hook-context-materializer.d.ts +6 -9
  83. package/dist/store/materializers/hook-context-materializer.js +11 -21
  84. package/dist/store/materializers/host-file-materializer.js +6 -0
  85. package/dist/store/materializers/projection-manager.d.ts +0 -1
  86. package/dist/store/materializers/projection-manager.js +5 -13
  87. package/dist/store/materializers/scheduler-projection-materializer.js +1 -1
  88. package/dist/store/materializers/vericify-projector.d.ts +7 -7
  89. package/dist/store/materializers/vericify-projector.js +11 -11
  90. package/dist/store/repositories/local-model-runtime-repository.d.ts +120 -3
  91. package/dist/store/repositories/local-model-runtime-repository.js +242 -6
  92. package/dist/store/repositories/vericify-repository.d.ts +1 -1
  93. package/dist/store/skills-install.d.ts +4 -0
  94. package/dist/store/skills-install.js +21 -12
  95. package/dist/store/state-reader.d.ts +2 -0
  96. package/dist/store/state-reader.js +20 -0
  97. package/dist/store/store-artifacts.d.ts +7 -0
  98. package/dist/store/store-artifacts.js +27 -1
  99. package/dist/store/store-authority-audit.d.ts +18 -1
  100. package/dist/store/store-authority-audit.js +115 -5
  101. package/dist/store/store-snapshot.d.ts +3 -0
  102. package/dist/store/store-snapshot.js +22 -2
  103. package/dist/store/workspace-store-paths.d.ts +39 -0
  104. package/dist/store/workspace-store-paths.js +94 -0
  105. package/dist/store/write-coordinator.d.ts +65 -0
  106. package/dist/store/write-coordinator.js +386 -0
  107. package/dist/todo-state.js +5 -5
  108. package/dist/tools-agent.d.ts +20 -0
  109. package/dist/tools-agent.js +789 -25
  110. package/dist/tools-discovery.js +136 -1
  111. package/dist/tools-files.d.ts +7 -0
  112. package/dist/tools-files.js +1002 -11
  113. package/dist/tools-framework.js +105 -66
  114. package/dist/tools-handoff.js +2 -2
  115. package/dist/tools-lifecycle.js +4 -4
  116. package/dist/tools-memory.js +6 -6
  117. package/dist/tools-todo.js +2 -2
  118. package/dist/tracker-adapters.d.ts +1 -1
  119. package/dist/tracker-adapters.js +13 -18
  120. package/dist/tracker-sync.js +5 -3
  121. package/dist/tui/agent-runner.js +3 -1
  122. package/dist/tui/chat.js +103 -7
  123. package/dist/tui/dashboard.d.ts +1 -0
  124. package/dist/tui/dashboard.js +43 -0
  125. package/dist/tui/index.js +10 -1
  126. package/dist/tui/layout.d.ts +20 -0
  127. package/dist/tui/layout.js +31 -1
  128. package/dist/tui/local-model-contract.d.ts +6 -2
  129. package/dist/tui/local-model-contract.js +16 -3
  130. package/dist/tui/ollama.d.ts +8 -1
  131. package/dist/tui/ollama.js +53 -12
  132. package/dist/tui/openai-compatible.d.ts +13 -0
  133. package/dist/tui/openai-compatible.js +305 -5
  134. package/dist/tui/provider-discovery.d.ts +1 -0
  135. package/dist/tui/provider-discovery.js +35 -11
  136. package/dist/vericify-bridge.d.ts +6 -1
  137. package/dist/vericify-bridge.js +27 -3
  138. package/dist/workspace-manager.d.ts +30 -3
  139. package/dist/workspace-manager.js +257 -27
  140. package/package.json +1 -2
  141. package/dist/internal-tool-runtime.d.ts +0 -21
  142. package/dist/internal-tool-runtime.js +0 -136
  143. package/dist/store/workspace-snapshot.d.ts +0 -26
  144. package/dist/store/workspace-snapshot.js +0 -107
@@ -28,6 +28,9 @@ const ACE_CORE_FILES = [
28
28
  "agent-state/SCOPE.md",
29
29
  "agent-state/EVIDENCE_LOG.md",
30
30
  ];
31
+ const STORE_MAGIC = "ACEPACK1";
32
+ const STORE_HEADER_SIZE = 32;
33
+ const HOOK_CONTEXT_STORE_KEY = "state/runtime/hook_context";
31
34
 
32
35
  const PROTECTED_PATH_FRAGMENTS = [
33
36
  ".github/hooks/",
@@ -114,9 +117,61 @@ function resolveAcePath(workspaceRoot, relativePath) {
114
117
  return canonical;
115
118
  }
116
119
 
120
+ function getWorkspaceStorePath(workspaceRoot) {
121
+ const canonical = resolve(workspaceRoot, "agent-state", "ace-state.ace");
122
+ if (existsSync(canonical)) return canonical;
123
+ return resolve(workspaceRoot, ACE_ROOT, "ace-state.ace");
124
+ }
125
+
126
+ function readStoreUint64(buffer, offset) {
127
+ return buffer.readUInt32BE(offset) * 2 ** 32 + buffer.readUInt32BE(offset + 4);
128
+ }
129
+
130
+ function readStoreBlob(workspaceRoot, key) {
131
+ const storePath = getWorkspaceStorePath(workspaceRoot);
132
+ if (!existsSync(storePath)) return undefined;
133
+ try {
134
+ const file = readFileSync(storePath);
135
+ if (file.length < STORE_HEADER_SIZE) return undefined;
136
+ if (file.subarray(0, 8).toString("utf8") !== STORE_MAGIC) return undefined;
137
+ const indexOffset = readStoreUint64(file, 16);
138
+ const indexLength = readStoreUint64(file, 24);
139
+ const index = JSON.parse(file.subarray(indexOffset, indexOffset + indexLength).toString("utf8"));
140
+ const entry = index?.[key];
141
+ if (!entry || typeof entry.offset !== "number" || typeof entry.length !== "number") {
142
+ return undefined;
143
+ }
144
+ const start = entry.offset + 4;
145
+ const end = start + entry.length;
146
+ return file.subarray(start, end).toString("utf8");
147
+ } catch {
148
+ return undefined;
149
+ }
150
+ }
151
+
117
152
  function readIfPresent(workspaceRoot, relativePath) {
118
153
  const absolute = resolveAcePath(workspaceRoot, relativePath);
119
- if (!existsSync(absolute)) return undefined;
154
+ if (!existsSync(absolute)) {
155
+ const normalized = String(relativePath).replace(/\\/g, "/").replace(/^\.?\//, "");
156
+ const logicalPath = normalized.startsWith(`${ACE_ROOT}/`)
157
+ ? normalized.slice(`${ACE_ROOT}/`.length)
158
+ : normalized;
159
+ const rel = logicalPath.startsWith("agent-state/") ? logicalPath.slice("agent-state/".length) : "";
160
+ const keys = [];
161
+ if (rel) {
162
+ keys.push(`state/artifacts/agent-state/${rel}`);
163
+ keys.push(`knowledge/agent-state/${rel}`);
164
+ if (rel === "ACE_WORKFLOW.md") {
165
+ keys.unshift("knowledge/runtime/ACE_WORKFLOW.md");
166
+ keys.push("knowledge/kernel/ACE_WORKFLOW.md");
167
+ }
168
+ }
169
+ for (const key of keys) {
170
+ const blob = readStoreBlob(workspaceRoot, key);
171
+ if (typeof blob === "string") return blob;
172
+ }
173
+ return undefined;
174
+ }
120
175
  try {
121
176
  return readFileSync(absolute, "utf8");
122
177
  } catch {
@@ -203,12 +258,19 @@ function buildRoleToolHint(role) {
203
258
  }
204
259
 
205
260
  /**
206
- * Try to read the store-materialized hook context snapshot.
261
+ * Try to read the store-backed hook context snapshot.
207
262
  * Returns the parsed snapshot or undefined if not present / unreadable.
208
- * When present this is the single authoritative source; the dispatcher
209
- * never needs to open the .ace store directly.
210
263
  */
211
264
  function tryReadHookContextSnapshot(workspaceRoot) {
265
+ const storeRaw = readStoreBlob(workspaceRoot, HOOK_CONTEXT_STORE_KEY);
266
+ if (typeof storeRaw === "string") {
267
+ try {
268
+ const parsed = JSON.parse(storeRaw);
269
+ if (parsed?.schema_version === 1) return parsed;
270
+ } catch {
271
+ // Fall through to legacy file compatibility.
272
+ }
273
+ }
212
274
  const snapshotPath = resolve(workspaceRoot, ACE_ROOT, "ace-hook-context.json");
213
275
  if (!existsSync(snapshotPath)) return undefined;
214
276
  try {
@@ -222,7 +284,7 @@ function tryReadHookContextSnapshot(workspaceRoot) {
222
284
  }
223
285
 
224
286
  function aceContext(workspaceRoot) {
225
- // ── Fast path: store-materialized snapshot ──────────────────────────────
287
+ // ── Fast path: store-backed snapshot ────────────────────────────────────
226
288
  const snapshot = tryReadHookContextSnapshot(workspaceRoot);
227
289
  if (snapshot) {
228
290
  const taskObjective = snapshot.task?.content
@@ -233,7 +295,9 @@ function aceContext(workspaceRoot) {
233
295
  const scopeReminder = scopeLines.length
234
296
  ? `ACE scope reminder: ${truncate(scopeLines.slice(0, 4).join(" "), 220)}`
235
297
  : undefined;
236
- const hasAgentState = existsSync(resolve(workspaceRoot, ACE_ROOT, "agent-state"));
298
+ const hasAgentState =
299
+ existsSync(resolve(workspaceRoot, ACE_ROOT, "agent-state")) ||
300
+ existsSync(resolve(workspaceRoot, "agent-state", "ace-state.ace"));
237
301
  const hasSkills = existsSync(resolve(workspaceRoot, ACE_ROOT, "skills")) ||
238
302
  existsSync(resolve(workspaceRoot, ".agents", "skills"));
239
303
  return {
@@ -12,6 +12,9 @@ CURSOR_DIR="${ACE_ROOT}/.cursor"
12
12
  VSCODE_DIR="${ACE_ROOT}/.vscode"
13
13
  HOOK_COMMAND="node ./.agents/ACE/scripts/ace/ace-hook-dispatch.mjs"
14
14
  HOOK_COMMAND_WINDOWS="node .\\\\.agents\\\\ACE\\\\scripts\\\\ace\\\\ace-hook-dispatch.mjs"
15
+ NODE_BIN_DIR="$(dirname -- "$(command -v node)")"
16
+ CLI_ENTRY="${WORKSPACE_ROOT}/dist/cli.js"
17
+ MCP_LAUNCH="export PATH=\"${NODE_BIN_DIR}:\$PATH\" && if ! command -v node >/dev/null 2>&1; then [ -s \"\$HOME/.zprofile\" ] && . \"\$HOME/.zprofile\"; [ -s \"\$HOME/.zshrc\" ] && . \"\$HOME/.zshrc\"; fi && cd \"${WORKSPACE_ROOT}\" && ACE_WORKSPACE_ROOT=\"${WORKSPACE_ROOT}\" node \"${CLI_ENTRY}\" mcp"
15
18
 
16
19
  mkdir -p "${MCP_CONFIG_DIR}"
17
20
  mkdir -p "${GITHUB_DIR}/hooks"
@@ -25,7 +28,18 @@ cat > "${MCP_CONFIG_DIR}/vscode.mcp.json" <<JSON
25
28
  "ace-swarm": {
26
29
  "type": "stdio",
27
30
  "command": "/bin/zsh",
28
- "args": ["-lc", "cd \"${WORKSPACE_ROOT}\" && ACE_WORKSPACE_ROOT=\"${WORKSPACE_ROOT}\" npx -y ace-swarm mcp"]
31
+ "args": ["-lc", "${MCP_LAUNCH}"]
32
+ }
33
+ }
34
+ }
35
+ JSON
36
+
37
+ cat > "${WORKSPACE_ROOT}/.mcp.json" <<JSON
38
+ {
39
+ "mcpServers": {
40
+ "ace-swarm": {
41
+ "command": "/bin/zsh",
42
+ "args": ["-lc", "${MCP_LAUNCH}"]
29
43
  }
30
44
  }
31
45
  }
@@ -36,7 +50,7 @@ cat > "${MCP_CONFIG_DIR}/cursor.mcp.json" <<JSON
36
50
  "mcpServers": {
37
51
  "ace-swarm": {
38
52
  "command": "/bin/zsh",
39
- "args": ["-lc", "cd \"${WORKSPACE_ROOT}\" && ACE_WORKSPACE_ROOT=\"${WORKSPACE_ROOT}\" npx -y ace-swarm mcp"]
53
+ "args": ["-lc", "${MCP_LAUNCH}"]
40
54
  }
41
55
  }
42
56
  }
@@ -47,7 +61,7 @@ cat > "${MCP_CONFIG_DIR}/claude_desktop_config.json" <<JSON
47
61
  "mcpServers": {
48
62
  "ace-swarm": {
49
63
  "command": "/bin/zsh",
50
- "args": ["-lc", "cd \"${WORKSPACE_ROOT}\" && ACE_WORKSPACE_ROOT=\"${WORKSPACE_ROOT}\" npx -y ace-swarm mcp"]
64
+ "args": ["-lc", "${MCP_LAUNCH}"]
51
65
  }
52
66
  }
53
67
  }
@@ -58,7 +72,7 @@ cat > "${MCP_CONFIG_DIR}/antigravity.mcp.json" <<JSON
58
72
  "mcpServers": {
59
73
  "ace-swarm": {
60
74
  "command": "/bin/zsh",
61
- "args": ["-lc", "cd \"${WORKSPACE_ROOT}\" && ACE_WORKSPACE_ROOT=\"${WORKSPACE_ROOT}\" npx -y ace-swarm mcp"]
75
+ "args": ["-lc", "${MCP_LAUNCH}"]
62
76
  }
63
77
  }
64
78
  }
@@ -68,7 +82,7 @@ cat > "${MCP_CONFIG_DIR}/codex.config.toml" <<TOML
68
82
  # ACE instructions are scaffolded into .agents/ACE/AGENTS.md during bootstrap.
69
83
  [mcp_servers.ace-swarm]
70
84
  command = "/bin/zsh"
71
- args = ["-lc", "cd \"${WORKSPACE_ROOT}\" && ACE_WORKSPACE_ROOT=\"${WORKSPACE_ROOT}\" npx -y ace-swarm mcp"]
85
+ args = ["-lc", "${MCP_LAUNCH}"]
72
86
  TOML
73
87
 
74
88
  cat > "${ACE_ROOT}/CLAUDE.md" <<MD
@@ -1,6 +1,6 @@
1
1
  import { buildAceContinuityPacket, buildAceRecallContext, formatAceContinuityPacketMarkdown, formatAceRecallMarkdown, normalizeAutonomyPolicy, normalizeContinuityPolicy, } from "./ace-autonomy.js";
2
2
  import { listAceInternalToolCatalog, } from "./ace-internal-tools.js";
3
- import { ALL_AGENTS, readAgentInstructions } from "./helpers.js";
3
+ import { ALL_AGENTS, listAvailableSkills, readAgentInstructions } from "./helpers.js";
4
4
  import { readRuntimeProfileState, renderRuntimePrompt } from "./runtime-profile.js";
5
5
  const ROLE_TOOL_DEFAULTS = {
6
6
  orchestrator: [
@@ -22,9 +22,15 @@ const ROLE_TOOL_DEFAULTS = {
22
22
  "emit_status_event",
23
23
  ],
24
24
  coders: [
25
- "read_workspace_file",
26
- "write_workspace_file",
25
+ "outline_file",
26
+ "astgrep_query",
27
+ "astgrep_locate",
28
+ "read_file_lines",
29
+ "compile_structural_edit",
30
+ "preview_structural_edit",
31
+ "astgrep_rewrite",
27
32
  "safe_edit_file",
33
+ "write_workspace_file",
28
34
  "run_tests",
29
35
  "git_diff",
30
36
  "git_status",
@@ -91,8 +97,13 @@ export function renderAceContinuityPromptBlock(packet) {
91
97
  return packet ? formatAceContinuityPacketMarkdown(packet) : "";
92
98
  }
93
99
  function keywordScore(task, tool) {
94
- const haystack = `${tool.name} ${tool.description} ${tool.input_keys.join(" ")}`.toLowerCase();
95
- return task
100
+ const name = typeof tool?.name === "string" ? tool.name : String(tool?.name ?? "");
101
+ const desc = typeof tool?.description === "string" ? tool.description : String(tool?.description ?? "");
102
+ const inputs = Array.isArray(tool?.input_keys) ? tool.input_keys.join(" ") : "";
103
+ const haystack = `${name} ${desc} ${inputs}`.toLowerCase();
104
+ if (!task)
105
+ return 0;
106
+ return String(task)
96
107
  .toLowerCase()
97
108
  .split(/[^a-z0-9_]+/)
98
109
  .filter((token) => token.length > 2)
@@ -107,17 +118,26 @@ function selectToolCount(tier) {
107
118
  }
108
119
  function scopeTools(role, task, tier, explicitTools) {
109
120
  const catalog = listAceInternalToolCatalog();
110
- if (explicitTools && explicitTools.length > 0) {
121
+ if (Array.isArray(explicitTools)) {
122
+ if (explicitTools.length === 0) {
123
+ return [];
124
+ }
111
125
  const explicitSet = new Set(explicitTools);
112
126
  return catalog.filter((tool) => explicitSet.has(tool.name));
113
127
  }
114
- const roleDefaults = new Set(ROLE_TOOL_DEFAULTS[role.trim().toLowerCase()] ?? []);
128
+ const defaultTools = ROLE_TOOL_DEFAULTS[role.trim().toLowerCase()] ?? [];
129
+ const roleDefaults = new Set(defaultTools);
130
+ const rolePriority = new Map(defaultTools.map((name, index) => [name, index]));
115
131
  const ranked = catalog
116
132
  .map((tool) => ({
117
133
  tool,
118
134
  score: (roleDefaults.has(tool.name) ? 5 : 0) + keywordScore(task, tool),
119
135
  }))
120
- .sort((left, right) => right.score - left.score || left.tool.name.localeCompare(right.tool.name))
136
+ .sort((left, right) => (roleDefaults.has(left.tool.name) && roleDefaults.has(right.tool.name)
137
+ ? (rolePriority.get(left.tool.name) ?? 999) - (rolePriority.get(right.tool.name) ?? 999)
138
+ : right.score - left.score) ||
139
+ right.score - left.score ||
140
+ left.tool.name.localeCompare(right.tool.name))
121
141
  .slice(0, selectToolCount(tier))
122
142
  .map((entry) => entry.tool);
123
143
  if (ranked.length > 0 && ranked.some((tool) => roleDefaults.has(tool.name))) {
@@ -130,7 +150,7 @@ function renderToolCatalogMarkdown(tools, tier) {
130
150
  return tools
131
151
  .map((tool) => {
132
152
  const parts = [`- \`${tool.name}\` — ${tool.description || "ACE MCP tool"}`];
133
- if (includeInputs && tool.input_keys.length > 0) {
153
+ if (includeInputs && Array.isArray(tool.input_keys) && tool.input_keys.length > 0) {
134
154
  parts.push(` - input keys: ${tool.input_keys.join(", ")}`);
135
155
  }
136
156
  return parts.join("\n");
@@ -156,9 +176,27 @@ function buildPlanningCatalog(role, task, tier) {
156
176
  .slice(0, limit)
157
177
  .map((entry) => entry.tool);
158
178
  }
179
+ function inferRelevantSkills(task) {
180
+ const lowered = String(task ?? "").toLowerCase();
181
+ const skillSignals = [
182
+ { name: "state-auditor", pattern: /\b(state|drift|authority|audit)\b/ },
183
+ { name: "handoff-lint", pattern: /\b(handoff|transition|payload)\b/ },
184
+ { name: "schema-forge", pattern: /\b(schema|contract|interface)\b/ },
185
+ { name: "release-sentry", pattern: /\b(ship|release|promotion|rollback)\b/ },
186
+ { name: "risk-quant", pattern: /\b(risk|blocker|severity)\b/ },
187
+ { name: "eval-harness", pattern: /\b(eval|regression|verify|validation)\b/ },
188
+ { name: "incident-commander", pattern: /\b(incident|outage|blocker storm)\b/ },
189
+ { name: "problem-triage", pattern: /\b(triage|classify|scope)\b/ },
190
+ ];
191
+ const available = new Set(listAvailableSkills().map((skill) => skill.name));
192
+ return skillSignals
193
+ .filter((signal) => signal.pattern.test(lowered) && available.has(signal.name))
194
+ .map((signal) => signal.name);
195
+ }
159
196
  export function buildToolPlan(options) {
160
197
  const tier = options.tier ?? "compressed";
161
- const catalog = Array.isArray(options.tools) && options.tools.length > 0
198
+ const explicitToolsProvided = Array.isArray(options.tools);
199
+ const catalog = explicitToolsProvided
162
200
  ? listAceInternalToolCatalog().filter((tool) => options.tools?.includes(tool.name))
163
201
  : buildPlanningCatalog(options.role, options.task, tier);
164
202
  const recommended = scopeTools(options.role, options.task, tier, options.tools).map((tool) => tool.name);
@@ -185,6 +223,42 @@ export function buildToolPlan(options) {
185
223
  selection_limit: selectionLimit,
186
224
  };
187
225
  }
226
+ /**
227
+ * Per-role negative output-shape guardrails injected into the system prompt.
228
+ * These complement the shared "## Output Contract" block with role-specific drift constraints.
229
+ *
230
+ * - coders/builder: bridge response must be a JSON envelope; they CAN write HTML/code via tools.
231
+ * - vos/ui: primary output is prose; snippets are fine inline but full HTML docs are not their job.
232
+ */
233
+ const ROLE_OUTPUT_SHAPE_GUARDRAILS = {
234
+ coders: [
235
+ "- Your bridge response MUST be a JSON envelope (status: tool, complete, etc.) — do not send raw prose as your top-level reply.",
236
+ "- For code mutation, default to astgrep_locate -> compile_structural_edit -> preview_structural_edit -> astgrep_rewrite(plan_id).",
237
+ "- Use write_workspace_file for new files, generated non-code artifacts, or explicit structural_edit_waiver cases only.",
238
+ "- If you bypass the structural lane for code, state the waiver reason and provide evidence_refs.",
239
+ "- Do NOT echo the task description or expected artifact shape as your response without executing via tool calls.",
240
+ "- If you are writing files, call the write tool first, then close with status:complete and evidence_refs.",
241
+ ],
242
+ builder: [
243
+ "- Your bridge response MUST be a JSON envelope — raw prose as the top-level reply is a contract violation.",
244
+ "- For code mutation, default to astgrep_locate -> compile_structural_edit -> preview_structural_edit -> astgrep_rewrite(plan_id).",
245
+ "- Use write_workspace_file for new files, generated non-code artifacts, or explicit structural_edit_waiver cases only.",
246
+ "- If you bypass the structural lane for code, state the waiver reason and provide evidence_refs.",
247
+ "- Do NOT echo the artifact shape without executing the build step via tool calls.",
248
+ "- File content (code, HTML, configs) belongs inside tool calls, not in raw text output.",
249
+ "- If you are writing files, close with status:complete and evidence_refs after the write tool succeeds.",
250
+ ],
251
+ vos: [
252
+ "- Your primary output is prose. Inline code snippets or JSON examples are acceptable when illustrating a point.",
253
+ "- Do NOT produce a full HTML document or a raw JSON structure as your main deliverable.",
254
+ "- You are a planning and narrative role; do not attempt to write files or persist artifacts directly.",
255
+ ],
256
+ ui: [
257
+ "- Your primary output is prose. You may reference or sketch HTML snippets inline as examples.",
258
+ "- Do NOT produce a full HTML document as your response — HTML authoring is the coders role's job.",
259
+ "- Do not emit large raw JSON blobs as your main output; summarize or reference them instead.",
260
+ ],
261
+ };
188
262
  export function renderAceContext(options) {
189
263
  const tier = options.tier ?? "compressed";
190
264
  const active = readRuntimeProfileState();
@@ -211,6 +285,7 @@ export function renderAceContext(options) {
211
285
  const roleInstructions = resolveRoleInstructions(options.role);
212
286
  const toolCatalog = renderToolCatalogMarkdown(tools, tier);
213
287
  const stateDigest = recall.task_contract.summary;
288
+ const relevantSkills = inferRelevantSkills(options.task);
214
289
  const systemPrompt = [
215
290
  runtimePrompt,
216
291
  "",
@@ -223,17 +298,22 @@ export function renderAceContext(options) {
223
298
  "## Tool Scope",
224
299
  toolCatalog || "- No ACE tools available.",
225
300
  "",
301
+ "## Relevant Skills",
302
+ relevantSkills.length > 0 ? `- ${relevantSkills.join(", ")}` : "- No task-specific skill hint matched.",
303
+ "",
226
304
  "## Compact Guardrails",
227
305
  "- Stop if you are about to claim tested, verified, or passed without evidence.",
228
306
  "- If ACE state is thin or contradictory, call recall_context or validate_framework before improvising.",
229
307
  "- For obviously multi-step work, prefer run_orchestrator over a single free-form answer.",
308
+ "- When you mutate workspace, use the smallest safe mutation lane: structural edit for existing code, write_workspace_file for new/generated artifacts; never assert file creation in prose alone.",
230
309
  "- Do not declare completion while approval, retry, or blocker state remains active.",
310
+ ...(ROLE_OUTPUT_SHAPE_GUARDRAILS[options.role.trim().toLowerCase()] ?? []),
231
311
  "",
232
312
  "## Output Contract",
233
313
  'Respond in JSON only with one of these shapes:',
234
314
  '- `{"status":"tool","thinking":"...","tool_calls":[{"tool":"name","input":{}}]}`',
235
315
  '- `{"status":"message","thinking":"...","message":"..."}`',
236
- '- `{"status":"complete","thinking":"...","summary":"..."}`',
316
+ '- `{"status":"complete","thinking":"...","summary":"...","evidence_refs":["agent-state/..."]}`',
237
317
  '- `{"status":"need_input","thinking":"...","message":"..."}`',
238
318
  ].join("\n");
239
319
  return {
@@ -4,5 +4,7 @@ export interface AceInternalToolCatalogEntry {
4
4
  input_keys: string[];
5
5
  }
6
6
  export declare function listAceInternalToolCatalog(): AceInternalToolCatalogEntry[];
7
- export declare function executeAceInternalTool(name: string, input: Record<string, unknown> | undefined, sessionId?: string): Promise<any>;
7
+ export declare function executeAceInternalTool(name: string, input: Record<string, unknown> | undefined, sessionId?: string, options?: {
8
+ workspace_path?: string;
9
+ }): Promise<any>;
8
10
  //# sourceMappingURL=ace-internal-tools.d.ts.map
@@ -42,7 +42,7 @@ export function listAceInternalToolCatalog() {
42
42
  }))
43
43
  .sort((left, right) => left.name.localeCompare(right.name));
44
44
  }
45
- export async function executeAceInternalTool(name, input, sessionId) {
45
+ export async function executeAceInternalTool(name, input, sessionId, options = {}) {
46
46
  const tool = getRegistry()[name];
47
47
  if (!tool || !tool.enabled) {
48
48
  return {
@@ -50,8 +50,16 @@ export async function executeAceInternalTool(name, input, sessionId) {
50
50
  isError: true,
51
51
  };
52
52
  }
53
+ const inputWithContext = options.workspace_path && name === "write_workspace_file"
54
+ ? {
55
+ ...(input ?? {}),
56
+ workspace_path: typeof input?.workspace_path === "string" && input.workspace_path.trim()
57
+ ? input.workspace_path
58
+ : options.workspace_path,
59
+ }
60
+ : input;
53
61
  const parsedInput = tool.inputSchema
54
- ? tool.inputSchema.safeParse(input ?? {})
62
+ ? tool.inputSchema.safeParse(inputWithContext ?? {})
55
63
  : { success: true, data: undefined };
56
64
  if (!parsedInput.success) {
57
65
  const errorText = parsedInput.error.issues
@@ -229,10 +229,10 @@ export function buildHostInstructionText(host, workspaceRoot) {
229
229
  "3. Prefer ACE prompts/resources (`ace-orchestrator`, `get_task_pack`, `get_skill_instructions`) over relying on this file.",
230
230
  "",
231
231
  "Ground truth:",
232
- "- `agent-state/*` is the logical state surface; the resolver reads it from top-level, nested, or store-backed layouts.",
233
- "- `.agents/ACE/ace-state.ace` remains the canonical store-backed projection when that layout is used.",
232
+ "- `agent-state/*` is the visible runtime state surface for this workspace.",
233
+ "- `agent-state/ace-state.ace` is the canonical store path; legacy nested store layouts are compatibility fallback only.",
234
234
  "- `.agents/ACE/tasks/todo.md` is the human-facing todo surface.",
235
- "- `.agents/ACE/ace-hook-context.json` is the compact hook snapshot.",
235
+ "- Hook context is store-backed in `agent-state/ace-state.ace` and consumed without extra workspace projections.",
236
236
  "",
237
237
  `Workspace digest hint: ${buildAceDigest(workspaceRoot)}.`,
238
238
  ].join("\n");
@@ -95,9 +95,11 @@ export function resolveAceStateLayout(workspaceRoot) {
95
95
  scopePath: resolveAceLogicalPath(workspaceRoot, "agent-state/SCOPE.md"),
96
96
  evidencePath: resolveAceLogicalPath(workspaceRoot, "agent-state/EVIDENCE_LOG.md"),
97
97
  storePath: storePresent ? storePath : undefined,
98
- hookSnapshotPath: existsSync(nestedAcePath(workspaceRoot, "ace-hook-context.json"))
99
- ? nestedAcePath(workspaceRoot, "ace-hook-context.json")
100
- : undefined,
98
+ hookSnapshotPath: readStoreBlobSync(workspaceRoot, "state/runtime/hook_context") != null
99
+ ? toVirtualStorePath(storePath, "state/runtime/hook_context")
100
+ : existsSync(nestedAcePath(workspaceRoot, "ace-hook-context.json"))
101
+ ? nestedAcePath(workspaceRoot, "ace-hook-context.json")
102
+ : undefined,
101
103
  isAcePresent: physicalMode !== "missing" ||
102
104
  CRITICAL_LOGICAL_PATHS.some((relativePath) => typeof readAceLogicalFile(workspaceRoot, relativePath) === "string"),
103
105
  missingCriticalArtifacts,
@@ -21,9 +21,26 @@ export interface AdapterDecision {
21
21
  domain: AdapterDomain;
22
22
  confidence: number;
23
23
  confidenceLevel: "low" | "medium" | "high";
24
+ preflight?: AcePreflightPacket;
25
+ }
26
+ export interface AcePreflightPacket {
27
+ required: boolean;
28
+ status: "passed" | "attention_required" | "blocked";
29
+ summary: string;
30
+ evidence_refs: string[];
31
+ model_visible_tools: ToolIntent[];
24
32
  }
25
33
  export declare function inferDomain(input: string): AdapterDomain;
26
34
  export declare function parseManualHandoffCommand(input: string): HandoffIntent | undefined;
27
35
  export declare function parseManualToolCommand(input: string): ToolIntent | undefined;
28
- export declare function planRoleExecution(roleValue: string, input: string): AdapterDecision;
36
+ export declare function buildAcePreflightPacket(input: {
37
+ role: string;
38
+ model_class?: "frontier" | "mid" | "small_local";
39
+ text: string;
40
+ domain: AdapterDomain;
41
+ preflight_required: boolean;
42
+ }): AcePreflightPacket;
43
+ export declare function planRoleExecution(roleValue: string, input: string, options?: {
44
+ model_class?: "frontier" | "mid" | "small_local";
45
+ }): AdapterDecision;
29
46
  //# sourceMappingURL=role-adapters.d.ts.map
@@ -273,7 +273,31 @@ function buildCreateHandoffIntent(fromRole, toRole, input) {
273
273
  },
274
274
  };
275
275
  }
276
- export function planRoleExecution(roleValue, input) {
276
+ export function buildAcePreflightPacket(input) {
277
+ const routeIntent = { tool: "route_task", args: { description: input.text, domain: input.domain } };
278
+ const smallLocalOrchestrator = input.role === "orchestrator" && input.model_class === "small_local";
279
+ return {
280
+ required: input.preflight_required,
281
+ status: input.preflight_required ? "attention_required" : "passed",
282
+ summary: smallLocalOrchestrator
283
+ ? "ACE-owned preflight covers recall_context, validate_framework, and get_framework_status before model dispatch."
284
+ : "Adapter preflight remains model-visible for this caller.",
285
+ evidence_refs: [
286
+ "agent-state/TASK.md",
287
+ "agent-state/STATUS.md",
288
+ "agent-state/QUALITY_GATES.md",
289
+ ],
290
+ model_visible_tools: smallLocalOrchestrator
291
+ ? [routeIntent]
292
+ : [
293
+ { tool: "recall_context", args: {} },
294
+ { tool: "validate_framework", args: {} },
295
+ { tool: "get_framework_status", args: {} },
296
+ routeIntent,
297
+ ],
298
+ };
299
+ }
300
+ export function planRoleExecution(roleValue, input, options = {}) {
277
301
  const normalizedRole = canonicalizeRole(roleValue);
278
302
  const text = input.toLowerCase();
279
303
  const domainScore = scoreDomain(input);
@@ -316,10 +340,17 @@ export function planRoleExecution(roleValue, input) {
316
340
  domainScore.confidenceLevel === "low" ||
317
341
  contradictoryStateRequested ||
318
342
  aceStateRequested;
319
- toolCalls.push({ tool: "recall_context", args: {} });
320
- toolCalls.push({ tool: "validate_framework", args: {} });
321
- toolCalls.push({ tool: "get_framework_status", args: {} });
322
- toolCalls.push({ tool: "route_task", args: { description: input, domain } });
343
+ const preflight = buildAcePreflightPacket({
344
+ role: normalizedRole,
345
+ model_class: options.model_class,
346
+ text: input,
347
+ domain,
348
+ preflight_required: preflightRequired,
349
+ });
350
+ toolCalls.push(...preflight.model_visible_tools);
351
+ if (options.model_class === "small_local") {
352
+ notes.push(preflight.summary);
353
+ }
323
354
  if (preflightRequired) {
324
355
  toolCalls.push({ tool: "execute_gates", args: {} });
325
356
  notes.push("Orchestrator preflight required before dispatch.");
@@ -568,6 +599,19 @@ export function planRoleExecution(roleValue, input) {
568
599
  domain,
569
600
  confidence: domainScore.confidence,
570
601
  confidenceLevel: domainScore.confidenceLevel,
602
+ preflight: normalizedRole === "orchestrator"
603
+ ? buildAcePreflightPacket({
604
+ role: normalizedRole,
605
+ model_class: options.model_class,
606
+ text: input,
607
+ domain,
608
+ preflight_required: dispatchRequested ||
609
+ domainScore.ambiguous ||
610
+ domainScore.confidenceLevel === "low" ||
611
+ contradictoryStateRequested ||
612
+ aceStateRequested,
613
+ })
614
+ : undefined,
571
615
  };
572
616
  }
573
617
  //# sourceMappingURL=role-adapters.js.map
@@ -1,3 +1,57 @@
1
+ interface AstGrepMatch {
2
+ file?: string;
3
+ text?: string;
4
+ range?: {
5
+ start?: {
6
+ line?: number;
7
+ column?: number;
8
+ };
9
+ end?: {
10
+ line?: number;
11
+ column?: number;
12
+ };
13
+ };
14
+ metaVariables?: Record<string, unknown>;
15
+ kind?: string;
16
+ nodeKind?: string;
17
+ }
18
+ export interface AstgrepMatch {
19
+ file: string;
20
+ line: number;
21
+ column: number;
22
+ text: string;
23
+ context_lines?: string[];
24
+ }
25
+ export interface AstgrepLocateInput {
26
+ pattern: string;
27
+ lang: string;
28
+ scope?: string;
29
+ symbol_hint?: string;
30
+ max_results?: number;
31
+ }
32
+ export interface AstgrepLocatedMatch {
33
+ match_id: string;
34
+ file: string;
35
+ range: NonNullable<AstGrepMatch["range"]>;
36
+ text_preview: string;
37
+ matched_text: string;
38
+ captures?: Record<string, string>;
39
+ node_kind?: string;
40
+ file_hash: string;
41
+ }
42
+ export interface AstgrepLocateResult {
43
+ ok: boolean;
44
+ pattern: string;
45
+ lang: string;
46
+ scope: string;
47
+ symbol_hint?: string;
48
+ astgrep_command: string | null;
49
+ total_matches: number;
50
+ matches: AstgrepLocatedMatch[];
51
+ error?: string;
52
+ }
53
+ export declare function runAstgrepQuery(pattern: string, lang: string, roots: string[], _contextLines?: number): AstgrepMatch[];
54
+ export declare function locateAstgrepMatches(input: AstgrepLocateInput): AstgrepLocateResult;
1
55
  export interface RefreshAstgrepIndexInput {
2
56
  scope?: string;
3
57
  append_evidence?: boolean;
@@ -20,5 +74,7 @@ export interface RefreshAstgrepIndexResult {
20
74
  todo_signals: number;
21
75
  };
22
76
  }
23
- export declare function refreshAstgrepIndex(input?: RefreshAstgrepIndexInput): RefreshAstgrepIndexResult;
77
+ export declare function detectAstgrepCommand(): string | null;
78
+ export declare function refreshAstgrepIndex(input?: RefreshAstgrepIndexInput): Promise<RefreshAstgrepIndexResult>;
79
+ export {};
24
80
  //# sourceMappingURL=astgrep-index.d.ts.map