principles-disciple 1.8.0 → 1.8.2

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 (460) hide show
  1. package/ADVANCED_CONFIG_ZH.md +97 -0
  2. package/AGENT_INSTALL.md +173 -0
  3. package/AGENT_INSTALL_EN.md +173 -0
  4. package/INSTALL.md +256 -0
  5. package/SKILL.md +63 -0
  6. package/docs/COMMAND_REFERENCE.md +76 -0
  7. package/docs/COMMAND_REFERENCE_EN.md +79 -0
  8. package/esbuild.config.js +75 -0
  9. package/openclaw.plugin.json +6 -1
  10. package/package.json +13 -15
  11. package/scripts/build-web.mjs +46 -0
  12. package/scripts/install-dependencies.cjs +47 -0
  13. package/scripts/sync-plugin.mjs +802 -0
  14. package/scripts/verify-build.mjs +109 -0
  15. package/src/agents/nocturnal-dreamer.md +152 -0
  16. package/src/agents/nocturnal-philosopher.md +138 -0
  17. package/src/agents/nocturnal-reflector.md +126 -0
  18. package/src/agents/nocturnal-scribe.md +164 -0
  19. package/src/commands/capabilities.ts +85 -0
  20. package/{dist/commands/context.js → src/commands/context.ts} +78 -38
  21. package/src/commands/evolution-status.ts +146 -0
  22. package/src/commands/export.ts +111 -0
  23. package/src/commands/focus.ts +533 -0
  24. package/src/commands/nocturnal-review.ts +311 -0
  25. package/src/commands/nocturnal-rollout.ts +763 -0
  26. package/src/commands/nocturnal-train.ts +1002 -0
  27. package/{dist/commands/pain.js → src/commands/pain.ts} +68 -49
  28. package/src/commands/principle-rollback.ts +27 -0
  29. package/{dist/commands/rollback.js → src/commands/rollback.ts} +44 -12
  30. package/src/commands/samples.ts +60 -0
  31. package/src/commands/strategy.ts +38 -0
  32. package/{dist/commands/thinking-os.js → src/commands/thinking-os.ts} +59 -36
  33. package/src/commands/workflow-debug.ts +128 -0
  34. package/{dist/config/defaults/runtime.js → src/config/defaults/runtime.ts} +12 -5
  35. package/src/config/errors.ts +163 -0
  36. package/{dist/config/index.d.ts → src/config/index.ts} +2 -1
  37. package/src/constants/diagnostician.ts +66 -0
  38. package/src/constants/tools.ts +62 -0
  39. package/src/core/adaptive-thresholds.ts +476 -0
  40. package/{dist/core/config-service.js → src/core/config-service.ts} +7 -4
  41. package/{dist/core/config.js → src/core/config.ts} +158 -46
  42. package/src/core/control-ui-db.ts +435 -0
  43. package/{dist/core/detection-funnel.js → src/core/detection-funnel.ts} +36 -21
  44. package/{dist/core/detection-service.js → src/core/detection-service.ts} +7 -4
  45. package/{dist/core/dictionary-service.js → src/core/dictionary-service.ts} +7 -4
  46. package/{dist/core/dictionary.js → src/core/dictionary.ts} +57 -34
  47. package/src/core/empathy-keyword-matcher.ts +327 -0
  48. package/src/core/empathy-types.ts +218 -0
  49. package/src/core/event-log.ts +544 -0
  50. package/src/core/evolution-engine.ts +612 -0
  51. package/src/core/evolution-logger.ts +353 -0
  52. package/src/core/evolution-migration.ts +77 -0
  53. package/src/core/evolution-reducer.ts +731 -0
  54. package/src/core/evolution-types.ts +456 -0
  55. package/src/core/external-training-contract.ts +527 -0
  56. package/src/core/focus-history.ts +1458 -0
  57. package/src/core/hygiene/tracker.ts +117 -0
  58. package/{dist/core/init.js → src/core/init.ts} +39 -26
  59. package/src/core/local-worker-routing.ts +617 -0
  60. package/{dist/core/migration.js → src/core/migration.ts} +18 -11
  61. package/src/core/model-deployment-registry.ts +722 -0
  62. package/src/core/model-training-registry.ts +813 -0
  63. package/src/core/nocturnal-arbiter.ts +706 -0
  64. package/src/core/nocturnal-candidate-scoring.ts +392 -0
  65. package/src/core/nocturnal-compliance.ts +1075 -0
  66. package/src/core/nocturnal-dataset.ts +668 -0
  67. package/src/core/nocturnal-executability.ts +428 -0
  68. package/src/core/nocturnal-export.ts +390 -0
  69. package/{dist/core/nocturnal-paths.js → src/core/nocturnal-paths.ts} +49 -23
  70. package/src/core/nocturnal-trajectory-extractor.ts +484 -0
  71. package/src/core/nocturnal-trinity.ts +1384 -0
  72. package/src/core/pain.ts +122 -0
  73. package/{dist/core/path-resolver.js → src/core/path-resolver.ts} +157 -36
  74. package/{dist/core/paths.js → src/core/paths.ts} +13 -4
  75. package/src/core/principle-training-state.ts +450 -0
  76. package/src/core/profile.ts +226 -0
  77. package/src/core/promotion-gate.ts +822 -0
  78. package/{dist/core/risk-calculator.js → src/core/risk-calculator.ts} +42 -16
  79. package/{dist/core/session-tracker.js → src/core/session-tracker.ts} +185 -63
  80. package/src/core/shadow-observation-registry.ts +534 -0
  81. package/{dist/core/system-logger.js → src/core/system-logger.ts} +9 -5
  82. package/src/core/thinking-models.ts +217 -0
  83. package/src/core/training-program.ts +630 -0
  84. package/src/core/trajectory-types.ts +243 -0
  85. package/src/core/trajectory.ts +1673 -0
  86. package/{dist/core/workspace-context.js → src/core/workspace-context.ts} +57 -32
  87. package/src/hooks/bash-risk.ts +171 -0
  88. package/src/hooks/edit-verification.ts +295 -0
  89. package/src/hooks/gate-block-helper.ts +160 -0
  90. package/src/hooks/gate.ts +210 -0
  91. package/src/hooks/gfi-gate.ts +177 -0
  92. package/src/hooks/lifecycle.ts +326 -0
  93. package/{dist/hooks/llm.js → src/hooks/llm.ts} +166 -139
  94. package/src/hooks/message-sanitize.ts +45 -0
  95. package/src/hooks/pain.ts +384 -0
  96. package/src/hooks/progressive-trust-gate.ts +174 -0
  97. package/src/hooks/prompt.ts +920 -0
  98. package/src/hooks/subagent.ts +207 -0
  99. package/src/hooks/thinking-checkpoint.ts +73 -0
  100. package/src/hooks/trajectory-collector.ts +290 -0
  101. package/src/http/principles-console-route.ts +716 -0
  102. package/src/i18n/commands.ts +117 -0
  103. package/src/index.ts +694 -0
  104. package/src/service/central-database.ts +831 -0
  105. package/src/service/control-ui-query-service.ts +888 -0
  106. package/src/service/evolution-query-service.ts +405 -0
  107. package/src/service/evolution-worker.ts +1646 -0
  108. package/src/service/health-query-service.ts +836 -0
  109. package/{dist/service/nocturnal-runtime.js → src/service/nocturnal-runtime.ts} +263 -36
  110. package/src/service/nocturnal-service.ts +1015 -0
  111. package/src/service/nocturnal-target-selector.ts +532 -0
  112. package/src/service/phase3-input-filter.ts +237 -0
  113. package/src/service/runtime-summary-service.ts +757 -0
  114. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +513 -0
  115. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +603 -0
  116. package/src/service/subagent-workflow/index.ts +51 -0
  117. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +856 -0
  118. package/src/service/subagent-workflow/runtime-direct-driver.ts +166 -0
  119. package/src/service/subagent-workflow/types.ts +378 -0
  120. package/src/service/subagent-workflow/workflow-store.ts +328 -0
  121. package/src/service/trajectory-service.ts +15 -0
  122. package/{dist/tools/critique-prompt.js → src/tools/critique-prompt.ts} +25 -8
  123. package/src/tools/deep-reflect.ts +349 -0
  124. package/{dist/tools/model-index.js → src/tools/model-index.ts} +33 -17
  125. package/src/types/event-types.ts +453 -0
  126. package/src/types/hygiene-types.ts +31 -0
  127. package/src/types/principle-tree-schema.ts +244 -0
  128. package/src/types/runtime-summary.ts +49 -0
  129. package/src/types.ts +74 -0
  130. package/src/utils/file-lock.ts +391 -0
  131. package/{dist/utils/glob-match.js → src/utils/glob-match.ts} +21 -20
  132. package/{dist/utils/hashing.js → src/utils/hashing.ts} +6 -4
  133. package/src/utils/io.ts +110 -0
  134. package/{dist/utils/nlp.js → src/utils/nlp.ts} +19 -12
  135. package/{dist/utils/plugin-logger.js → src/utils/plugin-logger.ts} +33 -8
  136. package/src/utils/subagent-probe.ts +94 -0
  137. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +70 -1
  138. package/templates/pain_settings.json +2 -1
  139. package/tests/README.md +120 -0
  140. package/tests/build-artifacts.test.ts +111 -0
  141. package/tests/commands/evolution-status.test.ts +222 -0
  142. package/tests/commands/evolver.test.ts +22 -0
  143. package/tests/commands/export.test.ts +78 -0
  144. package/tests/commands/nocturnal-review.test.ts +448 -0
  145. package/tests/commands/nocturnal-train.test.ts +97 -0
  146. package/tests/commands/pain.test.ts +108 -0
  147. package/tests/commands/samples.test.ts +65 -0
  148. package/tests/commands/strategy.test.ts +34 -0
  149. package/tests/commands/thinking-os.test.ts +88 -0
  150. package/tests/core/adaptive-thresholds.test.ts +261 -0
  151. package/tests/core/config-service.test.ts +89 -0
  152. package/tests/core/config.test.ts +90 -0
  153. package/tests/core/control-ui-db.test.ts +75 -0
  154. package/tests/core/core-template-guidance.test.ts +21 -0
  155. package/tests/core/detection-funnel.test.ts +63 -0
  156. package/tests/core/detection-service.test.ts +50 -0
  157. package/tests/core/dictionary-service.test.ts +116 -0
  158. package/tests/core/dictionary.test.ts +168 -0
  159. package/tests/core/empathy-keyword-matcher.test.ts +209 -0
  160. package/tests/core/event-log.test.ts +181 -0
  161. package/tests/core/evolution-e2e.test.ts +58 -0
  162. package/tests/core/evolution-engine-gate-integration.test.ts +543 -0
  163. package/tests/core/evolution-engine.test.ts +562 -0
  164. package/tests/core/evolution-logger.test.ts +148 -0
  165. package/tests/core/evolution-migration.test.ts +50 -0
  166. package/tests/core/evolution-paths.test.ts +21 -0
  167. package/tests/core/evolution-reducer.detector-metadata.test.ts +602 -0
  168. package/tests/core/evolution-reducer.test.ts +180 -0
  169. package/tests/core/evolution-types-loop.test.ts +48 -0
  170. package/tests/core/evolution-user-stories.e2e.test.ts +249 -0
  171. package/tests/core/external-training-contract.test.ts +463 -0
  172. package/tests/core/focus-history.test.ts +682 -0
  173. package/tests/core/init-flatten.test.ts +69 -0
  174. package/tests/core/init-refactor.test.ts +87 -0
  175. package/tests/core/init-v1.3.test.ts +46 -0
  176. package/tests/core/init.test.ts +190 -0
  177. package/tests/core/local-worker-routing.test.ts +757 -0
  178. package/tests/core/migration.test.ts +84 -0
  179. package/tests/core/model-deployment-registry.test.ts +845 -0
  180. package/tests/core/model-training-registry.test.ts +889 -0
  181. package/tests/core/nocturnal-arbiter.test.ts +494 -0
  182. package/tests/core/nocturnal-candidate-scoring.test.ts +400 -0
  183. package/tests/core/nocturnal-compliance.test.ts +646 -0
  184. package/tests/core/nocturnal-dataset.test.ts +892 -0
  185. package/tests/core/nocturnal-executability.test.ts +357 -0
  186. package/tests/core/nocturnal-export.test.ts +462 -0
  187. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +428 -0
  188. package/tests/core/nocturnal-trajectory-extractor.test.ts +634 -0
  189. package/tests/core/nocturnal-trinity.test.ts +953 -0
  190. package/tests/core/pain.test.ts +33 -0
  191. package/tests/core/path-resolver.test.ts +57 -0
  192. package/tests/core/paths-refactor.test.ts +42 -0
  193. package/tests/core/phase7-rollout-integration.test.ts +477 -0
  194. package/tests/core/principle-training-state.test.ts +712 -0
  195. package/tests/core/profile.test.ts +56 -0
  196. package/tests/core/promotion-gate.test.ts +556 -0
  197. package/tests/core/risk-calculator.test.ts +168 -0
  198. package/tests/core/session-tracker.test.ts +191 -0
  199. package/tests/core/training-program.test.ts +472 -0
  200. package/tests/core/trajectory.test.ts +265 -0
  201. package/tests/core/workspace-context-factory.test.ts +18 -0
  202. package/tests/core/workspace-context.test.ts +134 -0
  203. package/tests/fixtures/nocturnal-reviewed-subset.json +183 -0
  204. package/tests/fixtures/production-compatibility.test.ts +147 -0
  205. package/tests/fixtures/production-mock-generator.ts +282 -0
  206. package/tests/hooks/bash-risk-integration.test.ts +137 -0
  207. package/tests/hooks/bash-risk.test.ts +81 -0
  208. package/tests/hooks/edit-verification.test.ts +678 -0
  209. package/tests/hooks/gate-edit-verification-p1.test.ts +632 -0
  210. package/tests/hooks/gate-edit-verification.test.ts +435 -0
  211. package/tests/hooks/gate-pipeline-integration.test.ts +404 -0
  212. package/tests/hooks/gate.test.ts +271 -0
  213. package/tests/hooks/gfi-gate-unit.test.ts +422 -0
  214. package/tests/hooks/gfi-gate.test.ts +669 -0
  215. package/tests/hooks/lifecycle.test.ts +248 -0
  216. package/tests/hooks/llm.test.ts +308 -0
  217. package/tests/hooks/message-sanitize.test.ts +36 -0
  218. package/tests/hooks/pain.test.ts +141 -0
  219. package/tests/hooks/progressive-trust-gate.test.ts +277 -0
  220. package/tests/hooks/prompt.test.ts +1411 -0
  221. package/tests/hooks/subagent.test.ts +467 -0
  222. package/tests/hooks/thinking-gate.test.ts +313 -0
  223. package/tests/http/principles-console-route.test.ts +140 -0
  224. package/tests/hygiene-tracker.test.ts +77 -0
  225. package/tests/index.integration.test.ts +179 -0
  226. package/tests/index.shadow-routing.integration.test.ts +140 -0
  227. package/tests/index.test.ts +9 -0
  228. package/tests/integration/empathy-workflow-integration.test.ts +627 -0
  229. package/tests/service/control-ui-query-service.test.ts +121 -0
  230. package/tests/service/empathy-observer-workflow-manager.test.ts +176 -0
  231. package/tests/service/evolution-worker.test.ts +585 -0
  232. package/tests/service/nocturnal-runtime.test.ts +470 -0
  233. package/tests/service/nocturnal-service.test.ts +577 -0
  234. package/tests/service/nocturnal-target-selector.test.ts +615 -0
  235. package/tests/service/nocturnal-workflow-manager.test.ts +439 -0
  236. package/tests/service/phase3-input-filter.test.ts +289 -0
  237. package/tests/service/runtime-summary-service.test.ts +919 -0
  238. package/tests/task-compliance.test.ts +166 -0
  239. package/tests/test-utils.ts +48 -0
  240. package/tests/tools/critique-prompt.test.ts +260 -0
  241. package/tests/tools/deep-reflect.test.ts +232 -0
  242. package/tests/tools/model-index.test.ts +246 -0
  243. package/tests/ui/app.test.tsx +114 -0
  244. package/tests/utils/file-lock.test.ts +407 -0
  245. package/tests/utils/hashing.test.ts +32 -0
  246. package/tests/utils/io.test.ts +39 -0
  247. package/tests/utils/nlp.test.ts +53 -0
  248. package/tests/utils/plugin-logger.test.ts +156 -0
  249. package/tsconfig.json +16 -0
  250. package/tsconfig.tsbuildinfo +1 -0
  251. package/ui/src/App.tsx +45 -0
  252. package/ui/src/api.ts +216 -0
  253. package/ui/src/charts.tsx +586 -0
  254. package/ui/src/components/ErrorState.tsx +6 -0
  255. package/ui/src/components/Loading.tsx +13 -0
  256. package/ui/src/components/ProtectedRoute.tsx +12 -0
  257. package/ui/src/components/Shell.tsx +91 -0
  258. package/ui/src/components/WorkspaceConfig.tsx +146 -0
  259. package/ui/src/components/index.ts +5 -0
  260. package/ui/src/context/auth.tsx +80 -0
  261. package/ui/src/context/theme.tsx +66 -0
  262. package/ui/src/hooks/useAutoRefresh.ts +39 -0
  263. package/ui/src/i18n/ui.ts +363 -0
  264. package/ui/src/main.tsx +16 -0
  265. package/ui/src/pages/EvolutionPage.tsx +352 -0
  266. package/ui/src/pages/FeedbackPage.tsx +140 -0
  267. package/ui/src/pages/GateMonitorPage.tsx +136 -0
  268. package/ui/src/pages/LoginPage.tsx +88 -0
  269. package/ui/src/pages/OverviewPage.tsx +238 -0
  270. package/ui/src/pages/SamplesPage.tsx +174 -0
  271. package/ui/src/pages/ThinkingModelsPage.tsx +127 -0
  272. package/ui/src/styles.css +1661 -0
  273. package/ui/src/types.ts +368 -0
  274. package/ui/src/utils/format.ts +15 -0
  275. package/vitest.config.ts +23 -0
  276. package/dist/commands/capabilities.d.ts +0 -3
  277. package/dist/commands/capabilities.js +0 -73
  278. package/dist/commands/context.d.ts +0 -5
  279. package/dist/commands/evolution-status.d.ts +0 -4
  280. package/dist/commands/evolution-status.js +0 -117
  281. package/dist/commands/evolver.d.ts +0 -9
  282. package/dist/commands/evolver.js +0 -26
  283. package/dist/commands/export.d.ts +0 -2
  284. package/dist/commands/export.js +0 -98
  285. package/dist/commands/focus.d.ts +0 -14
  286. package/dist/commands/focus.js +0 -457
  287. package/dist/commands/nocturnal-review.d.ts +0 -24
  288. package/dist/commands/nocturnal-review.js +0 -265
  289. package/dist/commands/nocturnal-rollout.d.ts +0 -27
  290. package/dist/commands/nocturnal-rollout.js +0 -671
  291. package/dist/commands/nocturnal-train.d.ts +0 -25
  292. package/dist/commands/nocturnal-train.js +0 -919
  293. package/dist/commands/pain.d.ts +0 -5
  294. package/dist/commands/principle-rollback.d.ts +0 -4
  295. package/dist/commands/principle-rollback.js +0 -22
  296. package/dist/commands/rollback.d.ts +0 -19
  297. package/dist/commands/samples.d.ts +0 -2
  298. package/dist/commands/samples.js +0 -55
  299. package/dist/commands/strategy.d.ts +0 -3
  300. package/dist/commands/strategy.js +0 -29
  301. package/dist/commands/thinking-os.d.ts +0 -2
  302. package/dist/config/defaults/runtime.d.ts +0 -40
  303. package/dist/config/errors.d.ts +0 -84
  304. package/dist/config/errors.js +0 -94
  305. package/dist/config/index.js +0 -7
  306. package/dist/constants/diagnostician.d.ts +0 -12
  307. package/dist/constants/diagnostician.js +0 -56
  308. package/dist/constants/tools.d.ts +0 -17
  309. package/dist/constants/tools.js +0 -54
  310. package/dist/core/adaptive-thresholds.d.ts +0 -186
  311. package/dist/core/adaptive-thresholds.js +0 -300
  312. package/dist/core/config-service.d.ts +0 -15
  313. package/dist/core/config.d.ts +0 -127
  314. package/dist/core/control-ui-db.d.ts +0 -95
  315. package/dist/core/control-ui-db.js +0 -292
  316. package/dist/core/detection-funnel.d.ts +0 -33
  317. package/dist/core/detection-service.d.ts +0 -15
  318. package/dist/core/dictionary-service.d.ts +0 -15
  319. package/dist/core/dictionary.d.ts +0 -38
  320. package/dist/core/event-log.d.ts +0 -82
  321. package/dist/core/event-log.js +0 -463
  322. package/dist/core/evolution-engine.d.ts +0 -118
  323. package/dist/core/evolution-engine.js +0 -464
  324. package/dist/core/evolution-logger.d.ts +0 -137
  325. package/dist/core/evolution-logger.js +0 -256
  326. package/dist/core/evolution-migration.d.ts +0 -5
  327. package/dist/core/evolution-migration.js +0 -65
  328. package/dist/core/evolution-reducer.d.ts +0 -98
  329. package/dist/core/evolution-reducer.js +0 -465
  330. package/dist/core/evolution-types.d.ts +0 -287
  331. package/dist/core/evolution-types.js +0 -78
  332. package/dist/core/external-training-contract.d.ts +0 -276
  333. package/dist/core/external-training-contract.js +0 -269
  334. package/dist/core/focus-history.d.ts +0 -210
  335. package/dist/core/focus-history.js +0 -1185
  336. package/dist/core/hygiene/tracker.d.ts +0 -22
  337. package/dist/core/hygiene/tracker.js +0 -106
  338. package/dist/core/init.d.ts +0 -12
  339. package/dist/core/local-worker-routing.d.ts +0 -175
  340. package/dist/core/local-worker-routing.js +0 -525
  341. package/dist/core/migration.d.ts +0 -6
  342. package/dist/core/model-deployment-registry.d.ts +0 -218
  343. package/dist/core/model-deployment-registry.js +0 -503
  344. package/dist/core/model-training-registry.d.ts +0 -295
  345. package/dist/core/model-training-registry.js +0 -475
  346. package/dist/core/nocturnal-arbiter.d.ts +0 -159
  347. package/dist/core/nocturnal-arbiter.js +0 -534
  348. package/dist/core/nocturnal-candidate-scoring.d.ts +0 -137
  349. package/dist/core/nocturnal-candidate-scoring.js +0 -266
  350. package/dist/core/nocturnal-compliance.d.ts +0 -175
  351. package/dist/core/nocturnal-compliance.js +0 -824
  352. package/dist/core/nocturnal-dataset.d.ts +0 -224
  353. package/dist/core/nocturnal-dataset.js +0 -443
  354. package/dist/core/nocturnal-executability.d.ts +0 -85
  355. package/dist/core/nocturnal-executability.js +0 -331
  356. package/dist/core/nocturnal-export.d.ts +0 -124
  357. package/dist/core/nocturnal-export.js +0 -275
  358. package/dist/core/nocturnal-paths.d.ts +0 -124
  359. package/dist/core/nocturnal-trajectory-extractor.d.ts +0 -242
  360. package/dist/core/nocturnal-trajectory-extractor.js +0 -307
  361. package/dist/core/nocturnal-trinity.d.ts +0 -311
  362. package/dist/core/nocturnal-trinity.js +0 -880
  363. package/dist/core/pain.d.ts +0 -4
  364. package/dist/core/pain.js +0 -70
  365. package/dist/core/path-resolver.d.ts +0 -46
  366. package/dist/core/paths.d.ts +0 -65
  367. package/dist/core/principle-training-state.d.ts +0 -121
  368. package/dist/core/principle-training-state.js +0 -321
  369. package/dist/core/profile.d.ts +0 -62
  370. package/dist/core/profile.js +0 -210
  371. package/dist/core/promotion-gate.d.ts +0 -238
  372. package/dist/core/promotion-gate.js +0 -529
  373. package/dist/core/risk-calculator.d.ts +0 -22
  374. package/dist/core/session-tracker.d.ts +0 -99
  375. package/dist/core/shadow-observation-registry.d.ts +0 -217
  376. package/dist/core/shadow-observation-registry.js +0 -308
  377. package/dist/core/system-logger.d.ts +0 -8
  378. package/dist/core/thinking-models.d.ts +0 -38
  379. package/dist/core/thinking-models.js +0 -170
  380. package/dist/core/training-program.d.ts +0 -233
  381. package/dist/core/training-program.js +0 -433
  382. package/dist/core/trajectory.d.ts +0 -411
  383. package/dist/core/trajectory.js +0 -1307
  384. package/dist/core/workspace-context.d.ts +0 -71
  385. package/dist/hooks/bash-risk.d.ts +0 -57
  386. package/dist/hooks/bash-risk.js +0 -137
  387. package/dist/hooks/edit-verification.d.ts +0 -62
  388. package/dist/hooks/edit-verification.js +0 -256
  389. package/dist/hooks/gate-block-helper.d.ts +0 -44
  390. package/dist/hooks/gate-block-helper.js +0 -119
  391. package/dist/hooks/gate.d.ts +0 -24
  392. package/dist/hooks/gate.js +0 -173
  393. package/dist/hooks/gfi-gate.d.ts +0 -40
  394. package/dist/hooks/gfi-gate.js +0 -113
  395. package/dist/hooks/lifecycle.d.ts +0 -5
  396. package/dist/hooks/lifecycle.js +0 -284
  397. package/dist/hooks/llm.d.ts +0 -12
  398. package/dist/hooks/message-sanitize.d.ts +0 -3
  399. package/dist/hooks/message-sanitize.js +0 -37
  400. package/dist/hooks/pain.d.ts +0 -5
  401. package/dist/hooks/pain.js +0 -301
  402. package/dist/hooks/progressive-trust-gate.d.ts +0 -51
  403. package/dist/hooks/progressive-trust-gate.js +0 -89
  404. package/dist/hooks/prompt.d.ts +0 -47
  405. package/dist/hooks/prompt.js +0 -884
  406. package/dist/hooks/subagent.d.ts +0 -10
  407. package/dist/hooks/subagent.js +0 -387
  408. package/dist/hooks/thinking-checkpoint.d.ts +0 -37
  409. package/dist/hooks/thinking-checkpoint.js +0 -51
  410. package/dist/hooks/trajectory-collector.d.ts +0 -32
  411. package/dist/hooks/trajectory-collector.js +0 -256
  412. package/dist/http/principles-console-route.d.ts +0 -9
  413. package/dist/http/principles-console-route.js +0 -567
  414. package/dist/i18n/commands.d.ts +0 -26
  415. package/dist/i18n/commands.js +0 -116
  416. package/dist/index.d.ts +0 -7
  417. package/dist/index.js +0 -581
  418. package/dist/service/central-database.d.ts +0 -104
  419. package/dist/service/central-database.js +0 -649
  420. package/dist/service/control-ui-query-service.d.ts +0 -221
  421. package/dist/service/control-ui-query-service.js +0 -543
  422. package/dist/service/empathy-observer-manager.d.ts +0 -52
  423. package/dist/service/empathy-observer-manager.js +0 -229
  424. package/dist/service/evolution-query-service.d.ts +0 -155
  425. package/dist/service/evolution-query-service.js +0 -258
  426. package/dist/service/evolution-worker.d.ts +0 -101
  427. package/dist/service/evolution-worker.js +0 -974
  428. package/dist/service/nocturnal-runtime.d.ts +0 -183
  429. package/dist/service/nocturnal-service.d.ts +0 -163
  430. package/dist/service/nocturnal-service.js +0 -787
  431. package/dist/service/nocturnal-target-selector.d.ts +0 -145
  432. package/dist/service/nocturnal-target-selector.js +0 -315
  433. package/dist/service/phase3-input-filter.d.ts +0 -73
  434. package/dist/service/phase3-input-filter.js +0 -172
  435. package/dist/service/runtime-summary-service.d.ts +0 -122
  436. package/dist/service/runtime-summary-service.js +0 -485
  437. package/dist/service/trajectory-service.d.ts +0 -2
  438. package/dist/service/trajectory-service.js +0 -15
  439. package/dist/tools/critique-prompt.d.ts +0 -14
  440. package/dist/tools/deep-reflect.d.ts +0 -39
  441. package/dist/tools/deep-reflect.js +0 -350
  442. package/dist/tools/model-index.d.ts +0 -9
  443. package/dist/types/event-types.d.ts +0 -306
  444. package/dist/types/event-types.js +0 -106
  445. package/dist/types/hygiene-types.d.ts +0 -20
  446. package/dist/types/hygiene-types.js +0 -12
  447. package/dist/types/runtime-summary.d.ts +0 -47
  448. package/dist/types/runtime-summary.js +0 -1
  449. package/dist/types.d.ts +0 -50
  450. package/dist/types.js +0 -22
  451. package/dist/utils/file-lock.d.ts +0 -71
  452. package/dist/utils/file-lock.js +0 -309
  453. package/dist/utils/glob-match.d.ts +0 -28
  454. package/dist/utils/hashing.d.ts +0 -9
  455. package/dist/utils/io.d.ts +0 -6
  456. package/dist/utils/io.js +0 -106
  457. package/dist/utils/nlp.d.ts +0 -9
  458. package/dist/utils/plugin-logger.d.ts +0 -39
  459. package/dist/utils/subagent-probe.d.ts +0 -34
  460. package/dist/utils/subagent-probe.js +0 -81
