principles-disciple 1.72.0 → 1.74.0

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 (319) hide show
  1. package/INSTALL.md +1 -3
  2. package/openclaw.plugin.json +10 -5
  3. package/package.json +17 -19
  4. package/scripts/acceptance-test.mjs +16 -73
  5. package/scripts/sync-plugin.mjs +382 -77
  6. package/src/commands/archive-impl.ts +2 -1
  7. package/src/commands/capabilities.ts +2 -2
  8. package/src/commands/context.ts +2 -2
  9. package/src/commands/disable-impl.ts +2 -1
  10. package/src/commands/evolution-status.ts +16 -16
  11. package/src/commands/export.ts +12 -67
  12. package/src/commands/pain.ts +91 -1
  13. package/src/commands/principle-rollback.ts +2 -1
  14. package/src/commands/promote-impl.ts +7 -43
  15. package/src/commands/rollback-impl.ts +2 -1
  16. package/src/commands/rollback.ts +2 -1
  17. package/src/commands/samples.ts +2 -1
  18. package/src/commands/thinking-os.ts +2 -1
  19. package/src/config/errors.ts +18 -2
  20. package/src/constants/diagnostician.ts +2 -2
  21. package/src/constants/tools.ts +2 -1
  22. package/src/core/__tests__/focus-history.test.ts +210 -0
  23. package/src/core/config.ts +1 -1
  24. package/src/core/correction-cue-learner.ts +2 -136
  25. package/src/core/correction-types.ts +16 -88
  26. package/src/core/dictionary.ts +19 -20
  27. package/src/core/empathy-keyword-matcher.ts +17 -289
  28. package/src/core/empathy-types.ts +18 -229
  29. package/src/core/event-log.ts +29 -132
  30. package/src/core/evolution-reducer.ts +21 -2
  31. package/src/core/evolution-types.ts +76 -464
  32. package/src/core/file-store.ts +80 -0
  33. package/src/core/focus-history.ts +228 -955
  34. package/src/core/local-worker-routing.ts +34 -314
  35. package/src/core/merge-gate-audit.ts +0 -195
  36. package/src/core/migration.ts +0 -1
  37. package/src/core/pain-diagnostic-gate.ts +154 -0
  38. package/src/core/pain-signal.ts +21 -138
  39. package/src/core/pain.ts +15 -88
  40. package/src/core/path-resolver.ts +0 -1
  41. package/src/core/paths.ts +0 -1
  42. package/src/core/pd-task-reconciler.ts +26 -115
  43. package/src/core/pd-task-service.ts +9 -9
  44. package/src/core/pd-task-types.ts +23 -127
  45. package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
  46. package/src/core/principle-compiler/code-validator.ts +15 -42
  47. package/src/core/principle-compiler/compiler.ts +100 -15
  48. package/src/core/principle-compiler/index.ts +5 -2
  49. package/src/core/principle-compiler/template-generator.ts +4 -104
  50. package/src/core/principle-injection.ts +10 -202
  51. package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
  52. package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
  53. package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
  54. package/src/core/principle-tree-ledger-adapter.ts +145 -0
  55. package/src/core/principle-tree-ledger.ts +8 -6
  56. package/src/core/reflection/reflection-context.ts +14 -109
  57. package/src/core/replay-engine.ts +8 -500
  58. package/src/core/rule-host-helpers.ts +5 -35
  59. package/src/core/rule-host-types.ts +10 -82
  60. package/src/core/rule-host.ts +6 -63
  61. package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
  62. package/src/core/session-tracker.ts +87 -101
  63. package/src/core/shadow-observation-registry.ts +19 -48
  64. package/src/core/trajectory.ts +3 -1
  65. package/src/core/workflow-funnel-loader.ts +62 -68
  66. package/src/core/workspace-context.ts +46 -0
  67. package/src/core/workspace-dir-service.ts +1 -1
  68. package/src/core/workspace-dir-validation.ts +18 -9
  69. package/src/hooks/AGENTS.md +1 -1
  70. package/src/hooks/gate-block-helper.ts +71 -64
  71. package/src/hooks/gate.ts +183 -31
  72. package/src/hooks/lifecycle.ts +30 -32
  73. package/src/hooks/llm.ts +60 -32
  74. package/src/hooks/pain.ts +297 -103
  75. package/src/hooks/prompt.ts +400 -440
  76. package/src/hooks/subagent.ts +2 -29
  77. package/src/i18n/commands.ts +2 -10
  78. package/src/index.ts +95 -85
  79. package/src/openclaw-sdk.ts +311 -0
  80. package/src/service/central-database.ts +8 -4
  81. package/src/service/evolution-queue-migration.ts +2 -1
  82. package/src/service/evolution-worker.ts +163 -1786
  83. package/src/service/internalization-trigger-adapter.ts +302 -0
  84. package/src/service/keyword-optimization-service.ts +4 -4
  85. package/src/service/monitoring-query-service.ts +1 -215
  86. package/src/service/queue-io.ts +60 -331
  87. package/src/service/runtime-summary-service.ts +59 -16
  88. package/src/service/subagent-workflow/index.ts +0 -41
  89. package/src/service/subagent-workflow/types.ts +9 -120
  90. package/src/service/subagent-workflow/workflow-store.ts +2 -119
  91. package/src/service/workflow-watchdog.ts +0 -43
  92. package/src/types/event-payload.ts +16 -74
  93. package/src/types/event-types.ts +38 -547
  94. package/src/types/hygiene-types.ts +7 -30
  95. package/src/types/principle-tree-schema.ts +20 -222
  96. package/src/types/queue.ts +15 -70
  97. package/src/types/runtime-summary.ts +5 -49
  98. package/src/utils/io.ts +8 -20
  99. package/src/utils/retry.ts +1 -1
  100. package/src/utils/shadow-fingerprint.ts +2 -2
  101. package/src/utils/workspace-resolver.ts +50 -0
  102. package/templates/langs/en/core/AGENTS.md +7 -7
  103. package/templates/langs/en/core/BOOT.md +1 -1
  104. package/templates/langs/en/core/HEARTBEAT.md +2 -2
  105. package/templates/langs/en/principles/THINKING_OS.md +3 -2
  106. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  107. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  108. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  109. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  110. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  111. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  112. package/templates/langs/en/skills/evolve-task/SKILL.md +3 -3
  113. package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
  114. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
  115. package/templates/langs/en/skills/pd-mentor/SKILL.md +2 -3
  116. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
  117. package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
  118. package/templates/langs/zh/core/AGENTS.md +7 -7
  119. package/templates/langs/zh/core/BOOT.md +1 -1
  120. package/templates/langs/zh/core/HEARTBEAT.md +2 -2
  121. package/templates/langs/zh/principles/THINKING_OS.md +3 -2
  122. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  123. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  124. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  125. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
  126. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  127. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  128. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  129. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
  130. package/templates/langs/zh/skills/evolve-task/SKILL.md +4 -4
  131. package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
  132. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
  133. package/templates/langs/zh/skills/pd-mentor/SKILL.md +2 -3
  134. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
  135. package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
  136. package/tests/build-artifacts.test.ts +1 -3
  137. package/tests/commands/evolution-status.test.ts +0 -118
  138. package/tests/core/bootstrap-rules.test.ts +1 -1
  139. package/tests/core/config.test.ts +1 -1
  140. package/tests/core/event-log.test.ts +35 -0
  141. package/tests/core/evolution-engine.test.ts +610 -0
  142. package/tests/core/file-store.test.ts +102 -0
  143. package/tests/core/focus-history.test.ts +203 -11
  144. package/tests/core/merge-gate-audit.test.ts +2 -169
  145. package/tests/core/migration.test.ts +7 -7
  146. package/tests/core/model-deployment-registry.test.ts +7 -1
  147. package/tests/core/model-training-registry.test.ts +19 -0
  148. package/tests/core/observability.test.ts +0 -1
  149. package/tests/core/pain-diagnostic-gate.test.ts +498 -0
  150. package/tests/core/pain.test.ts +0 -1
  151. package/tests/core/path-resolver.test.ts +1 -1
  152. package/tests/core/paths-refactor.test.ts +0 -22
  153. package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
  154. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
  155. package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
  156. package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
  157. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
  158. package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
  159. package/tests/core/reflection-context.test.ts +0 -14
  160. package/tests/core/replay-engine.test.ts +127 -215
  161. package/tests/core/rule-host-helpers.test.ts +2 -2
  162. package/tests/core/rule-implementation-runtime.test.ts +0 -27
  163. package/tests/core/workflow-funnel-loader.test.ts +162 -0
  164. package/tests/core/workspace-context.test.ts +2 -2
  165. package/tests/core/workspace-dir-validation.test.ts +8 -1
  166. package/tests/core-anti-growth.test.ts +191 -0
  167. package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
  168. package/tests/hooks/confirm-first-removal.test.ts +188 -0
  169. package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
  170. package/tests/hooks/gate-auto-correct.test.ts +665 -0
  171. package/tests/hooks/gate-no-path-write-tool.test.ts +172 -0
  172. package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
  173. package/tests/hooks/pain.test.ts +269 -12
  174. package/tests/hooks/prompt-characterization.test.ts +500 -0
  175. package/tests/hooks/prompt-size-guard.test.ts +32 -17
  176. package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
  177. package/tests/index.test.ts +94 -1
  178. package/tests/integration/auto-entry-gate.test.ts +248 -0
  179. package/tests/integration/internalization-trigger-guard.test.ts +69 -0
  180. package/tests/integration/m8-legacy-paths.test.ts +63 -0
  181. package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
  182. package/tests/plugin-config-resolution-cutover.test.ts +359 -0
  183. package/tests/runtime-v2-discovery-guard.test.ts +154 -0
  184. package/tests/service/central-database.test.ts +457 -0
  185. package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
  186. package/tests/service/evolution-worker.timeout.test.ts +11 -129
  187. package/tests/service/internalization-trigger-adapter.test.ts +251 -0
  188. package/tests/service/monitoring-query-service.test.ts +1 -47
  189. package/tests/service/queue-io.test.ts +1 -62
  190. package/tests/service/runtime-summary-service.test.ts +3 -1
  191. package/tests/service/workflow-watchdog.test.ts +0 -91
  192. package/tests/utils/file-lock.test.ts +5 -3
  193. package/tests/utils/session-key.test.ts +52 -0
  194. package/tests/utils/subagent-probe.test.ts +48 -1
  195. package/vitest.config.ts +4 -11
  196. package/.planning/codebase/ARCHITECTURE.md +0 -157
  197. package/.planning/codebase/CONCERNS.md +0 -145
  198. package/.planning/codebase/CONVENTIONS.md +0 -148
  199. package/.planning/codebase/INTEGRATIONS.md +0 -81
  200. package/.planning/codebase/STACK.md +0 -87
  201. package/.planning/codebase/STRUCTURE.md +0 -193
  202. package/.planning/codebase/TESTING.md +0 -243
  203. package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
  204. package/docs/COMMAND_REFERENCE.md +0 -76
  205. package/docs/COMMAND_REFERENCE_EN.md +0 -79
  206. package/scripts/build-web.mjs +0 -46
  207. package/scripts/diagnose-nocturnal.mjs +0 -537
  208. package/scripts/seed-nocturnal-scenarios.mjs +0 -384
  209. package/src/commands/nocturnal-review.ts +0 -322
  210. package/src/commands/nocturnal-rollout.ts +0 -790
  211. package/src/commands/nocturnal-train.ts +0 -986
  212. package/src/commands/pd-reflect.ts +0 -88
  213. package/src/core/adaptive-thresholds.ts +0 -478
  214. package/src/core/diagnostician-task-store.ts +0 -192
  215. package/src/core/nocturnal-arbiter.ts +0 -715
  216. package/src/core/nocturnal-artifact-lineage.ts +0 -116
  217. package/src/core/nocturnal-artificer.ts +0 -257
  218. package/src/core/nocturnal-candidate-scoring.ts +0 -530
  219. package/src/core/nocturnal-compliance.ts +0 -1146
  220. package/src/core/nocturnal-dataset.ts +0 -763
  221. package/src/core/nocturnal-executability.ts +0 -428
  222. package/src/core/nocturnal-export.ts +0 -499
  223. package/src/core/nocturnal-paths.ts +0 -240
  224. package/src/core/nocturnal-reasoning-deriver.ts +0 -343
  225. package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
  226. package/src/core/nocturnal-snapshot-contract.ts +0 -99
  227. package/src/core/nocturnal-trajectory-extractor.ts +0 -512
  228. package/src/core/nocturnal-trinity-types.ts +0 -218
  229. package/src/core/nocturnal-trinity.ts +0 -2680
  230. package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
  231. package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
  232. package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
  233. package/src/http/principles-console-route.ts +0 -709
  234. package/src/service/central-health-service.ts +0 -49
  235. package/src/service/central-overview-service.ts +0 -138
  236. package/src/service/control-ui-query-service.ts +0 -900
  237. package/src/service/cooldown-strategy.ts +0 -97
  238. package/src/service/evolution-pain-context.ts +0 -79
  239. package/src/service/evolution-query-service.ts +0 -407
  240. package/src/service/health-query-service.ts +0 -1038
  241. package/src/service/nocturnal-config.ts +0 -214
  242. package/src/service/nocturnal-runtime.ts +0 -734
  243. package/src/service/nocturnal-service.ts +0 -1605
  244. package/src/service/nocturnal-target-selector.ts +0 -545
  245. package/src/service/sleep-cycle.ts +0 -157
  246. package/src/service/startup-reconciler.ts +0 -112
  247. package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
  248. package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
  249. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
  250. package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
  251. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
  252. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
  253. package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
  254. package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
  255. package/src/tools/write-pain-flag.ts +0 -215
  256. package/templates/langs/en/skills/plan-script/SKILL.md +0 -32
  257. package/templates/langs/zh/skills/plan-script/SKILL.md +0 -32
  258. package/tests/commands/nocturnal-review.test.ts +0 -448
  259. package/tests/commands/nocturnal-train.test.ts +0 -97
  260. package/tests/commands/pd-reflect.test.ts +0 -49
  261. package/tests/core/adaptive-thresholds.test.ts +0 -261
  262. package/tests/core/nocturnal-arbiter.test.ts +0 -559
  263. package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
  264. package/tests/core/nocturnal-artificer.test.ts +0 -241
  265. package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
  266. package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
  267. package/tests/core/nocturnal-compliance.test.ts +0 -646
  268. package/tests/core/nocturnal-dataset.test.ts +0 -892
  269. package/tests/core/nocturnal-e2e.test.ts +0 -234
  270. package/tests/core/nocturnal-executability.test.ts +0 -357
  271. package/tests/core/nocturnal-export.test.ts +0 -517
  272. package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
  273. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
  274. package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
  275. package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
  276. package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
  277. package/tests/core/nocturnal-trinity.test.ts +0 -2053
  278. package/tests/core/pain-auto-repair.test.ts +0 -96
  279. package/tests/core/pain-integration.test.ts +0 -510
  280. package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
  281. package/tests/http/principles-console-route.test.ts +0 -162
  282. package/tests/integration/chaos-resilience.test.ts +0 -348
  283. package/tests/integration/empathy-workflow-integration.test.ts +0 -626
  284. package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
  285. package/tests/service/control-ui-query-service.test.ts +0 -121
  286. package/tests/service/cooldown-strategy.test.ts +0 -164
  287. package/tests/service/data-endpoints-regression.test.ts +0 -834
  288. package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
  289. package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
  290. package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
  291. package/tests/service/nocturnal-runtime.test.ts +0 -473
  292. package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
  293. package/tests/service/nocturnal-target-selector.test.ts +0 -615
  294. package/tests/service/startup-reconciler.test.ts +0 -148
  295. package/tests/tools/write-pain-flag.test.ts +0 -358
  296. package/ui/src/App.tsx +0 -45
  297. package/ui/src/api.ts +0 -220
  298. package/ui/src/charts.tsx +0 -955
  299. package/ui/src/components/ErrorState.tsx +0 -6
  300. package/ui/src/components/Loading.tsx +0 -13
  301. package/ui/src/components/ProtectedRoute.tsx +0 -12
  302. package/ui/src/components/Shell.tsx +0 -91
  303. package/ui/src/components/WorkspaceConfig.tsx +0 -178
  304. package/ui/src/components/index.ts +0 -5
  305. package/ui/src/context/auth.tsx +0 -80
  306. package/ui/src/context/theme.tsx +0 -66
  307. package/ui/src/hooks/useAutoRefresh.ts +0 -39
  308. package/ui/src/i18n/ui.ts +0 -473
  309. package/ui/src/main.tsx +0 -16
  310. package/ui/src/pages/EvolutionPage.tsx +0 -333
  311. package/ui/src/pages/FeedbackPage.tsx +0 -138
  312. package/ui/src/pages/GateMonitorPage.tsx +0 -136
  313. package/ui/src/pages/LoginPage.tsx +0 -89
  314. package/ui/src/pages/OverviewPage.tsx +0 -599
  315. package/ui/src/pages/SamplesPage.tsx +0 -174
  316. package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
  317. package/ui/src/styles.css +0 -2020
  318. package/ui/src/types.ts +0 -384
  319. package/ui/src/utils/format.ts +0 -15
