@voybio/ace-swarm 2.4.9 → 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 (185) 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 +48 -14
  58. package/dist/job-scheduler.js +169 -23
  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 +108 -6
  89. package/dist/orchestrator-supervisor.js +4 -32
  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.js +28 -4
  165. package/dist/tools-lifecycle.js +1 -1
  166. package/dist/tools-memory.d.ts +0 -3
  167. package/dist/tools-memory.js +51 -6
  168. package/dist/tools-scheduler.js +19 -17
  169. package/dist/tools-todo.js +160 -148
  170. package/dist/vericify-bridge.d.ts +8 -0
  171. package/dist/vericify-bridge.js +18 -0
  172. package/dist/vericify-context.d.ts +4 -0
  173. package/dist/vericify-context.js +26 -5
  174. package/dist/vericify-native.d.ts +101 -0
  175. package/dist/vericify-native.js +221 -0
  176. package/dist/verification-worker.d.ts +2 -0
  177. package/dist/verification-worker.js +31 -0
  178. package/docs/mcp-tool-reference.md +44 -9
  179. package/package.json +19 -6
  180. package/scripts/dev/run-tests-isolated.mjs +165 -0
  181. package/scripts/dev/run_hermes_crispr_live.mjs +1 -0
  182. package/scripts/dev/test-isolation-guard.mjs +20 -0
  183. package/scripts/dev/test-preload.mjs +153 -0
  184. package/scripts/dev/test-source-inventory.mjs +31 -0
  185. package/docs/development.md +0 -46
package/dist/safe-edit.js CHANGED
@@ -3,10 +3,11 @@
3
3
  * Prevents bricking by never leaving core files in a broken state.
4
4
  */
5
5
  import { copyFileSync, cpSync, existsSync, mkdtempSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync, } from "node:fs";
6
- import { createHash } from "node:crypto";
6
+ import { createHash, randomUUID } from "node:crypto";
7
7
  import { basename, dirname, relative, resolve } from "node:path";
8
8
  import { spawnSync } from "node:child_process";
9
9
  import { tmpdir } from "node:os";
10
+ import { runProgressCommand } from "./progress-command.js";
10
11
  import { WORKSPACE_ROOT, safeRead, wsPath } from "./helpers.js";
11
12
  import { isInside, isReadError, normalizeRelPath } from "./shared.js";
12
13
  import { appendSourceEditEvent, SourceEditLeaseBusyError, withSourceMutationCoordinatorSync, } from "./source-mutation-coordinator.js";
@@ -176,38 +177,31 @@ export function gcStaging(input = {}) {
176
177
  errors,
177
178
  };
178
179
  }
