@voybio/ace-swarm 0.2.0 → 0.2.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 (64) hide show
  1. package/README.md +15 -15
  2. package/assets/agent-state/EVIDENCE_LOG.md +1 -1
  3. package/assets/agent-state/STATUS.md +2 -2
  4. package/dist/ace-autonomy.js +38 -1
  5. package/dist/ace-context.js +8 -0
  6. package/dist/ace-server-instructions.js +55 -19
  7. package/dist/ace-state-resolver.d.ts +18 -0
  8. package/dist/ace-state-resolver.js +106 -0
  9. package/dist/cli.js +67 -0
  10. package/dist/handoff-registry.js +11 -7
  11. package/dist/helpers.js +74 -8
  12. package/dist/job-scheduler.js +94 -44
  13. package/dist/run-ledger.js +3 -4
  14. package/dist/server.d.ts +1 -1
  15. package/dist/server.js +1 -1
  16. package/dist/shared.d.ts +1 -1
  17. package/dist/status-events.js +12 -14
  18. package/dist/store/bootstrap-store.js +20 -9
  19. package/dist/store/materializers/context-snapshot-materializer.d.ts +10 -0
  20. package/dist/store/materializers/context-snapshot-materializer.js +51 -0
  21. package/dist/store/materializers/host-file-materializer.d.ts +6 -0
  22. package/dist/store/materializers/host-file-materializer.js +13 -0
  23. package/dist/store/materializers/projection-manager.d.ts +14 -0
  24. package/dist/store/materializers/projection-manager.js +73 -0
  25. package/dist/store/materializers/scheduler-projection-materializer.d.ts +16 -0
  26. package/dist/store/materializers/scheduler-projection-materializer.js +48 -0
  27. package/dist/store/repositories/context-snapshot-repository.d.ts +46 -0
  28. package/dist/store/repositories/context-snapshot-repository.js +105 -0
  29. package/dist/store/repositories/local-model-runtime-repository.d.ts +98 -0
  30. package/dist/store/repositories/local-model-runtime-repository.js +165 -0
  31. package/dist/store/repositories/scheduler-repository.d.ts +21 -39
  32. package/dist/store/repositories/scheduler-repository.js +123 -93
  33. package/dist/store/repositories/todo-repository.d.ts +4 -0
  34. package/dist/store/repositories/todo-repository.js +50 -0
  35. package/dist/store/state-reader.d.ts +8 -1
  36. package/dist/store/state-reader.js +12 -1
  37. package/dist/store/store-artifacts.js +31 -5
  38. package/dist/store/store-authority-audit.d.ts +30 -0
  39. package/dist/store/store-authority-audit.js +448 -0
  40. package/dist/store/types.d.ts +2 -0
  41. package/dist/store/types.js +1 -0
  42. package/dist/todo-state.js +179 -11
  43. package/dist/tools-files.js +2 -1
  44. package/dist/tools-framework.js +60 -0
  45. package/dist/tools-memory.js +69 -34
  46. package/dist/tools-todo.js +1 -1
  47. package/dist/tui/agent-worker.d.ts +1 -1
  48. package/dist/tui/agent-worker.js +5 -3
  49. package/dist/tui/chat.d.ts +19 -0
  50. package/dist/tui/chat.js +275 -9
  51. package/dist/tui/commands.d.ts +2 -0
  52. package/dist/tui/commands.js +62 -0
  53. package/dist/tui/dashboard.d.ts +5 -0
  54. package/dist/tui/dashboard.js +38 -2
  55. package/dist/tui/index.d.ts +5 -0
  56. package/dist/tui/index.js +146 -2
  57. package/dist/tui/input.js +5 -0
  58. package/dist/tui/layout.d.ts +24 -0
  59. package/dist/tui/layout.js +76 -2
  60. package/dist/tui/local-model-contract.d.ts +50 -0
  61. package/dist/tui/local-model-contract.js +272 -0
  62. package/dist/vericify-bridge.js +3 -4
  63. package/dist/vericify-context.js +18 -6
  64. package/package.json +1 -1
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  Autonomous Coding Entity (ACE) is a local MCP server and CLI for agent-assisted coding.
4
4
 
5
- `ace-swarm` gives Claude Code, Codex, Cursor, VS Code, and local-model workflows a shared runtime: durable workspace state, typed handoffs, scheduler primitives, change intelligence, and operator tooling. All state lives in a single binary file — `.agents/ACE/ace-state.ace` — so the workspace keeps one source of truth instead of a forest of generated state files.
5
+ `ace-swarm` provides Claude Code, Codex, Cursor, VS Code, and local-model workflows with a shared runtime: durable workspace state, typed handoffs, scheduler support, change intelligence, and operator tooling. All state lives in a single binary file — `.agents/ACE/ace-state.ace` — so the workspace keeps one source of truth instead of a forest of generated state files.
6
6
 