@@ -0,0 +1,102 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+ import { JsonFileStore } from '../../src/core/file-store.js';
6
+ import { safeRmDir } from '../test-utils.js';
7
+
8
+ function tmpDir(): string {
9
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'pd-filestore-test-'));
10
+ }
11
+
12
+ interface TestData {
13
+ count: number;
14
+ items: string[];
15
+ }
16
+
17
+ describe('JsonFileStore', () => {
18
+ let dir: string;
19
+ let filePath: string;
20
+ let store: JsonFileStore<TestData>;
21
+
22
+ beforeEach(() => {
23
+ dir = tmpDir();
24
+ filePath = path.join(dir, 'test.json');
25
+ store = new JsonFileStore<TestData>(filePath, () => ({ count: 0, items: [] }));
26
+ });
27
+
28
+ afterEach(() => {
29
+ safeRmDir(dir);
30
+ });
31
+
32
+ describe('load', () => {
33
+ it('returns default data when file does not exist', () => {
34
+ const data = store.load();
35
+ expect(data).toEqual({ count: 0, items: [] });
36
+ });
37
+
38
+ it('returns parsed JSON when file exists', () => {
39
+ fs.writeFileSync(filePath, JSON.stringify({ count: 42, items: ['a', 'b'] }), 'utf8');
40
+ const data = store.load();
41
+ expect(data).toEqual({ count: 42, items: ['a', 'b'] });
42
+ });
43
+
44
+ it('returns default data when file is empty', () => {
45
+ fs.writeFileSync(filePath, '', 'utf8');
46
+ const data = store.load();
47
+ expect(data).toEqual({ count: 0, items: [] });
48
+ });
49
+
50
+ it('returns default data when file has malformed JSON', () => {
51
+ fs.writeFileSync(filePath, '{broken json', 'utf8');
52
+ const data = store.load();
53
+ expect(data).toEqual({ count: 0, items: [] });
54
+ });
55
+ });
56
+
57
+ describe('save', () => {
58
+ it('writes data to file atomically', () => {
59
+ store.save({ count: 10, items: ['x'] });
60
+ const raw = fs.readFileSync(filePath, 'utf8');
61
+ expect(JSON.parse(raw)).toEqual({ count: 10, items: ['x'] });
62
+ });
63
+
64
+ it('overwrites existing file', () => {
65
+ store.save({ count: 1, items: [] });
66
+ store.save({ count: 2, items: ['y'] });
67
+ const raw = fs.readFileSync(filePath, 'utf8');
68
+ expect(JSON.parse(raw)).toEqual({ count: 2, items: ['y'] });
69
+ });
70
+ });
71
+
72
+ describe('mutate', () => {
73
+ it('performs read-modify-write and returns result', () => {
74
+ const result = store.mutate((d) => {
75
+ d.count += 5;
76
+ d.items.push('new');
77
+ return d.count;
78
+ });
79
+ expect(result).toBe(5);
80
+ const loaded = store.load();
81
+ expect(loaded.count).toBe(5);
82
+ expect(loaded.items).toEqual(['new']);
83
+ });
84
+
85
+ it('does not write when mutate function throws', () => {
86
+ expect(() => {
87
+ store.mutate(() => {
88
+ throw new Error('boom');
89
+ });
90
+ }).toThrow('boom');
91
+ const loaded = store.load();
92
+ expect(loaded).toEqual({ count: 0, items: [] });
93
+ });
94
+
95
+ it('preserves existing data across mutate calls', () => {
96
+ store.mutate((d) => { d.count = 1; });
97
+ store.mutate((d) => { d.count += 1; });
98
+ const loaded = store.load();
99
+ expect(loaded.count).toBe(2);
100
+ });
101
+ });
102
+ });
@@ -7,7 +7,7 @@ import {
7
7
  cleanupHistory,
8
8
  getHistoryVersions,
9
9
  extractSummary,
10
- // 工作记忆相关函数
10
+ cleanupStaleInfo,
11
11
  extractWorkingMemory,
12
12
  parseWorkingMemorySection,
13
13
  mergeWorkingMemory,
@@ -25,6 +25,11 @@ vi.mock('fs', () => ({
25
25
  writeFileSync: vi.fn(),
26
26
  statSync: vi.fn(),
27
27
  unlinkSync: vi.fn(),
28
+ promises: {
29
+ readdir: vi.fn(),
30
+ stat: vi.fn(),
31
+ readFile: vi.fn(),
32
+ }
28
33
  }));
29
34
 
30
35
  describe('focus-history', () => {
@@ -164,30 +169,83 @@ describe('focus-history', () => {
164
169
  });
165
170
 
166
171
  describe('getHistoryVersions', () => {
167
- it('should return empty array if no history', () => {
168
- vi.mocked(fs.existsSync).mockReturnValue(false);
172
+ it('should return empty array if no history', async () => {
173
+ const err = new Error('ENOENT: no such file or directory') as NodeJS.ErrnoException;
174
+ err.code = 'ENOENT';
175
+ vi.mocked(fs.promises.readdir).mockRejectedValue(err);
169
176
 
170
- const result = getHistoryVersions(mockFocusPath, 3);
177
+ const result = await getHistoryVersions(mockFocusPath, 3);
171
178
 
172
179
  expect(result).toEqual([]);
173
180
  });
174
181
 
175
- it('should return history versions sorted by mtime', () => {
182
+ it('should return history versions sorted by mtime', async () => {
176
183
  vi.mocked(fs.existsSync).mockReturnValue(true);
177
- vi.mocked(fs.readdirSync).mockReturnValue([
184
+ // fs.promises.readdir mock
185
+ vi.mocked(fs.promises.readdir).mockResolvedValue([
178
186
  'CURRENT_FOCUS.v1.2026-03-10.md',
179
187
  'CURRENT_FOCUS.v2.2026-03-11.md',
180
188
  ] as any);
181
- vi.mocked(fs.statSync)
182
- .mockReturnValueOnce({ mtime: new Date('2026-03-10') } as any)
183
- .mockReturnValueOnce({ mtime: new Date('2026-03-11') } as any);
184
- vi.mocked(fs.readFileSync).mockReturnValue('history content');
189
+ // fs.promises.stat mock
190
+ vi.mocked(fs.promises.stat)
191
+ .mockResolvedValueOnce({ mtime: new Date('2026-03-10') } as any)
192
+ .mockResolvedValueOnce({ mtime: new Date('2026-03-11') } as any);
193
+ // fs.promises.readFile mock
194
+ vi.mocked(fs.promises.readFile).mockResolvedValue('history content');
185
195
 
186
- const result = getHistoryVersions(mockFocusPath, 3);
196
+ const result = await getHistoryVersions(mockFocusPath, 3);
187
197
 
188
198
  expect(result.length).toBe(2);
189
199
  expect(result[0]).toBe('history content');
190
200
  });
201
+
202
+ it('should return empty array if readdir throws ENOENT', async () => {
203
+ const err = new Error('ENOENT') as NodeJS.ErrnoException;
204
+ err.code = 'ENOENT';
205
+ vi.mocked(fs.promises.readdir).mockRejectedValue(err);
206
+
207
+ const result = await getHistoryVersions(mockFocusPath, 3);
208
+
209
+ expect(result).toEqual([]);
210
+ });
211
+
212
+ it('should handle partial stat failures', async () => {
213
+ vi.mocked(fs.promises.readdir).mockResolvedValue([
214
+ 'CURRENT_FOCUS.v1.md',
215
+ 'CURRENT_FOCUS.v2.md',
216
+ ] as any);
217
+
218
+ // First fails, second succeeds
219
+ vi.mocked(fs.promises.stat)
220
+ .mockRejectedValueOnce(new Error('Deleted'))
221
+ .mockResolvedValueOnce({ mtime: new Date() } as any);
222
+
223
+ vi.mocked(fs.promises.readFile).mockResolvedValue('content');
224
+
225
+ const result = await getHistoryVersions(mockFocusPath, 3);
226
+
227
+ expect(result.length).toBe(1);
228
+ expect(result[0]).toBe('content');
229
+ });
230
+
231
+ it('should handle partial readFile failures', async () => {
232
+ vi.mocked(fs.promises.readdir).mockResolvedValue([
233
+ 'CURRENT_FOCUS.v1.md',
234
+ 'CURRENT_FOCUS.v2.md',
235
+ ] as any);
236
+
237
+ vi.mocked(fs.promises.stat).mockResolvedValue({ mtime: new Date() } as any);
238
+
239
+ // First fails, second succeeds
240
+ vi.mocked(fs.promises.readFile)
241
+ .mockRejectedValueOnce(new Error('Read error'))
242
+ .mockResolvedValueOnce('content v2');
243
+
244
+ const result = await getHistoryVersions(mockFocusPath, 3);
245
+
246
+ expect(result.length).toBe(1);
247
+ expect(result[0]).toBe('content v2');
248
+ });
191
249
  });
192
250
 
193
251
  describe('extractSummary', () => {
@@ -567,6 +625,140 @@ ${Array.from({ length: 40 }, (_, i) => `| Item ${i + 1} | Value ${i + 1} |`).joi
567
625
  });
568
626
  });
569
627
 
628
+ describe('cleanupStaleInfo', () => {
629
+ it('should filter stale file artifacts when workspaceDir is provided', () => {
630
+ const content = `# 🎯 CURRENT_FOCUS
631
+
632
+ ## 🧠 Working Memory
633
+
634
+ | 文件路径 | 操作 | 描述 |
635
+ |----------|------|------|
636
+ | \`src/exists.ts\` | modified | exists |
637
+ | \`src/missing.ts\` | modified | missing |
638
+ | \`src/also-missing.ts\` | modified | also missing |
639
+
640
+ ### ➡️ 下一步行动
641
+
642
+ 1. Next action`;
643
+
644
+ vi.mocked(fs.existsSync).mockImplementation((p: any) => {
645
+ const path = String(p);
646
+ return path.includes('exists');
647
+ });
648
+
649
+ const result = cleanupStaleInfo(content, '/workspace');
650
+
651
+ expect(result).toContain('exists.ts');
652
+ expect(result).not.toContain('missing.ts');
653
+ expect(result).not.toContain('also-missing.ts');
654
+ });
655
+
656
+ it('should keep table state after skipping a missing file row', () => {
657
+ const content = `# 🎯 CURRENT_FOCUS
658
+
659
+ ## 🧠 Working Memory
660
+
661
+ | 文件路径 | 操作 | 描述 |
662
+ |----------|------|------|
663
+ | \`src/stale-a.ts\` | modified | stale |
664
+ | \`src/stale-b.ts\` | modified | stale |
665
+ | \`src/valid.ts\` | modified | valid |
666
+
667
+ ### ➡️ 下一步行动
668
+
669
+ 1. Action`;
670
+
671
+ vi.mocked(fs.existsSync).mockImplementation((p: any) => {
672
+ const path = String(p);
673
+ return path.includes('valid');
674
+ });
675
+
676
+ const result = cleanupStaleInfo(content, '/workspace');
677
+
678
+ expect(result).toContain('valid.ts');
679
+ expect(result).not.toContain('stale-a.ts');
680
+ expect(result).not.toContain('stale-b.ts');
681
+ expect(result).toContain('### ➡️ 下一步行动');
682
+ });
683
+
684
+ it('should preserve table header when filtering artifacts', () => {
685
+ const content = `# 🎯 CURRENT_FOCUS
686
+
687
+ ## 🧠 Working Memory
688
+
689
+ | 文件路径 | 操作 | 描述 |
690
+ |----------|------|------|
691
+ | \`src/stale.ts\` | modified | stale |
692
+
693
+ ### ➡️ 下一步行动
694
+
695
+ 1. Action`;
696
+
697
+ vi.mocked(fs.existsSync).mockReturnValue(false);
698
+
699
+ const result = cleanupStaleInfo(content, '/workspace');
700
+
701
+ expect(result).toContain('| 文件路径 | 操作 | 描述 |');
702
+ expect(result).not.toContain('stale.ts');
703
+ expect(result).toContain('### ➡️ 下一步行动');
704
+ });
705
+ });
706
+
707
+ describe('cleanupStaleInfo I/O integration (autoCompressFocus prefilter path)', () => {
708
+ it('should filter stale artifacts when workspaceDir is provided', () => {
709
+ const focusContent = `# 🎯 CURRENT_FOCUS
710
+
711
+ **版本**: v3
712
+ **更新**: 2026-01-01
713
+
714
+ ## 🔄 当前任务
715
+
716
+ ${Array.from({ length: 150 }, (_, i) => `- [x] Task ${i + 1}`).join('\n')}
717
+
718
+ ## 🧠 Working Memory
719
+
720
+ | 文件路径 | 操作 | 描述 |
721
+ |----------|------|------|
722
+ | \`src/missing-a.ts\` | modified | stale |
723
+ | \`src/existing.ts\` | modified | valid |
724
+ | \`src/missing-b.ts\` | modified | stale |
725
+
726
+ ### ➡️ 下一步行动
727
+
728
+ 1. Next action`;
729
+
730
+ vi.spyOn(fs, 'existsSync').mockImplementation((p: unknown) => {
731
+ const s = String(p);
732
+ if (s.includes('existing')) return true;
733
+ return false;
734
+ });
735
+
736
+ const result = cleanupStaleInfo(focusContent, '/workspace');
737
+
738
+ expect(result).toContain('existing.ts');
739
+ expect(result).not.toContain('missing-a.ts');
740
+ expect(result).not.toContain('missing-b.ts');
741
+ });
742
+
743
+ it('should not filter when workspaceDir is omitted', () => {
744
+ const focusContent = `# 🎯 CURRENT_FOCUS
745
+
746
+ ## 🧠 Working Memory
747
+
748
+ | 文件路径 | 操作 | 描述 |
749
+ |----------|------|------|
750
+ | \`src/stale.ts\` | modified | stale |
751
+
752
+ ### ➡️ 下一步行动
753
+
754
+ 1. Action`;
755
+
756
+ const result = cleanupStaleInfo(focusContent);
757
+
758
+ expect(result).toContain('stale.ts');
759
+ });
760
+ });
761
+
570
762
  // ============================================================================
571
763
  // 端到端测试:压缩时文件路径落盘
572
764
  // ============================================================================
@@ -6,30 +6,9 @@ import {
6
6
  formatMergeGateAuditReport,
7
7
  runMergeGateAudit,
8
8
  } from '../../src/core/merge-gate-audit.js';
9
- import type { NocturnalArtifact } from '../../src/core/nocturnal-arbiter.js';
10
- import {
11
- registerSample,
12
- updateReviewStatus,
13
- } from '../../src/core/nocturnal-dataset.js';
14
- import { appendArtifactLineageRecord } from '../../src/core/nocturnal-artifact-lineage.js';
15
- import { exportORPOSamples } from '../../src/core/nocturnal-export.js';
16
9
  import { createImplementationAssetDir, getImplementationAssetRoot } from '../../src/core/code-implementation-storage.js';
17
10
  import { safeRmDir } from '../test-utils.js';
18
11
 
19
- function makeArtifact(overrides: Partial<NocturnalArtifact> = {}): NocturnalArtifact {
20
- return {
21
- artifactId: 'artifact-1',
22
- sessionId: 'session-1',
23
- principleId: 'T-08',
24
- sourceSnapshotRef: 'snapshot-1',
25
- badDecision: 'Retried without checking state',
26
- betterDecision: 'Inspect state before retrying',
27
- rationale: 'Evidence first.',
28
- createdAt: '2026-04-12T09:00:00.000Z',
29
- ...overrides,
30
- };
31
- }
32
-
33
12
  describe('merge-gate-audit', () => {
34
13
  let tempDir: string;
35
14
  let workspaceDir: string;
@@ -47,50 +26,13 @@ describe('merge-gate-audit', () => {
47
26
  safeRmDir(tempDir);
48
27
  });
49
28
 
50
- function registerApprovedArtifact(artifactId = 'artifact-1'): string {
51
- const artifact = makeArtifact({ artifactId });
52
- const artifactPath = path.join(
53
- workspaceDir,
54
- '.state',
55
- 'nocturnal',
56
- 'samples',
57
- `${artifactId}.json`,
58
- );
59
- fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
60
- fs.writeFileSync(artifactPath, JSON.stringify(artifact, null, 2), 'utf-8');
61
-
62
- const record = registerSample(workspaceDir, artifact, artifactPath, 'gpt-4').record;
63
- updateReviewStatus(
64
- workspaceDir,
65
- record.sampleFingerprint,
66
- 'approved_for_training',
67
- 'approved for merge gate audit',
68
- );
69
-
70
- appendArtifactLineageRecord(workspaceDir, {
71
- artifactKind: 'behavioral-sample',
72
- artifactId: record.artifactId,
73
- principleId: record.principleId,
74
- ruleId: null,
75
- sessionId: record.sessionId,
76
- sourceSnapshotRef: record.sourceSnapshotRef,
77
- sourcePainIds: ['pain-1'],
78
- sourceGateBlockIds: ['gate-1'],
79
- storagePath: artifactPath,
80
- implementationId: null,
81
- createdAt: record.createdAt,
82
- });
83
-
84
- return record.sampleFingerprint;
85
- }
86
-
87
29
  it('returns defer when audit surfaces are not populated yet', () => {
88
30
  const report = runMergeGateAudit(workspaceDir, stateDir);
89
31
 
90
32
  expect(report.overallStatus).toBe('defer');
91
33
  expect(report.checks.find((check) => check.id === 'pain_flag_path_contract')?.status).toBe('pass');
92
34
  expect(report.checks.find((check) => check.id === 'queue_path_contract')?.status).toBe('pass');
93
- expect(report.checks.find((check) => check.id === 'runtime_adapter_contract')?.status).toBe('pass');
35
+ expect(report.checks.find((check) => check.id === 'replay_evidence_integrity')?.status).toBe('defer');
94
36
  expect(report.counts.defer).toBeGreaterThan(0);
95
37
  });
96
38
 
@@ -129,115 +71,6 @@ describe('merge-gate-audit', () => {
129
71
  expect(replayCheck?.status).toBe('block');
130
72
  });
131
73
 
132
- it('passes populated dataset, lineage, export, and replay evidence surfaces', () => {
133
- registerApprovedArtifact('artifact-pass');
134
- const exportResult = exportORPOSamples(workspaceDir, 'gpt-4');
135
- expect(exportResult.success).toBe(true);
136
-
137
- createImplementationAssetDir(stateDir, 'IMPL-1', '1.0.0');
138
- const replayDir = path.join(getImplementationAssetRoot(stateDir, 'IMPL-1'), 'replays');
139
- fs.mkdirSync(replayDir, { recursive: true });
140
- fs.writeFileSync(
141
- path.join(replayDir, 'good-report.json'),
142
- JSON.stringify(
143
- {
144
- overallDecision: 'pass',
145
- replayResults: {
146
- painNegative: { total: 1, passed: 1, failed: 0, details: [] },
147
- successPositive: { total: 0, passed: 0, failed: 0, details: [] },
148
- principleAnchor: { total: 0, passed: 0, failed: 0, details: [] },
149
- },
150
- blockers: [],
151
- generatedAt: '2026-04-12T09:00:00.000Z',
152
- implementationId: 'IMPL-1',
153
- sampleFingerprints: ['sample-1'],
154
- evidenceSummary: {
155
- evidenceStatus: 'observed',
156
- totalSamples: 1,
157
- classifiedCounts: {
158
- painNegative: 1,
159
- successPositive: 0,
160
- principleAnchor: 0,
161
- },
162
- },
163
- },
164
- null,
165
- 2,
166
- ),
167
- 'utf-8',
168
- );
169
-
170
- const report = runMergeGateAudit(workspaceDir, stateDir);
171
-
172
- expect(report.overallStatus).toBe('pass');
173
- expect(report.counts.block).toBe(0);
174
- expect(report.counts.defer).toBe(0);
175
- expect(formatMergeGateAuditReport(report)).toContain('Overall Status: PASS');
176
- });
177
-
178
- it('blocks when dataset artifacts are missing', () => {
179
- const artifactPath = path.join(
180
- workspaceDir,
181
- '.state',
182
- 'nocturnal',
183
- 'samples',
184
- 'artifact-missing.json',
185
- );
186
- fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
187
- const artifact = makeArtifact({ artifactId: 'artifact-missing' });
188
- fs.writeFileSync(artifactPath, JSON.stringify(artifact, null, 2), 'utf-8');
189
-
190
- const record = registerSample(workspaceDir, artifact, artifactPath, 'gpt-4').record;
191
- updateReviewStatus(workspaceDir, record.sampleFingerprint, 'approved_for_training', 'approved');
192
-
193
- // Append lineage pointing to a real file (so lineage passes)
194
- appendArtifactLineageRecord(workspaceDir, {
195
- artifactKind: 'behavioral-sample',
196
- artifactId: record.artifactId,
197
- principleId: record.principleId,
198
- ruleId: null,
199
- sessionId: record.sessionId,
200
- sourceSnapshotRef: record.sourceSnapshotRef,
201
- sourcePainIds: [],
202
- sourceGateBlockIds: [],
203
- storagePath: artifactPath,
204
- implementationId: null,
205
- createdAt: record.createdAt,
206
- });
207
-
208
- // Delete the artifact to simulate a missing file
209
- fs.unlinkSync(artifactPath);
210
-
211
- const report = runMergeGateAudit(workspaceDir, stateDir);
212
- const datasetCheck = report.checks.find((c) => c.id === 'dataset_artifact_integrity');
213
-
214
- expect(report.overallStatus).toBe('block');
215
- expect(datasetCheck?.status).toBe('block');
216
- });
217
-
218
- it('blocks when artifact lineage storage paths are missing', () => {
219
- const badPath = path.join(workspaceDir, '.state', 'nocturnal', 'samples', 'nonexistent.json');
220
- appendArtifactLineageRecord(workspaceDir, {
221
- artifactKind: 'behavioral-sample',
222
- artifactId: 'lineage-missing',
223
- principleId: 'T-08',
224
- ruleId: null,
225
- sessionId: 'session-1',
226
- sourceSnapshotRef: 'snap-1',
227
- sourcePainIds: [],
228
- sourceGateBlockIds: [],
229
- storagePath: badPath,
230
- implementationId: null,
231
- createdAt: new Date().toISOString(),
232
- });
233
-
234
- const report = runMergeGateAudit(workspaceDir, stateDir);
235
- const lineageCheck = report.checks.find((c) => c.id === 'artifact_lineage_integrity');
236
-
237
- expect(report.overallStatus).toBe('block');
238
- expect(lineageCheck?.status).toBe('block');
239
- });
240
-
241
74
  it('blocks when replay reports are malformed', () => {
242
75
  createImplementationAssetDir(stateDir, 'IMPL-BAD', '1.0.0');
243
76
  const replayDir = path.join(getImplementationAssetRoot(stateDir, 'IMPL-BAD'), 'replays');
@@ -268,7 +101,7 @@ describe('merge-gate-audit', () => {
268
101
  blockers: [],
269
102
  generatedAt: '2026-04-12T09:00:00.000Z',
270
103
  implementationId: 'IMPL-NOEVID',
271
- evidenceSummary: { evidenceStatus: 'observed' }, // missing totalSamples
104
+ evidenceSummary: { evidenceStatus: 'observed' },
272
105
  }),
273
106
  'utf-8',
274
107
  );
@@ -18,25 +18,25 @@ describe('Directory Structure Migration', () => {
18
18
  vi.clearAllMocks();
19
19
  });
20
20
 
21
- it('should move non-security files from docs/ to new locations', () => {
22
- const legacyPlan = path.join(workspaceDir, 'docs', 'PLAN.md');
23
- const newPlan = '/mock/workspace/PLAN.md';
21
+ it('should move THINKING_OS.md from docs/ to .principles/', () => {
22
+ const legacyThinkingOs = path.join(workspaceDir, 'docs', 'THINKING_OS.md');
23
+ const newThinkingOs = '/mock/workspace/.principles/THINKING_OS.md';
24
24
 
25
25
  vi.mocked(fs.existsSync).mockImplementation((p) => {
26
26
  const pathStr = p.toString();
27
27
  if (pathStr === path.join(workspaceDir, 'docs')) return true;
28
- if (pathStr === legacyPlan) return true;
28
+ if (pathStr === legacyThinkingOs) return true;
29
29
  // Destination directories don't exist yet
30
30
  if (pathStr === path.join(workspaceDir, '.principles')) return false;
31
31
  // Destination files don't exist yet
32
- if (pathStr === newPlan) return false;
32
+ if (pathStr === newThinkingOs) return false;
33
33
  return false;
34
34
  });
35
35
 
36
36
  migrateDirectoryStructure(mockApi, workspaceDir);
37
37
 
38
- // Verify it moved PLAN.md to root
39
- expect(fs.renameSync).toHaveBeenCalledWith(legacyPlan, newPlan);
38
+ // Verify it moved THINKING_OS.md to .principles/
39
+ expect(fs.renameSync).toHaveBeenCalledWith(legacyThinkingOs, newThinkingOs);
40
40
 
41
41
  expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining('Successfully migrated'));
42
42
  });
@@ -66,6 +66,7 @@ function rmdir(dir: string): void {
66
66
  */
67
67
  function setupDeployableReaderCheckpoint(tmpDir: string): { runId: string; checkpointId: string } {
68
68
  const run = registerTrainingRun(tmpDir, {
69
+ experimentId: 'mock-experiment-id',
69
70
  targetModelFamily: 'claude-reader-latest',
70
71
  datasetFingerprint: 'sha256-reader-001',
71
72
  exportId: 'export-reader',
@@ -108,6 +109,7 @@ function setupDeployableReaderCheckpoint(tmpDir: string): { runId: string; check
108
109
  */
109
110
  function setupDeployableEditorCheckpoint(tmpDir: string): { runId: string; checkpointId: string } {
110
111
  const run = registerTrainingRun(tmpDir, {
112
+ experimentId: 'mock-experiment-id',
111
113
  targetModelFamily: 'gpt-editor-v4',
112
114
  datasetFingerprint: 'sha256-editor-001',
113
115
  exportId: 'export-editor',
@@ -149,6 +151,7 @@ function setupDeployableEditorCheckpoint(tmpDir: string): { runId: string; check
149
151
  */
150
152
  function setupNonDeployableCheckpoint(tmpDir: string): { runId: string; checkpointId: string } {
151
153
  const run = registerTrainingRun(tmpDir, {
154
+ experimentId: 'mock-experiment-id',
152
155
  targetModelFamily: 'claude-reader-latest',
153
156
  datasetFingerprint: 'sha256-abc',
154
157
  exportId: 'export-abc',
@@ -280,6 +283,7 @@ describe('ModelDeploymentRegistry bindCheckpointToWorkerProfile', () => {
280
283
 
281
284
  // Set up a second reader checkpoint
282
285
  const run2 = registerTrainingRun(tmpDir, {
286
+ experimentId: 'mock-experiment-id',
283
287
  targetModelFamily: 'claude-reader-latest',
284
288
  datasetFingerprint: 'sha256-reader-002',
285
289
  exportId: 'export-reader-2',
@@ -564,6 +568,7 @@ describe('ModelDeploymentRegistry rollbackDeployment', () => {
564
568
 
565
569
  // Set up ck2
566
570
  const run2 = registerTrainingRun(tmpDir, {
571
+ experimentId: 'mock-experiment-id',
567
572
  targetModelFamily: 'claude-reader-latest',
568
573
  datasetFingerprint: 'sha256-reader-002',
569
574
  exportId: 'export-reader-2',
@@ -619,8 +624,8 @@ describe('ModelDeploymentRegistry rollbackDeployment', () => {
619
624
 
620
625
  it('rollback fails when previous checkpoint no longer exists', () => {
621
626
  const { checkpointId: ck1 } = setupDeployableReaderCheckpoint(tmpDir);
622
-
623
627
  const run2 = registerTrainingRun(tmpDir, {
628
+ experimentId: 'mock-experiment-id',
624
629
  targetModelFamily: 'claude-reader-latest',
625
630
  datasetFingerprint: 'sha256-reader-002',
626
631
  exportId: 'export-reader-2',
@@ -677,6 +682,7 @@ describe('ModelDeploymentRegistry rollbackDeployment', () => {
677
682
  const { checkpointId: ck1 } = setupDeployableReaderCheckpoint(tmpDir);
678
683
 
679
684
  const run2 = registerTrainingRun(tmpDir, {
685
+ experimentId: 'mock-experiment-id',
680
686
  targetModelFamily: 'claude-reader-latest',
681
687
  datasetFingerprint: 'sha256-reader-002',
682
688
  exportId: 'export-reader-2',