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
@@ -1,763 +0,0 @@
1
- /**
2
- * Nocturnal Dataset — Sample Lineage Store and Review State Registry
3
- * =================================================================
4
- *
5
- * PURPOSE: Establish each approved nocturnal sample as a first-class auditable
6
- * data asset with fingerprint, lineage, review state, and model family binding.
7
- *
8
- * ARCHITECTURE:
9
- * - Registry file: {stateDir}/.state/nocturnal/dataset-registry.json
10
- * - One JSON array of NocturnalDatasetRecord
11
- * - Each record is immutable except for reviewStatus and reviewReason
12
- * - sampleFingerprint is the primary key (deterministic: SHA-256 of artifactId+principleId+sessionId)
13
- *
14
- * RELATIONSHIP TO NOCTURNAL ARTIFACTS:
15
- * - Artifacts live in: .state/nocturnal/samples/{artifactId}.json
16
- * - Dataset records reference artifacts via artifactId and artifactPath
17
- * - Artifacts are NOT modified by dataset operations
18
- *
19
- * DESIGN CONSTRAINTS:
20
- * - No training run registry (Phase 4)
21
- * - No checkpoint registry (Phase 4)
22
- * - No worker routing changes
23
- * - No JSONL export (that's Task 3.2)
24
- * - Lineage is append-only for approved records
25
- * - reviewStatus transitions are the only state mutations allowed
26
- */
27
-
28
- import * as fs from 'fs';
29
- import * as path from 'path';
30
- import * as crypto from 'crypto';
31
- import { NocturnalPathResolver, resolveNocturnalDir } from './nocturnal-paths.js';
32
- import type { NocturnalArtifact } from './nocturnal-arbiter.js';
33
- import { withLock } from '../utils/file-lock.js';
34
- import { atomicWriteFileSync } from '../utils/io.js';
35
-
36
- // ---------------------------------------------------------------------------
37
- // Types
38
- // ---------------------------------------------------------------------------
39
-
40
- /**
41
- * Review status for a nocturnal dataset sample.
42
- * Follows the lifecycle: pending_review → approved_for_training | rejected | superseded
43
- */
44
- export type NocturnalReviewStatus =
45
- | 'pending_review'
46
- | 'approved_for_training'
47
- | 'rejected'
48
- | 'superseded';
49
-
50
- /**
51
- * Sample classification for replay evaluation.
52
- * Used by the ReplayEngine to select appropriate test samples.
53
- * - 'pain-negative': samples that triggered pain signals or were blocked by gates
54
- * - 'success-positive': samples that were successful interactions
55
- * - 'principle-anchor': samples that embody core principle behavior
56
- *
57
- * Artifact kind is intentionally tracked outside this type so replay
58
- * classification remains behavioral-only.
59
- */
60
- export type SampleClassification = 'pain-negative' | 'success-positive' | 'principle-anchor';
61
-
62
- /**
63
- * A nocturnal dataset record — the immutable lineage entry for one sample.
64
- *
65
- * PRIMARY KEY: sampleFingerprint (deterministic SHA-256)
66
- * MUTABLE FIELDS: reviewStatus, reviewReason only
67
- * IMMUTABLE FIELDS: all others
68
- */
69
- export interface NocturnalDatasetRecord {
70
- /**
71
- * Deterministic fingerprint: SHA-256(artifactId + principleId + sessionId).
72
- * Primary key for dataset operations.
73
- */
74
- sampleFingerprint: string;
75
-
76
- /** Reference to the original artifact */
77
- artifactId: string;
78
-
79
- /** Source session */
80
- sessionId: string;
81
-
82
- /** Target principle that generated this sample */
83
- principleId: string;
84
-
85
- /** Reference to the trajectory snapshot used */
86
- sourceSnapshotRef: string;
87
-
88
- /**
89
- * Current review state.
90
- * Only transitions allowed: pending_review → approved_for_training | rejected | superseded
91
- */
92
- reviewStatus: NocturnalReviewStatus;
93
-
94
- /**
95
- * Human-provided reason for the review decision.
96
- * Required for approved_for_training and rejected; optional for superseded.
97
- */
98
- reviewReason?: string;
99
-
100
- /**
101
- * Target model family this sample is bound to.
102
- * REQUIRED for export-ready samples.
103
- * NULL means "not yet assigned" (pending_review defaults to null).
104
- */
105
- targetModelFamily: string | null;
106
-
107
- /**
108
- * When this sample was first registered in the dataset.
109
- */
110
- createdAt: string;
111
-
112
- /**
113
- * Last time reviewStatus or reviewReason was updated.
114
- */
115
- updatedAt: string;
116
-
117
- /**
118
- * Absolute path to the artifact file.
119
- */
120
- artifactPath: string;
121
-
122
- /**
123
- * Sample classification for replay evaluation.
124
- * Used by ReplayEngine to select samples by category.
125
- * Null means not yet classified for replay.
126
- */
127
- classification: SampleClassification | null;
128
- }
129
-
130
- /**
131
- * Filter options for listing dataset records.
132
- */
133
- export interface DatasetFilterOptions {
134
- /**
135
- * Filter by review status.
136
- */
137
- reviewStatus?: NocturnalReviewStatus | NocturnalReviewStatus[];
138
-
139
- /**
140
- * Filter by target model family.
141
- * NULL means "any" (including null/unassigned).
142
- */
143
- targetModelFamily?: string | null;
144
-
145
- /**
146
- * Include only export-ready records.
147
- * An export-ready record must have:
148
- * - reviewStatus === 'approved_for_training'
149
- * - targetModelFamily !== null
150
- * - artifactPath points to an existing file
151
- */
152
- exportReadyOnly?: boolean;
153
- }
154
-
155
- /**
156
- * Result of registering a sample.
157
- */
158
- export interface RegisterSampleResult {
159
- /** The registered record */
160
- record: NocturnalDatasetRecord;
161
- /** Whether this was a new registration (true) or duplicate link (false) */
162
- isNew: boolean;
163
- /**
164
- * If isNew === false, this points to the existing record.
165
- */
166
- existingRecord?: NocturnalDatasetRecord;
167
- }
168
-
169
- // ---------------------------------------------------------------------------
170
- // Fingerprint Generation
171
- // ---------------------------------------------------------------------------
172
-
173
- /**
174
- * Generate a deterministic sample fingerprint from an artifact.
175
- *
176
- * FINGERPRINT = SHA-256(artifactId || principleId || sessionId)
177
- *
178
- * The fingerprint is deterministic so the same sample always produces
179
- * the same fingerprint, enabling duplicate detection.
180
- */
181
- export function generateSampleFingerprint(
182
- artifactId: string,
183
- principleId: string,
184
- sessionId: string
185
- ): string {
186
- const input = `${artifactId}|${principleId}|${sessionId}`;
187
- return crypto.createHash('sha256').update(input, 'utf8').digest('hex');
188
- }
189
-
190
- /**
191
- * Generate a fingerprint from an existing NocturnalArtifact.
192
- */
193
- export function generateFingerprintFromArtifact(artifact: NocturnalArtifact): string {
194
- return generateSampleFingerprint(
195
- artifact.artifactId,
196
- artifact.principleId,
197
- artifact.sessionId
198
- );
199
- }
200
-
201
- // ---------------------------------------------------------------------------
202
- // Registry Path
203
- // ---------------------------------------------------------------------------
204
-
205
- /**
206
- * Path to the dataset registry file.
207
- */
208
- function getRegistryPath(workspaceDir: string): string {
209
- // Registry lives in .state/nocturnal/dataset-registry.json
210
- const nocturnalRoot = resolveNocturnalDir(workspaceDir, 'ROOT');
211
- return path.join(nocturnalRoot, 'dataset-registry.json');
212
- }
213
-
214
- /**
215
- * Ensure the registry directory exists.
216
- */
217
- function ensureRegistryDir(workspaceDir: string): void {
218
- const registryPath = getRegistryPath(workspaceDir);
219
- const dir = path.dirname(registryPath);
220
- if (!fs.existsSync(dir)) {
221
- fs.mkdirSync(dir, { recursive: true });
222
- }
223
- }
224
-
225
- /**
226
- * Read the registry file. Returns empty array if missing.
227
- */
228
- function readRegistry(workspaceDir: string): NocturnalDatasetRecord[] {
229
- const registryPath = getRegistryPath(workspaceDir);
230
- if (!fs.existsSync(registryPath)) {
231
- return [];
232
- }
233
- try {
234
- const content = fs.readFileSync(registryPath, 'utf-8');
235
- return JSON.parse(content) as NocturnalDatasetRecord[];
236
- } catch (err) {
237
- // Corrupted registry — fail-safe to empty array, but log the problem
238
- console.warn(`[nocturnal-dataset] Registry corrupted at ${registryPath}, recovering with empty state: ${String(err)}`);
239
- return [];
240
- }
241
- }
242
-
243
- /**
244
- * Write the registry file atomically (write-then-rename for atomicity).
245
- * Caller must hold the registry lock (via withRegistryLock).
246
- */
247
- function writeRegistry(workspaceDir: string, records: NocturnalDatasetRecord[]): void {
248
- ensureRegistryDir(workspaceDir);
249
- const registryPath = getRegistryPath(workspaceDir);
250
- atomicWriteFileSync(registryPath, JSON.stringify(records, null, 2));
251
- }
252
-
253
- // ---------------------------------------------------------------------------
254
- // Core Operations
255
- // ---------------------------------------------------------------------------
256
-
257
- /**
258
- * Execute a read-modify-write on the registry under an exclusive lock.
259
- * This prevents concurrent writers from racing on the same file.
260
- */
261
-
262
- function withRegistryLock<T>(workspaceDir: string, fn: (_records: NocturnalDatasetRecord[]) => T): T {
263
- const registryPath = getRegistryPath(workspaceDir);
264
- return withLock(registryPath, () => {
265
- const records = readRegistry(workspaceDir);
266
- return fn(records);
267
- });
268
- }
269
-
270
- /**
271
- * Register an approved nocturnal artifact in the dataset registry.
272
- *
273
- * DUPLICATE HANDLING:
274
- * - If a record with the same sampleFingerprint already exists, returns
275
- * existingRecord (isNew === false) instead of creating a duplicate.
276
- * - The original artifact file is never modified.
277
- *
278
- * @param workspaceDir - Workspace directory
279
- * @param artifact - The approved NocturnalArtifact
280
- * @param artifactPath - Absolute path where the artifact file is stored
281
- * @param targetModelFamily - Model family binding (required for export-ready)
282
- * @param classification - Optional replay classification
283
- * @returns RegisterSampleResult
284
- */
285
-
286
-
287
- export function registerSample(
288
- workspaceDir: string,
289
- artifact: NocturnalArtifact,
290
- artifactPath: string,
291
- targetModelFamily: string | null = null,
292
- classification: SampleClassification | null = null
293
- ): RegisterSampleResult {
294
- const fingerprint = generateFingerprintFromArtifact(artifact);
295
- const now = new Date().toISOString();
296
-
297
- return withRegistryLock(workspaceDir, (records) => {
298
- const existing = records.find((r) => r.sampleFingerprint === fingerprint);
299
- if (existing) {
300
- return {
301
- record: existing,
302
- isNew: false,
303
- existingRecord: existing,
304
- };
305
- }
306
-
307
- const record: NocturnalDatasetRecord = {
308
- sampleFingerprint: fingerprint,
309
- artifactId: artifact.artifactId,
310
- sessionId: artifact.sessionId,
311
- principleId: artifact.principleId,
312
- sourceSnapshotRef: artifact.sourceSnapshotRef,
313
- reviewStatus: 'pending_review',
314
- reviewReason: undefined,
315
- targetModelFamily,
316
- createdAt: now,
317
- updatedAt: now,
318
- artifactPath: path.normalize(artifactPath),
319
- classification,
320
- };
321
-
322
- records.push(record);
323
- writeRegistry(workspaceDir, records);
324
-
325
- return { record, isNew: true };
326
- });
327
- }
328
-
329
- /**
330
- * Get a dataset record by fingerprint.
331
- */
332
- export function getDatasetRecord(
333
- workspaceDir: string,
334
- sampleFingerprint: string
335
- ): NocturnalDatasetRecord | null {
336
- const records = readRegistry(workspaceDir);
337
- return records.find((r) => r.sampleFingerprint === sampleFingerprint) ?? null;
338
- }
339
-
340
- /**
341
- * Get a dataset record by artifactId.
342
- */
343
- export function getDatasetRecordByArtifactId(
344
- workspaceDir: string,
345
- artifactId: string
346
- ): NocturnalDatasetRecord | null {
347
- const records = readRegistry(workspaceDir);
348
- return records.find((r) => r.artifactId === artifactId) ?? null;
349
- }
350
-
351
- /**
352
- * List dataset records with optional filtering.
353
- *
354
- * @param workspaceDir - Workspace directory
355
- * @param filter - Optional filter criteria
356
- * @returns Filtered records sorted by createdAt descending
357
- */
358
- export function listDatasetRecords(
359
- workspaceDir: string,
360
- filter?: DatasetFilterOptions
361
- ): NocturnalDatasetRecord[] {
362
- let records = readRegistry(workspaceDir);
363
-
364
- if (!filter) {
365
- return records.sort(
366
- (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
367
- );
368
- }
369
-
370
- // Filter by reviewStatus
371
- if (filter.reviewStatus !== undefined) {
372
- const statuses = Array.isArray(filter.reviewStatus)
373
- ? filter.reviewStatus
374
- : [filter.reviewStatus];
375
- records = records.filter((r) => statuses.includes(r.reviewStatus));
376
- }
377
-
378
- // Filter by targetModelFamily
379
- if (filter.targetModelFamily !== undefined) {
380
- if (filter.targetModelFamily === null) {
381
- // Include only null/unassigned
382
- records = records.filter((r) => r.targetModelFamily === null);
383
- } else {
384
- records = records.filter((r) => r.targetModelFamily === filter.targetModelFamily);
385
- }
386
- }
387
-
388
- // Filter export-ready only
389
- if (filter.exportReadyOnly === true) {
390
- records = records.filter((r) => {
391
- if (r.reviewStatus !== 'approved_for_training') return false;
392
- if (r.targetModelFamily === null) return false;
393
- // Verify artifact file exists
394
- if (!fs.existsSync(r.artifactPath)) return false;
395
- return true;
396
- });
397
- }
398
-
399
- return records.sort(
400
- (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
401
- );
402
- }
403
-
404
- /**
405
- * Valid review status transitions.
406
- *pending_review → approved_for_training | rejected | superseded
407
- * approved_for_training → superseded (if a better sample replaces it)
408
- * rejected → pending_review (if re-review is requested)
409
- * superseded → (terminal state, no transitions)
410
- */
411
- const VALID_TRANSITIONS: Record<NocturnalReviewStatus, NocturnalReviewStatus[]> = {
412
- pending_review: ['approved_for_training', 'rejected', 'superseded'],
413
- approved_for_training: ['superseded'],
414
- rejected: ['pending_review', 'superseded'],
415
- superseded: [], // terminal
416
- };
417
-
418
- /**
419
- * Update the review status of a dataset record.
420
- *
421
- * @param workspaceDir - Workspace directory
422
- * @param sampleFingerprint - The fingerprint of the record to update
423
- * @param newStatus - The new review status
424
- * @param reason - Optional reason (required for approved/rejected per spec)
425
- * @returns Updated record, or null if not found
426
- * @throws Error if transition is invalid
427
- */
428
-
429
-
430
- export function updateReviewStatus(
431
- workspaceDir: string,
432
- sampleFingerprint: string,
433
- newStatus: NocturnalReviewStatus,
434
- reason?: string
435
- ): NocturnalDatasetRecord {
436
- return withRegistryLock(workspaceDir, (records) => {
437
- const idx = records.findIndex((r) => r.sampleFingerprint === sampleFingerprint);
438
-
439
- if (idx === -1) {
440
- throw new Error(`Dataset record not found: ${sampleFingerprint}`);
441
- }
442
-
443
- const record = records[idx];
444
-
445
- // Validate transition
446
- const allowed = VALID_TRANSITIONS[record.reviewStatus];
447
- if (!allowed.includes(newStatus)) {
448
- throw new Error(
449
- `Invalid review status transition: ${record.reviewStatus} → ${newStatus}. ` +
450
- `Allowed transitions from ${record.reviewStatus}: ${allowed.join(', ') || 'none'}`
451
- );
452
- }
453
-
454
- // Enforce reason requirement for approved/rejected
455
- if (
456
- (newStatus === 'approved_for_training' || newStatus === 'rejected') &&
457
- !reason
458
- ) {
459
- throw new Error(
460
- `reviewReason is required when transitioning to ${newStatus}`
461
- );
462
- }
463
-
464
- // Apply update
465
- records[idx] = {
466
- ...record,
467
- reviewStatus: newStatus,
468
- reviewReason: reason ?? record.reviewReason,
469
- updatedAt: new Date().toISOString(),
470
- };
471
-
472
- writeRegistry(workspaceDir, records);
473
- return records[idx];
474
- });
475
- }
476
-
477
- /**
478
- * Update the target model family binding.
479
- */
480
- export function updateTargetModelFamily(
481
- workspaceDir: string,
482
- sampleFingerprint: string,
483
- targetModelFamily: string | null
484
- ): NocturnalDatasetRecord {
485
- return withRegistryLock(workspaceDir, (records) => {
486
- const idx = records.findIndex((r) => r.sampleFingerprint === sampleFingerprint);
487
-
488
- if (idx === -1) {
489
- throw new Error(`Dataset record not found: ${sampleFingerprint}`);
490
- }
491
-
492
- records[idx] = {
493
- ...records[idx],
494
- targetModelFamily,
495
- updatedAt: new Date().toISOString(),
496
- };
497
-
498
- writeRegistry(workspaceDir, records);
499
- return records[idx];
500
- });
501
- }
502
-
503
- /**
504
- * Check if a sample is export-ready.
505
- *
506
- * EXPORT-READY means:
507
- * - reviewStatus === 'approved_for_training'
508
- * - targetModelFamily !== null
509
- * - artifact file exists
510
- * - lineage fields are complete
511
- */
512
- export function isExportReady(
513
- workspaceDir: string,
514
- sampleFingerprint: string
515
- ): boolean {
516
- const record = getDatasetRecord(workspaceDir, sampleFingerprint);
517
- if (!record) return false;
518
- if (record.reviewStatus !== 'approved_for_training') return false;
519
- if (record.targetModelFamily === null) return false;
520
- if (!fs.existsSync(record.artifactPath)) return false;
521
- return true;
522
- }
523
-
524
- /**
525
- * List all export-ready records for a specific target model family.
526
- */
527
- export function listExportReadyRecords(
528
- workspaceDir: string,
529
- targetModelFamily?: string | null
530
- ): NocturnalDatasetRecord[] {
531
- return listDatasetRecords(workspaceDir, {
532
- exportReadyOnly: true,
533
- targetModelFamily: targetModelFamily ?? undefined,
534
- });
535
- }
536
-
537
- /**
538
- * Get the artifact path for a dataset record.
539
- * Verifies the file exists before returning.
540
- */
541
- export function getArtifactPath(
542
- workspaceDir: string,
543
- sampleFingerprint: string
544
- ): string | null {
545
- const record = getDatasetRecord(workspaceDir, sampleFingerprint);
546
- if (!record) return null;
547
- if (!fs.existsSync(record.artifactPath)) return null;
548
- return record.artifactPath;
549
- }
550
-
551
- /**
552
- * Read the artifact file for a dataset record.
553
- * @throws Error if record not found, artifact file missing, or unreadable
554
- */
555
- export function readDatasetArtifact(
556
- workspaceDir: string,
557
- sampleFingerprint: string
558
- ): NocturnalArtifact {
559
- const artifactPath = getArtifactPath(workspaceDir, sampleFingerprint);
560
- if (!artifactPath) {
561
- throw new Error(`Artifact file not found for sample ${sampleFingerprint}`);
562
- }
563
-
564
- const content = fs.readFileSync(artifactPath, 'utf-8');
565
- const parsed = JSON.parse(content);
566
- // Return only the NocturnalArtifact fields (not the extended sample record)
567
- return {
568
- artifactId: parsed.artifactId,
569
- sessionId: parsed.sessionId,
570
- principleId: parsed.principleId,
571
- sourceSnapshotRef: parsed.sourceSnapshotRef,
572
- badDecision: parsed.badDecision,
573
- betterDecision: parsed.betterDecision,
574
- rationale: parsed.rationale,
575
- createdAt: parsed.createdAt,
576
- } as NocturnalArtifact;
577
- }
578
-
579
- /**
580
- * Count records by status for dashboard purposes.
581
- */
582
- export function getDatasetStats(
583
- workspaceDir: string
584
- ): {
585
- total: number;
586
- pendingReview: number;
587
- approvedForTraining: number;
588
- rejected: number;
589
- superseded: number;
590
- exportReadyByFamily: Record<string, number>;
591
- } {
592
- const records = readRegistry(workspaceDir);
593
-
594
- const counts = {
595
- total: records.length,
596
- pendingReview: 0,
597
- approvedForTraining: 0,
598
- rejected: 0,
599
- superseded: 0,
600
- exportReadyByFamily: {} as Record<string, number>,
601
- };
602
-
603
- for (const record of records) {
604
- switch (record.reviewStatus) {
605
- case 'pending_review':
606
- counts.pendingReview++;
607
- break;
608
- case 'approved_for_training':
609
- counts.approvedForTraining++;
610
- break;
611
- case 'rejected':
612
- counts.rejected++;
613
- break;
614
- case 'superseded':
615
- counts.superseded++;
616
- break;
617
- }
618
-
619
- // Count export-ready by family
620
- if (
621
- record.reviewStatus === 'approved_for_training' &&
622
- record.targetModelFamily !== null &&
623
- fs.existsSync(record.artifactPath)
624
- ) {
625
- const family = record.targetModelFamily;
626
- counts.exportReadyByFamily[family] = (counts.exportReadyByFamily[family] || 0) + 1;
627
- }
628
- }
629
-
630
- return counts;
631
- }
632
-
633
- // ---------------------------------------------------------------------------
634
- // Auto-registration from persisted samples
635
- // ---------------------------------------------------------------------------
636
-
637
- /**
638
- * Scan the samples directory and register any approved artifacts
639
- * that are not yet in the dataset registry.
640
- *
641
- * This is used for:
642
- * 1. Initial migration of Phase 2 artifacts to Phase 3 dataset
643
- * 2. Recovering from registry corruption
644
- *
645
- * @param workspaceDir - Workspace directory
646
- * @param targetModelFamily - Default target family for migrated samples
647
- * @returns Number of newly registered samples
648
- */
649
-
650
- export function migrateSampleArtifacts(
651
- workspaceDir: string,
652
- targetModelFamily: string | null = null
653
- ): number {
654
- const samplePaths = NocturnalPathResolver.listSamples(workspaceDir);
655
- let newCount = 0;
656
-
657
- for (const samplePath of samplePaths) {
658
- try {
659
- const content = fs.readFileSync(samplePath, 'utf-8');
660
- const sample = JSON.parse(content);
661
-
662
- // Only process approved samples
663
- if (sample.status !== 'approved') continue;
664
- if (!sample.artifactId || !sample.sessionId || !sample.principleId) continue;
665
-
666
- // Skip if already in registry
667
- const fingerprint = generateSampleFingerprint(
668
- sample.artifactId,
669
- sample.principleId,
670
- sample.sessionId
671
- );
672
- const existing = getDatasetRecord(workspaceDir, fingerprint);
673
- if (existing) continue;
674
-
675
- // Register the artifact
676
- const artifact: NocturnalArtifact = {
677
- artifactId: sample.artifactId,
678
- sessionId: sample.sessionId,
679
- principleId: sample.principleId,
680
- sourceSnapshotRef: sample.sourceSnapshotRef || '',
681
- badDecision: sample.badDecision || '',
682
- betterDecision: sample.betterDecision || '',
683
- rationale: sample.rationale || '',
684
- createdAt: sample.createdAt || new Date().toISOString(),
685
- };
686
-
687
- registerSample(workspaceDir, artifact, samplePath, targetModelFamily);
688
- newCount++;
689
- } catch {
690
- // Skip malformed files
691
- }
692
- }
693
-
694
- return newCount;
695
- }
696
-
697
- // ---------------------------------------------------------------------------
698
- // Replay Classification Support
699
- // ---------------------------------------------------------------------------
700
-
701
- /**
702
- * List samples filtered by replay classification.
703
- *
704
- * @param workspaceDir - Workspace directory
705
- * @param classification - Classification to filter by
706
- * @returns Records matching the classification, sorted by createdAt descending
707
- */
708
- export function listSamplesByClassification(
709
- workspaceDir: string,
710
- classification: SampleClassification
711
- ): NocturnalDatasetRecord[] {
712
- const records = readRegistry(workspaceDir);
713
- return records
714
- .filter((r) => r.classification === classification)
715
- .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
716
- }
717
-
718
- /**
719
- * Load and parse the artifact content for a given dataset record.
720
- *
721
- * @param workspaceDir - Workspace directory
722
- * @param record - The dataset record whose artifact to load
723
- * @returns The parsed artifact JSON content
724
- * @throws Error if artifact file is missing or unreadable
725
- */
726
- export function loadSampleContent(
727
- workspaceDir: string,
728
- record: NocturnalDatasetRecord
729
- ): unknown {
730
- if (!fs.existsSync(record.artifactPath)) {
731
- throw new Error(`Artifact file missing for sample ${record.sampleFingerprint}: ${record.artifactPath}`);
732
- }
733
- const content = fs.readFileSync(record.artifactPath, 'utf-8');
734
- return JSON.parse(content);
735
- }
736
-
737
- /**
738
- * Update the classification of a dataset record.
739
- *
740
- * @param workspaceDir - Workspace directory
741
- * @param sampleFingerprint - The fingerprint of the record to update
742
- * @param classification - New classification value
743
- * @returns Updated record
744
- */
745
- export function updateSampleClassification(
746
- workspaceDir: string,
747
- sampleFingerprint: string,
748
- classification: SampleClassification | null
749
- ): NocturnalDatasetRecord {
750
- return withRegistryLock(workspaceDir, (records) => {
751
- const idx = records.findIndex((r) => r.sampleFingerprint === sampleFingerprint);
752
- if (idx === -1) {
753
- throw new Error(`Dataset record not found: ${sampleFingerprint}`);
754
- }
755
- records[idx] = {
756
- ...records[idx],
757
- classification,
758
- updatedAt: new Date().toISOString(),
759
- };
760
- writeRegistry(workspaceDir, records);
761
- return records[idx];
762
- });
763
- }