@voybio/ace-swarm 2.4.8 → 2.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/CHANGELOG.md +52 -14
  2. package/README.md +14 -10
  3. package/assets/.agents/ACE/ACE_coders/AGENTS.md +23 -30
  4. package/assets/.agents/ACE/ACE_coders/INSTRUCTIONS.md +21 -30
  5. package/assets/.agents/skills/landing-review-watcher/SKILL.md +1 -1
  6. package/assets/.agents/skills/learn-from-work/SKILL.md +41 -0
  7. package/assets/agent-state/ACE_WORKFLOW.md +25 -2
  8. package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +143 -2
  9. package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +2 -2
  10. package/assets/agent-state/MODULES/schemas/SWARM_HANDOFF.schema.json +10 -0
  11. package/assets/eval-battery/battery.json +1 -1
  12. package/assets/instructions/ACE_Coder.instructions.md +9 -13
  13. package/assets/scripts/ace-hook-dispatch.mjs +16 -411
  14. package/assets/tasks/SWARM_HANDOFF.example_ui_to_coders.json +1 -2
  15. package/assets/vendor/README.md +9 -0
  16. package/assets/vendor/vericify-reader.mjs +272 -0
  17. package/assets/vendor/vericify-reader.provenance.json +10 -0
  18. package/dist/ace-autonomy.d.ts +58 -0
  19. package/dist/ace-autonomy.js +211 -6
  20. package/dist/ace-internal-tools.d.ts +1 -0
  21. package/dist/ace-internal-tools.js +3 -3
  22. package/dist/ace-server-instructions.d.ts +3 -3
  23. package/dist/ace-server-instructions.js +46 -192
  24. package/dist/cli.js +44 -6
  25. package/dist/experience-reflector.d.ts +7 -0
  26. package/dist/experience-reflector.js +39 -0
  27. package/dist/goal-engine.d.ts +64 -7
  28. package/dist/goal-engine.js +197 -41
  29. package/dist/handoff-registry.d.ts +10 -0
  30. package/dist/handoff-registry.js +59 -0
  31. package/dist/helpers/artifacts.d.ts +6 -2
  32. package/dist/helpers/artifacts.js +8 -6
  33. package/dist/helpers/bootstrap.js +13 -20
  34. package/dist/helpers/path-utils.js +2 -0
  35. package/dist/helpers/store-resolution.js +18 -3
  36. package/dist/helpers/workspace-root.d.ts +2 -0
  37. package/dist/helpers/workspace-root.js +9 -0
  38. package/dist/helpers.d.ts +1 -0
  39. package/dist/helpers.js +1 -0
  40. package/dist/hermes/bridge-protocol.d.ts +1 -1
  41. package/dist/hermes/session-manager.d.ts +17 -0
  42. package/dist/hermes/session-manager.js +67 -14
  43. package/dist/host-goal-engine.d.ts +138 -0
  44. package/dist/host-goal-engine.js +793 -0
  45. package/dist/host-goal-status.d.ts +113 -0
  46. package/dist/host-goal-status.js +175 -0
  47. package/dist/host-learning.d.ts +68 -0
  48. package/dist/host-learning.js +115 -0
  49. package/dist/host-observation.d.ts +12 -0
  50. package/dist/host-observation.js +91 -0
  51. package/dist/host-plan-revision.d.ts +14 -0
  52. package/dist/host-plan-revision.js +101 -0
  53. package/dist/host-verification-job.d.ts +12 -0
  54. package/dist/host-verification-job.js +183 -0
  55. package/dist/host-work-context.d.ts +12 -0
  56. package/dist/host-work-context.js +8 -0
  57. package/dist/job-scheduler.d.ts +58 -13
  58. package/dist/job-scheduler.js +298 -48
  59. package/dist/job-worker.d.ts +3 -1
  60. package/dist/job-worker.js +10 -3
  61. package/dist/kanban.d.ts +4 -1
  62. package/dist/kanban.js +44 -16
  63. package/dist/learned-artifacts.d.ts +64 -0
  64. package/dist/learned-artifacts.js +321 -0
  65. package/dist/learning-controls.d.ts +142 -0
  66. package/dist/learning-controls.js +135 -0
  67. package/dist/learning-input-context.d.ts +9 -0
  68. package/dist/learning-input-context.js +62 -0
  69. package/dist/learning-lifecycle.d.ts +10 -0
  70. package/dist/learning-lifecycle.js +11 -0
  71. package/dist/learning-overlays.d.ts +3 -0
  72. package/dist/learning-overlays.js +17 -0
  73. package/dist/learning-policy.d.ts +40 -0
  74. package/dist/learning-policy.js +38 -0
  75. package/dist/learning-records.d.ts +35 -0
  76. package/dist/learning-records.js +17 -0
  77. package/dist/learning-scorecard.d.ts +82 -0
  78. package/dist/learning-scorecard.js +86 -0
  79. package/dist/learning-serving.d.ts +29 -0
  80. package/dist/learning-serving.js +134 -0
  81. package/dist/linked-work.d.ts +9 -0
  82. package/dist/linked-work.js +28 -0
  83. package/dist/local-model-runtime.js +1 -1
  84. package/dist/mcp-command-progress.d.ts +16 -0
  85. package/dist/mcp-command-progress.js +70 -0
  86. package/dist/model-bridge.d.ts +14 -13
  87. package/dist/model-bridge.js +73 -41
  88. package/dist/orchestrator-supervisor.d.ts +111 -6
  89. package/dist/orchestrator-supervisor.js +67 -43
  90. package/dist/plan-proposal.js +3 -3
  91. package/dist/playbook-curator.d.ts +1 -0
  92. package/dist/playbook-curator.js +11 -9
  93. package/dist/process-identity.d.ts +8 -0
  94. package/dist/process-identity.js +38 -0
  95. package/dist/progress-command.d.ts +34 -0
  96. package/dist/progress-command.js +153 -0
  97. package/dist/prompts.js +15 -15
  98. package/dist/resources.js +2 -2
  99. package/dist/runtime-command.d.ts +8 -1
  100. package/dist/runtime-command.js +48 -7
  101. package/dist/runtime-executor.js +33 -11
  102. package/dist/runtime-profile-syntax.d.ts +6 -0
  103. package/dist/runtime-profile-syntax.js +121 -0
  104. package/dist/runtime-profile.d.ts +37 -5
  105. package/dist/runtime-profile.js +36 -129
  106. package/dist/runtime-tool-specs.js +1 -1
  107. package/dist/safe-edit.d.ts +7 -3
  108. package/dist/safe-edit.js +40 -47
  109. package/dist/schemas.js +4 -2
  110. package/dist/server.d.ts +2 -16
  111. package/dist/server.js +14 -20
  112. package/dist/shared.d.ts +2 -0
  113. package/dist/shared.js +2 -0
  114. package/dist/skill-auditor.d.ts +19 -0
  115. package/dist/skill-auditor.js +32 -2
  116. package/dist/skill-catalog.d.ts +4 -1
  117. package/dist/skill-catalog.js +6 -5
  118. package/dist/store/ace-packed-store.d.ts +5 -1
  119. package/dist/store/ace-packed-store.js +33 -1
  120. package/dist/store/bootstrap-store.js +19 -12
  121. package/dist/store/cache-workspace.d.ts +8 -0
  122. package/dist/store/cache-workspace.js +94 -27
  123. package/dist/store/importer.js +11 -20
  124. package/dist/store/knowledge-bake.d.ts +3 -1
  125. package/dist/store/knowledge-bake.js +10 -1
  126. package/dist/store/legacy-vericify-posts.d.ts +8 -0
  127. package/dist/store/legacy-vericify-posts.js +76 -0
  128. package/dist/store/materializers/hook-context-materializer.d.ts +2 -1
  129. package/dist/store/materializers/hook-context-materializer.js +6 -2
  130. package/dist/store/materializers/host-file-materializer.js +2 -2
  131. package/dist/store/materializers/projection-manager.d.ts +4 -1
  132. package/dist/store/materializers/projection-manager.js +10 -3
  133. package/dist/store/materializers/todo-syncer.d.ts +39 -20
  134. package/dist/store/materializers/todo-syncer.js +208 -110
  135. package/dist/store/materializers/vericify-projector.js +15 -26
  136. package/dist/store/repositories/goal-repository.d.ts +51 -2
  137. package/dist/store/repositories/goal-repository.js +114 -48
  138. package/dist/store/repositories/playbook-repository.d.ts +41 -3
  139. package/dist/store/repositories/playbook-repository.js +61 -5
  140. package/dist/store/repositories/todo-repository.d.ts +8 -2
  141. package/dist/store/repositories/todo-repository.js +1 -1
  142. package/dist/store/repositories/vericify-repository.d.ts +4 -0
  143. package/dist/store/repositories/vericify-repository.js +46 -0
  144. package/dist/store/skills-install.d.ts +1 -0
  145. package/dist/store/skills-install.js +20 -16
  146. package/dist/store/source-preservation.d.ts +24 -0
  147. package/dist/store/source-preservation.js +79 -0
  148. package/dist/store/store-artifacts.js +8 -1
  149. package/dist/store/test-write-boundary.d.ts +16 -0
  150. package/dist/store/test-write-boundary.js +175 -0
  151. package/dist/store/types.d.ts +5 -1
  152. package/dist/store/write-coordinator.js +16 -0
  153. package/dist/task-plan-context.d.ts +11 -0
  154. package/dist/task-plan-context.js +59 -0
  155. package/dist/todo-state.d.ts +19 -9
  156. package/dist/todo-state.js +204 -321
  157. package/dist/tools-agent.d.ts +1 -1
  158. package/dist/tools-agent.js +38 -62
  159. package/dist/tools-files.d.ts +0 -3
  160. package/dist/tools-files.js +21 -11
  161. package/dist/tools-framework.js +16 -25
  162. package/dist/tools-goal.d.ts +2 -4
  163. package/dist/tools-goal.js +119 -16
  164. package/dist/tools-handoff.d.ts +3 -2
  165. package/dist/tools-handoff.js +53 -23
  166. package/dist/tools-lifecycle.js +1 -1
  167. package/dist/tools-memory.d.ts +0 -3
  168. package/dist/tools-memory.js +51 -6
  169. package/dist/tools-scheduler.js +20 -17
  170. package/dist/tools-todo.js +160 -148
  171. package/dist/vericify-bridge.d.ts +8 -0
  172. package/dist/vericify-bridge.js +18 -0
  173. package/dist/vericify-context.d.ts +4 -0
  174. package/dist/vericify-context.js +26 -5
  175. package/dist/vericify-native.d.ts +101 -0
  176. package/dist/vericify-native.js +221 -0
  177. package/dist/verification-worker.d.ts +2 -0
  178. package/dist/verification-worker.js +31 -0
  179. package/docs/mcp-tool-reference.md +50 -10
  180. package/docs/troubleshooting.md +29 -0
  181. package/package.json +19 -6
  182. package/scripts/dev/run-tests-isolated.mjs +165 -0
  183. package/scripts/dev/run_hermes_crispr_live.mjs +1 -0
  184. package/scripts/dev/test-isolation-guard.mjs +20 -0
  185. package/scripts/dev/test-preload.mjs +153 -0
  186. package/scripts/dev/test-source-inventory.mjs +31 -0
  187. package/docs/development.md +0 -46