7
- Modern coding agents can already read files, write code, run commands, and call tools. The missing layer is coordination that survives restarts, model switches, and multi-agent work. ACE fills that gap by keeping the workflow in the store and projecting only the few files external clients still need. For local models, ACE provides the eyes and ears they usually lack: task context, tool surfaces, status events, evidence trails, and resumable state.
7
+ Modern coding agents can already read files, write code, run commands, and call tools. The missing layer is coordination that survives restarts, model switches, and multi-agent work. ACE keeps the workflow in the store and projects only the files external clients still need. For local models, ACE provides task context, tool surfaces, status events, evidence trails, and resumable state.
8
8
 
9
9
  ## ACE Manifesto
10
10
 
@@ -16,7 +16,7 @@ Modern coding agents can already read files, write code, run commands, and call
16
16
 
17
17
  ### Serving local models
18
18
 
19
- ACE is the eyes and ears for local models. It gives them the context they cannot infer on their own: task state, tool surfaces, status events, evidence trails, and the next move in the loop.
19
+ ACE provides local models with the context they cannot infer on their own: task state, tool surfaces, status events, evidence trails, and the next move in the loop.
20
20
 
21
21
  - `ace init --llm ollama` or `ace init --llm llama.cpp` records a local runtime profile in the store.
22
22
  - `ace doctor` discovers the endpoint, checks the selected model, and keeps the runtime honest.
@@ -31,7 +31,9 @@ Frontier models are getting more capable and more restricted at the same time. P
31
31
  - Ollama and llama.cpp keep local models in play.
32
32
  - MCP remains the common protocol surface across clients and runtimes.
33
33
 
34
- ACE sits underneath that stack. It does not compete with the host you already use. It gives that host a durable local runtime.
34
+ ACE sits underneath that stack. It does not compete with the host you already use. It provides that host with a durable local runtime.
35
+
36
+ ACE also provides scheduler primitives for queued work, dependency gates, leases, and resource locks so multi-agent runs stay coordinated.
35
37
 
36
38
  ## ACEPACK State Model
37
39
 
@@ -175,18 +177,16 @@ Developer or MCP host
175
177
  +----------------------+--------------------------------------------------------------+
176
178
  | Surface | Current scope |
177
179
  +----------------------+--------------------------------------------------------------+
178
- | Runtime roles | 17 runtime roles: 4 swarm roles + 13 composable roles |
179
- | Skills | 15 packaged skills |
180
- | MCP server | stdio server for tools, prompts, resources, and workflows |
181
- | Durable state | single .ace authority for handoffs, status events, todo |
182
- | | state, run ledger, job queue, and discovery |
183
- | Projections | hook context, todo surface, and host configs when needed |
184
- | Coordination | priority dispatch, dependency gates, leases, resource locks |
180
+ | Runtime roles | swarm and composable runtime roles |
181
+ | Skills | packaged skills |
182
+ | MCP server | stdio surface for tools, prompts, resources, and workflows |
183
+ | Durable state | single .ace authority for handoffs, status events, todos, run ledger, job queue, and discovery |
184
+ | Projections | hook context, todo surface, and host config bundles |
185
+ | Coordination | scheduler queues, dependency gates, leases, and resource locks |
185
186
  | Change intelligence | delta scan, semantic snapshots, drift reports, rewrite hints |
186
- | Terminal UI | provider-aware TUI with chat, tabs, telemetry, agent tabs |
187
- | Local models | Ollama and llama.cpp bootstrap, doctor checks, model bridge, |
188
- | | tool loops |
189
- | Verification | 242 tests passing in the current suite |
187
+ | Terminal UI | provider-aware TUI with chat, tabs, telemetry, and agent UI |
188
+ | Local models | Ollama and llama.cpp bootstrap, doctor checks, and bridges |
189
+ | Verification | automated test suite and regression coverage |
190
190
  +----------------------+--------------------------------------------------------------+
191
191
  ```
192
192
 
@@ -4,4 +4,4 @@ Append-only chain-of-custody for checks, decisions, and execution proofs.
4
4
 
5
5
  ## Entries
6
6
 
7
- - 2026-03-03T00:00:00Z | bootstrap initialized | evidence_ref: `EVIDENCE_LOG.md#ts:2026-03-03T00:00:00Z`
7
+ - {{BOOTSTRAP_TIMESTAMP}} | bootstrap initialized | evidence_ref: `EVIDENCE_LOG.md#ts:{{BOOTSTRAP_TIMESTAMP}}`
@@ -1,8 +1,8 @@
1
1
  # STATUS
2
2
 
3
- - Current role: `capability-skeptic`
3
+ - Current role: `ace-orchestrator`
4
4
  - Current phase: `STATE_ANALYSIS`
5
5
  - Active pipeline: `standard`
6
6
  - Blockers: `none`
7
7
  - Current objective delta: `bootstrap complete, awaiting first scoped objective`