@@ -0,0 +1,56 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { normalizeProfile, PROFILE_DEFAULTS } from '../../src/core/profile';
3
+
4
+ describe('Profile Module', () => {
5
+ it('should apply defaults to an empty object', () => {
6
+ const profile = normalizeProfile({});
7
+ expect(profile.audit_level).toBe(PROFILE_DEFAULTS.audit_level);
8
+ expect(profile.tests.on_change).toBe(PROFILE_DEFAULTS.tests.on_change);
9
+ expect(profile._profile_invalid).toBe(false);
10
+ expect(profile._profile_warnings.length).toBe(0);
11
+ });
12
+
13
+ it('should handle invalid profile type', () => {
14
+ const profile = normalizeProfile(null);
15
+ expect(profile.audit_level).toBe(PROFILE_DEFAULTS.audit_level);
16
+ expect(profile._profile_invalid).toBe(true);
17
+ expect(profile._profile_warnings.length).toBeGreaterThan(0);
18
+ });
19
+
20
+ it('should retain custom valid values', () => {
21
+ const custom = {
22
+ audit_level: 'high',
23
+ risk_paths: ['src/core/'],
24
+ tests: {
25
+ on_change: 'full'
26
+ }
27
+ };
28
+ const profile = normalizeProfile(custom);
29
+ expect(profile.audit_level).toBe('high');
30
+ expect(profile.risk_paths).toEqual(['src/core/']);
31
+ expect(profile.tests.on_change).toBe('full');
32
+ expect(profile._profile_invalid).toBe(false);
33
+ });
34
+
35
+ it('should validate and normalize risk_paths', () => {
36
+ const custom = {
37
+ risk_paths: 'src/core/' // string instead of array
38
+ };
39
+ const profile = normalizeProfile(custom);
40
+ expect(profile.risk_paths).toEqual(['src/core/']);
41
+ });
42
+
43
+ it('should handle custom_guards', () => {
44
+ const custom = {
45
+ custom_guards: [
46
+ { pattern: 'src/secret', severity: 'fatal', message: 'No secrets' },
47
+ { pattern: 'src/test', severity: 'invalid' } // should default to error
48
+ ]
49
+ };
50
+ const profile = normalizeProfile(custom);
51
+ expect(profile.custom_guards.length).toBe(2);
52
+ expect(profile.custom_guards[0].severity).toBe('fatal');
53
+ expect(profile.custom_guards[1].severity).toBe('error');
54
+ expect(profile.custom_guards[1].message).toBe('Custom guard triggered');
55
+ });
56
+ });
@@ -0,0 +1,556 @@
1
+ /**
2
+ * Tests for Promotion Gate
3
+ * ========================
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import * as os from 'os';
10
+ import {
11
+ evaluatePromotionGate,
12
+ advancePromotion,
13
+ getPromotionState,
14
+ getPromotionRecord,
15
+ listPromotionsByState,
16
+ rejectCheckpoint,
17
+ DEFAULT_BASELINE_METRICS,
18
+ DEFAULT_MIN_DELTA,
19
+ DEFAULT_ALLOWED_MARGIN,
20
+ type PromotionState,
21
+ } from '../../src/core/promotion-gate.js';
22
+ import {
23
+ registerTrainingRun,
24
+ registerCheckpoint,
25
+ attachEvalSummary,
26
+ getFullRegistry as getTrainingRegistry,
27
+ } from '../../src/core/model-training-registry.js';
28
+
29
+ describe('promotion-gate', () => {
30
+ // -------------------------------------------------------------------------
31
+ // Test setup
32
+ // -------------------------------------------------------------------------
33
+
34
+ let tempDir: string;
35
+ let stateDir: string;
36
+
37
+ beforeEach(() => {
38
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-promotion-test-'));
39
+ stateDir = path.join(tempDir, '.state', 'nocturnal');
40
+ fs.mkdirSync(stateDir, { recursive: true });
41
+ });
42
+
43
+ afterEach(() => {
44
+ try {
45
+ if (fs.existsSync(tempDir)) {
46
+ fs.rmSync(tempDir, { recursive: true, force: true });
47
+ }
48
+ } catch {
49
+ // Ignore cleanup errors
50
+ }
51
+ });
52
+
53
+ // -------------------------------------------------------------------------
54
+ // Helper to create a checkpoint with eval
55
+ // -------------------------------------------------------------------------
56
+
57
+ function createCheckpointWithEval(params: {
58
+ delta?: number;
59
+ verdict?: 'pass' | 'fail' | 'compare_only';
60
+ candidateScore?: number;
61
+ }): { checkpointId: string; evalId: string } {
62
+ const {
63
+ delta = 0.1,
64
+ verdict = 'pass',
65
+ candidateScore = 0.8,
66
+ } = params;
67
+
68
+ // Register training run
69
+ const run = registerTrainingRun(stateDir, {
70
+ targetModelFamily: 'qwen2.5-7b-reader',
71
+ datasetFingerprint: 'fp-dataset-abc',
72
+ exportId: 'export-123',
73
+ sampleCount: 100,
74
+ configFingerprint: 'fp-config-xyz',
75
+ });
76
+
77
+ // Register checkpoint
78
+ const checkpoint = registerCheckpoint(stateDir, {
79
+ trainRunId: run.trainRunId,
80
+ targetModelFamily: 'qwen2.5-7b-reader',
81
+ artifactPath: path.join(tempDir, 'checkpoints', 'ckpt-001'),
82
+ });
83
+
84
+ // Attach eval
85
+ const evalId = `eval-${Date.now()}`;
86
+ attachEvalSummary(stateDir, checkpoint.checkpointId, {
87
+ evalId,
88
+ checkpointId: checkpoint.checkpointId,
89
+ benchmarkId: 'benchmark-001',
90
+ targetModelFamily: 'qwen2.5-7b-reader',
91
+ mode: 'reduced_prompt',
92
+ baselineScore: 0.7,
93
+ candidateScore,
94
+ delta,
95
+ verdict,
96
+ });
97
+
98
+ return { checkpointId: checkpoint.checkpointId, evalId };
99
+ }
100
+
101
+ // -------------------------------------------------------------------------
102
+ // DEFAULT_BASELINE_METRICS
103
+ // -------------------------------------------------------------------------
104
+
105
+ describe('DEFAULT_BASELINE_METRICS', () => {
106
+ it('should have sensible Phase 7 defaults', () => {
107
+ expect(DEFAULT_BASELINE_METRICS.arbiterRejectRate).toBe(0.15);
108
+ expect(DEFAULT_BASELINE_METRICS.executabilityRejectRate).toBe(0.10);
109
+ expect(DEFAULT_BASELINE_METRICS.reviewedSubsetQuality).toBe(0.70);
110
+ });
111
+ });
112
+
113
+ describe('DEFAULT_MIN_DELTA', () => {
114
+ it('should be 0.05', () => {
115
+ expect(DEFAULT_MIN_DELTA).toBe(0.05);
116
+ });
117
+ });
118
+
119
+ describe('DEFAULT_ALLOWED_MARGIN', () => {
120
+ it('should be 0.05', () => {
121
+ expect(DEFAULT_ALLOWED_MARGIN).toBe(0.05);
122
+ });
123
+ });
124
+
125
+ // -------------------------------------------------------------------------
126
+ // evaluatePromotionGate
127
+ // -------------------------------------------------------------------------
128
+
129
+ describe('evaluatePromotionGate', () => {
130
+ it('should fail when checkpoint does not exist', () => {
131
+ const result = evaluatePromotionGate(stateDir, {
132
+ checkpointId: 'nonexistent',
133
+ targetProfile: 'local-reader',
134
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
135
+ });
136
+
137
+ expect(result.passes).toBe(false);
138
+ expect(result.blockers).toContainEqual(
139
+ expect.stringContaining('not found')
140
+ );
141
+ });
142
+
143
+ it('should fail when no eval is attached', () => {
144
+ // Register run and checkpoint without eval
145
+ const run = registerTrainingRun(stateDir, {
146
+ targetModelFamily: 'qwen2.5-7b-reader',
147
+ datasetFingerprint: 'fp-dataset-abc',
148
+ exportId: 'export-123',
149
+ sampleCount: 100,
150
+ configFingerprint: 'fp-config-xyz',
151
+ });
152
+
153
+ const checkpoint = registerCheckpoint(stateDir, {
154
+ trainRunId: run.trainRunId,
155
+ targetModelFamily: 'qwen2.5-7b-reader',
156
+ artifactPath: path.join(tempDir, 'checkpoints', 'ckpt-001'),
157
+ });
158
+
159
+ const result = evaluatePromotionGate(stateDir, {
160
+ checkpointId: checkpoint.checkpointId,
161
+ targetProfile: 'local-reader',
162
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
163
+ });
164
+
165
+ expect(result.passes).toBe(false);
166
+ expect(result.blockers).toContainEqual(
167
+ expect.stringContaining('no eval summary')
168
+ );
169
+ });
170
+
171
+ it('should fail when delta is below threshold', () => {
172
+ const { checkpointId } = createCheckpointWithEval({
173
+ delta: 0.01, // Below 0.05 threshold
174
+ verdict: 'compare_only',
175
+ });
176
+
177
+ const result = evaluatePromotionGate(stateDir, {
178
+ checkpointId,
179
+ targetProfile: 'local-reader',
180
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
181
+ });
182
+
183
+ expect(result.passes).toBe(false);
184
+ expect(result.deltaCheck.passed).toBe(false);
185
+ expect(result.blockers).toContainEqual(
186
+ expect.stringContaining('below threshold')
187
+ );
188
+ });
189
+
190
+ it('should pass when delta is above threshold', () => {
191
+ const { checkpointId } = createCheckpointWithEval({
192
+ delta: 0.1, // Above 0.05 threshold
193
+ verdict: 'pass',
194
+ candidateScore: 0.8,
195
+ });
196
+
197
+ const result = evaluatePromotionGate(stateDir, {
198
+ checkpointId,
199
+ targetProfile: 'local-reader',
200
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
201
+ });
202
+
203
+ expect(result.passes).toBe(true);
204
+ expect(result.suggestedState).toBeDefined();
205
+ });
206
+
207
+ it('should pass for any profile when checkpoint quality is good', () => {
208
+ const { checkpointId } = createCheckpointWithEval({
209
+ delta: 0.1,
210
+ verdict: 'pass',
211
+ });
212
+
213
+ // Promotion gate evaluates checkpoint quality, not rollout policy.
214
+ // Profile-specific rollout constraints are handled at the rollout decision level,
215
+ // not at the gate evaluation level.
216
+ const result = evaluatePromotionGate(stateDir, {
217
+ checkpointId,
218
+ targetProfile: 'local-editor',
219
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
220
+ });
221
+
222
+ expect(result.passes).toBe(true);
223
+ expect(result.suggestedState).toBeDefined();
224
+ });
225
+
226
+ it('should suggest shadow_ready for strong delta', () => {
227
+ const { checkpointId } = createCheckpointWithEval({
228
+ delta: 0.15, // Above 2x threshold
229
+ verdict: 'pass',
230
+ candidateScore: 0.85,
231
+ });
232
+
233
+ const result = evaluatePromotionGate(stateDir, {
234
+ checkpointId,
235
+ targetProfile: 'local-reader',
236
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
237
+ });
238
+
239
+ expect(result.passes).toBe(true);
240
+ expect(result.suggestedState).toBe('shadow_ready');
241
+ });
242
+
243
+ it('should return constraint check details', () => {
244
+ const { checkpointId } = createCheckpointWithEval({
245
+ delta: 0.1,
246
+ verdict: 'pass',
247
+ candidateScore: 0.75,
248
+ });
249
+
250
+ const result = evaluatePromotionGate(stateDir, {
251
+ checkpointId,
252
+ targetProfile: 'local-reader',
253
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
254
+ });
255
+
256
+ expect(result.constraintChecks.length).toBeGreaterThan(0);
257
+ expect(result.deltaCheck).toBeDefined();
258
+ });
259
+ });
260
+
261
+ // -------------------------------------------------------------------------
262
+ // advancePromotion
263
+ // -------------------------------------------------------------------------
264
+
265
+ describe('advancePromotion', () => {
266
+ it('should create promotion record with rejected state when gate fails', () => {
267
+ const { checkpointId } = createCheckpointWithEval({
268
+ delta: 0.01, // Below threshold
269
+ verdict: 'compare_only',
270
+ });
271
+
272
+ const promotion = advancePromotion(stateDir, {
273
+ checkpointId,
274
+ targetProfile: 'local-reader',
275
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
276
+ });
277
+
278
+ expect(promotion.state).toBe('rejected');
279
+ expect(promotion.promotionId).toBeDefined();
280
+ expect(promotion.checkpointId).toBe(checkpointId);
281
+ });
282
+
283
+ it('should create promotion record with candidate_only when gate passes but no review', () => {
284
+ const { checkpointId } = createCheckpointWithEval({
285
+ delta: 0.08,
286
+ verdict: 'pass',
287
+ });
288
+
289
+ const promotion = advancePromotion(stateDir, {
290
+ checkpointId,
291
+ targetProfile: 'local-reader',
292
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
293
+ });
294
+
295
+ expect(promotion.state).toBe('candidate_only');
296
+ expect(promotion.orchestratorReviewPassed).toBe(false);
297
+ });
298
+
299
+ it('should create promotion record with shadow_ready when gate passes with review', () => {
300
+ const { checkpointId } = createCheckpointWithEval({
301
+ delta: 0.15, // Strong delta
302
+ verdict: 'pass',
303
+ candidateScore: 0.85,
304
+ });
305
+
306
+ const promotion = advancePromotion(stateDir, {
307
+ checkpointId,
308
+ targetProfile: 'local-reader',
309
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
310
+ orchestratorReviewPassed: true,
311
+ reviewNote: 'Looks good, proceed with shadow rollout',
312
+ });
313
+
314
+ expect(promotion.state).toBe('shadow_ready');
315
+ expect(promotion.orchestratorReviewPassed).toBe(true);
316
+ expect(promotion.shadowStartedAt).toBeDefined();
317
+ });
318
+
319
+ it('should update existing promotion record', () => {
320
+ const { checkpointId } = createCheckpointWithEval({
321
+ delta: 0.08,
322
+ verdict: 'pass',
323
+ });
324
+
325
+ // First advancement
326
+ const promotion1 = advancePromotion(stateDir, {
327
+ checkpointId,
328
+ targetProfile: 'local-reader',
329
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
330
+ });
331
+ expect(promotion1.state).toBe('candidate_only');
332
+
333
+ // Second advancement with orchestrator review
334
+ const promotion2 = advancePromotion(stateDir, {
335
+ checkpointId,
336
+ targetProfile: 'local-reader',
337
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
338
+ orchestratorReviewPassed: true,
339
+ });
340
+ expect(promotion2.state).toBe('shadow_ready');
341
+ expect(promotion2.promotionId).toBe(promotion1.promotionId);
342
+ });
343
+
344
+ it('preserves original shadowStartedAt on repeated advancePromotion calls', () => {
345
+ const { checkpointId } = createCheckpointWithEval({
346
+ delta: 0.15,
347
+ verdict: 'pass',
348
+ candidateScore: 0.85,
349
+ });
350
+
351
+ // First advance: candidate_only → shadow_ready (sets shadowStartedAt)
352
+ const p1 = advancePromotion(stateDir, {
353
+ checkpointId,
354
+ targetProfile: 'local-reader',
355
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
356
+ orchestratorReviewPassed: true,
357
+ });
358
+ expect(p1.state).toBe('shadow_ready');
359
+ expect(p1.shadowStartedAt).toBeDefined();
360
+ const originalShadowStartedAt = p1.shadowStartedAt;
361
+
362
+ // Advance again: shadow_ready → shadow_ready (shadow window not elapsed)
363
+ // This MUST NOT reset shadowStartedAt
364
+ const p2 = advancePromotion(stateDir, {
365
+ checkpointId,
366
+ targetProfile: 'local-reader',
367
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
368
+ orchestratorReviewPassed: true,
369
+ });
370
+ expect(p2.state).toBe('shadow_ready');
371
+ expect(p2.shadowStartedAt).toBe(originalShadowStartedAt);
372
+ });
373
+
374
+ it('resets shadowStartedAt when re-entering shadow after demotion to candidate_only', () => {
375
+ const { checkpointId } = createCheckpointWithEval({
376
+ delta: 0.15,
377
+ verdict: 'pass',
378
+ candidateScore: 0.85,
379
+ });
380
+
381
+ // 1. Advance to shadow_ready — sets shadowStartedAt
382
+ const p1 = advancePromotion(stateDir, {
383
+ checkpointId,
384
+ targetProfile: 'local-reader',
385
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
386
+ orchestratorReviewPassed: true,
387
+ });
388
+ expect(p1.state).toBe('shadow_ready');
389
+ expect(p1.shadowStartedAt).toBeDefined();
390
+ const originalShadowStartedAt = p1.shadowStartedAt;
391
+
392
+ // 2. Demote: advance WITHOUT review → candidate_only
393
+ const p2 = advancePromotion(stateDir, {
394
+ checkpointId,
395
+ targetProfile: 'local-reader',
396
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
397
+ // No orchestratorReviewPassed → gate passes but stays candidate_only
398
+ });
399
+ expect(p2.state).toBe('candidate_only');
400
+
401
+ // 3. Re-enter shadow: advance WITH review → shadow_ready
402
+ const p3 = advancePromotion(stateDir, {
403
+ checkpointId,
404
+ targetProfile: 'local-reader',
405
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
406
+ orchestratorReviewPassed: true,
407
+ });
408
+ expect(p3.state).toBe('shadow_ready');
409
+ // MUST reset shadowStartedAt — the checkpoint left the shadow path
410
+ expect(p3.shadowStartedAt).toBeDefined();
411
+ expect(p3.shadowStartedAt).not.toBe(originalShadowStartedAt);
412
+ });
413
+
414
+ it('resets shadowStartedAt when re-entering shadow after rejection', () => {
415
+ const { checkpointId } = createCheckpointWithEval({
416
+ delta: 0.15,
417
+ verdict: 'pass',
418
+ candidateScore: 0.85,
419
+ });
420
+
421
+ // 1. Advance to shadow_ready
422
+ const p1 = advancePromotion(stateDir, {
423
+ checkpointId,
424
+ targetProfile: 'local-reader',
425
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
426
+ orchestratorReviewPassed: true,
427
+ });
428
+ expect(p1.state).toBe('shadow_ready');
429
+ const originalShadowStartedAt = p1.shadowStartedAt;
430
+
431
+ // 2. Reject
432
+ const p2 = rejectCheckpoint(stateDir, checkpointId, 'Shadow metrics regressed');
433
+ expect(p2.state).toBe('rejected');
434
+
435
+ // 3. Re-enter shadow: advance with review → shadow_ready
436
+ const p3 = advancePromotion(stateDir, {
437
+ checkpointId,
438
+ targetProfile: 'local-reader',
439
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
440
+ orchestratorReviewPassed: true,
441
+ });
442
+ expect(p3.state).toBe('shadow_ready');
443
+ // MUST reset — checkpoint was rejected, this is a fresh shadow entry
444
+ expect(p3.shadowStartedAt).toBeDefined();
445
+ expect(p3.shadowStartedAt).not.toBe(originalShadowStartedAt);
446
+ });
447
+ });
448
+
449
+ // -------------------------------------------------------------------------
450
+ // getPromotionState
451
+ // -------------------------------------------------------------------------
452
+
453
+ describe('getPromotionState', () => {
454
+ it('should return null for unknown checkpoint', () => {
455
+ const state = getPromotionState(stateDir, 'nonexistent');
456
+ expect(state).toBeNull();
457
+ });
458
+
459
+ it('should return current state for known checkpoint', () => {
460
+ const { checkpointId } = createCheckpointWithEval({
461
+ delta: 0.08, // Below minDelta * 2 (0.1), so stays candidate_only
462
+ verdict: 'pass',
463
+ });
464
+
465
+ advancePromotion(stateDir, {
466
+ checkpointId,
467
+ targetProfile: 'local-reader',
468
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
469
+ });
470
+
471
+ const state = getPromotionState(stateDir, checkpointId);
472
+ expect(state).toBe('candidate_only');
473
+ });
474
+ });
475
+
476
+ // -------------------------------------------------------------------------
477
+ // getPromotionRecord
478
+ // -------------------------------------------------------------------------
479
+
480
+ describe('getPromotionRecord', () => {
481
+ it('should return null for unknown checkpoint', () => {
482
+ const record = getPromotionRecord(stateDir, 'nonexistent');
483
+ expect(record).toBeNull();
484
+ });
485
+
486
+ it('should return full record for known checkpoint', () => {
487
+ const { checkpointId } = createCheckpointWithEval({
488
+ delta: 0.08,
489
+ verdict: 'pass',
490
+ });
491
+
492
+ advancePromotion(stateDir, {
493
+ checkpointId,
494
+ targetProfile: 'local-reader',
495
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
496
+ reviewNote: 'Initial review',
497
+ });
498
+
499
+ const record = getPromotionRecord(stateDir, checkpointId);
500
+ expect(record).not.toBeNull();
501
+ expect(record!.checkpointId).toBe(checkpointId);
502
+ expect(record!.state).toBe('candidate_only');
503
+ expect(record!.reviewNote).toBe('Initial review');
504
+ });
505
+ });
506
+
507
+ // -------------------------------------------------------------------------
508
+ // listPromotionsByState
509
+ // -------------------------------------------------------------------------
510
+
511
+ describe('listPromotionsByState', () => {
512
+ it('should return empty array when no promotions in state', () => {
513
+ const promotions = listPromotionsByState(stateDir, 'shadow_ready');
514
+ expect(promotions).toHaveLength(0);
515
+ });
516
+
517
+ it('should return promotions in specified state', () => {
518
+ const { checkpointId } = createCheckpointWithEval({
519
+ delta: 0.08,
520
+ verdict: 'pass',
521
+ });
522
+
523
+ advancePromotion(stateDir, {
524
+ checkpointId,
525
+ targetProfile: 'local-reader',
526
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
527
+ });
528
+
529
+ const candidates = listPromotionsByState(stateDir, 'candidate_only');
530
+ expect(candidates.length).toBeGreaterThan(0);
531
+ expect(candidates[0].state).toBe('candidate_only');
532
+ });
533
+ });
534
+
535
+ // -------------------------------------------------------------------------
536
+ // rejectCheckpoint
537
+ // -------------------------------------------------------------------------
538
+
539
+ describe('rejectCheckpoint', () => {
540
+ it('should create rejected promotion record', () => {
541
+ const { checkpointId } = createCheckpointWithEval({
542
+ delta: 0.1,
543
+ verdict: 'pass',
544
+ });
545
+
546
+ const promotion = rejectCheckpoint(
547
+ stateDir,
548
+ checkpointId,
549
+ 'Orchestrator review rejected this checkpoint'
550
+ );
551
+
552
+ expect(promotion.state).toBe('rejected');
553
+ expect(promotion.reviewNote).toBe('Orchestrator review rejected this checkpoint');
554
+ });
555
+ });
556
+ });