package/dist/kanban.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type KanbanColumn = "backlog" | "blocked" | "in_progress" | "gated" | "done";
1
+ export type KanbanColumn = "backlog" | "blocked" | "in_progress" | "gated" | "done" | "cancelled";
2
2
  export interface AceKanbanCard {
3
3
  id: string;
4
4
  title: string;
@@ -10,8 +10,10 @@ export interface AceKanbanBoard {
10
10
  in_progress: AceKanbanCard[];
11
11
  gated: AceKanbanCard[];
12
12
  done: AceKanbanCard[];
13
+ cancelled: AceKanbanCard[];
13
14
  }
14
15
  export interface AceKanbanSnapshot {
16
+ refresh_scheduled?: boolean;
15
17
  version: number;
16
18
  generated_at: string;
17
19
  trigger: string;
@@ -21,6 +23,7 @@ export interface AceKanbanSnapshot {
21
23
  json_path: string;
22
24
  }
23
25
  export declare function renderKanbanHtml(snapshot: AceKanbanSnapshot): string;
26
+ export declare function formatKanbanRefresh(snapshot: AceKanbanSnapshot): string;
24
27
  export declare function refreshKanbanArtifacts(trigger: string): AceKanbanSnapshot;
25
28
  export declare function getOrRefreshKanbanSnapshot(trigger: string): AceKanbanSnapshot;
26
29
  export declare function shouldAutoRefreshKanbanForPath(path: string): boolean;
package/dist/kanban.js CHANGED
@@ -1,13 +1,13 @@
1
- import { ACE_ROOT_REL, ACE_TASKS_ROOT_REL, safeRead, safeWrite, wsPath } from "./helpers.js";
1
+ import { ACE_ROOT_REL, ACE_TASKS_ROOT_REL, safeRead, safeWrite, wsPath, resolveWorkspaceRoot, withWorkspaceRoot } from "./helpers.js";
2
2
  import { isReadError, slugify } from "./shared.js";
3
3
  import { readHandoffRegistry } from "./handoff-registry.js";
4
4
  import { readTodoState } from "./todo-state.js";
5
5
  /** Debounce threshold in milliseconds. Skip re-render if within this window. */
6
6
  const KANBAN_DEBOUNCE_MS = 2000;
7
- let lastKanbanRenderTimestamp = 0;
7
+ const renderState = new Map();
8
8
  const KANBAN_JSON_REL = "agent-state/kanban.json";
9
9
  const KANBAN_HTML_REL = "agent-state/kanban.html";
10
- const COLUMNS = ["backlog", "blocked", "in_progress", "gated", "done"];
10
+ const COLUMNS = ["backlog", "blocked", "in_progress", "gated", "done", "cancelled"];
11
11
  function nowIso() {
12
12
  return new Date().toISOString();
13
13
  }
@@ -18,6 +18,7 @@ function emptyBoard() {
18
18
  in_progress: [],
19
19
  gated: [],
20
20
  done: [],
21
+ cancelled: [],
21
22
  };
22
23
  }
23
24
  function escapeHtml(input) {
@@ -236,7 +237,7 @@ export function renderKanbanHtml(snapshot) {
236
237
  "@keyframes spin{to{transform:rotate(360deg)}}",
237
238
  "h1{margin:0;font-family:'Space Grotesk','Avenir Next',sans-serif;font-size:24px}",
238
239
  ".stamp{margin:0;font-size:12px;opacity:.75}",
239
- ".grid{display:grid;grid-template-columns:repeat(5,minmax(220px,1fr));gap:12px}",
240
+ ".grid{display:grid;grid-template-columns:repeat(6,minmax(220px,1fr));gap:12px}",
240
241
  ".column{background:rgba(255,255,255,.62);border:1px solid var(--line);border-radius:12px;min-height:220px}",
241
242
  ".column>header{display:flex;justify-content:space-between;align-items:center;padding:10px 12px;border-bottom:1px solid var(--line)}",
242
243
  ".column h2{margin:0;font-size:14px;text-transform:capitalize}",
@@ -265,16 +266,38 @@ export function renderKanbanHtml(snapshot) {
265
266
  "</html>",
266
267
  ].join("");
267
268
  }
269
+ export function formatKanbanRefresh(snapshot) {
270
+ return snapshot.refresh_scheduled
271
+ ? `Kanban refresh scheduled: ${snapshot.html_path} (last generated ${snapshot.generated_at})`
272
+ : `Kanban updated: ${snapshot.html_path}`;
273
+ }
268
274
  export function refreshKanbanArtifacts(trigger) {
269
- // Debounce: skip expensive re-render if within threshold
270
- const now = Date.now();
271
- if (now - lastKanbanRenderTimestamp < KANBAN_DEBOUNCE_MS) {
272
- // Return cached snapshot if available
273
- const cached = getOrRefreshKanbanSnapshot(trigger);
274
- if (cached.generated_at)
275
- return cached;
275
+ const root = resolveWorkspaceRoot();
276
+ const state = renderState.get(root) ?? { last: 0 };
277
+ const delay = KANBAN_DEBOUNCE_MS - (Date.now() - state.last);
278
+ const cached = readKanbanSnapshot();
279
+ if (delay > 0 && cached) {
280
+ if (state.timer)
281
+ clearTimeout(state.timer);
282
+ state.timer = setTimeout(() => withWorkspaceRoot(root, () => {
283
+ state.timer = undefined;
284
+ try {
285
+ renderKanbanNow(trigger);
286
+ }
287
+ catch (error) {
288
+ state.last = 0;
289
+ console.error(`Kanban refresh failed for ${root}: ${error instanceof Error ? error.message : String(error)}`);
290
+ }
291
+ }), delay);
292
+ state.timer.unref();
293
+ renderState.set(root, state);
294
+ return { ...cached, refresh_scheduled: true };
276
295
  }
277
- lastKanbanRenderTimestamp = now;
296
+ if (state.timer)
297
+ clearTimeout(state.timer);
298
+ return renderKanbanNow(trigger);
299
+ }
300
+ function renderKanbanNow(trigger) {
278
301
  const board = emptyBoard();
279
302
  addTodoCards(board);
280
303
  addStatusCard(board);
@@ -292,15 +315,17 @@ export function refreshKanbanArtifacts(trigger) {
292
315
  in_progress: board.in_progress.length,
293
316
  gated: board.gated.length,
294
317
  done: board.done.length,
318
+ cancelled: board.cancelled.length,
295
319
  },
296
320
  html_path: wsPath(KANBAN_HTML_REL),
297
321
  json_path: wsPath(KANBAN_JSON_REL),
298
322
  };
299
323
  safeWrite(KANBAN_HTML_REL, renderKanbanHtml(snapshot));
300
324
  safeWrite(KANBAN_JSON_REL, JSON.stringify(snapshot, null, 2));
325
+ renderState.set(resolveWorkspaceRoot(), { last: Date.now() });
301
326
  return snapshot;
302
327
  }
303
- export function getOrRefreshKanbanSnapshot(trigger) {
328
+ function readKanbanSnapshot() {
304
329
  const raw = safeRead(KANBAN_JSON_REL);
305
330
  if (!isReadError(raw)) {
306
331
  const parsed = tryParseJson(raw);
@@ -316,14 +341,17 @@ export function getOrRefreshKanbanSnapshot(trigger) {
316
341
  version,
317
342
  generated_at: generated,
318
343
  trigger: savedTrigger,
319
- board,
320
- counts,
344
+ board: { ...emptyBoard(), ...board },
345
+ counts: { ...Object.fromEntries(COLUMNS.map((key) => [key, 0])), ...counts },
321
346
  html_path: htmlPath,
322
347
  json_path: jsonPath,
323
348
  };
324
349
  }
325
350
  }
326
- return refreshKanbanArtifacts(trigger);
351
+ return undefined;
352
+ }
353
+ export function getOrRefreshKanbanSnapshot(trigger) {
354
+ return readKanbanSnapshot() ?? refreshKanbanArtifacts(trigger);
327
355
  }
328
356
  export function shouldAutoRefreshKanbanForPath(path) {
329
357
  const normalized = path.replace(/\\/g, "/").replace(/^\.?\//, "");
@@ -0,0 +1,64 @@
1
+ import { type LearnedArtifact } from './learning-records.js';
2
+ export { decodeLearned, type LearnedArtifact } from './learning-records.js';
3
+ import { type PlaybookScope, type PlaybookSection } from './store/repositories/playbook-repository.js';
4
+ import { type IAcePackedStore } from './store/types.js';
5
+ import type { GoalEngineDeps } from './goal-engine.js';
6
+ export interface LearnedPayload {
7
+ lessons?: Array<{
8
+ op: 'add' | 'edit' | 'retire';
9
+ bullet_id?: string;
10
+ section: PlaybookSection;
11
+ content: string;
12
+ scope: PlaybookScope;
13
+ episodes: string[];
14
+ retired_episodes?: string[];
15
+ }>;
16
+ skill?: {
17
+ op: 'add' | 'edit';
18
+ slug: string;
19
+ skill_md: string;
20
+ references?: Record<string, string>;
21
+ episodes: string[];
22
+ retired_episodes?: string[];
23
+ };
24
+ instruction?: {
25
+ role: string;
26
+ text: string;
27
+ episodes: string[];
28
+ };
29
+ }
30
+ export interface LearningAcceptance {
31
+ ok: boolean;
32
+ ids: string[];
33
+ errors: string[];
34
+ reason_code?: string;
35
+ options?: Array<{
36
+ tool: string;
37
+ args: Record<string, unknown>;
38
+ why: string;
39
+ }>;
40
+ }
41
+ export declare function listLearnedArtifacts(root: string): LearnedArtifact[];
42
+ export declare function withLearnedStore<T>(root: string, apply: (store: IAcePackedStore) => Promise<T>): Promise<T>;
43
+ export declare function saveLearnedArtifact(root: string, item: LearnedArtifact): Promise<void>;
44
+ export declare function updateLearnedArtifact(root: string, key: string, update: (item: LearnedArtifact) => LearnedArtifact | undefined): Promise<void>;
45
+ export declare function prepareLearned(root: string, payload: LearnedPayload, context: {
46
+ goal_id: string;
47
+ step_id: string;
48
+ attempt: number;
49
+ }, deps?: GoalEngineDeps): Promise<{
50
+ result: LearningAcceptance;
51
+ publish: () => Promise<void>;
52
+ }>;
53
+ export declare function acceptLearned(root: string, payload: LearnedPayload, context: {
54
+ goal_id: string;
55
+ step_id: string;
56
+ attempt: number;
57
+ }, deps?: GoalEngineDeps): Promise<LearningAcceptance>;
58
+ export declare function regenerateLearnedProjection(root: string): Promise<void>;
59
+ export declare function ensureLearningStep(root: string, force?: {
60
+ scope: PlaybookScope;
61
+ reason: string;
62
+ }): Promise<void>;
63
+ export declare function reopenLearningCorrection(root: string, errors: string[]): Promise<void>;
64
+ //# sourceMappingURL=learned-artifacts.d.ts.map
@@ -0,0 +1,321 @@
1
+ import { learningLifecycle } from './learning-lifecycle.js';
2
+ import { decodeLearned } from './learning-records.js';
3
+ export { decodeLearned } from './learning-records.js';
4
+ import { readLearningPolicy, learningScopeKey, canonicalLearningScopeKey } from './learning-policy.js';
5
+ /** Checked learned artifacts live in existing playbook / knowledge owners. */
6
+ import { createHash } from 'node:crypto';
7
+ import { parse, stringify } from 'yaml';
8
+ import { join } from 'node:path';
9
+ import { GoalRepository, defaultGoalBudgets } from './store/repositories/goal-repository.js';
10
+ import { createTaskPlan, amendTaskPlan } from './orchestrator-supervisor.js';
11
+ import { PlaybookRepository, withPlaybookTransaction } from './store/repositories/playbook-repository.js';
12
+ import { listStoreKeysSync, readStoreBlobSync, getWorkspaceStorePath } from './store/store-snapshot.js';
13
+ import { withStoreWriteCoordinator } from './store/write-coordinator.js';
14
+ import { openStore } from './store/ace-packed-store.js';
15
+ import { ContentSource } from './store/types.js';
16
+ import { operationalArtifactKey } from './store/store-artifacts.js';
17
+ import { listEpisodes, compactEpisode } from './host-learning.js';
18
+ import { exactRedGreen } from './experience-reflector.js';
19
+ import { proveLearnedSkill } from './skill-auditor.js';
20
+ import { ALL_AGENTS, DEFAULTS_SKILLS_ROOT } from './helpers/constants.js';
21
+ import { existsSync } from 'node:fs';
22
+ import { getShingles, calculateJaccard } from './semantic-hash.js';
23
+ const hash = (value) => createHash('sha256').update(JSON.stringify(value)).digest('hex').slice(0, 16);
24
+ function encode(item, front = {}) {
25
+ const { body, key: _, ...metadata } = item;
26
+ return `---\n${stringify({ ...front, origin: 'learned', status: item.status, label: item.label, episodes: item.episodes, checked_at: item.checked_at, ace_learning: metadata }).trim()}\n---\n${body}`;
27
+ }
28
+ export function listLearnedArtifacts(root) {
29
+ return listStoreKeysSync(root, 'knowledge/').filter(k => /(?:\/SKILL\.md|\/LEARNED\.md|\/references\/proposals\/[^/]+\.md)$/.test(k)).flatMap(key => { const item = decodeLearned(readStoreBlobSync(root, key) ?? '', key); return item ? [item] : []; });
30
+ }
31
+ export async function withLearnedStore(root, apply) {
32
+ return withStoreWriteCoordinator(getWorkspaceStorePath(root), async () => {
33
+ const store = await openStore(getWorkspaceStorePath(root));
34
+ try {
35
+ const result = await apply(store);
36
+ await store.commit();
37
+ return result;
38
+ }
39
+ catch (error) {
40
+ store.discardPending();
41
+ throw error;
42
+ }
43
+ finally {
44
+ await store.close({ commit: false });
45
+ }
46
+ }, { operation_label: 'learned_artifact' });
47
+ }
48
+ export async function saveLearnedArtifact(root, item) {
49
+ const oldText = readStoreBlobSync(root, item.key);
50
+ let front = {};
51
+ try {
52
+ front = parse(oldText?.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? '') ?? {};
53
+ }
54
+ catch { }
55
+ await withLearnedStore(root, store => writeItem(store, item, front));
56
+ }
57
+ export async function updateLearnedArtifact(root, key, update) {
58
+ await withLearnedStore(root, async (store) => {
59
+ const text = await store.getBlob(key);
60
+ if (!text)
61
+ return;
62
+ const item = decodeLearned(text, key);
63
+ if (!item)
64
+ return;
65
+ const next = update(item);
66
+ if (next)
67
+ await writeItem(store, next, frontBody(text).front);
68
+ });
69
+ }
70
+ async function writeItem(store, item, front = {}) {
71
+ const previous = decodeLearned(await store.getBlob(item.key) ?? '', item.key);
72
+ Object.assign(item, learningLifecycle(item, previous?.id === item.id ? previous : undefined, new Date().toISOString()));
73
+ const skill = item.key.match(/^knowledge\/skills\/(.+)\/([^/]+)$/), agent = item.key.match(/^knowledge\/agents\/([^/]+)\/([^/]+)$/);
74
+ if (skill)
75
+ await store.setSkillContent(skill[1], skill[2], encode(item, front), ContentSource.User);
76
+ else if (agent)
77
+ await store.setAgentInstruction(agent[1], agent[2], encode(item, front), ContentSource.User);
78
+ else
79
+ await store.setSkillContent(item.key.replace(/^knowledge\/skills\//, '').split('/references/')[0], `references/proposals/${item.proposal_id}.md`, encode(item, front), ContentSource.User);
80
+ }
81
+ const similarity = (a, b) => calculateJaccard(getShingles(a.toLowerCase().replace(/\s+/g, ' ').trim(), 3), getShingles(b.toLowerCase().replace(/\s+/g, ' ').trim(), 3));
82
+ const scoped = (scope) => !!(scope.role || scope.failure_signature || scope.paths?.length || scope.task_tags?.length);
83
+ function frontBody(text) {
84
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
85
+ try {
86
+ return { front: m ? parse(m[1]) ?? {} : {}, body: m ? text.slice(m[0].length) : text };
87
+ }
88
+ catch {
89
+ return { front: {}, body: text };
90
+ }
91
+ }
92
+ export async function prepareLearned(root, payload, context, deps = {}) {
93
+ const policy = readLearningPolicy(root);
94
+ const episodes = listEpisodes(root), catalog = listLearnedArtifacts(root), id = hash([context, payload]), at = new Date().toISOString();
95
+ const effects = [];
96
+ const outcome = { ok: true, ids: [], errors: [] };
97
+ const resolveEpisodes = (ids, min = 1) => {
98
+ const cited = episodes.filter(e => ids.includes(e.id));
99
+ const errors = [];
100
+ if (new Set(ids).size < min)
101
+ errors.push(`Cite at least ${min} distinct episodes.`);
102
+ if (cited.length !== new Set(ids).size)
103
+ errors.push('Cited episode unavailable: ' + ids.filter(i => !cited.some(e => e.id === i)).join(', '));
104
+ return { cited, errors };
105
+ };
106
+ const run = deps.runVerification ?? (await import('./goal-engine.js')).resolveGoalDeps({ workspaceRoot: root }).runVerification;
107
+ const demonstrated = (eps, checks) => eps.some(ep => exactRedGreen(ep.observed_runs ?? []).length || (ep.checks ?? []).some(c => !c.ok && checks.some(p => p.ok && p.command === c.command && p.cwd === c.cwd)));
108
+ const snapshots = (eps) => eps.map(compactEpisode);
109
+ if ((payload.lessons?.length ?? 0) > 3)
110
+ outcome.errors.push('At most three lessons per completion.');
111
+ const proposedLessons = [];
112
+ for (const lesson of (payload.lessons ?? []).slice(0, 3)) {
113
+ const resolved = resolveEpisodes(lesson.episodes), errors = [...resolved.errors];
114
+ const repo = new PlaybookRepository(root), previous = lesson.bullet_id ? repo.getBullet(lesson.bullet_id) : undefined;
115
+ if (lesson.content.length > 400 || !lesson.content.trim())
116
+ errors.push('Lesson must contain 1–400 characters.');
117
+ if (!scoped(lesson.scope))
118
+ errors.push('Lesson needs an explicit scope.');
119
+ if (lesson.op !== 'add' && !previous)
120
+ errors.push('Existing lesson not found.');
121
+ if (previous && lesson.op === 'edit' && (lesson.content.length < previous.content.length * 0.6 || previous.evidence_snapshots?.some(e => !lesson.episodes.includes(e.id) && !lesson.retired_episodes?.includes(e.id))))
122
+ errors.push('Edit must retain 60% of content and prior episode citations.');
123
+ const duplicate = repo.listBullets({ status: 'active' }).find(b => b.bullet_id !== lesson.bullet_id && JSON.stringify(b.scope) === JSON.stringify(lesson.scope) && similarity(b.content, lesson.content) >= 0.9);
124
+ if (duplicate && lesson.op === 'add')
125
+ errors.push(`Use or edit existing lesson ${duplicate.bullet_id}.`);
126
+ if (proposedLessons.some(p => JSON.stringify(p.scope) === JSON.stringify(lesson.scope) && similarity(p.content, lesson.content) >= 0.9))
127
+ errors.push('Duplicate lesson in this completion; submit one scoped delta.');
128
+ proposedLessons.push({ content: lesson.content, scope: lesson.scope });
129
+ const check = lesson.content.match(/Check:\s*([^\n]+)/)?.[1]?.trim();
130
+ const checks = [];
131
+ if (!errors.length && lesson.op !== 'retire' && check) {
132
+ const result = await run(check, root).catch(error => ({ ok: false, output: String(error) }));
133
+ checks.push({ command: check, cwd: root, ok: result.ok });
134
+ if (!result.ok)
135
+ errors.push('Lesson check failed: ' + result.output.slice(-1000));
136
+ }
137
+ effects.push(async () => {
138
+ const saved = await withPlaybookTransaction(root, r => {
139
+ if (previous && !errors.length) {
140
+ return r.saveBullet({ ...previous, content: lesson.content, section: lesson.section, scope: lesson.scope, evidence_refs: lesson.episodes, evidence_snapshots: snapshots(resolved.cited), status: lesson.op === 'retire' ? 'retired' : 'active', retired_reason: lesson.op === 'retire' ? lesson.content : undefined, label: demonstrated(resolved.cited, checks) ? 'demonstrated' : 'checked', validation_checks: checks, acceptance_errors: [] });
141
+ }
142
+ const existing = r.listBullets().find(b => b.provenance.run_id === `distill:${id}` && b.content.replace(/\s+/g, ' ').trim() === lesson.content.replace(/\s+/g, ' ').trim() && JSON.stringify(b.scope) === JSON.stringify(lesson.scope));
143
+ if (existing)
144
+ return existing;
145
+ return r.createBullet({ section: lesson.section, scope: lesson.scope, content: lesson.content, evidence_refs: lesson.episodes, evidence_snapshots: snapshots(resolved.cited), status: errors.length ? 'quarantined' : 'active', label: demonstrated(resolved.cited, checks) ? 'demonstrated' : 'checked', validation_checks: checks, acceptance_errors: errors, provenance: { source: 'operator', run_id: `distill:${id}`, note: JSON.stringify(context) } });
146
+ });
147
+ outcome.ids.push(saved.bullet_id);
148
+ });
149
+ outcome.errors.push(...errors);
150
+ }
151
+ if (payload.skill) {
152
+ const s = payload.skill, resolved = resolveEpisodes(s.episodes, 3), errors = [...resolved.errors], parts = frontBody(s.skill_md), scope = (typeof parts.front.scope === 'object' ? parts.front.scope : { task_tags: typeof parts.front.scope === 'string' ? [parts.front.scope] : [] });
153
+ const isMeta = s.slug === 'learn-from-work' && context.goal_id === 'G-learning' && new GoalRepository(root).getPlan(context.goal_id)?.steps.find(s => s.step_id === context.step_id)?.learning_kind === 'meta';
154
+ const artifactId = `learned/${/^[a-z0-9-]{3,48}$/.test(s.slug) ? s.slug : 'invalid-' + id}`, key = isMeta ? 'knowledge/skills/learn-from-work/LEARNED.md' : `knowledge/skills/${artifactId}/SKILL.md`, previous = catalog.find(x => x.key === key && x.status === 'active');
155
+ if (!/^[a-z0-9-]{3,48}$/.test(s.slug))
156
+ errors.push('Skill slug must be 3–48 lowercase letters, digits or hyphens.');
157
+ if (new Set(resolved.cited.map(e => e.goal_id)).size < 2)
158
+ errors.push('A skill needs episodes from at least two goals.');
159
+ if (parts.front.name !== s.slug)
160
+ errors.push('Skill frontmatter name must match its slug.');
161
+ if (!scoped(scope))
162
+ errors.push('Skill frontmatter must have a matching scope.');
163
+ if (!isMeta && existsSync(join(DEFAULTS_SKILLS_ROOT, s.slug)))
164
+ errors.push('Cannot shadow a packaged skill.');
165
+ if (s.op === 'add' && previous)
166
+ errors.push('Skill exists; submit an edit.');
167
+ if (s.op === 'edit' && !previous && !isMeta)
168
+ errors.push('Cannot edit a missing active skill.');
169
+ if (previous && s.skill_md.length < Number(previous.source_length ?? previous.body.length) * 0.6)
170
+ errors.push('Edit retains less than 60% of prior skill.');
171
+ if (previous?.episodes.some(e => !s.episodes.includes(e) && !s.retired_episodes?.includes(e)))
172
+ errors.push('Edit must retain prior episode citations.');
173
+ if (!isMeta && !previous && catalog.filter(x => x.kind === 'skill' && x.status === 'active').length >= policy.max_learned_skills)
174
+ errors.push('Learned skill cap reached; edit an existing skill.');
175
+ const refs = Object.entries(s.references ?? {});
176
+ if (refs.length > 3 || refs.some(([name, text]) => !/^[-a-zA-Z0-9_]+\.md$/.test(name) || text.split(/\r?\n/).length > 400))
177
+ errors.push('At most three safe Markdown reference files, 400 lines each.');
178
+ const proof = await proveLearnedSkill(s.skill_md, root, errors.length ? async () => ({ ok: false, output: 'Not executed: proposal metadata did not qualify' }) : run, previous?.body);
179
+ errors.push(...proof.errors);
180
+ const duplicate = catalog.find(a => a.id !== artifactId && a.kind === 'skill' && a.status === 'active' && similarity(String(frontBody(readStoreBlobSync(root, a.key) ?? '').front.description ?? ''), proof.description) >= 0.9);
181
+ if (duplicate)
182
+ errors.push('Use or edit existing skill ' + duplicate.id);
183
+ const item = { id: isMeta ? `distiller:learn-from-work:${id}` : artifactId, key, kind: isMeta ? 'distiller' : 'skill', status: errors.length ? 'quarantined' : isMeta ? 'proposed' : 'active', label: demonstrated(resolved.cited, proof.checks) ? 'demonstrated' : 'checked', episodes: s.episodes, evidence_snapshots: snapshots(resolved.cited), scope, body: parts.body, acceptance_errors: errors, checked_at: at, proposal_id: id, helpful_count: previous?.helpful_count ?? 0, harmful_count: previous?.harmful_count ?? 0, references: s.references, served_count: previous?.served_count, used_count: previous?.used_count, used_goals: previous?.used_goals, scored_attempts: previous?.scored_attempts, served_outcomes: previous?.served_outcomes, source_length: s.skill_md.length, validation_checks: proof.checks.map(c => ({ command: c.command, cwd: c.cwd, ok: c.ok, output: c.output.slice(-1000) })) };
184
+ if (isMeta && previous)
185
+ item.key = `knowledge/skills/learn-from-work/references/proposals/${id}.md`;
186
+ if (errors.length && previous) {
187
+ item.id = `${artifactId}:proposal:${id}`;
188
+ item.key = `knowledge/skills/${isMeta ? 'learn-from-work' : artifactId}/references/proposals/${id}.md`;
189
+ }
190
+ effects.push(() => withLearnedStore(root, async (store) => {
191
+ const latestText = await store.getBlob(key), latest = latestText ? decodeLearned(latestText, key) : undefined;
192
+ if (latest && (!previous || latest.proposal_id !== previous.proposal_id || latest.status !== 'active')) {
193
+ const error = 'Active skill changed during qualification; review the retained proposal against its current version.';
194
+ errors.push(error);
195
+ outcome.errors.push(error);
196
+ item.status = 'quarantined';
197
+ item.id = `${artifactId}:proposal:${id}`;
198
+ item.key = `knowledge/skills/${isMeta ? 'learn-from-work' : artifactId}/references/proposals/${id}.md`;
199
+ outcome.ids = outcome.ids.map(value => value === artifactId ? item.id : value);
200
+ }
201
+ else if (latest)
202
+ for (const field of ['helpful_count', 'harmful_count', 'served_count', 'used_count', 'used_goals', 'scored_attempts', 'served_outcomes'])
203
+ item[field] = latest[field];
204
+ if (previous && !errors.length && !isMeta) {
205
+ const history = previous.history ?? [];
206
+ const version = `${at.replace(/[^0-9]/g, '')}-${id}`;
207
+ await store.setSkillContent(artifactId, `references/history/${version}.md`, latestText ?? '', ContentSource.User);
208
+ item.history = [...history, version].slice(-3);
209
+ for (const old of history.slice(0, Math.max(0, history.length - 2)))
210
+ await store.delete(`knowledge/skills/${artifactId}/references/history/${old}.md`);
211
+ }
212
+ await writeItem(store, item, parts.front);
213
+ if (!errors.length) {
214
+ for (const [name, body] of refs)
215
+ await store.setSkillContent(artifactId, `references/${name}`, body, ContentSource.User);
216
+ await store.setSkillContent(artifactId, 'references/episodes.md', resolved.cited.map(e => `## ${e.id}\n${JSON.stringify(snapshots([e])[0], null, 2)}`).join('\n\n'), ContentSource.User);
217
+ }
218
+ }));
219
+ outcome.ids.push(item.id);
220
+ outcome.errors.push(...errors);
221
+ }
222
+ if (payload.instruction) {
223
+ const i = payload.instruction, resolved = resolveEpisodes(i.episodes), errors = [...resolved.errors];
224
+ if (!ALL_AGENTS.includes(i.role))
225
+ errors.push('Unknown role.');
226
+ if (!i.text.trim() || i.text.length > 1200)
227
+ errors.push('Instruction must contain 1–1200 characters.');
228
+ if (catalog.some(a => a.kind === 'instruction' && a.scope.role === i.role && a.status !== 'quarantined' && similarity(a.body, i.text) >= 0.9))
229
+ errors.push('Use the existing matching instruction proposal.');
230
+ const key = `knowledge/agents/${i.role}/LEARNED.md`, prior = catalog.find(a => a.key === key);
231
+ const item = { id: `instruction:${i.role}:${id}`, key: prior?.status === 'active' ? `knowledge/skills/learn-from-work/references/proposals/${id}.md` : key, kind: 'instruction', status: errors.length ? 'quarantined' : 'proposed', label: 'checked', episodes: i.episodes, evidence_snapshots: snapshots(resolved.cited), scope: { role: i.role }, body: i.text, acceptance_errors: errors, checked_at: at, proposal_id: id, helpful_count: 0, harmful_count: 0 };
232
+ effects.push(() => saveLearnedArtifact(root, item));
233
+ outcome.ids.push(item.id);
234
+ outcome.errors.push(...errors);
235
+ }
236
+ outcome.ok = !outcome.errors.length;
237
+ if (!outcome.ok) {
238
+ outcome.reason_code = 'learning_unchecked';
239
+ outcome.options = [{ tool: 'next_step', args: { goal_id: 'G-learning' }, why: 'Use the corrective learning step with the retained acceptance errors.' }];
240
+ }
241
+ return { result: outcome, publish: async () => { for (const effect of effects)
242
+ await effect(); outcome.ok = !outcome.errors.length; if (!outcome.ok) {
243
+ outcome.reason_code = 'learning_unchecked';
244
+ outcome.options = [{ tool: 'next_step', args: { goal_id: 'G-learning' }, why: 'Review the retained acceptance errors in the corrective learning assignment.' }];
245
+ } await regenerateLearnedProjection(root); } };
246
+ }
247
+ export async function acceptLearned(root, payload, context, deps = {}) {
248
+ const checked = await prepareLearned(root, payload, context, deps);
249
+ await checked.publish();
250
+ return checked.result;
251
+ }
252
+ export async function regenerateLearnedProjection(root) {
253
+ const bullets = new PlaybookRepository(root).listBullets(), artifacts = listLearnedArtifacts(root);
254
+ const body = ['# Learned in this workspace', '', ...bullets.filter(b => b.evidence_snapshots?.length).map(b => `- ${b.bullet_id} [${b.status}; ${b.label ?? 'candidate'}]: ${b.content}\n Episodes: ${b.evidence_snapshots.map(e => e.id).join(', ')}`), ...artifacts.map(a => `- ${a.id} [${a.status}; ${a.label}]\n Episodes: ${a.episodes.join(', ')}${a.acceptance_errors.length ? '\n Acceptance: ' + a.acceptance_errors.join('; ') : ''}`)].join('\n');
255
+ await withLearnedStore(root, store => store.setBlob(operationalArtifactKey('agent-state/LEARNED.md'), body));
256
+ }
257
+ export async function ensureLearningStep(root, force) {
258
+ const policy = readLearningPolicy(root);
259
+ if (!policy.enabled)
260
+ return;
261
+ const bullets = new PlaybookRepository(root).listBullets().filter(b => ['active', 'quarantined'].includes(b.status) && b.evidence_snapshots?.length);
262
+ const groups = new Map();
263
+ for (const b of bullets) {
264
+ const scope = learningScopeKey(b.scope);
265
+ groups.set(scope, [...(groups.get(scope) ?? []), b]);
266
+ }
267
+ const eligible = [...groups.entries()].filter(([, items]) => items.length >= policy.distill_min_items && new Set(items.flatMap(b => b.evidence_snapshots.map(e => e.goal_id))).size >= policy.distill_min_goals);
268
+ if (!eligible.length && !force)
269
+ return;
270
+ const repo = new GoalRepository(root), now = new Date().toISOString();
271
+ await withStoreWriteCoordinator(repo.planPath('G-learning'), async () => {
272
+ let goal = repo.getGoal('G-learning'), plan = repo.getPlan('G-learning');
273
+ if (plan?.steps.some(s => ['planned', 'running', 'blocked'].includes(s.status)))
274
+ return;
275
+ const cooling = (key) => plan?.steps.some(s => canonicalLearningScopeKey(s.learning_scope ?? '') === key && Date.now() - Date.parse(s.learning_opened_at ?? s.started_at ?? now) < policy.distill_cooldown_days * 86400000);
276
+ const chosen = force ? undefined : eligible.find(([key]) => !cooling(key));
277
+ if (!force && !chosen)
278
+ return;
279
+ const scope = force?.scope ?? chosen[1][0].scope, scopeKey = learningScopeKey(scope);
280
+ if (cooling(scopeKey))
281
+ return;
282
+ const items = force ? bullets.filter(b => b.scope.failure_signature === scope.failure_signature) : chosen[1];
283
+ const cited = [...items.flatMap(b => b.evidence_snapshots ?? []), ...(force ? listEpisodes(root).filter(e => e.failure_signature === scope.failure_signature) : [])];
284
+ const task = `Distill what this workspace has learned about ${scopeKey}.\nInputs: the referenced episodes and existing artifacts.\nProduce, via complete_step outcome.learned:\n(a) up to 3 lessons: trigger → action → check; keep commands, paths, error text verbatim; edit or retire existing lessons rather than duplicate them.\n(b) optionally one skill, only if ≥3 episodes share a reusable procedure.\n(c) optionally one role-instruction proposal, only if a rule applies to every task of that role.\nCite episode ids for every item. Do not invent steps the episodes do not show.\nLoad get_skill_instructions(learn-from-work).\n${force?.reason ?? ''}`;
285
+ const learning_inputs = { episodes: [...new Set(cited.map(e => e.id))], artifacts: [...items.map(b => b.bullet_id), ...listLearnedArtifacts(root).map(a => a.id)] };
286
+ if (!plan) {
287
+ plan = createTaskPlan({ task: 'Keep this workspace\'s learned artifacts current', steps: [] });
288
+ goal = { goal_id: 'G-learning', objective: plan.task, executor: 'host', status: 'executing', plan_id: plan.plan_id, success_contract: { expected_artifacts: [], required_evidence_refs: [], verification_commands: [], gate_ids: [], uncompilable_criteria: [] }, budgets: defaultGoalBudgets(), created_at: now, updated_at: now, tick_count: 0, replan_count: 0, stop_requested: false, escalations: [] };
289
+ }
290
+ plan = amendTaskPlan(plan, { append_steps: [{ role: 'coders', task, learning_inputs, learning_scope: scopeKey, learning_opened_at: now }] });
291
+ // G-learning is a retained container. New work after a closed batch
292
+ // gets a start time here; host restart/resume never resets deadlines.
293
+ if (goal.status === 'done' || !goal.started_at)
294
+ goal.started_at = now;
295
+ goal.status = 'executing';
296
+ goal.stop_requested = false;
297
+ repo.savePlan('G-learning', plan);
298
+ repo.saveGoal(goal);
299
+ }, { operation_label: 'learning_step' });
300
+ }
301
+ export async function reopenLearningCorrection(root, errors) {
302
+ const repo = new GoalRepository(root);
303
+ await withStoreWriteCoordinator(repo.planPath('G-learning'), async () => {
304
+ const g = repo.getGoal('G-learning');
305
+ let p = repo.getPlan('G-learning');
306
+ if (!g || !p || p.steps.some(s => ['planned', 'running'].includes(s.status)))
307
+ return;
308
+ if (g.budgets.replan_budget_explicit && g.replan_count >= g.budgets.max_replans) {
309
+ g.status = 'blocked';
310
+ g.reason_code = 'Learning correction budget reached: ' + errors.join('; ');
311
+ repo.saveGoal(g);
312
+ return;
313
+ }
314
+ p = amendTaskPlan(p, { append_steps: [{ role: 'coders', learning_scope: p.steps.at(-1)?.learning_scope ?? 'correction', learning_inputs: { rejected_steps: p.steps.at(-1) ? [p.steps.at(-1).step_id] : [] }, task: 'Correct the retained learned payload using the referenced acceptance errors and original payload.' }] });
315
+ g.replan_count++;
316
+ g.status = 'executing';
317
+ repo.savePlan('G-learning', p);
318
+ repo.saveGoal(g);
319
+ }, { operation_label: 'learning_correction' });
320
+ }
321
+ //# sourceMappingURL=learned-artifacts.js.map