@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
@@ -17,6 +17,50 @@
17
17
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
18
18
  import { join } from "node:path";
19
19
  import { randomUUID } from "node:crypto";
20
+ import { withStoreWriteCoordinatorSync } from "../write-coordinator.js";
21
+ /** Wire representation understood by pre-marker 2.4.x engines, which compare
22
+ * numeric limits directly. In-memory callers continue to use zero for unlimited.
23
+ * This is a finite compatibility sentinel, not a mathematical infinity. */
24
+ export const UNBOUNDED_GOAL_BUDGET_WIRE_VALUE = Number.MAX_SAFE_INTEGER;
25
+ const LEGACY_GOAL_DEFAULTS = { max_wall_clock_ms: 14_400_000, max_ticks: 200, max_replans: 3 };
26
+ function normalizeGoalRecord(goal) {
27
+ const source = goal.budgets ?? {};
28
+ const budgets = { ...source };
29
+ const legacyShape = [source.max_wall_clock_ms, source.max_ticks, source.max_replans]
30
+ .some(value => Number.isFinite(value) && value > 0 && value !== UNBOUNDED_GOAL_BUDGET_WIRE_VALUE);
31
+ const infer = (field) => {
32
+ const value = source[field];
33
+ return Number.isFinite(value) && value >= 0 && value !== UNBOUNDED_GOAL_BUDGET_WIRE_VALUE
34
+ && value !== LEGACY_GOAL_DEFAULTS[field] && (value > 0 || (field !== 'max_wall_clock_ms' && legacyShape));
35
+ };
36
+ // Old records did not retain whether a value equal to the default was explicit.
37
+ // Those ambiguous defaults follow the approved unlimited upgrade policy.
38
+ const duration = goal.executor === 'host'
39
+ ? source.host_deadline_explicit ?? source.deadline_explicit ?? infer('max_wall_clock_ms')
40
+ : source.deadline_explicit ?? infer('max_wall_clock_ms');
41
+ budgets.deadline_explicit = source.deadline_explicit ?? duration;
42
+ if (goal.executor === 'host' || source.host_deadline_explicit !== undefined) {
43
+ budgets.host_deadline_explicit = source.host_deadline_explicit ?? duration;
44
+ }
45
+ budgets.max_wall_clock_ms = duration ? source.max_wall_clock_ms : 0;
46
+ budgets.tick_budget_explicit = source.tick_budget_explicit ?? infer('max_ticks');
47
+ budgets.max_ticks = budgets.tick_budget_explicit ? source.max_ticks : 0;
48
+ budgets.replan_budget_explicit = source.replan_budget_explicit ?? infer('max_replans');
49
+ budgets.max_replans = budgets.replan_budget_explicit ? source.max_replans : 0;
50
+ budgets.max_step_retries = source.max_step_retries ?? 0;
51
+ return { ...goal, budgets };
52
+ }
53
+ function encodeGoalRecord(goal) {
54
+ const budgets = { ...goal.budgets };
55
+ const duration = goal.executor === 'host' ? budgets.host_deadline_explicit : budgets.deadline_explicit;
56
+ if (!duration)
57
+ budgets.max_wall_clock_ms = UNBOUNDED_GOAL_BUDGET_WIRE_VALUE;
58
+ if (!budgets.tick_budget_explicit)
59
+ budgets.max_ticks = UNBOUNDED_GOAL_BUDGET_WIRE_VALUE;
60
+ if (!budgets.replan_budget_explicit)
61
+ budgets.max_replans = UNBOUNDED_GOAL_BUDGET_WIRE_VALUE;
62
+ return { ...goal, budgets };
63
+ }
20
64
  export function newGoalId() {
21
65
  return `goal-${randomUUID()}`;
22
66
  }
@@ -36,9 +80,13 @@ export class GoalRepository {
36
80
  }
37
81
  }
38
82
  goalPath(goalId) {
83
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(goalId))
84
+ throw new Error("Invalid goal id");
39
85
  return join(this.dir, `${goalId}.json`);
40
86
  }
41
87
  planPath(goalId) {
88
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(goalId))
89
+ throw new Error("Invalid goal id");
42
90
  return join(this.dir, `${goalId}.plan.json`);
43
91
  }
