@voybio/ace-swarm 2.4.8 → 2.4.10

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 (187) hide show
  1. package/CHANGELOG.md +52 -14
  2. package/README.md +14 -10
  3. package/assets/.agents/ACE/ACE_coders/AGENTS.md +23 -30
  4. package/assets/.agents/ACE/ACE_coders/INSTRUCTIONS.md +21 -30
  5. package/assets/.agents/skills/landing-review-watcher/SKILL.md +1 -1
  6. package/assets/.agents/skills/learn-from-work/SKILL.md +41 -0
  7. package/assets/agent-state/ACE_WORKFLOW.md +25 -2
  8. package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +143 -2
  9. package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +2 -2
  10. package/assets/agent-state/MODULES/schemas/SWARM_HANDOFF.schema.json +10 -0
  11. package/assets/eval-battery/battery.json +1 -1
  12. package/assets/instructions/ACE_Coder.instructions.md +9 -13
  13. package/assets/scripts/ace-hook-dispatch.mjs +16 -411
  14. package/assets/tasks/SWARM_HANDOFF.example_ui_to_coders.json +1 -2
  15. package/assets/vendor/README.md +9 -0
  16. package/assets/vendor/vericify-reader.mjs +272 -0
  17. package/assets/vendor/vericify-reader.provenance.json +10 -0
  18. package/dist/ace-autonomy.d.ts +58 -0
  19. package/dist/ace-autonomy.js +211 -6
  20. package/dist/ace-internal-tools.d.ts +1 -0
  21. package/dist/ace-internal-tools.js +3 -3
  22. package/dist/ace-server-instructions.d.ts +3 -3
  23. package/dist/ace-server-instructions.js +46 -192
  24. package/dist/cli.js +44 -6
  25. package/dist/experience-reflector.d.ts +7 -0
  26. package/dist/experience-reflector.js +39 -0
  27. package/dist/goal-engine.d.ts +64 -7
  28. package/dist/goal-engine.js +197 -41
  29. package/dist/handoff-registry.d.ts +10 -0
  30. package/dist/handoff-registry.js +59 -0
  31. package/dist/helpers/artifacts.d.ts +6 -2
  32. package/dist/helpers/artifacts.js +8 -6
  33. package/dist/helpers/bootstrap.js +13 -20
  34. package/dist/helpers/path-utils.js +2 -0
  35. package/dist/helpers/store-resolution.js +18 -3
  36. package/dist/helpers/workspace-root.d.ts +2 -0
  37. package/dist/helpers/workspace-root.js +9 -0
  38. package/dist/helpers.d.ts +1 -0
  39. package/dist/helpers.js +1 -0
  40. package/dist/hermes/bridge-protocol.d.ts +1 -1
  41. package/dist/hermes/session-manager.d.ts +17 -0
  42. package/dist/hermes/session-manager.js +67 -14
  43. package/dist/host-goal-engine.d.ts +138 -0
  44. package/dist/host-goal-engine.js +793 -0
  45. package/dist/host-goal-status.d.ts +113 -0
  46. package/dist/host-goal-status.js +175 -0
  47. package/dist/host-learning.d.ts +68 -0
  48. package/dist/host-learning.js +115 -0
  49. package/dist/host-observation.d.ts +12 -0
  50. package/dist/host-observation.js +91 -0
  51. package/dist/host-plan-revision.d.ts +14 -0
  52. package/dist/host-plan-revision.js +101 -0
  53. package/dist/host-verification-job.d.ts +12 -0
  54. package/dist/host-verification-job.js +183 -0
  55. package/dist/host-work-context.d.ts +12 -0
  56. package/dist/host-work-context.js +8 -0
  57. package/dist/job-scheduler.d.ts +58 -13
  58. package/dist/job-scheduler.js +298 -48
  59. package/dist/job-worker.d.ts +3 -1
  60. package/dist/job-worker.js +10 -3
  61. package/dist/kanban.d.ts +4 -1
  62. package/dist/kanban.js +44 -16
  63. package/dist/learned-artifacts.d.ts +64 -0
  64. package/dist/learned-artifacts.js +321 -0
  65. package/dist/learning-controls.d.ts +142 -0
  66. package/dist/learning-controls.js +135 -0
  67. package/dist/learning-input-context.d.ts +9 -0
  68. package/dist/learning-input-context.js +62 -0
  69. package/dist/learning-lifecycle.d.ts +10 -0
  70. package/dist/learning-lifecycle.js +11 -0
  71. package/dist/learning-overlays.d.ts +3 -0
  72. package/dist/learning-overlays.js +17 -0
  73. package/dist/learning-policy.d.ts +40 -0
  74. package/dist/learning-policy.js +38 -0
  75. package/dist/learning-records.d.ts +35 -0
  76. package/dist/learning-records.js +17 -0
  77. package/dist/learning-scorecard.d.ts +82 -0
  78. package/dist/learning-scorecard.js +86 -0
  79. package/dist/learning-serving.d.ts +29 -0
  80. package/dist/learning-serving.js +134 -0
  81. package/dist/linked-work.d.ts +9 -0
  82. package/dist/linked-work.js +28 -0
  83. package/dist/local-model-runtime.js +1 -1
  84. package/dist/mcp-command-progress.d.ts +16 -0
  85. package/dist/mcp-command-progress.js +70 -0
  86. package/dist/model-bridge.d.ts +14 -13
  87. package/dist/model-bridge.js +73 -41
  88. package/dist/orchestrator-supervisor.d.ts +111 -6
  89. package/dist/orchestrator-supervisor.js +67 -43
  90. package/dist/plan-proposal.js +3 -3
  91. package/dist/playbook-curator.d.ts +1 -0
  92. package/dist/playbook-curator.js +11 -9
  93. package/dist/process-identity.d.ts +8 -0
  94. package/dist/process-identity.js +38 -0
  95. package/dist/progress-command.d.ts +34 -0
  96. package/dist/progress-command.js +153 -0
  97. package/dist/prompts.js +15 -15
  98. package/dist/resources.js +2 -2
  99. package/dist/runtime-command.d.ts +8 -1
  100. package/dist/runtime-command.js +48 -7
  101. package/dist/runtime-executor.js +33 -11
  102. package/dist/runtime-profile-syntax.d.ts +6 -0
  103. package/dist/runtime-profile-syntax.js +121 -0
  104. package/dist/runtime-profile.d.ts +37 -5
  105. package/dist/runtime-profile.js +36 -129
  106. package/dist/runtime-tool-specs.js +1 -1
  107. package/dist/safe-edit.d.ts +7 -3
  108. package/dist/safe-edit.js +40 -47
  109. package/dist/schemas.js +4 -2
  110. package/dist/server.d.ts +2 -16
  111. package/dist/server.js +14 -20
  112. package/dist/shared.d.ts +2 -0
  113. package/dist/shared.js +2 -0
  114. package/dist/skill-auditor.d.ts +19 -0
  115. package/dist/skill-auditor.js +32 -2
  116. package/dist/skill-catalog.d.ts +4 -1
  117. package/dist/skill-catalog.js +6 -5
  118. package/dist/store/ace-packed-store.d.ts +5 -1
  119. package/dist/store/ace-packed-store.js +33 -1
  120. package/dist/store/bootstrap-store.js +19 -12
  121. package/dist/store/cache-workspace.d.ts +8 -0
  122. package/dist/store/cache-workspace.js +94 -27
  123. package/dist/store/importer.js +11 -20
  124. package/dist/store/knowledge-bake.d.ts +3 -1
  125. package/dist/store/knowledge-bake.js +10 -1
  126. package/dist/store/legacy-vericify-posts.d.ts +8 -0
  127. package/dist/store/legacy-vericify-posts.js +76 -0
  128. package/dist/store/materializers/hook-context-materializer.d.ts +2 -1
  129. package/dist/store/materializers/hook-context-materializer.js +6 -2
  130. package/dist/store/materializers/host-file-materializer.js +2 -2
  131. package/dist/store/materializers/projection-manager.d.ts +4 -1
  132. package/dist/store/materializers/projection-manager.js +10 -3
  133. package/dist/store/materializers/todo-syncer.d.ts +39 -20
  134. package/dist/store/materializers/todo-syncer.js +208 -110
  135. package/dist/store/materializers/vericify-projector.js +15 -26
  136. package/dist/store/repositories/goal-repository.d.ts +51 -2
  137. package/dist/store/repositories/goal-repository.js +114 -48
  138. package/dist/store/repositories/playbook-repository.d.ts +41 -3
  139. package/dist/store/repositories/playbook-repository.js +61 -5
  140. package/dist/store/repositories/todo-repository.d.ts +8 -2
  141. package/dist/store/repositories/todo-repository.js +1 -1
  142. package/dist/store/repositories/vericify-repository.d.ts +4 -0
  143. package/dist/store/repositories/vericify-repository.js +46 -0
  144. package/dist/store/skills-install.d.ts +1 -0
  145. package/dist/store/skills-install.js +20 -16
  146. package/dist/store/source-preservation.d.ts +24 -0
  147. package/dist/store/source-preservation.js +79 -0
  148. package/dist/store/store-artifacts.js +8 -1
  149. package/dist/store/test-write-boundary.d.ts +16 -0
  150. package/dist/store/test-write-boundary.js +175 -0
  151. package/dist/store/types.d.ts +5 -1
  152. package/dist/store/write-coordinator.js +16 -0
  153. package/dist/task-plan-context.d.ts +11 -0
  154. package/dist/task-plan-context.js +59 -0
  155. package/dist/todo-state.d.ts +19 -9
  156. package/dist/todo-state.js +204 -321
  157. package/dist/tools-agent.d.ts +1 -1
  158. package/dist/tools-agent.js +38 -62
  159. package/dist/tools-files.d.ts +0 -3
  160. package/dist/tools-files.js +21 -11
  161. package/dist/tools-framework.js +16 -25
  162. package/dist/tools-goal.d.ts +2 -4
  163. package/dist/tools-goal.js +119 -16
  164. package/dist/tools-handoff.d.ts +3 -2
  165. package/dist/tools-handoff.js +53 -23
  166. package/dist/tools-lifecycle.js +1 -1
  167. package/dist/tools-memory.d.ts +0 -3
  168. package/dist/tools-memory.js +51 -6
  169. package/dist/tools-scheduler.js +20 -17
  170. package/dist/tools-todo.js +160 -148
  171. package/dist/vericify-bridge.d.ts +8 -0
  172. package/dist/vericify-bridge.js +18 -0
  173. package/dist/vericify-context.d.ts +4 -0
  174. package/dist/vericify-context.js +26 -5
  175. package/dist/vericify-native.d.ts +101 -0
  176. package/dist/vericify-native.js +221 -0
  177. package/dist/verification-worker.d.ts +2 -0
  178. package/dist/verification-worker.js +31 -0
  179. package/docs/mcp-tool-reference.md +50 -10
  180. package/docs/troubleshooting.md +29 -0
  181. package/package.json +19 -6
  182. package/scripts/dev/run-tests-isolated.mjs +165 -0
  183. package/scripts/dev/run_hermes_crispr_live.mjs +1 -0
  184. package/scripts/dev/test-isolation-guard.mjs +20 -0
  185. package/scripts/dev/test-preload.mjs +153 -0
  186. package/scripts/dev/test-source-inventory.mjs +31 -0
  187. package/docs/development.md +0 -46