8
- - Last update: `2026-03-03T00:00:00Z`
8
+ - Last update: `{{BOOTSTRAP_TIMESTAMP}}`
@@ -1,7 +1,8 @@
1
- import { safeRead, wsPath } from "./helpers.js";
1
+ import { resolveWorkspaceRoot, safeRead, wsPath } from "./helpers.js";
2
2
  import { readRunLedger } from "./run-ledger.js";
3
3
  import { isReadError } from "./shared.js";
4
4
  import { readStatusEvents } from "./status-events.js";
5
+ import { getWorkspaceStorePath, readStoreJsonSync, storeExistsSync, toVirtualStorePath, } from "./store/store-snapshot.js";
5
6
  export const ACE_TASK_CONTRACT_FILES = [
6
7
  "agent-state/TASK.md",
7
8
  "agent-state/SCOPE.md",
@@ -102,6 +103,27 @@ function readStringArray(value) {
102
103
  return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
103
104
  }
104
105
  function loadSnapshotIndex() {
106
+ const workspaceRoot = resolveWorkspaceRoot();
107
+ if (storeExistsSync(workspaceRoot)) {
108
+ const storeIndex = readStoreJsonSync(workspaceRoot, "state/memory/context_snapshots/index.json");
109
+ const snapshots = Array.isArray(storeIndex?.snapshots) ? storeIndex.snapshots : undefined;
110
+ if (snapshots) {
111
+ return {
112
+ snapshots: snapshots
113
+ .map((entry) => {
114
+ const item = coerceObject(entry);
115
+ const name = readString(item?.name);
116
+ const file = readString(item?.file);
117
+ const timestamp = readString(item?.timestamp);
118
+ const summary = readString(item?.summary);
119
+ if (!name || !file || !timestamp || !summary)
120
+ return undefined;
121
+ return { name, file, timestamp, summary };
122
+ })
123
+ .filter((entry) => Boolean(entry)),
124
+ };
125
+ }
126
+ }
105
127
  const raw = safeRead("agent-state/context-snapshots/index.json");
106
128
  if (isReadError(raw))
107
129
  return undefined;
@@ -240,6 +262,21 @@ export function readAceContextSnapshot(name) {
240
262
  : index.snapshots[0];
241
263
  if (!selected)
242
264
  return undefined;
265
+ const workspaceRoot = resolveWorkspaceRoot();
266
+ if (storeExistsSync(workspaceRoot)) {
267
+ const storePath = getWorkspaceStorePath(workspaceRoot);
268
+ const key = `state/memory/context_snapshots/${selected.file}`;
269
+ const record = readStoreJsonSync(workspaceRoot, key);
270
+ if (record) {
271
+ return {
272
+ name: selected.name,
273
+ timestamp: selected.timestamp,
274
+ summary: selected.summary,
275
+ path: toVirtualStorePath(storePath, key),
276
+ content: clipText(JSON.stringify(record, null, 2), 1200),
277
+ };
278
+ }
279
+ }
243
280
  const relPath = wsPath("agent-state", "context-snapshots", selected.file);
244
281
  const content = safeRead(relPath);
245
282
  if (isReadError(content))
@@ -5,7 +5,9 @@ import { readRuntimeProfileState, renderRuntimePrompt } from "./runtime-profile.
5
5
  const ROLE_TOOL_DEFAULTS = {
6
6
  orchestrator: [
7
7
  "recall_context",
8
+ "validate_framework",
8
9
  "route_task",
10
+ "run_orchestrator",
9
11
  "run_local_model",
10
12
  "get_vericify_context",
11
13
  "get_vericify_delta",
@@ -221,6 +223,12 @@ export function renderAceContext(options) {
221
223
  "## Tool Scope",
222
224
  toolCatalog || "- No ACE tools available.",
223
225
  "",
226
+ "## Compact Guardrails",
227
+ "- Stop if you are about to claim tested, verified, or passed without evidence.",
228
+ "- If ACE state is thin or contradictory, call recall_context or validate_framework before improvising.",
229
+ "- For obviously multi-step work, prefer run_orchestrator over a single free-form answer.",
230
+ "- Do not declare completion while approval, retry, or blocker state remains active.",
231
+ "",
224
232
  "## Output Contract",
225
233
  'Respond in JSON only with one of these shapes:',
226
234
  '- `{"status":"tool","thinking":"...","tool_calls":[{"tool":"name","input":{}}]}`',
@@ -1,18 +1,19 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
+ import { readAceLogicalFile, resolveAceLogicalPath, resolveAceStateLayout, } from "./ace-state-resolver.js";
3
4
  const ACE_ROOT_REL = ".agents/ACE";
4
5
  const CORE_FILES = [
5
- `${ACE_ROOT_REL}/agent-state/TASK.md`,
6
- `${ACE_ROOT_REL}/agent-state/STATUS.md`,
7
- `${ACE_ROOT_REL}/agent-state/SCOPE.md`,
8
- `${ACE_ROOT_REL}/agent-state/EVIDENCE_LOG.md`,
6
+ "agent-state/TASK.md",
7
+ "agent-state/STATUS.md",
8
+ "agent-state/SCOPE.md",
9
+ "agent-state/EVIDENCE_LOG.md",
9
10
  ];
10
11
  const STATE_DIRS = [
11
- `${ACE_ROOT_REL}/agent-state`,
12
- `${ACE_ROOT_REL}/global-state`,
13
- `${ACE_ROOT_REL}/venture-state`,
14
- `${ACE_ROOT_REL}/brand-state`,
15
- `${ACE_ROOT_REL}/engineering-state`,
12
+ "agent-state",
13
+ "global-state",
14
+ "venture-state",
15
+ "brand-state",
16
+ "engineering-state",
16
17
  ];
17
18
  const BOOTSTRAP_TOOLS = new Set([
18
19
  "bootstrap_state",
@@ -81,6 +82,17 @@ function truncate(input, max = 140) {
81
82
  return `${input.slice(0, Math.max(0, max - 1)).trimEnd()}…`;
82
83
  }
83
84
  function resolveWorkspacePath(workspaceRoot, relativePath) {
85
+ if (relativePath === "agent-state") {
86
+ const layout = resolveAceStateLayout(workspaceRoot);
87
+ if (layout.physicalMode === "top_level") {
88
+ return resolve(workspaceRoot, "agent-state");
89
+ }
90
+ return resolve(workspaceRoot, ACE_ROOT_REL, "agent-state");
91
+ }
92
+ if (relativePath.startsWith("agent-state/")) {
93
+ return (resolveAceLogicalPath(workspaceRoot, relativePath) ??
94
+ resolve(workspaceRoot, ACE_ROOT_REL, relativePath));
95
+ }
84
96
  const canonicalRel = relativePath.startsWith(`${ACE_ROOT_REL}/`)
85
97
  ? relativePath
86
98
  : `agent-state|global-state|venture-state|brand-state|engineering-state`
@@ -97,11 +109,33 @@ function resolveWorkspacePath(workspaceRoot, relativePath) {
97
109
  }
98
110
  return canonicalPath;
99
111
  }
112
+ function displayWorkspacePath(workspaceRoot, relativePath) {
113
+ if (relativePath === "agent-state" || relativePath.startsWith("agent-state/")) {
114
+ return relativePath;
115
+ }
116
+ const canonicalRel = relativePath.startsWith(`${ACE_ROOT_REL}/`)
117
+ ? relativePath
118
+ : `agent-state|global-state|venture-state|brand-state|engineering-state`
119
+ .split("|")
120
+ .some((prefix) => relativePath === prefix || relativePath.startsWith(`${prefix}/`))
121
+ ? `${ACE_ROOT_REL}/${relativePath}`
122
+ : relativePath;
123
+ const canonicalPath = resolve(workspaceRoot, canonicalRel);
124
+ if (existsSync(canonicalPath)) {
125
+ return canonicalRel;
126
+ }
127
+ if (canonicalRel !== relativePath && existsSync(resolve(workspaceRoot, relativePath))) {
128
+ return relativePath;
129
+ }
130
+ return canonicalRel;
131
+ }
100
132
  function listExistingCoreFiles(workspaceRoot) {
101
- return CORE_FILES.filter((relativePath) => existsSync(resolveWorkspacePath(workspaceRoot, relativePath)));
133
+ return CORE_FILES.filter((relativePath) => Boolean(resolveAceLogicalPath(workspaceRoot, relativePath)))
134
+ .map((relativePath) => displayWorkspacePath(workspaceRoot, relativePath));
102
135
  }
103
136
  function listExistingStateDirs(workspaceRoot) {
104
- return STATE_DIRS.filter((relativePath) => existsSync(resolveWorkspacePath(workspaceRoot, relativePath)));
137
+ return STATE_DIRS.filter((relativePath) => existsSync(resolveWorkspacePath(workspaceRoot, relativePath)))
138
+ .map((relativePath) => displayWorkspacePath(workspaceRoot, relativePath));
105
139
  }
106
140
  function extractObjective(taskRaw) {
107
141
  if (!taskRaw)
@@ -125,7 +159,7 @@ function extractStatusValue(statusRaw, label) {
125
159
  return match?.[1] ? truncate(oneLine(match[1])) : undefined;
126
160
  }
127
161
  function extractAutonomyHint(workspaceRoot) {
128
- const workflowRaw = readIfPresent(resolveWorkspacePath(workspaceRoot, "agent-state/ACE_WORKFLOW.md"));
162
+ const workflowRaw = readAceLogicalFile(workspaceRoot, "agent-state/ACE_WORKFLOW.md");
129
163
  if (!workflowRaw)
130
164
  return undefined;
131
165
  const hints = [];
@@ -142,8 +176,8 @@ function extractAutonomyHint(workspaceRoot) {
142
176
  return hints.length > 0 ? hints.join(", ") : undefined;
143
177
  }
144
178
  export function buildAceDigest(workspaceRoot) {
145
- const taskRaw = readIfPresent(resolveWorkspacePath(workspaceRoot, "agent-state/TASK.md"));
146
- const statusRaw = readIfPresent(resolveWorkspacePath(workspaceRoot, "agent-state/STATUS.md"));
179
+ const taskRaw = readAceLogicalFile(workspaceRoot, "agent-state/TASK.md");
180
+ const statusRaw = readAceLogicalFile(workspaceRoot, "agent-state/STATUS.md");
147
181
  const objective = extractObjective(taskRaw);
148
182
  const phase = extractStatusValue(statusRaw, "Current phase");
149
183
  const blockers = extractStatusValue(statusRaw, "Blockers");
@@ -187,7 +221,7 @@ export function buildHostInstructionText(host, workspaceRoot) {
187
221
  `# ACE Bootstrap For ${hostLabel}`,
188
222
  "",
189
223
  "This is a thin ACE bootstrap stub.",
190
- "Full instructions, skills, task packs, and runtime state live inside `.agents/ACE/ace-state.ace` and should be accessed through the ACE MCP server.",
224
+ "Full instructions, skills, task packs, and runtime state live in ACE artifacts and should be accessed through the ACE MCP server.",
191
225
  "",
192
226
  "Session start protocol:",
193
227
  "1. Call `recall_context` to load task, scope, and status from ACE state.",
@@ -195,7 +229,8 @@ export function buildHostInstructionText(host, workspaceRoot) {
195
229
  "3. Prefer ACE prompts/resources (`ace-orchestrator`, `get_task_pack`, `get_skill_instructions`) over relying on this file.",
196
230
  "",
197
231
  "Ground truth:",
198
- "- `.agents/ACE/ace-state.ace` is authoritative.",
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.",
199
234
  "- `.agents/ACE/tasks/todo.md` is the human-facing todo surface.",
200
235
  "- `.agents/ACE/ace-hook-context.json` is the compact hook snapshot.",
201
236
  "",
@@ -216,9 +251,10 @@ export function buildBootstrapGuidance(workspaceRoot, toolName) {
216
251
  return `ACE context loaded via \`${toolName}\`. Current digest: ${buildAceDigest(workspaceRoot)}.`;
217
252
  }
218
253
  export function buildMutationGuidance(workspaceRoot, toolName) {
254
+ const evidencePath = displayWorkspacePath(workspaceRoot, "agent-state/EVIDENCE_LOG.md");
219
255
  return [
220
256
  `ACE digest after \`${toolName}\`: ${buildAceDigest(workspaceRoot)}.`,
221
- `If this changed code or state, reconcile \`${ACE_ROOT_REL}/agent-state/EVIDENCE_LOG.md\`, run \`execute_gates\` when appropriate, and use structured handoffs/process posts for delegated follow-up.`,
257
+ `If this changed code or state, reconcile \`${evidencePath}\`, run \`execute_gates\` when appropriate, and use structured handoffs/process posts for delegated follow-up.`,
222
258
  ].join(" ");
223
259
  }
224
260
  export function buildRefreshGuidance(workspaceRoot, toolName) {
@@ -312,12 +348,12 @@ export function buildHookAdditionalContext(workspaceRoot) {
312
348
  return parts.join("\n");
313
349
  }
314
350
  export function buildScopeReminder(workspaceRoot) {
315
- const scopeRaw = readIfPresent(resolveWorkspacePath(workspaceRoot, "agent-state/SCOPE.md"));
351
+ const scopeRaw = readAceLogicalFile(workspaceRoot, "agent-state/SCOPE.md");
316
352
  if (!scopeRaw)
317
353
  return undefined;
318
354
  const scopeLines = nonEmptyLines(scopeRaw).filter((line) => !line.startsWith("#"));
319
355
  if (scopeLines.length === 0) {
320
- return `ACE scope reminder: review ${ACE_ROOT_REL}/agent-state/SCOPE.md before mutating files or state.`;
356
+ return `ACE scope reminder: review ${displayWorkspacePath(workspaceRoot, "agent-state/SCOPE.md")} before mutating files or state.`;
321
357
  }
322
358
  return `ACE scope reminder: ${truncate(oneLine(scopeLines.slice(0, 4).join(" ")), 220)}`;
323
359
  }
@@ -0,0 +1,18 @@
1
+ export type AcePhysicalMode = "top_level" | "nested_projection" | "store_projection" | "missing";
2
+ export interface AceStateResolution {
3
+ workspaceRoot: string;
4
+ logicalStateRoot: "agent-state";
5
+ physicalMode: AcePhysicalMode;
6
+ taskPath?: string;
7
+ statusPath?: string;
8
+ scopePath?: string;
9
+ evidencePath?: string;
10
+ storePath?: string;
11
+ hookSnapshotPath?: string;
12
+ isAcePresent: boolean;
13
+ missingCriticalArtifacts: string[];
14
+ }
15
+ export declare function readAceLogicalFile(workspaceRoot: string, relativePath: string): string | undefined;
16
+ export declare function resolveAceLogicalPath(workspaceRoot: string, relativePath: string): string | undefined;
17
+ export declare function resolveAceStateLayout(workspaceRoot: string): AceStateResolution;
18
+ //# sourceMappingURL=ace-state-resolver.d.ts.map
@@ -0,0 +1,106 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { getWorkspaceStorePath, readStoreBlobSync, toVirtualStorePath } from "./store/store-snapshot.js";
4
+ import { isOperationalArtifactPath, operationalArtifactKey, } from "./store/store-artifacts.js";
5
+ const CRITICAL_LOGICAL_PATHS = [
6
+ "agent-state/TASK.md",
7
+ "agent-state/STATUS.md",
8
+ "agent-state/SCOPE.md",
9
+ ];
10
+ const DISPLAY_LOGICAL_PATHS = [
11
+ "agent-state/TASK.md",
12
+ "agent-state/STATUS.md",
13
+ "agent-state/SCOPE.md",
14
+ "agent-state/EVIDENCE_LOG.md",
15
+ ];
16
+ function workspacePath(root, relativePath) {
17
+ return resolve(root, relativePath);
18
+ }
19
+ function nestedAcePath(root, relativePath) {
20
+ return resolve(root, ".agents", "ACE", relativePath);
21
+ }
22
+ function readFileIfPresent(filePath) {
23
+ if (!existsSync(filePath))
24
+ return undefined;
25
+ try {
26
+ return readFileSync(filePath, "utf-8");
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ function storeFallbackKeys(relativePath) {
33
+ const keys = [];
34
+ if (isOperationalArtifactPath(relativePath)) {
35
+ keys.push(operationalArtifactKey(relativePath));
36
+ }
37
+ keys.push(`knowledge/${relativePath}`);
38
+ return keys;
39
+ }
40
+ export function readAceLogicalFile(workspaceRoot, relativePath) {
41
+ const direct = readFileIfPresent(workspacePath(workspaceRoot, relativePath));
42
+ if (direct !== undefined)
43
+ return direct;
44
+ const nested = readFileIfPresent(nestedAcePath(workspaceRoot, relativePath));
45
+ if (nested !== undefined)
46
+ return nested;
47
+ for (const key of storeFallbackKeys(relativePath)) {
48
+ const storeValue = readStoreBlobSync(workspaceRoot, key);
49
+ if (typeof storeValue === "string")
50
+ return storeValue;
51
+ }
52
+ return undefined;
53
+ }
54
+ export function resolveAceLogicalPath(workspaceRoot, relativePath) {
55
+ const direct = workspacePath(workspaceRoot, relativePath);
56
+ if (existsSync(direct))
57
+ return direct;
58
+ const nested = nestedAcePath(workspaceRoot, relativePath);
59
+ if (existsSync(nested))
60
+ return nested;
61
+ const storePath = getWorkspaceStorePath(workspaceRoot);
62
+ if (!existsSync(storePath))
63
+ return undefined;
64
+ for (const key of storeFallbackKeys(relativePath)) {
65
+ if (typeof readStoreBlobSync(workspaceRoot, key) === "string") {
66
+ return toVirtualStorePath(storePath, key);
67
+ }
68
+ }
69
+ return undefined;
70
+ }
71
+ export function resolveAceStateLayout(workspaceRoot) {
72
+ const storePath = getWorkspaceStorePath(workspaceRoot);
73
+ const nestedStateDir = nestedAcePath(workspaceRoot, "agent-state");
74
+ const topLevelStateDir = workspacePath(workspaceRoot, "agent-state");
75
+ const topLevelPresent = DISPLAY_LOGICAL_PATHS.some((relativePath) => existsSync(workspacePath(workspaceRoot, relativePath)));
76
+ const nestedPresent = DISPLAY_LOGICAL_PATHS.some((relativePath) => existsSync(nestedAcePath(workspaceRoot, relativePath)));
77
+ const storePresent = existsSync(storePath);
78
+ let physicalMode = "missing";
79
+ if (topLevelPresent || existsSync(topLevelStateDir)) {
80
+ physicalMode = "top_level";
81
+ }
82
+ else if (nestedPresent || existsSync(nestedStateDir)) {
83
+ physicalMode = "nested_projection";
84
+ }
85
+ else if (storePresent) {
86
+ physicalMode = "store_projection";
87
+ }
88
+ const missingCriticalArtifacts = CRITICAL_LOGICAL_PATHS.filter((relativePath) => typeof readAceLogicalFile(workspaceRoot, relativePath) !== "string");
89
+ return {
90
+ workspaceRoot,
91
+ logicalStateRoot: "agent-state",
92
+ physicalMode,
93
+ taskPath: resolveAceLogicalPath(workspaceRoot, "agent-state/TASK.md"),
94
+ statusPath: resolveAceLogicalPath(workspaceRoot, "agent-state/STATUS.md"),
95
+ scopePath: resolveAceLogicalPath(workspaceRoot, "agent-state/SCOPE.md"),
96
+ evidencePath: resolveAceLogicalPath(workspaceRoot, "agent-state/EVIDENCE_LOG.md"),
97
+ storePath: storePresent ? storePath : undefined,
98
+ hookSnapshotPath: existsSync(nestedAcePath(workspaceRoot, "ace-hook-context.json"))
99
+ ? nestedAcePath(workspaceRoot, "ace-hook-context.json")
100
+ : undefined,
101
+ isAcePresent: physicalMode !== "missing" ||
102
+ CRITICAL_LOGICAL_PATHS.some((relativePath) => typeof readAceLogicalFile(workspaceRoot, relativePath) === "string"),
103
+ missingCriticalArtifacts,
104
+ };
105
+ }
106
+ //# sourceMappingURL=ace-state-resolver.js.map
package/dist/cli.js CHANGED
@@ -6,8 +6,10 @@ import { startStdioServer } from "./server.js";
6
6
  import { appendRunLedgerEntrySafe } from "./run-ledger.js";
7
7
  import { waitForPendingStatusEventMirrors } from "./status-events.js";
8
8
  import { bootstrapStoreWorkspace } from "./store/bootstrap-store.js";
9
+ import { HostFileMaterializer } from "./store/materializers/host-file-materializer.js";
9
10
  import { openStore } from "./store/ace-packed-store.js";
10
11
  import { withStoreWriteQueue } from "./store/write-queue.js";
12
+ import { DiscoveryRepository } from "./store/repositories/discovery-repository.js";
11
13
  import { getWorkspaceStorePath, readStoreBlobSync, readStoreJsonSync, } from "./store/store-snapshot.js";
12
14
  import { readFileSync } from "node:fs";
13
15
  import { runTui } from "./tui/index.js";
@@ -23,6 +25,7 @@ Usage:
23
25
  ace turnkey [options] Project minimal workspace bootstrap stubs from the ACE store
24
26
  ace doctor [options] Validate local LLM + MCP readiness
25
27
  ace mcp-config [options] Print global/client MCP config snippet(s) from store
28
+ ace preconfig Write .mcp-config/ bundle for all supported clients to workspace root
26
29
  ace paths Show resolved package/workspace paths
27
30
  ace help Show help
28
31
 
@@ -52,6 +55,9 @@ Options for doctor:
52
55
  Options for mcp-config:
53
56
  --client <name> codex|vscode|claude|cursor|antigravity
54
57
  --all Print all client snippets for optional global install
58
+
59
+ preconfig writes .mcp-config/ at the workspace root with ready-to-use config files for every
60
+ supported MCP client. Run once after ace init. Each file includes the install hint for its client.
55
61
  `);
56
62
  }
57
63
  function readFlagValue(args, flag) {
@@ -152,6 +158,33 @@ async function writeLlmProfile(profile) {
152
158
  });
153
159
  return `${storePath}#state/runtime/llm_profile`;
154
160
  }
161
+ async function recordDiscoveryProfile(input) {
162
+ const storePath = getWorkspaceStorePath(WORKSPACE_ROOT);
163
+ await withStoreWriteQueue(storePath, async () => {
164
+ const store = await openStore(storePath);
165
+ try {
166
+ const discovery = new DiscoveryRepository(store);
167
+ await discovery.record({
168
+ provider: input.provider,
169
+ available: input.available,
170
+ endpoint: input.baseUrl,
171
+ models: input.models,
172
+ metadata: input.selectedModel
173
+ ? {
174
+ profile_model: input.selectedModel,
175
+ runtime_kind: "local",
176
+ }
177
+ : {
178
+ runtime_kind: "local",
179
+ },
180
+ });
181
+ await store.commit();
182
+ }
183
+ finally {
184
+ await store.close();
185
+ }
186
+ });
187
+ }
155
188
  async function runInit(args, mode = "init") {
156
189
  const projectName = readFlagValue(args, "--project");
157
190
  const force = args.includes("--force");
@@ -400,6 +433,13 @@ async function runDoctor(args) {
400
433
  });
401
434
  }
402
435
  const hasModel = modelNames.includes(model);
436
+ await recordDiscoveryProfile({
437
+ provider,
438
+ baseUrl,
439
+ models: modelNames,
440
+ selectedModel: model,
441
+ available: modelNames.length > 0,
442
+ });
403
443
  checks.push({
404
444
  name: provider === "ollama" ? "Requested model installed" : "Requested model served",
405
445
  ok: hasModel,
@@ -437,6 +477,29 @@ function parseClientFlag(args) {
437
477
  }
438
478
  return match;
439
479
  }
480
+ async function runPreconfig() {
481
+ const storePath = wsPath(".agents", "ACE", "ace-state.ace");
482
+ if (!fileExists(storePath)) {
483
+ console.error(`No ACE store found at ${storePath}. Run 'ace init' first.`);
484
+ process.exit(1);
485
+ }
486
+ const store = await openStore(storePath);
487
+ try {
488
+ const mat = new HostFileMaterializer(store, WORKSPACE_ROOT);
489
+ const written = await mat.materializeMcpBundle();
490
+ console.log(`ACE preconfig complete — wrote ${written.length} files to .mcp-config/\n`);
491
+ for (const p of written) {
492
+ console.log(` ${p}`);
493
+ }
494
+ console.log("\nInstall hints:");
495
+ for (const client of ALL_MCP_CLIENTS) {
496
+ console.log(` ${client}: ${getMcpClientInstallHint(client)}`);
497
+ }
498
+ }
499
+ finally {
500
+ await store.close();
501
+ }
502
+ }
440
503
  async function runMcpConfig(args) {
441
504
  const all = args.includes("--all");
442
505
  const explicitClient = parseClientFlag(args);
@@ -504,6 +567,10 @@ async function main() {
504
567
  await runMcpConfig(args.slice(1));
505
568
  return;
506
569
  }
570
+ if (command === "preconfig") {
571
+ await runPreconfig();
572
+ return;
573
+ }
507
574
  if (command === "paths") {
508
575
  console.log(`PACKAGE_ROOT=${PACKAGE_ROOT}`);
509
576
  console.log(`DEFAULTS_ROOT=${DEFAULTS_ROOT}`);
@@ -1,10 +1,12 @@
1
1
  import { resolveWorkspaceRoot, safeRead, safeWrite, wsPath, withFileLock } from "./helpers.js";
2
2
  import { isReadError } from "./shared.js";
3
3
  import { openStore } from "./store/ace-packed-store.js";
4
- import { VericifyProjector } from "./store/materializers/vericify-projector.js";
4
+ import { ProjectionManager } from "./store/materializers/projection-manager.js";
5
5
  import { HandoffRepository } from "./store/repositories/handoff-repository.js";
6
6
  import { getWorkspaceStorePath, listStoreKeysSync, readStoreJsonSync, storeExistsSync, } from "./store/store-snapshot.js";
7
7
  import { withStoreWriteQueue } from "./store/write-queue.js";
8
+ import { waitForPendingStatusEventMirrors } from "./status-events.js";
9
+ import { waitForTodoStoreMirror } from "./todo-state.js";
8
10
  const HANDOFF_REGISTRY_REL = "agent-state/handoff-registry.json";
9
11
  function workspaceRoot() {
10
12
  return resolveWorkspaceRoot();
@@ -291,6 +293,8 @@ export function getHandoffRegistryPath() {
291
293
  export async function registerHandoffSafe(input) {
292
294
  const root = workspaceRoot();
293
295
  const storePath = getWorkspaceStorePath(root);
296
+ await waitForTodoStoreMirror();
297
+ await waitForPendingStatusEventMirrors();
294
298
  if (!storeExistsSync(root)) {
295
299
  return withFileLock(HANDOFF_REGISTRY_REL, () => registerHandoff(input));
296
300
  }
@@ -332,9 +336,8 @@ export async function registerHandoffSafe(input) {
332
336
  },
333
337
  });
334
338
  await store.commit();
335
- const projector = new VericifyProjector(store, root);
336
- await projector.projectHandoffs();
337
- await store.commit();
339
+ const projections = new ProjectionManager(store, root);
340
+ await projections.projectAfterCommit(["handoffs"]);
338
341
  return {
339
342
  record: toLegacyHandoffRecord(record),
340
343
  path: wsPath(HANDOFF_REGISTRY_REL),
@@ -352,6 +355,8 @@ export async function registerHandoffSafe(input) {
352
355
  export async function acknowledgeHandoffSafe(input) {
353
356
  const root = workspaceRoot();
354
357
  const storePath = getWorkspaceStorePath(root);
358
+ await waitForTodoStoreMirror();
359
+ await waitForPendingStatusEventMirrors();
355
360
  if (!storeExistsSync(root)) {
356
361
  return withFileLock(HANDOFF_REGISTRY_REL, () => acknowledgeHandoff(input));
357
362
  }
@@ -403,9 +408,8 @@ export async function acknowledgeHandoffSafe(input) {
403
408
  },
404
409
  });
405
410
  await store.commit();
406
- const projector = new VericifyProjector(store, root);
407
- await projector.projectHandoffs();
408
- await store.commit();
411
+ const projections = new ProjectionManager(store, root);
412
+ await projections.projectAfterCommit(["handoffs"]);
409
413
  return {
410
414
  ok: true,
411
415
  path,