@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
@@ -1,140 +1,238 @@
1
- /**
2
- * TodoSyncer
3
- *
4
- * Bidirectional sync between tasks/todo.md (human-editable) and the store.
5
- *
6
- * - On write: store → tasks/todo.md (ACE writes todo state from store)
7
- * - On read: tasks/todo.md → store (if externally modified by mtime + hash)
8
- *
9
- * tasks/todo.md format:
10
- * - [ ] Todo item title (id: todo-001)
11
- * - [x] Completed item (id: todo-002)
12
- * - [~] In progress (id: todo-003)
13
- * - [!] Blocked (id: todo-004)
14
- */
15
- import { writeFileSync, readFileSync, existsSync, mkdirSync } from "fs";
16
- import { join, dirname } from "path";
17
- import { createHash } from "crypto";
1
+ /** Human-editable todo projection and its single inverse parser. */
2
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
3
+ import { join, dirname } from "node:path";
4
+ import { createHash } from "node:crypto";
5
+ import { EntityKind } from "../types.js";
18
6
  import { TodoRepository } from "../repositories/todo-repository.js";
19
- const STATUS_ICONS = {
20
- pending: " ",
21
- in_progress: "~",
22
- done: "x",
23
- blocked: "!",
24
- cancelled: "-",
7
+ export const STATUS_ICONS = {
8
+ pending: " ", in_progress: "~", done: "x", blocked: "!", gated: "?", cancelled: "-",
25
9
  };
26
- const ICON_STATUS = {
27
- " ": "pending",
28
- "~": "in_progress",
29
- x: "done",
30
- "!": "blocked",
31
- "-": "cancelled",
10
+ export const ICON_STATUS = {
11
+ " ": "pending", "~": "in_progress", x: "done", X: "done", "!": "blocked", "?": "gated", "-": "cancelled",
32
12
  };
33
- function sha256(content) {
34
- return createHash("sha256").update(content, "utf8").digest("hex");
35
- }
36
- function renderTodoMd(todos) {
37
- const lines = ["# Tasks\n"];
38
- const byStatus = {};
39
- for (const t of todos) {
40
- (byStatus[t.status] ??= []).push(t);
41
- }
42
- const order = ["in_progress", "pending", "blocked", "done", "cancelled"];
43
- for (const status of order) {
44
- const group = byStatus[status];
45
- if (!group?.length)
13
+ const CHECKSUMS_KEY = "host/materializations/checksums";
14
+ const TODO_REL = ".agents/ACE/tasks/todo.md";
15
+ const sha256 = (text) => createHash("sha256").update(text, "utf8").digest("hex");
16
+ export function parseTodoMd(content) {
17
+ const items = [];
18
+ const counts = new Map();
19
+ const import_warnings = [];
20
+ let section;
21
+ let notesTarget;
22
+ for (const [offset, line] of content.split(/\r?\n/).entries()) {
23
+ if (/^\s*>/.test(line)) {
24
+ if (notesTarget)
25
+ notesTarget.notes = [notesTarget.notes, line.replace(/^\s*>\s?/, "")].filter((v) => v !== undefined).join("\n");
26
+ else
27
+ import_warnings.push(`Line ${offset + 1}: unattached note retained in markdown.`);
28
+ continue;
29
+ }
30
+ notesTarget = undefined;
31
+ const header = line.match(/^##\s+(.+)$/);
32
+ if (header) {
33
+ section = header[1].trim();
34
+ continue;
35
+ }
36
+ const match = line.match(/^\s*-\s+\[([^\]]*)\]\s*(.*)$/);
37
+ if (!match) {
38
+ if (line.trim() && !/^#/.test(line))
39
+ import_warnings.push(`Line ${offset + 1}: unparseable todo row; retained canonical records.`);
40
+ continue;
41
+ }
42
+ const status = ICON_STATUS[match[1]];
43
+ const meta = {};
44
+ const metadata = (value) => {
45
+ for (const part of value.split(";")) {
46
+ const field = part.match(/^\s*(id|depends_on|depends)\s*[:=]\s*(.*?)\s*$/);
47
+ if (field)
48
+ meta[field[1]] = field[2];
49
+ }
50
+ return "";
51
+ };
52
+ let title = match[2].replace(/<!--\s*ace:([\s\S]*?)-->/g, (_, value) => metadata(value))
53
+ .replace(/\s*\((id\s*[:=][^)]*)\)\s*$/, (_, value) => metadata(value)).trim();
54
+ if (meta.id)
55
+ counts.set(meta.id, (counts.get(meta.id) ?? 0) + 1);
56
+ const assignee = title.match(/\s+@(\S+)$/);
57
+ if (assignee)
58
+ title = title.slice(0, assignee.index).trimEnd();
59
+ const priority = title.match(/\s+\[(high|medium|low)\]$/);
60
+ if (priority)
61
+ title = title.slice(0, priority.index).trimEnd();
62
+ if (!status || !title || ("id" in meta && !meta.id)) {
63
+ import_warnings.push(`Line ${offset + 1}: invalid checkbox, title or id; canonical row unchanged.`);
46
64
  continue;
47
- const icon = STATUS_ICONS[status];
48
- for (const t of group) {
49
- const priority = t.priority ? ` [${t.priority}]` : "";
50
- const assignee = t.assigned_to ? ` @${t.assigned_to}` : "";
51
- lines.push(`- [${icon}] ${t.title}${priority}${assignee} (id: ${t.id})`);
52
- if (t.notes)
53
- lines.push(` > ${t.notes}`);
54
65
  }
66
+ const dependencies = meta.depends_on ?? meta.depends;
67
+ const item = { raw_line: line, id: meta.id, title, status, section, source_line: offset + 1,
68
+ ...(dependencies === undefined ? {} : { depends_on: dependencies.split(",").map((s) => s.trim()).filter(Boolean) }),
69
+ ...(priority ? { priority: priority[1] } : {}),
70
+ ...(assignee ? { assigned_to: assignee[1] } : {}), };
71
+ items.push(item);
72
+ notesTarget = item;
55
73
  }
56
- return lines.join("\n") + "\n";
74
+ for (const [id, count] of counts)
75
+ if (count > 1)
76
+ import_warnings.push(`Duplicate id ${id}; canonical row unchanged.`);
77
+ return { items: items.filter((item) => !item.id || counts.get(item.id) === 1), import_warnings };
57
78
  }
58
- function parseTodoMd(content) {
59
- const results = [];
60
- for (const line of content.split("\n")) {
61
- const match = line.match(/^- \[(.)\] (.+)/);
62
- if (!match)
63
- continue;
64
- const [, iconChar, rest] = match;
65
- const status = ICON_STATUS[iconChar] ?? "pending";
66
- // Extract id if present
67
- const idMatch = rest.match(/\(id: ([^)]+)\)/);
68
- const id = idMatch?.[1];
69
- const title = rest.replace(/\s*\(id: [^)]+\)/, "").replace(/\s*\[[^\]]+\]/, "").replace(/\s*@\S+/, "").trim();
70
- results.push({ id, title, status });
79
+ export function renderTodoMd(todos) {
80
+ const lines = ["# Tasks", ""];
81
+ for (const todo of todos) {
82
+ const priority = todo.priority ? ` [${todo.priority}]` : "";
83
+ const assignee = todo.assigned_to ? ` @${todo.assigned_to}` : "";
84
+ const deps = todo.depends_on?.length ? `; depends_on: ${todo.depends_on.join(",")}` : "";
85
+ lines.push(`- [${STATUS_ICONS[todo.status]}] ${todo.title}${priority}${assignee} (id: ${todo.id}${deps})`);
86
+ if (todo.notes)
87
+ lines.push(...todo.notes.split("\n").map((line) => ` > ${line}`));
71
88
  }
72
- return results;
89
+ return lines.join("\n") + "\n";
73
90
  }
74
91
  export class TodoSyncer {
75
92
  store;
76
93
  workspaceRoot;
77
94
  repo;
78
- lastHash = "";
95
+ submittedStamp;
79
96
  todoMdPath;
80
97
  constructor(store, workspaceRoot) {
81
98
  this.store = store;
82
99
  this.workspaceRoot = workspaceRoot;
83
100
  this.repo = new TodoRepository(store);
84
- // Canonical ACE path: .agents/ACE/tasks/todo.md (mirrors ACE_TASKS_ROOT_REL in helpers.ts)
85
- this.todoMdPath = join(workspaceRoot, ".agents", "ACE", "tasks", "todo.md");
101
+ this.todoMdPath = join(workspaceRoot, TODO_REL);
86
102
  }
87
- /** Write store todo state → tasks/todo.md */
88
- async writeToFile() {
89
- const todos = await this.repo.getAll();
90
- const content = renderTodoMd(todos);
91
- const dir = dirname(this.todoMdPath);
92
- if (!existsSync(dir))
93
- mkdirSync(dir, { recursive: true });
94
- writeFileSync(this.todoMdPath, content, "utf8");
95
- this.lastHash = sha256(content);
103
+ async stamp() {
104
+ return (await this.store.getJSON(CHECKSUMS_KEY))?.[TODO_REL];
105
+ }
106
+ async setStamp(stamp) {
107
+ const all = await this.store.getJSON(CHECKSUMS_KEY) ?? {};
108
+ all[TODO_REL] = stamp;
109
+ await this.store.setJSON(CHECKSUMS_KEY, all);
110
+ }
111
+ /** Stage a non-destructive native import. The caller owns commit/discard and the coordinator. */
112
+ async syncFromFile(content, force = false, observeNative = true) {
113
+ if (content === undefined && !existsSync(this.todoMdPath))
114
+ return { changed: false, import_warnings: [] };
115
+ const raw = content ?? readFileSync(this.todoMdPath, "utf8");
116
+ const hash = sha256(raw);
117
+ const stamp = await this.stamp();
118
+ if (!force && hash === (stamp?.observed_hash ?? stamp?.hash)) {
119
+ if (!observeNative)
120
+ this.submittedStamp = stamp;
121
+ return { changed: false, import_warnings: stamp?.import_warnings ?? [] };
122
+ }
123
+ const { items, import_warnings } = parseTodoMd(raw);
124
+ const existing = await this.repo.getAll();
125
+ const byId = new Map(existing.map((todo) => [todo.id, todo]));
126
+ const seen = new Set();
127
+ const observed_rows = {};
128
+ // Reuse deletion history: a stale, unrendered file must not resurrect an explicit delete.
129
+ const deleted = new Set();
130
+ for (const entry of await this.store.getEntries({ kind: EntityKind.Todo })) {
131
+ const value = entry.payload;
132
+ if (!value?.id)
133
+ continue;
134
+ if (value.op === "remove")
135
+ deleted.add(value.id);
136
+ else
137
+ deleted.delete(value.id);
138
+ }
139
+ for (const item of items) {
140
+ let id = item.id;
141
+ if (!id) {
142
+ const matching = existing.filter((todo) => todo.title === item.title && !seen.has(todo.id));
143
+ if (matching.length === 1)
144
+ id = matching[0].id;
145
+ else {
146
+ const base = `todo-${item.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "item"}`;
147
+ id = base;
148
+ for (let n = 2; byId.has(id) || seen.has(id); n++)
149
+ id = `${base}-${n}`;
150
+ }
151
+ }
152
+ seen.add(id);
153
+ if (deleted.has(id) && !byId.has(id)) {
154
+ import_warnings.push(`Deleted todo ${id} remains in markdown; not recreated.`);
155
+ continue;
156
+ }
157
+ const prior = byId.get(id);
158
+ const rowHash = sha256(JSON.stringify([item.raw_line, item.notes ?? ""]));
159
+ observed_rows[id] = rowHash;
160
+ // Only changed native rows are edits. An unchanged stale row must not undo
161
+ // accepted work while another malformed row is being repaired.
162
+ if (prior && stamp?.observed_rows?.[id] === rowHash)
163
+ continue;
164
+ const { id: _id, raw_line: _raw, ...fields } = item;
165
+ if (!prior) {
166
+ const section = item.section?.toLowerCase() ?? "";
167
+ const status = item.status === "pending" && section.includes("verification") ? "gated" : item.status === "pending" && section.includes("active") ? "in_progress" : item.status;
168
+ const created = await this.repo.create({ ...fields, id, status });
169
+ byId.set(id, created);
170
+ }
171
+ else {
172
+ if (renderTodoMd([prior]).split("\n").some((line) => line === item.raw_line)) {
173
+ fields.title = prior.title;
174
+ fields.priority = prior.priority;
175
+ fields.assigned_to = prior.assigned_to;
176
+ fields.depends_on = prior.depends_on;
177
+ }
178
+ const patch = Object.fromEntries(Object.entries(fields).filter(([key, value]) => value !== undefined && JSON.stringify(prior[key]) !== JSON.stringify(value)));
179
+ if (prior.step_ref && patch.status !== undefined) {
180
+ delete patch.status;
181
+ import_warnings.push(`Todo ${id} is a step view; use complete_step for its status.`);
182
+ }
183
+ // Line numbers are view metadata; a reordered file alone need not rewrite the task.
184
+ delete patch.source_line;
185
+ if (Object.keys(patch).length)
186
+ await this.repo.update(id, patch);
187
+ }
188
+ }
189
+ for (const todo of existing)
190
+ if (!seen.has(todo.id))
191
+ import_warnings.push(`Todo ${todo.id} missing from markdown; canonical record retained. Use explicit delete to remove it.`);
192
+ const nextStamp = { path: TODO_REL, hash: stamp?.hash ?? "", written_at: stamp?.written_at ?? 0, observed_hash: hash, observed_rows, import_warnings };
193
+ if (observeNative)
194
+ await this.setStamp(nextStamp);
195
+ else
196
+ this.submittedStamp = nextStamp;
197
+ return { changed: true, import_warnings };
96
198
  }
97
- /** Check if tasks/todo.md was modified externally, and if so ingest changes */
98
- async syncFromFile() {
199
+ /** Supplied content is an observation only after its file publication succeeds. */
200
+ async acknowledgeSubmittedFile(content) {
201
+ if (this.submittedStamp?.observed_hash === sha256(content))
202
+ await this.setStamp(this.submittedStamp);
203
+ }
204
+ async projectionIsCurrent() {
99
205
  if (!existsSync(this.todoMdPath))
100
206
  return false;
101
- const content = readFileSync(this.todoMdPath, "utf8");
102
- const currentHash = sha256(content);
103
- if (currentHash === this.lastHash)
104
- return false; // not modified
105
- const parsed = parseTodoMd(content);
106
- let changed = false;
107
- for (const item of parsed) {
108
- if (!item.id)
109
- continue; // can't sync without id
110
- const existing = await this.repo.get(item.id);
111
- if (!existing)
112
- continue;
113
- if (existing.status !== item.status || existing.title !== item.title) {
114
- await this.repo.update(item.id, { title: item.title, status: item.status });
115
- changed = true;
207
+ const expected = sha256(renderTodoMd(await this.repo.getAll()));
208
+ return (await this.stamp())?.hash === expected && sha256(readFileSync(this.todoMdPath, "utf8")) === expected;
209
+ }
210
+ /** Do not overwrite unimported edits or malformed source text. */
211
+ async writeToFile() {
212
+ const stamp = await this.stamp();
213
+ const warnings = stamp?.import_warnings ?? [];
214
+ if (existsSync(this.todoMdPath)) {
215
+ const current = sha256(readFileSync(this.todoMdPath, "utf8"));
216
+ if (warnings.length || current !== (stamp?.observed_hash ?? stamp?.hash)) {
217
+ return { written: false, import_warnings: warnings.length ? warnings : ["Todo markdown changed before projection; native edit retained, refresh pending."] };
116
218
  }
117
219
  }
118
- this.lastHash = currentHash;
119
- return changed;
220
+ const content = renderTodoMd(await this.repo.getAll());
221
+ mkdirSync(dirname(this.todoMdPath), { recursive: true });
222
+ writeFileSync(this.todoMdPath, content, "utf8");
223
+ const hash = sha256(content);
224
+ const observed_rows = Object.fromEntries(parseTodoMd(content).items.filter((item) => item.id).map((item) => [item.id, sha256(JSON.stringify([item.raw_line, item.notes ?? ""]))]));
225
+ await this.setStamp({ path: TODO_REL, hash, observed_hash: hash, observed_rows, written_at: Date.now(), import_warnings: [] });
226
+ return { written: true, import_warnings: [] };
120
227
  }
121
- /** Initialize todo.md with starter content if it doesn't exist */
122
228
  async initStarter() {
123
- if (existsSync(this.todoMdPath))
229
+ if (existsSync(this.todoMdPath)) {
230
+ await this.syncFromFile();
124
231
  return;
125
- const dir = dirname(this.todoMdPath);
126
- if (!existsSync(dir))
127
- mkdirSync(dir, { recursive: true });
128
- const starter = `# Tasks\n\n- [ ] Initiate ACE (id: todo-001)\n`;
129
- writeFileSync(this.todoMdPath, starter, "utf8");
130
- this.lastHash = sha256(starter);
131
- // Seed into store
132
- await this.repo.create({
133
- id: "todo-001",
134
- title: "Initiate ACE",
135
- status: "pending",
136
- priority: "high",
137
- });
232
+ }
233
+ if (!(await this.repo.getAll()).length)
234
+ await this.repo.create({ id: "todo-001", title: "Initiate ACE", status: "pending", priority: "high" });
235
+ await this.writeToFile();
138
236
  }
139
237
  }
140
238
  //# sourceMappingURL=todo-syncer.js.map
@@ -19,7 +19,8 @@ import { HandoffRepository } from "../repositories/handoff-repository.js";
19
19
  import { TodoRepository } from "../repositories/todo-repository.js";
20
20
  import { LedgerRepository } from "../repositories/ledger-repository.js";
21
21
  import { TrackerRepository } from "../repositories/tracker-repository.js";
22
- import { VericifyRepository } from "../repositories/vericify-repository.js";
22
+ import { VericifyRepository, projectVericifyPost } from "../repositories/vericify-repository.js";
23
+ import { preserveLegacyVericifyPosts } from "../legacy-vericify-posts.js";
23
24
  import { operationalArtifactKey } from "../store-artifacts.js";
24
25
  // Runtime-facing state now lives in top-level agent-state/.
25
26
  const ACE_STATE_ROOT = "agent-state";
@@ -74,6 +75,7 @@ export class VericifyProjector {
74
75
  task_type: typeof metadata.task_type === "string" ? metadata.task_type : r.task,
75
76
  priority: typeof metadata.priority === "string" ? metadata.priority : undefined,
76
77
  source_file: typeof metadata.source_file === "string" ? metadata.source_file : undefined,
78
+ step_ref: metadata.step_ref,
77
79
  status: legacyStatus,
78
80
  history: Array.isArray(metadata.history) ? metadata.history : [],
79
81
  };
@@ -90,10 +92,11 @@ export class VericifyProjector {
90
92
  for (const t of todos) {
91
93
  order.push(t.id);
92
94
  nodes[t.id] = {
95
+ ...t,
93
96
  id: t.id,
94
97
  title: t.title,
95
- section: "",
96
- source_line: 0,
98
+ section: t.section ?? (t.notes?.startsWith("section: ") ? t.notes.slice(9) : ""),
99
+ source_line: t.source_line ?? 0,
97
100
  depends_on: t.depends_on ?? [],
98
101
  status: mapStatus(t.status),
99
102
  };
@@ -147,32 +150,16 @@ export class VericifyProjector {
147
150
  await this._write("STATUS_EVENTS.ndjson", ndjson);
148
151
  }
149
152
  async projectVericifyPosts() {
153
+ const legacy = await preserveLegacyVericifyPosts(this.store, this.workspaceRoot);
154
+ for (const warning of legacy.warnings)
155
+ console.warn(warning);
156
+ if (!legacy.canProject)
157
+ return;
150
158
  const posts = await this.vericify.list();
151
159
  const content = JSON.stringify({
152
160
  version: 1,
153
161
  updated_at: new Date().toISOString(),
154
- posts: posts.map((post) => ({
155
- process_post_id: post.id,
156
- run_id: post.run_id,
157
- agent_id: post.agent_id,
158
- kind: post.kind,
159
- summary: post.summary,
160
- timestamp: new Date(post.ts).toISOString(),
161
- branch_id: typeof post.metadata?.branch_id === "string" ? post.metadata.branch_id : undefined,
162
- lane_id: typeof post.metadata?.lane_id === "string" ? post.metadata.lane_id : undefined,
163
- blocker_category: typeof post.metadata?.blocker_category === "string"
164
- ? post.metadata.blocker_category
165
- : undefined,
166
- checkpoint_ref: typeof post.metadata?.checkpoint_ref === "string"
167
- ? post.metadata.checkpoint_ref
168
- : undefined,
169
- tool_refs: Array.isArray(post.metadata?.tool_refs)
170
- ? post.metadata.tool_refs.filter((value) => typeof value === "string")
171
- : [],
172
- evidence_refs: Array.isArray(post.metadata?.evidence_refs)
173
- ? post.metadata.evidence_refs.filter((value) => typeof value === "string")
174
- : [],
175
- })),
162
+ posts: posts.map(projectVericifyPost),
176
163
  }, null, 2);
177
164
  await this._write("vericify/process-posts.json", content);
178
165
  }
@@ -203,8 +190,10 @@ function mapStatus(storeStatus) {
203
190
  return "blocked";
204
191
  case "done":
205
192
  return "done";
193
+ case "gated":
194
+ return "gated";
206
195
  case "cancelled":
207
- return "done"; // closest legacy equivalent
196
+ return "cancelled";
208
197
  default:
209
198
  return "backlog";
210
199
  }
@@ -18,11 +18,21 @@ import type { TaskPlan } from "../../orchestrator-supervisor.js";
18
18
  export type GoalStatus = "draft" | "planning" | "executing" | "verifying" | "blocked" | "done" | "failed" | "stopped";
19
19
  export type GoalEscalationKind = "approval_required" | "run_complete" | "run_error" | "gate_failed" | "budget_blocked" | "blocker";
20
20
  export interface GoalBudgets {
21
+ /** Zero means unlimited; only an explicitly marked allowance is enforced. */
21
22
  max_wall_clock_ms: number;
23
+ /** Only an explicit host budget_hours request enables this duration. */
24
+ host_deadline_explicit?: boolean;
25
+ deadline_explicit?: boolean;
22
26
  max_ticks: number;
27
+ tick_budget_explicit?: boolean;
23
28
  max_replans: number;
29
+ replan_budget_explicit?: boolean;
24
30
  max_step_retries: number;
25
31
  }
32
+ /** Wire representation understood by pre-marker 2.4.x engines, which compare
33
+ * numeric limits directly. In-memory callers continue to use zero for unlimited.
34
+ * This is a finite compatibility sentinel, not a mathematical infinity. */
35
+ export declare const UNBOUNDED_GOAL_BUDGET_WIRE_VALUE: number;
26
36
  export interface SuccessContract {
27
37
  /** Union of step-level and goal-level required artifacts. */
28
38
  expected_artifacts: Array<{
@@ -30,7 +40,7 @@ export interface SuccessContract {
30
40
  required?: boolean;
31
41
  }>;
32
42
  required_evidence_refs: string[];
33
- verification_commands: string[];
43
+ verification_commands: import('../../goal-engine.js').VerificationCommand[];
34
44
  /**
35
45
  * TODO(WS-F): wire gate_ids to execute_gates. Always `[]` today and never
36
46
  * read, so it is deliberately excluded from `enforceableObligationCount`.
@@ -61,12 +71,51 @@ export interface GoalRecord {
61
71
  started_at?: string;
62
72
  tick_count: number;
63
73
  replan_count: number;
74
+ /** Consecutive unchanged failure sets, not a ceiling on productive corrections. */
75
+ failure_repetition?: {
76
+ signature: string;
77
+ count: number;
78
+ };
64
79
  last_heartbeat_at?: string;
65
80
  stop_requested: boolean;
66
81
  escalations: GoalEscalation[];
67
82
  result_summary?: string;
68
83
  reason_code?: string;
69
84
  current_step_id?: string;
85
+ executor?: "engine" | "host";
86
+ verified?: boolean;
87
+ verification_results?: Array<{
88
+ command: string;
89
+ ok: boolean;
90
+ output: string;
91
+ }>;
92
+ verification_attempt?: number;
93
+ verification_run?: {
94
+ job_id: string;
95
+ attempt: number;
96
+ enqueued_at: string;
97
+ status: 'queued' | 'running' | 'interrupted' | 'uncertain' | 'done' | 'failed' | 'stopped';
98
+ worker?: import('../../process-identity.js').ProcessIdentity;
99
+ command?: import('../../progress-command.js').CommandProgress & {
100
+ index: number;
101
+ command: string;
102
+ process?: import('../../process-identity.js').ProcessIdentity;
103
+ log_path?: string;
104
+ };
105
+ error?: string;
106
+ };
107
+ final_contract_check?: {
108
+ ok: boolean;
109
+ missing_artifacts: string[];
110
+ missing_evidence_refs: string[];
111
+ failed_commands: string[];
112
+ };
113
+ pending_follow_ups?: Array<{
114
+ step_id: string;
115
+ task: string;
116
+ role: string;
117
+ depends_on?: string[];
118
+ }>;
70
119
  }
71
120
  export interface GoalIndexEntry {
72
121
  goal_id: string;
@@ -86,7 +135,7 @@ export declare class GoalRepository {
86
135
  constructor(workspaceRoot: string);
87
136
  private ensureDir;
88
137
  private goalPath;
89
- private planPath;
138
+ planPath(goalId: string): string;
90
139
  private indexPath;
91
140
  private writeAtomic;
92
141
  private readJson;