@@ -0,0 +1,79 @@
1
+ import { createHash } from "node:crypto";
2
+ import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, realpathSync } from "node:fs";
3
+ import { dirname, isAbsolute, relative } from "node:path";
4
+ import { readStoreJsonByPathSync } from "./store-snapshot.js";
5
+ export function sourceFromBytes(sourcePath, bytes) {
6
+ return {
7
+ source_path: sourcePath.replaceAll("\\", "/"),
8
+ sha256: createHash("sha256").update(bytes).digest("hex"),
9
+ byte_length: bytes.length,
10
+ content_base64: bytes.toString("base64"),
11
+ };
12
+ }
13
+ function identity(stat) {
14
+ const { dev, ino, size, mtimeMs, ctimeMs } = stat;
15
+ return { dev, ino, size, mtimeMs, ctimeMs };
16
+ }
17
+ function sameIdentity(a, b) {
18
+ return Object.entries(a).every(([key, value]) => b[key] === value);
19
+ }
20
+ /** Capture exact bytes without following a replaced final symlink or accepting an unstable read. */
21
+ export function captureWorkspaceSource(root, path) {
22
+ const parent = relative(realpathSync(root), realpathSync(dirname(path)));
23
+ if (parent.startsWith("..") || isAbsolute(parent))
24
+ throw new Error(`Source outside workspace: ${path}`);
25
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
26
+ try {
27
+ const before = fstatSync(fd);
28
+ if (!before.isFile())
29
+ throw new Error(`Source is not a regular file: ${path}`);
30
+ const bytes = readFileSync(fd);
31
+ const after = fstatSync(fd);
32
+ const current = lstatSync(path);
33
+ if (current.isSymbolicLink() || !sameIdentity(identity(before), identity(after)) || !sameIdentity(identity(after), identity(current))) {
34
+ throw new Error(`Source changed during read: ${path}`);
35
+ }
36
+ return { ...sourceFromBytes(relative(root, path), bytes), identity: identity(after) };
37
+ }
38
+ finally {
39
+ closeSync(fd);
40
+ }
41
+ }
42
+ export function sourceUnchanged(root, path, source) {
43
+ try {
44
+ const current = captureWorkspaceSource(root, path);
45
+ return current.sha256 === source.sha256 && !!source.identity && !!current.identity && sameIdentity(source.identity, current.identity);
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ }
51
+ export function preservedSourceKey(source) {
52
+ const pathHash = createHash("sha256").update(source.source_path).digest("hex");
53
+ return `state/migrations/sources/${pathHash}/${source.sha256}`;
54
+ }
55
+ export async function stagePreservedSource(store, source) {
56
+ const key = preservedSourceKey(source);
57
+ const existing = await store.getJSON(key);
58
+ if (existing) {
59
+ if (existing.source_path !== source.source_path || existing.content_base64 !== source.content_base64 || existing.sha256 !== source.sha256) {
60
+ throw new Error(`Preserved-source collision: ${source.source_path}`);
61
+ }
62
+ }
63
+ else {
64
+ await store.setJSON(key, source);
65
+ }
66
+ return key;
67
+ }
68
+ /** Uses the committed-file reader, never the writer's pending values. */
69
+ export function verifyPreservedSource(storePath, source) {
70
+ const retained = readStoreJsonByPathSync(storePath, preservedSourceKey(source));
71
+ if (!retained || retained.source_path !== source.source_path || retained.byte_length !== source.byte_length || retained.sha256 !== source.sha256 || retained.content_base64 !== source.content_base64) {
72
+ throw new Error(`Exact-source readback failed: ${source.source_path}`);
73
+ }
74
+ const bytes = Buffer.from(retained.content_base64, "base64");
75
+ if (bytes.length !== source.byte_length || sourceFromBytes(source.source_path, bytes).sha256 !== source.sha256) {
76
+ throw new Error(`Exact-source digest mismatch: ${source.source_path}`);
77
+ }
78
+ }
79
+ //# sourceMappingURL=source-preservation.js.map
@@ -5,6 +5,7 @@ import { getWorkspaceStorePath, invalidateStoreSnapshotCache, readStoreBlobSync,
5
5
  import { withStoreWriteCoordinator } from "./write-coordinator.js";
6
6
  import { HEADER_SIZE, HEADER_SIZE_V1, STORE_VERSION, MAGIC } from "./types.js";
7
7
  import { crc32 } from "./crc32.js";
8
+ import { assertTestWriteAllowed } from "./test-write-boundary.js";
8
9
  // ── v2 header constants (mirrors ace-packed-store.ts) ─────────────────────────
9
10
  const H_KV_IDX_OFF = 16;
10
11
  const H_KV_IDX_LEN = 24;
@@ -146,6 +147,7 @@ function validateStoreHeaderInvariants(hdr, index, fileLength) {
146
147
  }
147
148
  function quarantineCorruptStore(storePath, rawFile) {
148
149
  const quarantinePath = `${storePath}.corrupt-${Date.now()}`;
150
+ assertTestWriteAllowed(quarantinePath);
149
151
  writeFileSync(quarantinePath, rawFile);
150
152
  return quarantinePath;
151
153
  }
@@ -194,6 +196,7 @@ export function operationalArtifactVirtualPath(workspaceRoot, relPath) {
194
196
  }
195
197
  export function writeStoreBlobsSync(workspaceRoot, blobs) {
196
198
  const storePath = getWorkspaceStorePath(workspaceRoot);
199
+ assertTestWriteAllowed(storePath);
197
200
  const { file, kvChunkEnd, evtBytes, evtCount, evtBaseId, index } = readStoreBuffer(storePath);
198
201
  const encoded = blobs.map(({ key, content }) => ({
199
202
  key,
@@ -255,8 +258,12 @@ export async function writeStoreBlobsQueued(workspaceRoot, blobs, options = {})
255
258
  await store.commit();
256
259
  return blobs.map(({ key }) => toVirtualStorePath(storePath, key));
257
260
  }
261
+ catch (error) {
262
+ store.discardPending();
263
+ throw error;
264
+ }
258
265
  finally {
259
- await store.close();
266
+ await store.close({ commit: false });
260
267
  }
261
268
  }, { ...options, operation_label: options.operation_label ?? "writeStoreBlobsQueued" });
262
269
  }
@@ -0,0 +1,16 @@
1
+ export interface TestWriteAnchor {
2
+ path: string;
3
+ dev: string;
4
+ ino: string;
5
+ }
6
+ export declare class TestWriteBoundaryError extends Error {
7
+ readonly code = "ACE_TEST_WRITE_BOUNDARY";
8
+ constructor(message: string);
9
+ }
10
+ export declare function assertTestWriteAllowed(path: string): void;
11
+ /** Pin a store handle to the directory that actually existed when it opened. */
12
+ export declare function captureTestWriteBoundary(path: string): () => void;
13
+ /** Retain fixture ownership across promises and timers created by a mutation. */
14
+ export declare function withTestWriteBoundary<T>(path: string, fn: () => T): T;
15
+ export declare function currentTestWriteAnchors(): readonly TestWriteAnchor[];
16
+ //# sourceMappingURL=test-write-boundary.d.ts.map
@@ -0,0 +1,175 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
3
+ import { basename, dirname, isAbsolute, join, relative } from "node:path";
4
+ export class TestWriteBoundaryError extends Error {
5
+ code = "ACE_TEST_WRITE_BOUNDARY";
6
+ constructor(message) {
7
+ super(`Test write refused: ${message}`);
8
+ this.name = "TestWriteBoundaryError";
9
+ }
10
+ }
11
+ const contexts = new AsyncLocalStorage();
12
+ const boundarySymbol = Symbol.for("ace.test.write-boundary");
13
+ // Node's JS realpath implementation lexically normalizes `..` before symlinks.
14
+ // The native resolver follows the same traversal as an eventual filesystem write.
15
+ const canonicalRealpath = realpathSync.native;
16
+ function testContextPresent() {
17
+ return process.env.ACE_TEST_ISOLATION !== undefined ||
18
+ process.env.ACE_TEST_ALLOWED_ROOTS !== undefined ||
19
+ process.env.ACE_TEST_WORKER_ROOT !== undefined ||
20
+ process.env.NODE_TEST_CONTEXT !== undefined;
21
+ }
22
+ function anchor(path) {
23
+ const canonical = canonicalRealpath(path);
24
+ const info = statSync(canonical, { bigint: true });
25
+ if (!info.isDirectory())
26
+ throw new Error("allocated root must be a directory");
27
+ return { path: canonical, dev: String(info.dev), ino: String(info.ino) };
28
+ }
29
+ function parseAnchors(raw) {
30
+ if (!raw)
31
+ return [];
32
+ const value = JSON.parse(raw);
33
+ if (!Array.isArray(value) || value.some((item) => !item || typeof item !== "object" ||
34
+ typeof item.path !== "string" || !isAbsolute(item.path) || typeof item.dev !== "string" || typeof item.ino !== "string")) {
35
+ throw new Error("invalid inherited fixture identities");
36
+ }
37
+ return value;
38
+ }
39
+ function captureConfiguration() {
40
+ if (!testContextPresent())
41
+ return { active: false, roots: [], inherited: [] };
42
+ try {
43
+ if (process.env.ACE_TEST_ISOLATION !== "1")
44
+ throw new Error("isolated runner marker is missing");
45
+ const paths = JSON.parse(process.env.ACE_TEST_ALLOWED_ROOTS ?? "null");
46
+ if (!Array.isArray(paths) || paths.length === 0 || paths.some((path) => typeof path !== "string" || !isAbsolute(path))) {
47
+ throw new Error("allocated fixture roots are missing or invalid");
48
+ }
49
+ if (!process.env.ACE_TEST_LEASE_FILE || !isAbsolute(process.env.ACE_TEST_LEASE_FILE) || !process.env.ACE_TEST_RUN_ID) {
50
+ throw new Error("fixture lease identity is missing");
51
+ }
52
+ return { active: true, roots: paths.map(anchor), inherited: parseAnchors(process.env.ACE_TEST_WRITE_ANCHORS), lease: process.env.ACE_TEST_LEASE_FILE, run: process.env.ACE_TEST_RUN_ID };
53
+ }
54
+ catch (error) {
55
+ return { active: true, roots: [], inherited: [], error: error instanceof Error ? error.message : String(error) };
56
+ }
57
+ }
58
+ // Capture once before application loading: later environment changes cannot
59
+ // disable the test boundary or turn an outside workspace into an allowed root.
60
+ let configuration = captureConfiguration();
61
+ function validateAnchor(value) {
62
+ try {
63
+ const current = anchor(value.path);
64
+ if (current.path === value.path && current.dev === value.dev && current.ino === value.ino)
65
+ return;
66
+ }
67
+ catch { /* Removed fixtures and symlink substitutions are both refused. */ }
68
+ throw new TestWriteBoundaryError(`allocated fixture was removed or replaced: ${value.path}`);
69
+ }
70
+ /**
71
+ * Resolve existing symlinks before appending a not-yet-created suffix.
72
+ * `parent` is the directory holding the target's entry. An existing target
73
+ * directory (such as a contended lock) is never its own ownership anchor:
74
+ * another owner may legitimately remove it.
75
+ */
76
+ function destination(path) {
77
+ let cursor = isAbsolute(path) ? path : `${process.cwd()}/${path}`;
78
+ const suffix = [];
79
+ while (true) {
80
+ try {
81
+ const info = lstatSync(cursor);
82
+ const canonical = canonicalRealpath(cursor);
83
+ const parent = suffix.length > 0 && info.isDirectory() ? canonical : dirname(canonical);
84
+ return { canonical: join(canonical, ...suffix), parent };
85
+ }
86
+ catch (error) {
87
+ if (error.code !== "ENOENT")
88
+ throw error;
89
+ // A dangling symlink is an existing object, not a safe missing directory.
90
+ try {
91
+ if (lstatSync(cursor).isSymbolicLink())
92
+ throw new TestWriteBoundaryError(`unresolved symlink: ${cursor}`);
93
+ }
94
+ catch (probe) {
95
+ if (probe instanceof TestWriteBoundaryError)
96
+ throw probe;
97
+ }
98
+ const parent = dirname(cursor);
99
+ if (parent === cursor)
100
+ throw error;
101
+ suffix.unshift(basename(cursor));
102
+ cursor = parent;
103
+ }
104
+ }
105
+ }
106
+ function inside(root, path) {
107
+ const subpath = relative(root, path);
108
+ return subpath === "" || (!isAbsolute(subpath) && subpath !== ".." && !subpath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`));
109
+ }
110
+ export function assertTestWriteAllowed(path) {
111
+ // Also fail closed if a module was imported before a test context was set.
112
+ if (!configuration.active && testContextPresent())
113
+ configuration = captureConfiguration();
114
+ if (!configuration.active)
115
+ return;
116
+ if (configuration.error)
117
+ throw new TestWriteBoundaryError(configuration.error);
118
+ if (!globalThis[Symbol.for("ace.test.preload")]) {
119
+ throw new TestWriteBoundaryError("the isolated runner preloader has not established the fixture boundary");
120
+ }
121
+ try {
122
+ const lease = JSON.parse(readFileSync(configuration.lease, "utf8"));
123
+ if (lease.status !== "active" || lease.run_id !== configuration.run)
124
+ throw new Error("inactive lease");
125
+ }
126
+ catch {
127
+ throw new TestWriteBoundaryError("fixture lease is missing, closed, or belongs to another run");
128
+ }
129
+ for (const value of [...configuration.roots, ...configuration.inherited, ...(contexts.getStore() ?? [])])
130
+ validateAnchor(value);
131
+ let target;
132
+ try {
133
+ target = destination(path).canonical;
134
+ }
135
+ catch (error) {
136
+ throw error instanceof TestWriteBoundaryError ? error : new TestWriteBoundaryError(`cannot resolve destination: ${path}`);
137
+ }
138
+ if (!configuration.roots.some((root) => inside(root.path, target))) {
139
+ throw new TestWriteBoundaryError(`destination is outside allocated fixtures: ${path}`);
140
+ }
141
+ }
142
+ /** Pin a store handle to the directory that actually existed when it opened. */
143
+ export function captureTestWriteBoundary(path) {
144
+ assertTestWriteAllowed(path);
145
+ if (!configuration.active)
146
+ return () => { };
147
+ const captured = [...(contexts.getStore() ?? []), anchor(destination(path).parent)];
148
+ return () => {
149
+ assertTestWriteAllowed(path);
150
+ for (const value of captured)
151
+ validateAnchor(value);
152
+ };
153
+ }
154
+ /** Retain fixture ownership across promises and timers created by a mutation. */
155
+ export function withTestWriteBoundary(path, fn) {
156
+ assertTestWriteAllowed(path);
157
+ if (!configuration.active)
158
+ return fn();
159
+ const captured = [...(contexts.getStore() ?? []), anchor(destination(path).parent)];
160
+ return contexts.run(captured, fn);
161
+ }
162
+ export function currentTestWriteAnchors() {
163
+ return [...configuration.inherited, ...(contexts.getStore() ?? [])];
164
+ }
165
+ // The isolated runner propagates these identities into owned subprocesses. This
166
+ // contains paths/inodes only, never store contents or application state.
167
+ if (!globalThis[boundarySymbol]) {
168
+ Object.defineProperty(globalThis, boundarySymbol, { value: Object.freeze({
169
+ serialize: () => JSON.stringify(currentTestWriteAnchors()),
170
+ }), configurable: false, writable: false });
171
+ }
172
+ if (!globalThis[Symbol.for("ace.test.write.anchors")]) {
173
+ Object.defineProperty(globalThis, Symbol.for("ace.test.write.anchors"), { get: currentTestWriteAnchors, configurable: false });
174
+ }
175
+ //# sourceMappingURL=test-write-boundary.js.map
@@ -62,7 +62,10 @@ export interface IAcePackedStore extends AsyncReadable, AsyncWritable {
62
62
  open(path: string): Promise<void>;
63
63
  commit(): Promise<void>;
64
64
  compact(): Promise<void>;
65
- close(): Promise<void>;
65
+ discardPending(): void;
66
+ close(options?: {
67
+ commit?: boolean;
68
+ }): Promise<void>;
66
69
  list(): AsyncGenerator<string>;
67
70
  getJSON<T>(key: string): Promise<T | undefined>;
68
71
  setJSON(key: string, value: unknown): Promise<void>;
@@ -130,6 +133,7 @@ export interface MaterializationChecksum {
130
133
  }
131
134
  export interface HookContextSnapshot {
132
135
  schema_version: 1;
136
+ current_work?: string;
133
137
  generated_at: string;
134
138
  task: {
135
139
  content: string;
@@ -4,6 +4,7 @@ import { hostname } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
5
  import { getWorkspaceStorePath } from "./store-snapshot.js";
6
6
  import { withStoreWriteQueue } from "./write-queue.js";
7
+ import { assertTestWriteAllowed, withTestWriteBoundary } from "./test-write-boundary.js";
7
8
  const DEFAULT_WAIT_MS = 15_000;
8
9
  const DEFAULT_POLL_INTERVAL_MS = 100;
9
10
  const DEFAULT_LEASE_TTL_MS = 30_000;
@@ -203,20 +204,27 @@ function createLeaseRecord(storePath, lockPath, options, waitedMs) {
203
204
  };
204
205
  }
205
206
  function tryAcquireLeaseSync(storePath, lockPath, options, waitedMs) {
207
+ assertTestWriteAllowed(storePath);
208
+ assertTestWriteAllowed(lockPath);
206
209
  mkdirSync(dirname(lockPath), { recursive: true });
207
210
  mkdirSync(lockPath, { recursive: false });
208
211
  const lease = createLeaseRecord(storePath, lockPath, options, waitedMs);
212
+ assertTestWriteAllowed(lease.metadataPath);
209
213
  writeFileSync(lease.metadataPath, JSON.stringify({ kind: "store_write_lease", ...lease }, null, 2), "utf-8");
210
214
  return lease;
211
215
  }
212
216
  async function tryAcquireLeaseAsync(storePath, lockPath, options, waitedMs) {
217
+ assertTestWriteAllowed(storePath);
218
+ assertTestWriteAllowed(lockPath);
213
219
  mkdirSync(dirname(lockPath), { recursive: true });
214
220
  mkdirSync(lockPath, { recursive: false });
215
221
  const lease = createLeaseRecord(storePath, lockPath, options, waitedMs);
222
+ assertTestWriteAllowed(lease.metadataPath);
216
223
  writeFileSync(lease.metadataPath, JSON.stringify({ kind: "store_write_lease", ...lease }, null, 2), "utf-8");
217
224
  return lease;
218
225
  }
219
226
  function releaseLease(lease) {
227
+ assertTestWriteAllowed(lease.lockPath);
220
228
  const current = readLeaseRecord(lease.lockPath);
221
229
  if (!current) {
222
230
  rmSync(lease.lockPath, { recursive: true, force: true });
@@ -245,6 +253,7 @@ async function acquireLeaseAsync(storePath, options) {
245
253
  throw error;
246
254
  const current = readLeaseRecord(lockPath);
247
255
  if (current && canRecoverStaleLease(current, staleGraceMs)) {
256
+ assertTestWriteAllowed(lockPath);
248
257
  rmSync(lockPath, { recursive: true, force: true });
249
258
  continue;
250
259
  }
@@ -285,6 +294,7 @@ function acquireLeaseSync(storePath, options) {
285
294
  throw error;
286
295
  const current = readLeaseRecord(lockPath);
287
296
  if (current && canRecoverStaleLease(current, staleGraceMs)) {
297
+ assertTestWriteAllowed(lockPath);
288
298
  rmSync(lockPath, { recursive: true, force: true });
289
299
  continue;
290
300
  }
@@ -308,6 +318,9 @@ function acquireLeaseSync(storePath, options) {
308
318
  });
309
319
  }
310
320
  export async function withStoreWriteCoordinator(storePath, fn, options = {}) {
321
+ return withTestWriteBoundary(storePath, () => withStoreWriteCoordinatorInternal(storePath, fn, options));
322
+ }
323
+ async function withStoreWriteCoordinatorInternal(storePath, fn, options) {
311
324
  const operationLabel = getOperationLabel(options);
312
325
  registerAsyncStoreOwnership(storePath, operationLabel);
313
326
  emitLifecycleEvent(options, {
@@ -367,6 +380,9 @@ export async function withStoreWriteCoordinator(storePath, fn, options = {}) {
367
380
  }
368
381
  }
369
382
  export function withStoreWriteCoordinatorSync(storePath, fn, options = {}) {
383
+ return withTestWriteBoundary(storePath, () => withStoreWriteCoordinatorSyncInternal(storePath, fn, options));
384
+ }
385
+ function withStoreWriteCoordinatorSyncInternal(storePath, fn, options) {
370
386
  const operationLabel = getOperationLabel(options);
371
387
  const asyncOwnership = getAsyncStoreOwnership(storePath);
372
388
  if (asyncOwnership && asyncOwnership.count > 0) {
@@ -0,0 +1,11 @@
1
+ import type { TaskPlan, TaskStep } from "./orchestrator-supervisor.js";
2
+ /** Preserve complete Unicode code points while respecting the delivered byte budget. */
3
+ export declare function utf8Prefix(text: string, maxBytes: number): string;
4
+ export declare function getReadyTaskSteps(plan: TaskPlan): TaskStep[];
5
+ export declare function formatStepTaskForBridge(step: Pick<TaskStep, "task" | "upstream_outputs">): string;
6
+ export declare function collectUpstreamOutputs(plan: TaskPlan, step: TaskStep): {
7
+ step_id: string;
8
+ result_summary: string;
9
+ evidence_refs: string[];
10
+ }[];
11
+ //# sourceMappingURL=task-plan-context.d.ts.map
@@ -0,0 +1,59 @@
1
+ /** Preserve complete Unicode code points while respecting the delivered byte budget. */
2
+ export function utf8Prefix(text, maxBytes) {
3
+ let bytes = 0, result = '';
4
+ for (const char of text) {
5
+ const size = Buffer.byteLength(char);
6
+ if (bytes + size > maxBytes)
7
+ break;
8
+ result += char;
9
+ bytes += size;
10
+ }
11
+ return result;
12
+ }
13
+ // Shared pure plan readers: importing host context must not initialize an executor.
14
+ export function getReadyTaskSteps(plan) {
15
+ const completed = new Set(plan.steps
16
+ .filter((step) => step.status === "done" || step.status === "skipped")
17
+ .map((step) => step.step_id));
18
+ return plan.steps.filter((step) => {
19
+ if (step.status !== "planned")
20
+ return false;
21
+ const dependencies = step.depends_on ?? [];
22
+ return dependencies.every((dependency) => completed.has(dependency));
23
+ });
24
+ }
25
+ export function formatStepTaskForBridge(step) {
26
+ const upstreamOutputs = step.upstream_outputs ?? [];
27
+ if (upstreamOutputs.length === 0) {
28
+ return step.task;
29
+ }
30
+ const renderedOutputs = upstreamOutputs
31
+ .map((output) => `- ${output.step_id}: ${output.result_summary || "[no summary]"}${output.evidence_refs.length > 0 ? ` (evidence: ${output.evidence_refs.join(", ")})` : ""}`)
32
+ .join("\n");
33
+ return [
34
+ step.task,
35
+ "",
36
+ "Upstream outputs:",
37
+ renderedOutputs,
38
+ ].join("\n");
39
+ }
40
+ export function collectUpstreamOutputs(plan, step) {
41
+ const dependencyOutputs = (step.depends_on ?? [])
42
+ .map((depId) => plan.steps.find((s) => s.step_id === depId))
43
+ .filter((dep) => dep?.status === "done")
44
+ .map((dep) => ({
45
+ step_id: dep.step_id,
46
+ result_summary: dep.result_summary ?? "",
47
+ evidence_refs: dep.evidence_refs ?? [],
48
+ }));
49
+ const carriedOutputs = (step.upstream_outputs ?? []).filter((output) => Boolean(output) &&
50
+ typeof output.step_id === "string" &&
51
+ typeof output.result_summary === "string" &&
52
+ Array.isArray(output.evidence_refs));
53
+ const merged = new Map();
54
+ for (const output of [...carriedOutputs, ...dependencyOutputs]) {
55
+ merged.set(output.step_id, output);
56
+ }
57
+ return [...merged.values()];
58
+ }
59
+ //# sourceMappingURL=task-plan-context.js.map
@@ -1,4 +1,6 @@
1
- export type TodoNodeStatus = "backlog" | "in_progress" | "blocked" | "gated" | "done";
1
+ import type { TodoNode as StoredTodo } from "./store/repositories/todo-repository.js";
2
+ import type { TaskStep } from './orchestrator-supervisor.js';
3
+ export type TodoNodeStatus = "backlog" | "in_progress" | "blocked" | "gated" | "done" | "cancelled";
2
4
  export interface TodoNode {
3
5
  id: string;
4
6
  title: string;
@@ -21,6 +23,8 @@ export interface TodoAppendResult {
21
23
  node_id?: string;
22
24
  section?: string;
23
25
  error?: string;
26
+ import_warnings?: string[];
27
+ projection_pending?: boolean;
24
28
  }
25
29
  export interface TodoTransitionResult {
26
30
  ok: boolean;
@@ -32,6 +36,8 @@ export interface TodoTransitionResult {
32
36
  unresolved_dependencies?: string[];
33
37
  allowed?: TodoNodeStatus[];
34
38
  error?: string;
39
+ import_warnings?: string[];
40
+ projection_pending?: boolean;
35
41
  }
36
42
  export declare function waitForTodoStoreMirror(): Promise<void>;
37
43
  export declare function getTodoStatePath(): string;
@@ -39,24 +45,28 @@ export declare function syncTodoState(todoContent?: string): {
39
45
  state: TodoStateFile;
40
46
  path: string;
41
47
  };
48
+ /** Repair a linked projection after its authoritative plan has been saved. */
49
+ export declare function syncLinkedTodoStepSafe(id: string, ref: NonNullable<StoredTodo['step_ref']>, step: TaskStep): Promise<{
50
+ ok: true;
51
+ } & {
52
+ import_warnings: string[];
53
+ projection_pending: boolean;
54
+ }>;
42
55
  export declare function syncTodoStateSafe(todoContent?: string): Promise<{
43
56
  state: TodoStateFile;
57
+ markdown: string;
44
58
  path: string;
59
+ import_warnings: string[];
60
+ projection_pending?: boolean;
45
61
  }>;
46
62
  export declare function readTodoState(): TodoStateFile;
47
63
  export declare function transitionTodoNode(nodeId: string, targetStatus: TodoNodeStatus): TodoTransitionResult;
48
64
  export declare function transitionTodoNodeSafe(nodeId: string, targetStatus: TodoNodeStatus): Promise<TodoTransitionResult>;
49
- /**
50
- * Atomically append a new TODO item under a section, without requiring the
51
- * caller to read the whole board and resubmit it via update_todo(new_content).
52
- * That read-modify-write dance is what let concurrent agents silently clobber
53
- * each other's appended items: the read and write are separate MCP calls, so
54
- * a lock around the write alone cannot close the race. Here the read, insert,
55
- * and write all happen inside one TODO_MARKDOWN_REL lock acquisition.
56
- */
65
+ /** Insert once under the cross-process coordinator; never reconstruct existing records. */
57
66
  export declare function appendTodoItemSafe(title: string, options?: {
58
67
  section?: string;
59
68
  depends_on?: string[];
60
69
  id?: string;
61
70
  }): Promise<TodoAppendResult>;
71
+ export declare function deleteTodoItemSafe(nodeId: string): Promise<TodoAppendResult>;
62
72
  //# sourceMappingURL=todo-state.d.ts.map