principles-disciple 1.72.0 → 1.73.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 (309) hide show
  1. package/openclaw.plugin.json +10 -5
  2. package/package.json +17 -19
  3. package/scripts/acceptance-test.mjs +16 -73
  4. package/scripts/sync-plugin.mjs +382 -77
  5. package/src/commands/archive-impl.ts +2 -1
  6. package/src/commands/capabilities.ts +2 -2
  7. package/src/commands/context.ts +2 -2
  8. package/src/commands/disable-impl.ts +2 -1
  9. package/src/commands/evolution-status.ts +16 -16
  10. package/src/commands/export.ts +12 -67
  11. package/src/commands/pain.ts +91 -1
  12. package/src/commands/principle-rollback.ts +2 -1
  13. package/src/commands/promote-impl.ts +7 -43
  14. package/src/commands/rollback-impl.ts +2 -1
  15. package/src/commands/rollback.ts +2 -1
  16. package/src/commands/samples.ts +2 -1
  17. package/src/commands/thinking-os.ts +2 -1
  18. package/src/config/errors.ts +18 -2
  19. package/src/constants/diagnostician.ts +2 -2
  20. package/src/constants/tools.ts +2 -1
  21. package/src/core/__tests__/focus-history.test.ts +210 -0
  22. package/src/core/config.ts +1 -1
  23. package/src/core/confirm-first-gate.ts +255 -0
  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 +38 -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/pain-diagnostic-gate.ts +154 -0
  37. package/src/core/pain-signal.ts +21 -138
  38. package/src/core/pain.ts +15 -88
  39. package/src/core/pd-task-reconciler.ts +26 -115
  40. package/src/core/pd-task-service.ts +9 -9
  41. package/src/core/pd-task-types.ts +23 -127
  42. package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
  43. package/src/core/principle-compiler/code-validator.ts +15 -42
  44. package/src/core/principle-compiler/compiler.ts +100 -15
  45. package/src/core/principle-compiler/index.ts +5 -2
  46. package/src/core/principle-compiler/template-generator.ts +4 -104
  47. package/src/core/principle-injection.ts +10 -202
  48. package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
  49. package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
  50. package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
  51. package/src/core/principle-tree-ledger-adapter.ts +145 -0
  52. package/src/core/principle-tree-ledger.ts +8 -6
  53. package/src/core/reflection/reflection-context.ts +14 -109
  54. package/src/core/replay-engine.ts +8 -500
  55. package/src/core/rule-host-helpers.ts +5 -35
  56. package/src/core/rule-host-types.ts +10 -82
  57. package/src/core/rule-host.ts +6 -63
  58. package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
  59. package/src/core/session-tracker.ts +87 -101
  60. package/src/core/shadow-observation-registry.ts +19 -48
  61. package/src/core/trajectory.ts +3 -1
  62. package/src/core/workflow-funnel-loader.ts +62 -68
  63. package/src/core/workspace-context.ts +46 -0
  64. package/src/core/workspace-dir-service.ts +1 -1
  65. package/src/core/workspace-dir-validation.ts +18 -9
  66. package/src/hooks/AGENTS.md +1 -1
  67. package/src/hooks/gate-block-helper.ts +46 -44
  68. package/src/hooks/gate.ts +207 -7
  69. package/src/hooks/lifecycle.ts +30 -32
  70. package/src/hooks/llm.ts +60 -32
  71. package/src/hooks/pain.ts +297 -103
  72. package/src/hooks/prompt.ts +459 -439
  73. package/src/hooks/subagent.ts +2 -29
  74. package/src/i18n/commands.ts +2 -10
  75. package/src/index.ts +95 -85
  76. package/src/openclaw-sdk.ts +311 -0
  77. package/src/service/central-database.ts +8 -4
  78. package/src/service/evolution-queue-migration.ts +2 -1
  79. package/src/service/evolution-worker.ts +163 -1786
  80. package/src/service/internalization-trigger-adapter.ts +302 -0
  81. package/src/service/keyword-optimization-service.ts +4 -4
  82. package/src/service/monitoring-query-service.ts +1 -215
  83. package/src/service/queue-io.ts +60 -331
  84. package/src/service/runtime-summary-service.ts +59 -16
  85. package/src/service/subagent-workflow/index.ts +0 -41
  86. package/src/service/subagent-workflow/types.ts +9 -120
  87. package/src/service/subagent-workflow/workflow-store.ts +2 -119
  88. package/src/service/workflow-watchdog.ts +0 -43
  89. package/src/types/event-payload.ts +16 -74
  90. package/src/types/event-types.ts +39 -547
  91. package/src/types/hygiene-types.ts +7 -30
  92. package/src/types/principle-tree-schema.ts +20 -222
  93. package/src/types/queue.ts +15 -70
  94. package/src/types/runtime-summary.ts +5 -49
  95. package/src/utils/io.ts +10 -0
  96. package/src/utils/retry.ts +1 -1
  97. package/src/utils/shadow-fingerprint.ts +2 -2
  98. package/src/utils/workspace-resolver.ts +50 -0
  99. package/templates/langs/en/core/AGENTS.md +2 -2
  100. package/templates/langs/en/core/BOOT.md +1 -1
  101. package/templates/langs/en/core/HEARTBEAT.md +2 -2
  102. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  103. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  104. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  105. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  106. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  107. package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  108. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  109. package/templates/langs/en/skills/evolve-task/SKILL.md +1 -1
  110. package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
  111. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
  112. package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
  113. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
  114. package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
  115. package/templates/langs/zh/core/AGENTS.md +2 -2
  116. package/templates/langs/zh/core/BOOT.md +1 -1
  117. package/templates/langs/zh/core/HEARTBEAT.md +2 -2
  118. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  119. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  120. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  121. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
  122. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  123. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  124. package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  125. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  126. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
  127. package/templates/langs/zh/skills/evolve-task/SKILL.md +2 -2
  128. package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
  129. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
  130. package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
  131. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
  132. package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
  133. package/tests/build-artifacts.test.ts +1 -3
  134. package/tests/commands/evolution-status.test.ts +0 -118
  135. package/tests/core/bootstrap-rules.test.ts +1 -1
  136. package/tests/core/config.test.ts +1 -1
  137. package/tests/core/event-log.test.ts +35 -0
  138. package/tests/core/evolution-engine.test.ts +610 -0
  139. package/tests/core/file-store.test.ts +102 -0
  140. package/tests/core/focus-history.test.ts +203 -11
  141. package/tests/core/merge-gate-audit.test.ts +2 -169
  142. package/tests/core/model-deployment-registry.test.ts +7 -1
  143. package/tests/core/model-training-registry.test.ts +19 -0
  144. package/tests/core/observability.test.ts +0 -1
  145. package/tests/core/pain-diagnostic-gate.test.ts +498 -0
  146. package/tests/core/pain.test.ts +0 -1
  147. package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
  148. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
  149. package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
  150. package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
  151. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
  152. package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
  153. package/tests/core/reflection-context.test.ts +0 -14
  154. package/tests/core/replay-engine.test.ts +127 -215
  155. package/tests/core/rule-host-helpers.test.ts +2 -2
  156. package/tests/core/rule-implementation-runtime.test.ts +0 -27
  157. package/tests/core/workflow-funnel-loader.test.ts +162 -0
  158. package/tests/core/workspace-dir-validation.test.ts +8 -1
  159. package/tests/core-anti-growth.test.ts +192 -0
  160. package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
  161. package/tests/hooks/confirm-first-gate.test.ts +333 -0
  162. package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
  163. package/tests/hooks/gate-auto-correct.test.ts +665 -0
  164. package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
  165. package/tests/hooks/pain.test.ts +269 -12
  166. package/tests/hooks/prompt-characterization.test.ts +500 -0
  167. package/tests/hooks/prompt-size-guard.test.ts +32 -17
  168. package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
  169. package/tests/index.test.ts +94 -1
  170. package/tests/integration/auto-entry-gate.test.ts +248 -0
  171. package/tests/integration/internalization-trigger-guard.test.ts +69 -0
  172. package/tests/integration/m8-legacy-paths.test.ts +63 -0
  173. package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
  174. package/tests/plugin-config-resolution-cutover.test.ts +359 -0
  175. package/tests/runtime-v2-discovery-guard.test.ts +154 -0
  176. package/tests/service/central-database.test.ts +457 -0
  177. package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
  178. package/tests/service/evolution-worker.timeout.test.ts +11 -129
  179. package/tests/service/internalization-trigger-adapter.test.ts +251 -0
  180. package/tests/service/monitoring-query-service.test.ts +1 -47
  181. package/tests/service/queue-io.test.ts +1 -62
  182. package/tests/service/runtime-summary-service.test.ts +3 -1
  183. package/tests/service/workflow-watchdog.test.ts +0 -91
  184. package/tests/utils/file-lock.test.ts +5 -3
  185. package/tests/utils/session-key.test.ts +52 -0
  186. package/tests/utils/subagent-probe.test.ts +48 -1
  187. package/vitest.config.ts +4 -11
  188. package/.planning/codebase/ARCHITECTURE.md +0 -157
  189. package/.planning/codebase/CONCERNS.md +0 -145
  190. package/.planning/codebase/CONVENTIONS.md +0 -148
  191. package/.planning/codebase/INTEGRATIONS.md +0 -81
  192. package/.planning/codebase/STACK.md +0 -87
  193. package/.planning/codebase/STRUCTURE.md +0 -193
  194. package/.planning/codebase/TESTING.md +0 -243
  195. package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
  196. package/docs/COMMAND_REFERENCE.md +0 -76
  197. package/docs/COMMAND_REFERENCE_EN.md +0 -79
  198. package/scripts/build-web.mjs +0 -46
  199. package/scripts/diagnose-nocturnal.mjs +0 -537
  200. package/scripts/seed-nocturnal-scenarios.mjs +0 -384
  201. package/src/commands/nocturnal-review.ts +0 -322
  202. package/src/commands/nocturnal-rollout.ts +0 -790
  203. package/src/commands/nocturnal-train.ts +0 -986
  204. package/src/commands/pd-reflect.ts +0 -88
  205. package/src/core/adaptive-thresholds.ts +0 -478
  206. package/src/core/diagnostician-task-store.ts +0 -192
  207. package/src/core/nocturnal-arbiter.ts +0 -715
  208. package/src/core/nocturnal-artifact-lineage.ts +0 -116
  209. package/src/core/nocturnal-artificer.ts +0 -257
  210. package/src/core/nocturnal-candidate-scoring.ts +0 -530
  211. package/src/core/nocturnal-compliance.ts +0 -1146
  212. package/src/core/nocturnal-dataset.ts +0 -763
  213. package/src/core/nocturnal-executability.ts +0 -428
  214. package/src/core/nocturnal-export.ts +0 -499
  215. package/src/core/nocturnal-paths.ts +0 -240
  216. package/src/core/nocturnal-reasoning-deriver.ts +0 -343
  217. package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
  218. package/src/core/nocturnal-snapshot-contract.ts +0 -99
  219. package/src/core/nocturnal-trajectory-extractor.ts +0 -512
  220. package/src/core/nocturnal-trinity-types.ts +0 -218
  221. package/src/core/nocturnal-trinity.ts +0 -2680
  222. package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
  223. package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
  224. package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
  225. package/src/http/principles-console-route.ts +0 -709
  226. package/src/service/central-health-service.ts +0 -49
  227. package/src/service/central-overview-service.ts +0 -138
  228. package/src/service/control-ui-query-service.ts +0 -900
  229. package/src/service/cooldown-strategy.ts +0 -97
  230. package/src/service/evolution-pain-context.ts +0 -79
  231. package/src/service/evolution-query-service.ts +0 -407
  232. package/src/service/health-query-service.ts +0 -1038
  233. package/src/service/nocturnal-config.ts +0 -214
  234. package/src/service/nocturnal-runtime.ts +0 -734
  235. package/src/service/nocturnal-service.ts +0 -1605
  236. package/src/service/nocturnal-target-selector.ts +0 -545
  237. package/src/service/sleep-cycle.ts +0 -157
  238. package/src/service/startup-reconciler.ts +0 -112
  239. package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
  240. package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
  241. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
  242. package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
  243. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
  244. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
  245. package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
  246. package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
  247. package/src/tools/write-pain-flag.ts +0 -215
  248. package/tests/commands/nocturnal-review.test.ts +0 -448
  249. package/tests/commands/nocturnal-train.test.ts +0 -97
  250. package/tests/commands/pd-reflect.test.ts +0 -49
  251. package/tests/core/adaptive-thresholds.test.ts +0 -261
  252. package/tests/core/nocturnal-arbiter.test.ts +0 -559
  253. package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
  254. package/tests/core/nocturnal-artificer.test.ts +0 -241
  255. package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
  256. package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
  257. package/tests/core/nocturnal-compliance.test.ts +0 -646
  258. package/tests/core/nocturnal-dataset.test.ts +0 -892
  259. package/tests/core/nocturnal-e2e.test.ts +0 -234
  260. package/tests/core/nocturnal-executability.test.ts +0 -357
  261. package/tests/core/nocturnal-export.test.ts +0 -517
  262. package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
  263. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
  264. package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
  265. package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
  266. package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
  267. package/tests/core/nocturnal-trinity.test.ts +0 -2053
  268. package/tests/core/pain-auto-repair.test.ts +0 -96
  269. package/tests/core/pain-integration.test.ts +0 -510
  270. package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
  271. package/tests/http/principles-console-route.test.ts +0 -162
  272. package/tests/integration/chaos-resilience.test.ts +0 -348
  273. package/tests/integration/empathy-workflow-integration.test.ts +0 -626
  274. package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
  275. package/tests/service/control-ui-query-service.test.ts +0 -121
  276. package/tests/service/cooldown-strategy.test.ts +0 -164
  277. package/tests/service/data-endpoints-regression.test.ts +0 -834
  278. package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
  279. package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
  280. package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
  281. package/tests/service/nocturnal-runtime.test.ts +0 -473
  282. package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
  283. package/tests/service/nocturnal-target-selector.test.ts +0 -615
  284. package/tests/service/startup-reconciler.test.ts +0 -148
  285. package/tests/tools/write-pain-flag.test.ts +0 -358
  286. package/ui/src/App.tsx +0 -45
  287. package/ui/src/api.ts +0 -220
  288. package/ui/src/charts.tsx +0 -955
  289. package/ui/src/components/ErrorState.tsx +0 -6
  290. package/ui/src/components/Loading.tsx +0 -13
  291. package/ui/src/components/ProtectedRoute.tsx +0 -12
  292. package/ui/src/components/Shell.tsx +0 -91
  293. package/ui/src/components/WorkspaceConfig.tsx +0 -178
  294. package/ui/src/components/index.ts +0 -5
  295. package/ui/src/context/auth.tsx +0 -80
  296. package/ui/src/context/theme.tsx +0 -66
  297. package/ui/src/hooks/useAutoRefresh.ts +0 -39
  298. package/ui/src/i18n/ui.ts +0 -473
  299. package/ui/src/main.tsx +0 -16
  300. package/ui/src/pages/EvolutionPage.tsx +0 -333
  301. package/ui/src/pages/FeedbackPage.tsx +0 -138
  302. package/ui/src/pages/GateMonitorPage.tsx +0 -136
  303. package/ui/src/pages/LoginPage.tsx +0 -89
  304. package/ui/src/pages/OverviewPage.tsx +0 -599
  305. package/ui/src/pages/SamplesPage.tsx +0 -174
  306. package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
  307. package/ui/src/styles.css +0 -2020
  308. package/ui/src/types.ts +0 -384
  309. 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
  );
@@ -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',