44
92
  indexPath() {
@@ -68,31 +116,35 @@ export class GoalRepository {
68
116
  return stored;
69
117
  }
70
118
  upsertIndex(goal) {
71
- const index = this.readIndex();
72
- const entry = {
73
- goal_id: goal.goal_id,
74
- status: goal.status,
75
- objective: goal.objective,
76
- updated_at: goal.updated_at,
77
- };
78
- const next = index.goals.filter((row) => row.goal_id !== goal.goal_id);
79
- next.push(entry);
80
- next.sort((a, b) => (a.updated_at < b.updated_at ? 1 : -1));
81
- this.writeAtomic(this.indexPath(), {
82
- version: 1,
83
- updated_at: nowIso(),
84
- goals: next,
85
- });
119
+ // Per-goal leases protect records, but independent workers share this index.
120
+ withStoreWriteCoordinatorSync(this.indexPath(), () => {
121
+ const index = this.readIndex();
122
+ const entry = {
123
+ goal_id: goal.goal_id,
124
+ status: goal.status,
125
+ objective: goal.objective,
126
+ updated_at: goal.updated_at,
127
+ };
128
+ const next = index.goals.filter((row) => row.goal_id !== goal.goal_id);
129
+ next.push(entry);
130
+ next.sort((a, b) => (a.updated_at < b.updated_at ? 1 : -1));
131
+ this.writeAtomic(this.indexPath(), {
132
+ version: 1,
133
+ updated_at: nowIso(),
134
+ goals: next,
135
+ });
136
+ }, { operation_label: 'goal_index_upsert' });
86
137
  }
87
138
  /** Persist a goal record and refresh the index in one call. */
88
139
  saveGoal(goal) {
89
- const record = { ...goal, updated_at: nowIso() };
90
- this.writeAtomic(this.goalPath(record.goal_id), record);
140
+ const record = normalizeGoalRecord({ ...goal, updated_at: nowIso() });
141
+ this.writeAtomic(this.goalPath(record.goal_id), encodeGoalRecord(record));
91
142
  this.upsertIndex(record);
92
143
  return record;
93
144
  }
94
145
  getGoal(goalId) {
95
- return this.readJson(this.goalPath(goalId));
146
+ const record = this.readJson(this.goalPath(goalId));
147
+ return record ? normalizeGoalRecord(record) : undefined;
96
148
  }
97
149
  savePlan(goalId, plan) {
98
150
  this.writeAtomic(this.planPath(goalId), plan);
@@ -101,7 +153,13 @@ export class GoalRepository {
101
153
  return this.readJson(this.planPath(goalId));
102
154
  }
103
155
  listGoals(filter) {
104
- return this.readIndex().goals.filter((row) => filter?.status ? row.status === filter.status : true);
156
+ // Per-goal writers need not serialize on the shared listing cache. Read the
157
+ // existing authoritative records so concurrent goals cannot disappear there.
158
+ const records = existsSync(this.dir) ? readdirSync(this.dir).filter(name => name.endsWith('.json') && name !== 'index.json' && !name.endsWith('.plan.json')).flatMap(name => {
159
+ const goal = this.readJson(join(this.dir, name));
160
+ return goal?.goal_id ? [{ goal_id: goal.goal_id, status: goal.status, objective: goal.objective, updated_at: goal.updated_at }] : [];
161
+ }) : [];
162
+ return records.sort((a, b) => b.updated_at.localeCompare(a.updated_at)).filter((row) => filter?.status ? row.status === filter.status : true);
105
163
  }
106
164
  /** Delete a goal, its plan, and its index entry. Returns true if present. */
107
165
  deleteGoal(goalId) {
@@ -111,12 +169,14 @@ export class GoalRepository {
111
169
  if (existsSync(path))
112
170
  rmSync(path);
113
171
  }
114
- const index = this.readIndex();
115
- this.writeAtomic(this.indexPath(), {
116
- version: 1,
117
- updated_at: nowIso(),
118
- goals: index.goals.filter((row) => row.goal_id !== goalId),
119
- });
172
+ withStoreWriteCoordinatorSync(this.indexPath(), () => {
173
+ const index = this.readIndex();
174
+ this.writeAtomic(this.indexPath(), {
175
+ version: 1,
176
+ updated_at: nowIso(),
177
+ goals: index.goals.filter((row) => row.goal_id !== goalId),
178
+ });
179
+ }, { operation_label: 'goal_index_delete' });
120
180
  return existed;
121
181
  }
122
182
  /**
@@ -124,34 +184,40 @@ export class GoalRepository {
124
184
  * startup so a hand-deleted or partially-written index never hides live goals.
125
185
  */
126
186
  rebuildIndex() {
127
- this.ensureDir();
128
- const goals = [];
129
- for (const name of readdirSync(this.dir)) {
130
- if (!name.endsWith(".json") || name.endsWith(".plan.json") || name === "index.json") {
131
- continue;
132
- }
133
- const record = this.readJson(join(this.dir, name));
134
- if (record?.goal_id) {
135
- goals.push({
136
- goal_id: record.goal_id,
137
- status: record.status,
138
- objective: record.objective,
139
- updated_at: record.updated_at,
140
- });
187
+ return withStoreWriteCoordinatorSync(this.indexPath(), () => {
188
+ this.ensureDir();
189
+ const goals = [];
190
+ for (const name of readdirSync(this.dir)) {
191
+ if (!name.endsWith(".json") || name.endsWith(".plan.json") || name === "index.json") {
192
+ continue;
193
+ }
194
+ const record = this.readJson(join(this.dir, name));
195
+ if (record?.goal_id) {
196
+ goals.push({
197
+ goal_id: record.goal_id,
198
+ status: record.status,
199
+ objective: record.objective,
200
+ updated_at: record.updated_at,
201
+ });
202
+ }
141
203
  }
142
- }
143
- goals.sort((a, b) => (a.updated_at < b.updated_at ? 1 : -1));
144
- const file = { version: 1, updated_at: nowIso(), goals };
145
- this.writeAtomic(this.indexPath(), file);
146
- return file;
204
+ goals.sort((a, b) => (a.updated_at < b.updated_at ? 1 : -1));
205
+ const file = { version: 1, updated_at: nowIso(), goals };
206
+ this.writeAtomic(this.indexPath(), file);
207
+ return file;
208
+ }, { operation_label: 'goal_index_rebuild' });
147
209
  }
148
210
  }
149
211
  export function defaultGoalBudgets() {
150
212
  return {
151
- max_wall_clock_ms: 4 * 60 * 60 * 1000, // 4h
152
- max_ticks: 200,
153
- max_replans: 3,
154
- max_step_retries: 1,
213
+ max_wall_clock_ms: 0,
214
+ deadline_explicit: false,
215
+ host_deadline_explicit: false,
216
+ max_ticks: 0,
217
+ tick_budget_explicit: false,
218
+ max_replans: 0,
219
+ replan_budget_explicit: false,
220
+ max_step_retries: 0,
155
221
  };
156
222
  }
157
223
  //# sourceMappingURL=goal-repository.js.map
@@ -6,6 +6,9 @@ export interface PlaybookScope {
6
6
  model?: string;
7
7
  tool?: string;
8
8
  reason_code?: string;
9
+ paths?: string[];
10
+ task_tags?: string[];
11
+ failure_signature?: string;
9
12
  }
10
13
  export interface PlaybookBullet {
11
14
  bullet_id: string;
@@ -18,9 +21,32 @@ export interface PlaybookBullet {
18
21
  status: PlaybookBulletStatus;
19
22
  evidence_refs: string[];
20
23
  provenance: {
21
- source: "reflector" | "llm_reflector" | "operator" | "migration";
24
+ source: "reflector" | "llm_reflector" | "operator" | "migration" | "harvest";
22
25
  run_id?: string;
26
+ note?: string;
23
27
  };
28
+ served_count?: number;
29
+ used_count?: number;
30
+ used_goals?: string[];
31
+ scored_attempts?: string[];
32
+ served_outcomes?: {
33
+ done_first_attempt: number;
34
+ failed_same_signature: number;
35
+ other: number;
36
+ };
37
+ evidence_snapshots?: import("../../host-learning.js").Episode[];
38
+ label?: "checked" | "demonstrated";
39
+ validation_checks?: Array<{
40
+ command: string;
41
+ cwd: string;
42
+ ok: boolean;
43
+ }>;
44
+ acceptance_errors?: string[];
45
+ retired_reason?: string;
46
+ ever_active?: boolean;
47
+ activated_at?: string;
48
+ last_active_at?: string;
49
+ retired_at?: string;
24
50
  created_at: string;
25
51
  updated_at: string;
26
52
  }
@@ -31,6 +57,8 @@ export type PlaybookDeltaOp = {
31
57
  content: string;
32
58
  evidence_refs: string[];
33
59
  provenance: PlaybookBullet["provenance"];
60
+ status?: PlaybookBulletStatus;
61
+ evidence_snapshots?: PlaybookBullet["evidence_snapshots"];
34
62
  } | {
35
63
  type: "TAG";
36
64
  bullet_id: string;
@@ -65,9 +93,10 @@ export declare function isPlaybookSection(value: string): value is PlaybookSecti
65
93
  export declare function isPlaybookStatus(value: string): value is PlaybookBulletStatus;
66
94
  export declare class PlaybookRepository {
67
95
  private readonly workspaceRoot;
96
+ private readonly buffered?;
68
97
  private readonly dir;
69
98
  private migrated;
70
- constructor(workspaceRoot: string);
99
+ constructor(workspaceRoot: string, buffered?: Map<string, string> | undefined);
71
100
  get root(): string;
72
101
  private useStore;
73
102
  private ensureDir;
@@ -88,13 +117,19 @@ export declare class PlaybookRepository {
88
117
  readIndex(): PlaybookIndexFile;
89
118
  private upsertIndex;
90
119
  saveBullet(bullet: PlaybookBullet): PlaybookBullet;
91
- createBullet(input: Omit<PlaybookBullet, "bullet_id" | "helpful_count" | "harmful_count" | "observed_count" | "status" | "created_at" | "updated_at">): PlaybookBullet;
120
+ createBullet(input: Omit<PlaybookBullet, "bullet_id" | "helpful_count" | "harmful_count" | "observed_count" | "status" | "created_at" | "updated_at"> & {
121
+ status?: PlaybookBulletStatus;
122
+ }): PlaybookBullet;
92
123
  getBullet(bulletId: string): PlaybookBullet | undefined;
93
124
  listBullets(filter?: {
94
125
  section?: PlaybookSection;
95
126
  status?: PlaybookBulletStatus;
96
127
  scope?: PlaybookScope;
97
128
  }): PlaybookBullet[];
129
+ /** Pure listing for assignment/context selection; never migrates or repairs. */
130
+ listBulletsReadOnly(filter?: {
131
+ status?: PlaybookBulletStatus;
132
+ }): PlaybookBullet[];
98
133
  applyDeltaOps(ops: PlaybookDeltaOp[], config?: PlaybookConfig): PlaybookApplyResult;
99
134
  rebuildIndex(): PlaybookIndexFile;
100
135
  }
@@ -104,4 +139,7 @@ export declare function renderPlaybookMarkdown(repo: PlaybookRepository, filter?
104
139
  status?: PlaybookBulletStatus;
105
140
  }): string;
106
141
  export declare function materializePlaybookMarkdown(workspaceRoot: string): string;
142
+ /** One existing-pack transaction; the synchronous repository edits an ephemeral
143
+ * buffer, never recursively taking the non-reentrant cross-process lease. */
144
+ export declare function withPlaybookTransaction<T>(root: string, apply: (repo: PlaybookRepository) => T | Promise<T>): Promise<T>;
107
145
  //# sourceMappingURL=playbook-repository.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { learningLifecycle } from '../../learning-lifecycle.js';
1
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, } from "node:fs";
2
3
  import { dirname, join } from "node:path";
3
4
  import { randomUUID } from "node:crypto";
@@ -24,14 +25,16 @@ function truncateContent(content) {
24
25
  function scopeMatches(bulletScope, filter) {
25
26
  if (!filter)
26
27
  return true;
27
- return Object.entries(filter).every(([key, value]) => value === undefined || bulletScope[key] === undefined || bulletScope[key] === value);
28
+ return Object.entries(filter).every(([key, value]) => value === undefined || bulletScope[key] === undefined || JSON.stringify(bulletScope[key]) === JSON.stringify(value));
28
29
  }
29
30
  export class PlaybookRepository {
30
31
  workspaceRoot;
32
+ buffered;
31
33
  dir;
32
34
  migrated = false;
33
- constructor(workspaceRoot) {
35
+ constructor(workspaceRoot, buffered) {
34
36
  this.workspaceRoot = workspaceRoot;
37
+ this.buffered = buffered;
35
38
  this.dir = join(workspaceRoot, "agent-state", "playbook");
36
39
  }
37
40
  get root() {
@@ -67,6 +70,10 @@ export class PlaybookRepository {
67
70
  return operationalArtifactKey(relPath);
68
71
  }
69
72
  writeText(relPath, content) {
73
+ if (this.buffered) {
74
+ this.buffered.set(relPath, content);
75
+ return relPath;
76
+ }
70
77
  if (this.useStore()) {
71
78
  return writeOperationalArtifactSync(this.workspaceRoot, relPath, content);
72
79
  }
@@ -81,6 +88,8 @@ export class PlaybookRepository {
81
88
  return this.writeText(relPath, JSON.stringify(value, null, 2));
82
89
  }
83
90
  readText(relPath) {
91
+ if (this.buffered?.has(relPath))
92
+ return this.buffered.get(relPath);
84
93
  if (this.useStore()) {
85
94
  const storeText = readStoreBlobSync(this.workspaceRoot, this.artifactKey(relPath));
86
95
  if (typeof storeText === "string")
@@ -92,6 +101,8 @@ export class PlaybookRepository {
92
101
  return readFileSync(path, "utf8");
93
102
  }
94
103
  existsRel(relPath) {
104
+ if (this.buffered?.has(relPath))
105
+ return true;
95
106
  if (this.useStore() && typeof readStoreBlobSync(this.workspaceRoot, this.artifactKey(relPath)) === "string") {
96
107
  return true;
97
108
  }
@@ -109,7 +120,7 @@ export class PlaybookRepository {
109
120
  }
110
121
  }
111
122
  listBulletRelPaths() {
112
- const rels = new Set();
123
+ const rels = new Set([...(this.buffered?.keys() ?? [])].filter(k => /^agent-state\/playbook\/[^/]+\.json$/.test(k) && !k.endsWith("/__index.json")));
113
124
  if (this.useStore()) {
114
125
  const prefix = this.artifactKey("agent-state/playbook/");
115
126
  for (const key of listStoreKeysSync(this.workspaceRoot, prefix)) {
@@ -164,7 +175,8 @@ export class PlaybookRepository {
164
175
  }
165
176
  saveBullet(bullet) {
166
177
  this.ensureDir();
167
- const record = { ...bullet, content: truncateContent(bullet.content), updated_at: nowIso() };
178
+ const at = nowIso(), previous = this.readJson(this.bulletRelPath(bullet.bullet_id));
179
+ const record = { ...bullet, ...learningLifecycle(bullet, previous, at), content: truncateContent(bullet.content), updated_at: at };
168
180
  this.writeJson(this.bulletRelPath(record.bullet_id), record);
169
181
  this.upsertIndex(record);
170
182
  return record;
@@ -181,7 +193,11 @@ export class PlaybookRepository {
181
193
  observed_count: input.provenance.source === "llm_reflector" ? 0 : 1,
182
194
  // Evidence-free notes from every producer are retained but cannot be injected
183
195
  // until evidence is attached through a curated path.
184
- status: input.evidence_refs.length === 0 ? "quarantined" : "active",
196
+ status: input.evidence_refs.length === 0 ? "quarantined" : input.status ?? "active",
197
+ evidence_snapshots: input.evidence_snapshots,
198
+ label: input.label,
199
+ validation_checks: input.validation_checks,
200
+ acceptance_errors: input.acceptance_errors,
185
201
  evidence_refs: input.evidence_refs.slice(0, 5),
186
202
  provenance: input.provenance,
187
203
  created_at: at,
@@ -200,6 +216,10 @@ export class PlaybookRepository {
200
216
  return bullet && scopeMatches(bullet.scope, filter?.scope) ? [bullet] : [];
201
217
  });
202
218
  }
219
+ /** Pure listing for assignment/context selection; never migrates or repairs. */
220
+ listBulletsReadOnly(filter) {
221
+ return this.listBulletRelPaths().flatMap(rel => { const b = this.readJson(rel); return b?.bullet_id && (!filter?.status || b.status === filter.status) ? [b] : []; });
222
+ }
203
223
  applyDeltaOps(ops, config = {}) {
204
224
  const result = { added: 0, tagged: 0, refused: [] };
205
225
  for (const op of ops) {
@@ -311,4 +331,40 @@ export function materializePlaybookMarkdown(workspaceRoot) {
311
331
  writeFileSync(path, renderPlaybookMarkdown(repo), "utf8");
312
332
  return path;
313
333
  }
334
+ /** One existing-pack transaction; the synchronous repository edits an ephemeral
335
+ * buffer, never recursively taking the non-reentrant cross-process lease. */
336
+ export async function withPlaybookTransaction(root, apply) {
337
+ const { withStoreWriteCoordinator } = await import("../write-coordinator.js");
338
+ const { getWorkspaceStorePath } = await import("../store-snapshot.js");
339
+ const { openStore } = await import("../ace-packed-store.js");
340
+ const lock = storeExistsSync(root) ? getWorkspaceStorePath(root) : join(root, "agent-state/playbook/__index.json");
341
+ return withStoreWriteCoordinator(lock, async () => {
342
+ if (!storeExistsSync(root))
343
+ return apply(new PlaybookRepository(root));
344
+ const store = await openStore(getWorkspaceStorePath(root));
345
+ try {
346
+ const buffer = new Map();
347
+ for await (const key of store.list())
348
+ if (key.startsWith("state/artifacts/agent-state/playbook/") || key === "state/artifacts/agent-state/lessons.md") {
349
+ const value = await store.getBlob(key);
350
+ if (value !== undefined)
351
+ buffer.set(key.slice("state/artifacts/".length), value);
352
+ }
353
+ const prior = new Map(buffer);
354
+ const result = await apply(new PlaybookRepository(root, buffer));
355
+ for (const [rel, value] of buffer)
356
+ if (prior.get(rel) !== value)
357
+ await store.setBlob(operationalArtifactKey(rel), value);
358
+ await store.commit();
359
+ return result;
360
+ }
361
+ catch (error) {
362
+ store.discardPending();
363
+ throw error;
364
+ }
365
+ finally {
366
+ await store.close({ commit: false });
367
+ }
368
+ }, { operation_label: "host_playbook" });
369
+ }
314
370
  //# sourceMappingURL=playbook-repository.js.map
@@ -6,20 +6,26 @@ import { AcePackedStore } from "../ace-packed-store.js";
6
6
  export interface TodoNode {
7
7
  id: string;
8
8
  title: string;
9
- status: "pending" | "in_progress" | "done" | "blocked" | "cancelled";
9
+ status: "pending" | "in_progress" | "done" | "blocked" | "gated" | "cancelled";
10
10
  priority?: "high" | "medium" | "low";
11
11
  assigned_to?: string;
12
12
  depends_on?: string[];
13
13
  created_at: number;
14
14
  updated_at: number;
15
15
  notes?: string;
16
+ section?: string;
17
+ source_line?: number;
18
+ step_ref?: {
19
+ goal_id: string;
20
+ step_id: string;
21
+ };
16
22
  }
17
23
  type TodoUpsertInput = Omit<TodoNode, "created_at" | "updated_at"> & Partial<Pick<TodoNode, "created_at">>;
18
24
  export declare class TodoRepository {
19
25
  private store;
20
26
  constructor(store: AcePackedStore);
21
27
  private key;
22
- create(todo: Omit<TodoNode, "created_at" | "updated_at">): Promise<TodoNode>;
28
+ create(todo: TodoUpsertInput): Promise<TodoNode>;
23
29
  get(id: string): Promise<TodoNode | undefined>;
24
30
  update(id: string, patch: Partial<TodoNode>): Promise<TodoNode>;
25
31
  list(filter?: {
@@ -14,7 +14,7 @@ export class TodoRepository {
14
14
  }
15
15
  async create(todo) {
16
16
  const now = Date.now();
17
- const record = { ...todo, created_at: now, updated_at: now };
17
+ const record = { ...todo, created_at: todo.created_at ?? now, updated_at: now };
18
18
  await this.store.setJSON(this.key(todo.id), record);
19
19
  await this.store.appendEntry({
20
20
  kind: EntityKind.Todo,
@@ -13,7 +13,10 @@ export interface VericifyPost {
13
13
  summary: string;
14
14
  ts: number;
15
15
  metadata?: Record<string, unknown>;
16
+ /** Exact imported record, including historical spelling and unknown fields. */
17
+ legacy_record?: Record<string, unknown>;
16
18
  }
19
+ export declare function projectVericifyPost(post: VericifyPost): Record<string, unknown>;
17
20
  export declare class VericifyRepository {
18
21
  private store;
19
22
  private onMutation?;
@@ -27,6 +30,7 @@ export declare class VericifyRepository {
27
30
  agent_id?: string;
28
31
  kind?: VericifyPost["kind"];
29
32
  }): Promise<VericifyPost[]>;
33
+ importPost(original: Record<string, unknown>): Promise<VericifyPost>;
30
34
  private _addToIndex;
31
35
  }
32
36
  //# sourceMappingURL=vericify-repository.d.ts.map
@@ -5,6 +5,21 @@
5
5
  * Also triggers Vericify projection file writes on mutation.
6
6
  */
7
7
  import { ContentSource, EntityKind } from "../types.js";
8
+ import { isDeepStrictEqual } from "node:util";
9
+ export function projectVericifyPost(post) {
10
+ if (post.legacy_record)
11
+ return { ...post.legacy_record };
12
+ return {
13
+ process_post_id: post.id, run_id: post.run_id, agent_id: post.agent_id,
14
+ kind: post.kind, summary: post.summary, timestamp: new Date(post.ts).toISOString(),
15
+ branch_id: typeof post.metadata?.branch_id === "string" ? post.metadata.branch_id : undefined,
16
+ lane_id: typeof post.metadata?.lane_id === "string" ? post.metadata.lane_id : undefined,
17
+ blocker_category: typeof post.metadata?.blocker_category === "string" ? post.metadata.blocker_category : undefined,
18
+ checkpoint_ref: typeof post.metadata?.checkpoint_ref === "string" ? post.metadata.checkpoint_ref : undefined,
19
+ tool_refs: Array.isArray(post.metadata?.tool_refs) ? post.metadata.tool_refs.filter((v) => typeof v === "string") : [],
20
+ evidence_refs: Array.isArray(post.metadata?.evidence_refs) ? post.metadata.evidence_refs.filter((v) => typeof v === "string") : [],
21
+ };
22
+ }
8
23
  const INDEX_KEY = "state/vericify/posts/index";
9
24
  export class VericifyRepository {
10
25
  store;
@@ -47,6 +62,37 @@ export class VericifyRepository {
47
62
  }
48
63
  return posts.sort((a, b) => a.ts - b.ts);
49
64
  }
65
+ async importPost(original) {
66
+ const id = original.process_post_id ?? original.id;
67
+ const ts = typeof original.ts === "number" ? original.ts : Date.parse(String(original.timestamp ?? ""));
68
+ if (typeof id !== "string" || !id || id === "index" || !Number.isFinite(ts) ||
69
+ typeof original.run_id !== "string" || typeof original.agent_id !== "string" ||
70
+ typeof original.kind !== "string" || typeof original.summary !== "string") {
71
+ throw new Error("Historical post lacks a valid identity, timestamp or required fields");
72
+ }
73
+ const existing = await this.store.getJSON(this.key(id));
74
+ if (existing) {
75
+ // JSON roundtrip removes undefined optional projection properties.
76
+ const prior = existing.legacy_record ?? JSON.parse(JSON.stringify(projectVericifyPost(existing)));
77
+ if (!isDeepStrictEqual(prior, original))
78
+ throw new Error(`Historical post identity conflict: ${id}`);
79
+ await this._addToIndex(id);
80
+ return existing;
81
+ }
82
+ const metadata = {
83
+ ...(original.metadata && typeof original.metadata === "object" && !Array.isArray(original.metadata) ? original.metadata : {}),
84
+ ...Object.fromEntries(["branch_id", "lane_id", "blocker_category", "checkpoint_ref", "tool_refs", "evidence_refs"].filter((k) => k in original).map((k) => [k, original[k]])),
85
+ };
86
+ const record = {
87
+ id, ts, run_id: original.run_id, agent_id: original.agent_id,
88
+ kind: original.kind, summary: original.summary,
89
+ metadata, legacy_record: original,
90
+ };
91
+ await this.store.setJSON(this.key(id), record);
92
+ await this.store.appendEntry({ kind: EntityKind.VericifyPost, content_source: ContentSource.User, key: this.key(id), payload: record });
93
+ await this._addToIndex(id);
94
+ return record;
95
+ }
50
96
  async _addToIndex(id) {
51
97
  const index = await this.store.getJSON(INDEX_KEY) ?? [];
52
98
  if (!index.includes(id)) {
@@ -16,6 +16,7 @@ export interface InstallResult {
16
16
  skill: string;
17
17
  files: number;
18
18
  compacted: boolean;
19
+ skipped_files: string[];
19
20
  }
20
21
  export declare function installSkillPack(workspaceRoot: string, skillName: string): Promise<InstallResult>;
21
22
  export declare function installAllSkills(workspaceRoot: string): Promise<InstallResult[]>;
@@ -16,6 +16,7 @@ import { existsSync } from "fs";
16
16
  import { dirname, resolve } from "path";
17
17
  import { fileURLToPath } from "url";
18
18
  import { openStore } from "./ace-packed-store.js";
19
+ import { withStoreWriteCoordinator } from "./write-coordinator.js";
19
20
  import { bakeSkillPack, listAvailableSkills } from "./knowledge-bake.js";
20
21
  import { ensureCanonicalWorkspaceStore, resolveWorkspaceStorePath, } from "./workspace-store-paths.js";
21
22
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -40,24 +41,27 @@ export async function installSkillPack(workspaceRoot, skillName) {
40
41
  if (!available.includes(skillName)) {
41
42
  throw new Error(`Skill pack '${skillName}' not found. Available: ${available.join(", ")}`);
42
43
  }
43
- const store = await openStore(resolved.storePath);
44
- let compacted = false;
45
- try {
46
- const existing = await store.listSkills();
47
- if (existing.includes(skillName)) {
48
- console.log(`[skills-install] '${skillName}' already installed — reinstalling`);
44
+ return withStoreWriteCoordinator(resolved.storePath, async () => {
45
+ const store = await openStore(resolved.storePath);
46
+ let compacted = false;
47
+ const skipped = [];
48
+ try {
49
+ const existing = await store.listSkills();
50
+ if (existing.includes(skillName)) {
51
+ console.log(`[skills-install] '${skillName}' already installed — reinstalling`);
52
+ }
53
+ const files = await bakeSkillPack(store, assetsRoot, skillName, { onSkip: (key) => skipped.push(key) });
54
+ await store.commit();
55
+ if (store.deadSpaceRatio > 0.5) {
56
+ await store.compact();
57
+ compacted = true;
58
+ }
59
+ return { skill: skillName, files, compacted, skipped_files: skipped };
49
60
  }
50
- const files = await bakeSkillPack(store, assetsRoot, skillName);
51
- await store.commit();
52
- if (store.deadSpaceRatio > 0.5) {
53
- await store.compact();
54
- compacted = true;
61
+ finally {
62
+ await store.close({ commit: false });
55
63
  }
56
- return { skill: skillName, files, compacted };
57
- }
58
- finally {
59
- await store.close();
60
- }
64
+ }, { operation_label: "installSkillPack" });
61
65
  }
62
66
  export async function installAllSkills(workspaceRoot) {
63
67
  const assetsRoot = getAssetsRoot();
@@ -0,0 +1,24 @@
1
+ import type { AcePackedStore } from "./ace-packed-store.js";
2
+ export interface SourceIdentity {
3
+ dev: number;
4
+ ino: number;
5
+ size: number;
6
+ mtimeMs: number;
7
+ ctimeMs: number;
8
+ }
9
+ export interface PreservedSource {
10
+ source_path: string;
11
+ sha256: string;
12
+ byte_length: number;
13
+ content_base64: string;
14
+ identity?: SourceIdentity;
15
+ }
16
+ export declare function sourceFromBytes(sourcePath: string, bytes: Buffer): PreservedSource;
17
+ /** Capture exact bytes without following a replaced final symlink or accepting an unstable read. */
18
+ export declare function captureWorkspaceSource(root: string, path: string): PreservedSource;
19
+ export declare function sourceUnchanged(root: string, path: string, source: PreservedSource): boolean;
20
+ export declare function preservedSourceKey(source: PreservedSource): string;
21
+ export declare function stagePreservedSource(store: AcePackedStore, source: PreservedSource): Promise<string>;
22
+ /** Uses the committed-file reader, never the writer's pending values. */
23
+ export declare function verifyPreservedSource(storePath: string, source: PreservedSource): void;
24
+ //# sourceMappingURL=source-preservation.d.ts.map