179
- function runValidation(validationCmd, testCmd, cwd = WORKSPACE_ROOT) {
180
+ async function runValidation(validationCmd, testCmd, cwd = WORKSPACE_ROOT, policy = {}) {
180
181
  let validation_passed;
181
182
  let validation_output;
182
183
  let test_passed;
183
184
  let test_output;
185
+ let reason_code;
184
186
  if (validationCmd) {
185
- const res = spawnSync("sh", ["-c", validationCmd], {
186
- cwd,
187
- encoding: "utf-8",
188
- timeout: 60_000,
189
- maxBuffer: 4 * 1024 * 1024,
190
- });
191
- validation_passed = res.status === 0;
192
- validation_output = ((res.stdout ?? "") + (res.stderr ?? "")).slice(0, 4000);
187
+ const res = await runProgressCommand(validationCmd, cwd, policy);
188
+ validation_passed = res.ok;
189
+ validation_output = res.output.slice(-4000);
190
+ reason_code = res.reason_code;
193
191
  }
194
192
  if (testCmd && (validation_passed === undefined || validation_passed)) {
195
- const res = spawnSync("sh", ["-c", testCmd], {
196
- cwd,
197
- encoding: "utf-8",
198
- timeout: 120_000,
199
- maxBuffer: 4 * 1024 * 1024,
200
- });
201
- test_passed = res.status === 0;
202
- test_output = ((res.stdout ?? "") + (res.stderr ?? "")).slice(0, 4000);
193
+ const res = await runProgressCommand(testCmd, cwd, policy);
194
+ test_passed = res.ok;
195
+ test_output = res.output.slice(-4000);
196
+ reason_code = res.reason_code;
203
197
  }
204
198
  const passed = (validation_passed ?? true) && (test_passed ?? true);
205
- return { passed, validation_passed, validation_output, test_passed, test_output };
199
+ return { passed, validation_passed, validation_output, test_passed, test_output, reason_code };
206
200
  }
207
201
  // ────────────────────────────────────────────────────────────────────
208
202
  // Safe edit (copy → modify → validate → swap)
209
203
  // ────────────────────────────────────────────────────────────────────
210
- export function safeEditFile(input) {
204
+ export async function safeEditFile(input) {
211
205
  const target = resolveWorkspaceTarget(input.path);
212
206
  if ("error" in target) {
213
207
  return {
@@ -227,7 +221,7 @@ export function safeEditFile(input) {
227
221
  const originalHash = isNew ? "none" : fileHash(originalContent);
228
222
  const stagedNewHash = fileHash(input.content);
229
223
  // Step 1: Create staging directory
230
- const stagingDir = wsPath(STAGING_DIR, `${timestamp}-${isNew ? "new" : originalHash.slice(0, 8)}`);
224
+ const stagingDir = wsPath(STAGING_DIR, `${timestamp}-${isNew ? "new" : originalHash.slice(0, 8)}-${randomUUID()}`);
231
225
  ensureDir(stagingDir);
232
226
  const needsValidationWorkspace = Boolean(input.validation_command || input.test_command);
233
227
  const flatName = relPath.replace(/\//g, "__");
@@ -260,7 +254,7 @@ export function safeEditFile(input) {
260
254
  restored: true,
261
255
  };
262
256
  }
263
- check = runValidation(input.validation_command, input.test_command, stagedWorkspace);
257
+ check = await runValidation(input.validation_command, input.test_command, stagedWorkspace, input.command_policy);
264
258
  if (!check.passed) {
265
259
  return {
266
260
  ok: false,
@@ -273,7 +267,8 @@ export function safeEditFile(input) {
273
267
  validation_output: check.validation_output,
274
268
  test_passed: check.test_passed,
275
269
  test_output: check.test_output,
276
- error: "Validation/test failed — staged change not promoted",
270
+ error: `Validation/test failed — staged change not promoted${check.reason_code ? ` (${check.reason_code})` : ""}`,
271
+ reason_code: check.reason_code,
277
272
  restored: true,
278
273
  };
279
274
  }
@@ -281,17 +276,18 @@ export function safeEditFile(input) {
281
276
  try {
282
277
  return withSourceMutationCoordinatorSync(relPath, (lease) => {
283
278
  // Step 4: Promote staged content to the real target only after checks pass.
284
- if (input.expected_base_hash) {
279
+ {
280
+ const expectedBaseHash = input.expected_base_hash ?? (isNew ? "none" : fullFileHash(originalContent));
285
281
  const currentContent = safeRead(input.path);
286
282
  const observedHash = isReadError(currentContent) ? "none" : fullFileHash(currentContent);
287
- if (isReadError(currentContent) || !hashMatchesExpected(input.expected_base_hash, currentContent)) {
283
+ if (observedHash !== expectedBaseHash && (isReadError(currentContent) || !hashMatchesExpected(expectedBaseHash, currentContent))) {
288
284
  appendSourceEditEvent({
289
285
  event: "SOURCE_EDIT_CONFLICT",
290
286
  target_path: relPath,
291
287
  lock_path: lease.lockPath,
292
288
  waited_ms: lease.waitedMs,
293
289
  base_hash: originalHash,
294
- expected_hash: input.expected_base_hash,
290
+ expected_hash: expectedBaseHash,
295
291
  observed_hash: observedHash,
296
292
  reason_code: "source_edit_conflict",
297
293
  });
@@ -301,7 +297,7 @@ export function safeEditFile(input) {
301
297
  backupPath,
302
298
  originalHash,
303
299
  newHash: stagedNewHash,
304
- expectedHash: input.expected_base_hash,
300
+ expectedHash: expectedBaseHash,
305
301
  observedHash,
306
302
  validationCheck: check,
307
303
  leaseLockPath: lease.lockPath,
@@ -384,21 +380,16 @@ function detectTestCommand() {
384
380
  }
385
381
  return "echo 'No test command detected' && exit 1";
386
382
  }
387
- export function runTestSuite(command) {
383
+ export async function runTestSuite(command, options = {}) {
388
384
  const cmd = command ?? detectTestCommand();
389
- const start = Date.now();
390
- const res = spawnSync("sh", ["-c", cmd], {
391
- cwd: WORKSPACE_ROOT,
392
- encoding: "utf-8",
393
- timeout: 300_000,
394
- maxBuffer: 8 * 1024 * 1024,
395
- });
385
+ const res = await runProgressCommand(cmd, WORKSPACE_ROOT, options);
396
386
  return {
397
- ok: res.status === 0,
398
- exit_code: res.status ?? -1,
399
- output: ((res.stdout ?? "") + (res.stderr ?? "")).slice(0, 8000),
387
+ ok: res.ok,
388
+ exit_code: res.exit_code ?? -1,
389
+ output: res.output.slice(-8000),
400
390
  command: cmd,
401
- duration_ms: Date.now() - start,
391
+ duration_ms: res.duration_ms,
392
+ reason_code: res.reason_code,
402
393
  };
403
394
  }
404
395
  // ────────────────────────────────────────────────────────────────────
@@ -507,7 +498,7 @@ export function diffFiles(pathA, pathB) {
507
498
  }
508
499
  return diffContents(contentA, contentB);
509
500
  }
510
- export function applyPatch(input) {
501
+ export async function applyPatch(input) {
511
502
  const target = resolveWorkspaceTarget(input.path);
512
503
  if ("error" in target) {
513
504
  return {
@@ -525,7 +516,7 @@ export function applyPatch(input) {
525
516
  const isNew = isReadError(originalContent);
526
517
  const originalHash = isNew ? "none" : fileHash(originalContent);
527
518
  const timestamp = Date.now();
528
- const stagingDir = wsPath(STAGING_DIR, `${timestamp}-patch-${isNew ? "new" : originalHash.slice(0, 8)}`);
519
+ const stagingDir = wsPath(STAGING_DIR, `${timestamp}-patch-${isNew ? "new" : originalHash.slice(0, 8)}-${randomUUID()}`);
529
520
  ensureDir(stagingDir);
530
521
  const needsValidationWorkspace = Boolean(input.validation_command || input.test_command);
531
522
  const stagedWorkspace = needsValidationWorkspace ? prepareStagedWorkspace(stagingDir) : undefined;
@@ -559,7 +550,7 @@ export function applyPatch(input) {
559
550
  }
560
551
  const patchedContent = readFileSync(stagedTarget, "utf-8");
561
552
  const newHash = fileHash(patchedContent);
562
- const check = runValidation(input.validation_command, input.test_command, stagedWorkspace ?? stagingDir);
553
+ const check = await runValidation(input.validation_command, input.test_command, stagedWorkspace ?? stagingDir, input.command_policy);
563
554
  if (!check.passed) {
564
555
  return {
565
556
  ok: false,
@@ -571,24 +562,26 @@ export function applyPatch(input) {
571
562
  validation_output: check.validation_output,
572
563
  test_passed: check.test_passed,
573
564
  test_output: check.test_output,
574
- error: "Validation/test failed — staged patch not promoted",
565
+ error: `Validation/test failed — staged patch not promoted${check.reason_code ? ` (${check.reason_code})` : ""}`,
566
+ reason_code: check.reason_code,
575
567
  restored: true,
576
568
  };
577
569
  }
578
570
  try {
579
571
  return withSourceMutationCoordinatorSync(relPath, (lease) => {
580
572
  let backupPath;
581
- if (input.expected_base_hash) {
573
+ {
574
+ const expectedBaseHash = input.expected_base_hash ?? (isNew ? "none" : fullFileHash(originalContent));
582
575
  const currentContent = safeRead(input.path);
583
576
  const observedHash = isReadError(currentContent) ? "none" : fullFileHash(currentContent);
584
- if (isReadError(currentContent) || !hashMatchesExpected(input.expected_base_hash, currentContent)) {
577
+ if (observedHash !== expectedBaseHash && (isReadError(currentContent) || !hashMatchesExpected(expectedBaseHash, currentContent))) {
585
578
  appendSourceEditEvent({
586
579
  event: "SOURCE_EDIT_CONFLICT",
587
580
  target_path: relPath,
588
581
  lock_path: lease.lockPath,
589
582
  waited_ms: lease.waitedMs,
590
583
  base_hash: originalHash,
591
- expected_hash: input.expected_base_hash,
584
+ expected_hash: expectedBaseHash,
592
585
  observed_hash: observedHash,
593
586
  reason_code: "source_edit_conflict",
594
587
  });
@@ -597,7 +590,7 @@ export function applyPatch(input) {
597
590
  stagingDir,
598
591
  originalHash,
599
592
  newHash,
600
- expectedHash: input.expected_base_hash,
593
+ expectedHash: expectedBaseHash,
601
594
  observedHash,
602
595
  validationCheck: check,
603
596
  leaseLockPath: lease.lockPath,
package/dist/schemas.js CHANGED
@@ -19,6 +19,8 @@ const REQUIRED_PROVENANCE_FIELDS = [
19
19
  ];
20
20
  const swarmHandoffSchema = z
21
21
  .object({
22
+ step_ref: z.object({ goal_id: z.string().min(1), step_id: z.string().min(1) }).strict().optional(),
23
+ recipient_agent: z.string().min(1).optional(),
22
24
  handoff_id: NON_EMPTY,
23
25
  timestamp_utc: z.string().datetime({ offset: true }),
24
26
  router: z
@@ -405,8 +407,8 @@ const runtimeExecutorSessionRecordSchema = z
405
407
  objective_id: z.string().optional(),
406
408
  tracker_item_id: z.string().optional(),
407
409
  command: NON_EMPTY,
408
- max_turns: z.number().int().positive(),
409
- turn_timeout_ms: z.number().int().positive(),
410
+ max_turns: z.number().int().nonnegative(),
411
+ turn_timeout_ms: z.number().int().nonnegative(),
410
412
  turn_count: z.number().int().min(0),
411
413
  created_at: z.string().datetime({ offset: true }),
412
414
  updated_at: z.string().datetime({ offset: true }),
package/dist/server.d.ts CHANGED
@@ -9,22 +9,8 @@ export interface CreateAceServerOptions {
9
9
  }
10
10
  export declare function createAceServer(options?: CreateAceServerOptions): McpServer;
11
11
  export type AceHostProfile = "host-min" | "full";
12
- /**
13
- * Curated premium-host surface (report WS-G). ~15–25 high-value tools instead of the
14
- * full tool surface, cutting the per-session schema tax. `run_orchestrator` only reaches an
15
- * omitted tool if the model-bridge child it spawns happens to have that tool in its own MCP
16
- * session — it is a role/plan dispatcher, not a generic proxy for any omitted tool by name.
17
- * The reliable way to reach anything below is to start with `--profile full`.
18
- *
19
- * Whole domains are intentionally absent, not just individual tools: goal-engine
20
- * (start_goal/get_goal_status/resume_goal/stop_goal — until WS-F ships), git
21
- * (git_status/git_diff/git_log/git_commit), scheduler (enqueue_job/dispatch_jobs/
22
- * heartbeat_job/reap_stale_jobs/get_lock_table/…), drift/change-intelligence
23
- * (snapshot_state/semantic_diff/drift_report/rewrite_targets/list_snapshots), discovery
24
- * (scan_workspace_delta/web_research_packet/codemunch_snapshot/codemunch_index/
25
- * mcp_ingestion_probe/refresh_astgrep_index), skills (skill_catalog_summary/skill_audit/
26
- * problem_triage), and todo (get_todo/update_todo/get_run_ledger) all have zero
27
- * representation below.
12
+ /** Compact host surface, including work state and optional execution aids.
13
+ * Optional git, scheduler, discovery and skill-administration tools remain in full.
28
14
  */
29
15
  export declare const HOST_MIN_TOOL_PROFILE: readonly string[];
30
16
  export declare function resolveHostProfileAllowlist(profile: AceHostProfile): readonly string[] | undefined;
package/dist/server.js CHANGED
@@ -92,35 +92,30 @@ async function verifyMcpStoreStartup(workspaceRoot, logStartup) {
92
92
  console.error(message);
93
93
  return false;
94
94
  }
95
- /**
96
- * Curated premium-host surface (report WS-G). ~15–25 high-value tools instead of the
97
- * full tool surface, cutting the per-session schema tax. `run_orchestrator` only reaches an
98
- * omitted tool if the model-bridge child it spawns happens to have that tool in its own MCP
99
- * session — it is a role/plan dispatcher, not a generic proxy for any omitted tool by name.
100
- * The reliable way to reach anything below is to start with `--profile full`.
101
- *
102
- * Whole domains are intentionally absent, not just individual tools: goal-engine
103
- * (start_goal/get_goal_status/resume_goal/stop_goal — until WS-F ships), git
104
- * (git_status/git_diff/git_log/git_commit), scheduler (enqueue_job/dispatch_jobs/
105
- * heartbeat_job/reap_stale_jobs/get_lock_table/…), drift/change-intelligence
106
- * (snapshot_state/semantic_diff/drift_report/rewrite_targets/list_snapshots), discovery
107
- * (scan_workspace_delta/web_research_packet/codemunch_snapshot/codemunch_index/
108
- * mcp_ingestion_probe/refresh_astgrep_index), skills (skill_catalog_summary/skill_audit/
109
- * problem_triage), and todo (get_todo/update_todo/get_run_ledger) all have zero
110
- * representation below.
95
+ /** Compact host surface, including work state and optional execution aids.
96
+ * Optional git, scheduler, discovery and skill-administration tools remain in full.
111
97
  */
112
98
  export const HOST_MIN_TOOL_PROFILE = [
99
+ "get_todo",
100
+ "get_todo_state",
101
+ "update_todo",
102
+ "read_workspace_file",
103
+ "list_workspace",
104
+ "start_goal",
105
+ "next_step",
106
+ "complete_step",
107
+ "resume_goal",
108
+ "stop_goal",
109
+ "get_goal_status",
113
110
  "recall_context",
111
+ "get_skill_instructions",
114
112
  "route_task",
115
- "run_orchestrator",
116
113
  "propose_plan",
117
114
  "validate_plan",
118
115
  "create_handoff",
119
116
  "ack_handoff",
120
117
  "get_handoff_status",
121
118
  "execute_gates",
122
- "emit_status_event",
123
- "append_vericify_process_post",
124
119
  "get_vericify_context",
125
120
  "get_vericify_delta",
126
121
  "astgrep_locate",
@@ -129,7 +124,6 @@ export const HOST_MIN_TOOL_PROFILE = [
129
124
  "astgrep_rewrite",
130
125
  "safe_edit_file",
131
126
  "run_tests",
132
- "run_local_model",
133
127
  "context_snapshot",
134
128
  "build_continuity_packet",
135
129
  ];
package/dist/shared.d.ts CHANGED
@@ -60,6 +60,7 @@ export declare const TODO_STATUS_ENUM: z.ZodEnum<{
60
60
  blocked: "blocked";
61
61
  gated: "gated";
62
62
  done: "done";
63
+ cancelled: "cancelled";
63
64
  }>;
64
65
  export declare const HANDOFF_LIFECYCLE_STATUS_ENUM: z.ZodEnum<{
65
66
  blocked: "blocked";
@@ -101,6 +102,7 @@ export declare const EVENT_TYPE: {
101
102
  readonly RUNTIME_EXECUTOR_SESSION_FAILED: "RUNTIME_EXECUTOR_SESSION_FAILED";
102
103
  readonly RUNTIME_EXECUTOR_SESSION_STARTED: "RUNTIME_EXECUTOR_SESSION_STARTED";
103
104
  readonly RUNTIME_EXECUTOR_SESSION_STOPPED: "RUNTIME_EXECUTOR_SESSION_STOPPED";
105
+ readonly RUNTIME_EXECUTOR_SUSPECTED_STALL: "RUNTIME_EXECUTOR_SUSPECTED_STALL";
104
106
  readonly RUNTIME_EXECUTOR_TURN_COMPLETED: "RUNTIME_EXECUTOR_TURN_COMPLETED";
105
107
  readonly RUNTIME_EXECUTOR_TURN_FAILED: "RUNTIME_EXECUTOR_TURN_FAILED";
106
108
  readonly RUNTIME_EXECUTOR_TURN_STARTED: "RUNTIME_EXECUTOR_TURN_STARTED";
package/dist/shared.js CHANGED
@@ -102,6 +102,7 @@ export const TODO_STATUS_ENUM = z.enum([
102
102
  "blocked",
103
103
  "gated",
104
104
  "done",
105
+ "cancelled",
105
106
  ]);
106
107
  export const HANDOFF_LIFECYCLE_STATUS_ENUM = z.enum([
107
108
  "open",
@@ -146,6 +147,7 @@ export const EVENT_TYPE = {
146
147
  RUNTIME_EXECUTOR_SESSION_FAILED: "RUNTIME_EXECUTOR_SESSION_FAILED",
147
148
  RUNTIME_EXECUTOR_SESSION_STARTED: "RUNTIME_EXECUTOR_SESSION_STARTED",
148
149
  RUNTIME_EXECUTOR_SESSION_STOPPED: "RUNTIME_EXECUTOR_SESSION_STOPPED",
150
+ RUNTIME_EXECUTOR_SUSPECTED_STALL: "RUNTIME_EXECUTOR_SUSPECTED_STALL",
149
151
  RUNTIME_EXECUTOR_TURN_COMPLETED: "RUNTIME_EXECUTOR_TURN_COMPLETED",
150
152
  RUNTIME_EXECUTOR_TURN_FAILED: "RUNTIME_EXECUTOR_TURN_FAILED",
151
153
  RUNTIME_EXECUTOR_TURN_STARTED: "RUNTIME_EXECUTOR_TURN_STARTED",
@@ -20,7 +20,26 @@ export interface SkillAuditResult {
20
20
  rollout_backlog: string[];
21
21
  summary: SkillCatalogSummary;
22
22
  }
23
+ export declare function auditSkillEntry(entry: SkillCatalogEntry): SkillAuditEntry;
23
24
  export declare function auditSkillCatalog(entries?: SkillCatalogEntry[]): SkillAuditResult;
24
25
  export declare function renderSkillAuditReport(result: SkillAuditResult): string;
25
26
  export declare function writeSkillAuditReport(result: SkillAuditResult, targetPath?: string): string;
27
+ /** Mechanical qualification of host-written learned skills. Commands execute
28
+ * through the same goal verifier and its progress policy, outside the goal lease. */
29
+ export declare function proveLearnedSkill(content: string, root: string, run: (command: string, root: string, options?: {
30
+ timeout_ms?: number;
31
+ }) => Promise<{
32
+ ok: boolean;
33
+ output: string;
34
+ }>, previous?: string): Promise<{
35
+ ok: boolean;
36
+ errors: string[];
37
+ checks: {
38
+ command: string;
39
+ cwd: string;
40
+ ok: boolean;
41
+ output: string;
42
+ }[];
43
+ description: string;
44
+ }>;
26
45
  //# sourceMappingURL=skill-auditor.d.ts.map
@@ -1,12 +1,12 @@
1
1
  import { safeWrite } from "./helpers.js";
2
- import { listSkillCatalogEntries, summarizeSkillCatalog, } from "./skill-catalog.js";
2
+ import { listSkillCatalogEntries, readSkillCatalogEntry, summarizeSkillCatalog, } from "./skill-catalog.js";
3
3
  function pushFinding(findings, severity, code, message) {
4
4
  findings.push({ severity, code, message });
5
5
  }
6
6
  function clampScore(value) {
7
7
  return Math.max(0, Math.min(100, value));
8
8
  }
9
- function auditSkillEntry(entry) {
9
+ export function auditSkillEntry(entry) {
10
10
  const findings = [];
11
11
  const strengths = [];
12
12
  let score = 100;
@@ -181,4 +181,34 @@ export function renderSkillAuditReport(result) {
181
181
  export function writeSkillAuditReport(result, targetPath = "agent-state/SKILL_AUDIT_REPORT.md") {
182
182
  return safeWrite(targetPath, renderSkillAuditReport(result));
183
183
  }
184
+ /** Mechanical qualification of host-written learned skills. Commands execute
185
+ * through the same goal verifier and its progress policy, outside the goal lease. */
186
+ export async function proveLearnedSkill(content, root, run, previous) {
187
+ const entry = readSkillCatalogEntry({ name: 'learned/proposal', path: '', source: 'store' }, content);
188
+ const errors = auditSkillEntry(entry).findings.filter(f => f.severity === 'fail').map(f => f.message);
189
+ if (content.split(/\r?\n/).length > 500)
190
+ errors.push('Skill exceeds 500 lines.');
191
+ if (!/^scope:/m.test(content))
192
+ errors.push('Skill frontmatter must declare scope.');
193
+ if (previous && content.length < previous.length * 0.6)
194
+ errors.push('Edit retains less than 60% of prior skill.');
195
+ const validation = content.match(/^## Validation[^\n]*\n([\s\S]*?)(?=^## |$(?![\s\S]))/m)?.[1] ?? '';
196
+ const commands = [...validation.matchAll(/```bash\s*\n([\s\S]*?)```/g)].map(m => m[1].replace(/\r\n/g, '\n').replace(/\n$/, '')).filter(Boolean);
197
+ if (!commands.length)
198
+ errors.push('Validation needs at least one fenced bash command.');
199
+ const checks = [];
200
+ if (!errors.length)
201
+ for (const command of commands) {
202
+ try {
203
+ const result = await run(command, root);
204
+ checks.push({ command, cwd: root, ...result, output: result.output.slice(-12000) });
205
+ if (!result.ok)
206
+ errors.push(`Validation failed: ${command}: ${result.output.slice(-1000)}`);
207
+ }
208
+ catch (error) {
209
+ errors.push(`Validation failed: ${command}: ${String(error)}`);
210
+ }
211
+ }
212
+ return { ok: !errors.length, errors, checks, description: entry.description };
213
+ }
184
214
  //# sourceMappingURL=skill-auditor.js.map
@@ -7,6 +7,9 @@ export interface SkillAdapterOverlay {
7
7
  }
8
8
  export interface SkillCatalogEntry extends SkillReference {
9
9
  description: string;
10
+ origin?: string;
11
+ label?: string;
12
+ learning_status?: string;
10
13
  headings: string[];
11
14
  normalized_headings: string[];
12
15
  line_count: number;
@@ -52,7 +55,7 @@ interface ParsedFrontmatter {
52
55
  has_frontmatter: boolean;
53
56
  }
54
57
  export declare function parseSkillFrontmatter(raw: string): ParsedFrontmatter;
55
- export declare function readSkillCatalogEntry(skill: SkillReference): SkillCatalogEntry;
58
+ export declare function readSkillCatalogEntry(skill: SkillReference, content?: string): SkillCatalogEntry;
56
59
  export declare function listSkillCatalogEntries(): SkillCatalogEntry[];
57
60
  export declare function summarizeSkillCatalog(entries?: SkillCatalogEntry[]): SkillCatalogSummary;
58
61
  export declare function findSkillCatalogEntry(name: string, entries?: SkillCatalogEntry[]): SkillCatalogEntry | undefined;
@@ -134,7 +134,7 @@ function detectAdapterClient(relativePath) {
134
134
  function listSkillAdapterOverlays(skillPath) {
135
135
  const parsed = parseVirtualStorePath(skillPath);
136
136
  if (parsed) {
137
- const skillName = parsed.key.split("/")[2] ?? "";
137
+ const skillName = parsed.key.replace(/^knowledge\/skills\//, "").replace(/\/SKILL\.md$/, "");
138
138
  const prefix = `knowledge/skills/${skillName}/agents/`;
139
139
  return listStoreKeysByPathSync(parsed.storePath, prefix)
140
140
  .filter((key) => key.toLowerCase() !== `${prefix}readme.md`)
@@ -181,10 +181,10 @@ function listSkillAdapterOverlays(skillPath) {
181
181
  walk(agentsRoot);
182
182
  return overlays.sort((a, b) => a.relative_path.localeCompare(b.relative_path));
183
183
  }
184
- export function readSkillCatalogEntry(skill) {
185
- const raw = skill.source === "store" || !existsSync(skill.path)
184
+ export function readSkillCatalogEntry(skill, content) {
185
+ const raw = content ?? (skill.source === "store" || !existsSync(skill.path)
186
186
  ? readVirtualStorePathSync(skill.path) ?? readSkillInstructions(skill.name)
187
- : readFileSync(skill.path, "utf-8");
187
+ : readFileSync(skill.path, "utf-8"));
188
188
  const parsed = parseSkillFrontmatter(raw);
189
189
  const headings = extractHeadings(parsed.body);
190
190
  const normalizedHeadings = headings.map(normalizeHeading);
@@ -203,12 +203,13 @@ export function readSkillCatalogEntry(skill) {
203
203
  const parsed = parseVirtualStorePath(skill.path);
204
204
  if (!parsed)
205
205
  return false;
206
- const skillName = parsed.key.split("/")[2] ?? "";
206
+ const skillName = parsed.key.replace(/^knowledge\/skills\//, "").replace(/\/SKILL\.md$/, "");
207
207
  return listStoreKeysByPathSync(parsed.storePath, `knowledge/skills/${skillName}/references/`).length > 0;
208
208
  })();
209
209
  return {
210
210
  ...skill,
211
211
  description: parsed.attributes.description?.trim() ?? "",
212
+ origin: parsed.attributes.origin, label: parsed.attributes.label, learning_status: parsed.attributes.status,
212
213
  headings,
213
214
  normalized_headings: normalizedHeadings,
214
215
  line_count: raw.replace(/\r\n/g, "\n").split("\n").length,
@@ -67,6 +67,7 @@ export interface ArchiveColdEventsResult {
67
67
  bytes_written: number;
68
68
  }
69
69
  export declare class AcePackedStore implements IAcePackedStore {
70
+ private assertWriteBoundary;
70
71
  private storePath;
71
72
  private readOnly;
72
73
  private fh;
@@ -91,7 +92,10 @@ export declare class AcePackedStore implements IAcePackedStore {
91
92
  * first when old hot events should move into BGZF archive batches.
92
93
  */
93
94
  compact(): Promise<void>;
94
- close(): Promise<void>;
95
+ discardPending(): void;
96
+ close(options?: {
97
+ commit?: boolean;
98
+ }): Promise<void>;
95
99
  get(key: string): Promise<Uint8Array | undefined>;
96
100
  /** Events dropped as malformed since this instance was opened (v1 migration only). */
97
101
  get skippedMalformedEventCount(): number;
@@ -59,6 +59,7 @@ import { MAGIC, STORE_VERSION, HEADER_SIZE, HEADER_SIZE_V1, ContentSource, Entit
59
59
  import { invalidateStoreSnapshotCache, validateChunkBounds } from "./store-snapshot.js";
60
60
  import { crc32 } from "./crc32.js";
61
61
  import { bgzfCompress, bgzfDecompress } from "./bgzf.js";
62
+ import { assertTestWriteAllowed, captureTestWriteBoundary } from "./test-write-boundary.js";
62
63
  // ── Corruption error ─────────────────────────────────────────────────────────
63
64
  export class AcePackedStoreCorruptionError extends Error {
64
65
  constructor(message) {
@@ -317,6 +318,7 @@ function loadCommittedStateFromBytes(file) {
317
318
  }
318
319
  function quarantineCorruptSource(storePath, file, operation, error) {
319
320
  const quarantinePath = `${storePath}.corrupt-${Date.now()}`;
321
+ assertTestWriteAllowed(quarantinePath);
320
322
  writeFileSync(quarantinePath, file);
321
323
  const message = error instanceof Error ? error.message : String(error);
322
324
  throw new AcePackedStoreCorruptionError(`AcePackedStore ${operation} refused: corrupt source store quarantined at ${quarantinePath}; original left in place. Run 'ace repair' or 'ace fsck --repair'. Cause: ${message}`);
@@ -374,6 +376,7 @@ async function readExact(fh, buf, position, context = {}) {
374
376
  }
375
377
  // ── AcePackedStore ────────────────────────────────────────────────────────────
376
378
  export class AcePackedStore {
379
+ assertWriteBoundary = () => { };
377
380
  storePath = "";
378
381
  readOnly = false;
379
382
  fh = null;
@@ -392,6 +395,8 @@ export class AcePackedStore {
392
395
  async open(path, opts = {}) {
393
396
  this.storePath = path;
394
397
  this.readOnly = opts.readOnly ?? false;
398
+ if (!this.readOnly)
399
+ this.assertWriteBoundary = captureTestWriteBoundary(path);
395
400
  if (!existsSync(path)) {
396
401
  if (this.readOnly)
397
402
  throw new Error(`AcePackedStore: file not found: ${path}`);
@@ -400,9 +405,15 @@ export class AcePackedStore {
400
405
  else {
401
406
  await this._loadExisting(path);
402
407
  }
408
+ if (!this.readOnly) {
409
+ this.assertWriteBoundary();
410
+ // Pin the actual store directory after a permitted first creation.
411
+ this.assertWriteBoundary = captureTestWriteBoundary(path);
412
+ }
403
413
  this.fh = await fsOpen(path, this.readOnly ? "r" : "r+");
404
414
  }
405
415
  async _initNew(path) {
416
+ this.assertWriteBoundary();
406
417
  mkdirSync(dirname(path), { recursive: true });
407
418
  const evtBytes = serializeEventSection([]);
408
419
  const idxBytes = TEXT.encode("{}");
@@ -423,6 +434,7 @@ export class AcePackedStore {
423
434
  out.set(header, 0);
424
435
  out.set(evtBytes, evtOff);
425
436
  out.set(idxBytes, kvIdxOff);
437
+ this.assertWriteBoundary();
426
438
  writeFileSync(path, out);
427
439
  this.kvChunkEnd = HEADER_SIZE;
428
440
  this.kvIndex = new Map();
@@ -449,6 +461,7 @@ export class AcePackedStore {
449
461
  async commit() {
450
462
  if (this.readOnly || !this.fh || !this.storePath || !this.dirty)
451
463
  return;
464
+ this.assertWriteBoundary();
452
465
  const currentFile = readFileSync(this.storePath);
453
466
  const latest = loadCommittedStateOrQuarantine(this.storePath, new Uint8Array(currentFile.buffer, currentFile.byteOffset, currentFile.byteLength), "commit");
454
467
  this.skippedMalformedCount += latest.skippedMalformed;
@@ -506,10 +519,13 @@ export class AcePackedStore {
506
519
  out.set(evtBytes, evtOff);
507
520
  out.set(idxBytes, kvIdxOff);
508
521
  const tmpPath = `${this.storePath}.${process.pid}.${Date.now()}.commit.tmp`;
522
+ this.assertWriteBoundary();
523
+ assertTestWriteAllowed(tmpPath);
509
524
  writeFileSync(tmpPath, out);
510
525
  readHeaderV2(readFileSync(tmpPath).slice(0, HEADER_SIZE));
511
526
  await this.fh.close();
512
527
  this.fh = null;
528
+ this.assertWriteBoundary();
513
529
  renameSync(tmpPath, this.storePath);
514
530
  invalidateStoreSnapshotCache(this.storePath);
515
531
  this.fh = await fsOpen(this.storePath, "r+");
@@ -530,6 +546,7 @@ export class AcePackedStore {
530
546
  async compact() {
531
547
  if (!this.storePath || this.readOnly)
532
548
  return;
549
+ this.assertWriteBoundary();
533
550
  if (this.dirty) {
534
551
  await this.commit();
535
552
  }
@@ -585,12 +602,15 @@ export class AcePackedStore {
585
602
  out.set(evtBytes, evtOff);
586
603
  out.set(idxBytes, kvIdxOff);
587
604
  const tmpPath = this.storePath + ".tmp";
605
+ this.assertWriteBoundary();
606
+ assertTestWriteAllowed(tmpPath);
588
607
  writeFileSync(tmpPath, out);
589
608
  readHeaderV2(readFileSync(tmpPath).slice(0, HEADER_SIZE)); // validate
590
609
  if (this.fh) {
591
610
  await this.fh.close();
592
611
  this.fh = null;
593
612
  }
613
+ this.assertWriteBoundary();
594
614
  renameSync(tmpPath, this.storePath);
595
615
  invalidateStoreSnapshotCache(this.storePath);
596
616
  this.fh = await fsOpen(this.storePath, "r+");
@@ -599,7 +619,15 @@ export class AcePackedStore {
599
619
  this.pendingDeletes.clear();
600
620
  this.dirty = false;
601
621
  }
602
- async close() {
622
+ discardPending() {
623
+ this.pending = [];
624
+ this.pendingKv.clear();
625
+ this.pendingDeletes.clear();
626
+ this.dirty = false;
627
+ }
628
+ async close(options = {}) {
629
+ if (options.commit === false)
630
+ this.discardPending();
603
631
  let commitError;
604
632
  try {
605
633
  if (this.dirty) {
@@ -746,6 +774,7 @@ export class AcePackedStore {
746
774
  if (!this.storePath || this.readOnly) {
747
775
  return { batches_written: 0, events_archived: 0, bytes_written: 0 };
748
776
  }
777
+ this.assertWriteBoundary();
749
778
  if (this.dirty) {
750
779
  await this.commit();
751
780
  }
@@ -846,12 +875,15 @@ export class AcePackedStore {
846
875
  out.set(evtBytes, evtOff);
847
876
  out.set(idxBytes, kvIdxOff);
848
877
  const tmpPath = `${this.storePath}.${process.pid}.${Date.now()}.archive.tmp`;
878
+ this.assertWriteBoundary();
879
+ assertTestWriteAllowed(tmpPath);
849
880
  writeFileSync(tmpPath, out);
850
881
  readHeaderV2(readFileSync(tmpPath).slice(0, HEADER_SIZE));
851
882
  if (this.fh) {
852
883
  await this.fh.close();
853
884
  this.fh = null;
854
885
  }
886
+ this.assertWriteBoundary();
855
887
  renameSync(tmpPath, this.storePath);
856
888
  invalidateStoreSnapshotCache(this.storePath);
857
889
  this.fh = await fsOpen(this.storePath, "r+");