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,577 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ import {
6
+ executeNocturnalReflection,
7
+ executeNocturnalReflectionAsync,
8
+ listApprovedNocturnalArtifacts,
9
+ } from '../../src/service/nocturnal-service.js';
10
+ import { createNocturnalTrajectoryExtractor } from '../../src/core/nocturnal-trajectory-extractor.js';
11
+ import { TrajectoryDatabase, TrajectoryRegistry } from '../../src/core/trajectory.js';
12
+ import {
13
+ checkWorkspaceIdle,
14
+ clearAllCooldowns,
15
+ recordRunStart,
16
+ recordRunEnd,
17
+ } from '../../src/service/nocturnal-runtime.js';
18
+ import { NocturnalPathResolver } from '../../src/core/nocturnal-paths.js';
19
+ import { seedSessionForTest, clearSession, listSessions } from '../../src/core/session-tracker.js';
20
+
21
+ /**
22
+ * NocturnalService Integration Tests
23
+ *
24
+ * NOTE: These tests have complex setup due to:
25
+ * 1. TrajectoryRegistry singleton - must use same instance as service
26
+ * 2. Async cleanup on Windows - wrapped in try-catch
27
+ * 3. Fire-and-forget cooldowns - use awaitClearAllCooldowns helper
28
+ */
29
+ describe('NocturnalService', () => {
30
+ let tmpDir: string;
31
+ let workspaceDir: string;
32
+ let stateDir: string;
33
+ let trajectory: TrajectoryDatabase;
34
+
35
+ beforeEach(() => {
36
+ // Create a clean temp directory structure:
37
+ // tmpDir/
38
+ // workspace/ ← workspaceDir
39
+ // state/ ← stateDir
40
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-nocturnal-service-test-'));
41
+ workspaceDir = path.join(tmpDir, 'workspace');
42
+ stateDir = path.join(tmpDir, 'state');
43
+ fs.mkdirSync(workspaceDir, { recursive: true });
44
+ fs.mkdirSync(stateDir, { recursive: true });
45
+
46
+ // Initialize trajectory DB and prime the TrajectoryRegistry singleton.
47
+ // The service uses createNocturnalTrajectoryExtractor which internally
48
+ // calls TrajectoryRegistry.get(workspaceDir). We call it here so that
49
+ // the service sees the SAME instance we seed with test data.
50
+ trajectory = new TrajectoryDatabase({ workspaceDir });
51
+
52
+ // Prime the singleton so the service gets our seeded instance
53
+ const extractor = createNocturnalTrajectoryExtractor(workspaceDir);
54
+ // extractor holds the same TrajectoryDatabase instance via singleton
55
+
56
+ // Also clear any residual SessionTracker sessions from previous tests
57
+ // (SessionTracker uses an in-memory Map that persists across tests)
58
+ for (const session of listSessions()) {
59
+ clearSession(session.sessionId);
60
+ }
61
+ });
62
+
63
+ afterEach(() => {
64
+ // Dispose in correct order: trajectory first, then registry.
65
+ // On Windows, file handles may not be released immediately, so wrap in try-catch.
66
+ try {
67
+ trajectory.dispose();
68
+ } catch {
69
+ // Best effort
70
+ }
71
+ try {
72
+ TrajectoryRegistry.dispose(workspaceDir);
73
+ } catch {
74
+ // Best effort
75
+ }
76
+ try {
77
+ fs.rmSync(tmpDir, { recursive: true, force: true });
78
+ } catch {
79
+ // On Windows, some file handles may still be open
80
+ }
81
+ });
82
+
83
+ // -------------------------------------------------------------------------
84
+ // Helper: awaitable clear cooldowns (sync version writes directly)
85
+ // -------------------------------------------------------------------------
86
+
87
+ function clearCooldownsSync(): void {
88
+ // Write empty cooldown state directly without async
89
+ const runtimePath = path.join(stateDir, 'nocturnal-runtime.json');
90
+ const defaultState = {
91
+ principleCooldowns: {},
92
+ recentRunTimestamps: [],
93
+ };
94
+ fs.writeFileSync(runtimePath, JSON.stringify(defaultState, null, 2), 'utf-8');
95
+ }
96
+
97
+ // -------------------------------------------------------------------------
98
+ // Helper: create a properly idle override (bypasses real idle check)
99
+ // -------------------------------------------------------------------------
100
+
101
+ function makeIdleResult(): ReturnType<typeof checkWorkspaceIdle> {
102
+ return {
103
+ isIdle: true,
104
+ mostRecentActivityAt: Date.now() - 2 * 60 * 60 * 1000, // 2 hours ago
105
+ idleForMs: 2 * 60 * 60 * 1000,
106
+ userActiveSessions: 0,
107
+ abandonedSessionIds: [],
108
+ trajectoryGuardrailConfirmsIdle: true,
109
+ reason: 'test override — workspace considered idle',
110
+ };
111
+ }
112
+
113
+ // -------------------------------------------------------------------------
114
+ // Helper: seed a minimal trajectory
115
+ // -------------------------------------------------------------------------
116
+
117
+ function seedSession(
118
+ sessionId: string,
119
+ startedAt: string,
120
+ opts: {
121
+ withToolCalls?: number;
122
+ withPain?: boolean;
123
+ withGateBlock?: boolean;
124
+ outcome?: 'success' | 'failure';
125
+ } = {}
126
+ ): void {
127
+ trajectory.recordSession({ sessionId, startedAt });
128
+ const { withToolCalls = 1, withPain = false, withGateBlock = false, outcome = 'failure' } = opts;
129
+
130
+ for (let i = 0; i < withToolCalls; i++) {
131
+ trajectory.recordToolCall({
132
+ sessionId,
133
+ toolName: 'Bash',
134
+ outcome,
135
+ errorMessage: outcome === 'failure' ? 'Command failed: exit code 1' : null,
136
+ });
137
+ }
138
+
139
+ if (withPain) {
140
+ trajectory.recordPainEvent({
141
+ sessionId,
142
+ source: 'test',
143
+ score: 50,
144
+ reason: 'Test pain event',
145
+ });
146
+ }
147
+
148
+ if (withGateBlock) {
149
+ trajectory.recordGateBlock({
150
+ sessionId,
151
+ toolName: 'Edit',
152
+ reason: 'Safety check: RISK_PATH modification requires PLAN.md',
153
+ riskLevel: 'medium',
154
+ });
155
+ }
156
+
157
+ // Also seed SessionTracker.sessions so checkWorkspaceIdle can see the session
158
+ // (SessionTracker is separate from TrajectoryDatabase)
159
+ const lastActivityAt = new Date(startedAt).getTime();
160
+ seedSessionForTest(sessionId, workspaceDir, lastActivityAt);
161
+ }
162
+
163
+ // -------------------------------------------------------------------------
164
+ // Helper: seed evaluable principles (T-08 for most tests)
165
+ // -------------------------------------------------------------------------
166
+
167
+ function seedPrinciples(): void {
168
+ const trainingStatesPath = path.join(stateDir, 'principle_training_state.json');
169
+ // T-01 has low compliance so T-08 wins selection (higher compliance = lower score priority)
170
+ const data = {
171
+ 'T-01': {
172
+ principleId: 'T-01',
173
+ principleName: 'Map Before Territory',
174
+ evaluability: 'deterministic',
175
+ complianceRate: 0.3,
176
+ violationTrend: 1,
177
+ observedViolationCount: 3,
178
+ applicableOpportunityCount: 10,
179
+ generatedSampleCount: 1,
180
+ cooldownUntil: null,
181
+ internalizationStatus: 'internalized',
182
+ },
183
+ 'T-08': {
184
+ principleId: 'T-08',
185
+ principleName: 'Pain as Signal',
186
+ evaluability: 'deterministic',
187
+ complianceRate: 0.8,
188
+ violationTrend: 1,
189
+ observedViolationCount: 5,
190
+ applicableOpportunityCount: 8,
191
+ generatedSampleCount: 0,
192
+ cooldownUntil: null,
193
+ internalizationStatus: 'internalized',
194
+ },
195
+ };
196
+ fs.writeFileSync(trainingStatesPath, JSON.stringify(data, null, 2), 'utf-8');
197
+ }
198
+
199
+ // -------------------------------------------------------------------------
200
+ // Helper: force workspace to be "idle"
201
+ // -------------------------------------------------------------------------
202
+
203
+ function forceIdleWorkspace(): void {
204
+ // Workspace is idle when there are no recent sessions or sessions are old
205
+ // A session from 2+ hours ago will be considered abandoned
206
+ const oldSessionId = 'session-old-abandoned';
207
+ const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000 - 1000).toISOString();
208
+ seedSession(oldSessionId, twoHoursAgo, { withToolCalls: 0 });
209
+ }
210
+
211
+ // -------------------------------------------------------------------------
212
+ // Tests: executeNocturnalReflection — successful run
213
+ // -------------------------------------------------------------------------
214
+
215
+ describe('executeNocturnalReflection — successful run', () => {
216
+ it('produces an approved artifact when all conditions are met', () => {
217
+ // Setup: idle workspace + evaluable principles + clear cooldowns + violating session
218
+ forceIdleWorkspace();
219
+ seedPrinciples();
220
+ clearCooldownsSync();
221
+
222
+ const recentSessionId = 'session-recent-violation';
223
+ const now = new Date().toISOString();
224
+ seedSession(recentSessionId, now, { withToolCalls: 3, withPain: true, outcome: 'failure' });
225
+
226
+ const idleResult = makeIdleResult();
227
+ const result = executeNocturnalReflection(workspaceDir, stateDir, { idleCheckOverride: idleResult });
228
+
229
+ expect(result.success).toBe(true);
230
+ expect(result.artifact).toBeDefined();
231
+ expect(result.artifact?.principleId).toBe('T-08');
232
+ expect(result.artifact?.sessionId).toBe(recentSessionId);
233
+ expect(result.artifact?.badDecision).toBeTruthy();
234
+ expect(result.artifact?.betterDecision).toBeTruthy();
235
+ expect(result.artifact?.rationale).toBeTruthy();
236
+ expect(result.diagnostics.persisted).toBe(true);
237
+ expect(result.diagnostics.persistedPath).toBeDefined();
238
+ });
239
+
240
+ it('persists artifact to the samples directory', () => {
241
+ forceIdleWorkspace();
242
+ seedPrinciples();
243
+ clearCooldownsSync();
244
+
245
+ const recentSessionId = 'session-recent-2';
246
+ const now = new Date().toISOString();
247
+ seedSession(recentSessionId, now, { withToolCalls: 2, withPain: true, outcome: 'failure' });
248
+
249
+ const idleResult = makeIdleResult();
250
+ const result = executeNocturnalReflection(workspaceDir, stateDir, { idleCheckOverride: idleResult });
251
+ expect(result.success).toBe(true);
252
+ expect(result.diagnostics.persistedPath).toBeDefined();
253
+
254
+ // Verify file exists
255
+ const persistedPath = result.diagnostics.persistedPath!;
256
+ expect(fs.existsSync(persistedPath)).toBe(true);
257
+
258
+ // Verify content
259
+ const content = JSON.parse(fs.readFileSync(persistedPath, 'utf-8'));
260
+ expect(content.status).toBe('approved');
261
+ expect(content.artifactId).toBe(result.artifact?.artifactId);
262
+ expect(content.boundedAction).toBeDefined();
263
+ });
264
+
265
+ it('returns a boundedAction in the artifact', () => {
266
+ forceIdleWorkspace();
267
+ seedPrinciples();
268
+ clearCooldownsSync();
269
+
270
+ const recentSessionId = 'session-recent-3';
271
+ const now = new Date().toISOString();
272
+ // Seed with pain=true so T-08 is violated (pain+failure needed for T-08)
273
+ seedSession(recentSessionId, now, { withToolCalls: 3, withPain: true, outcome: 'failure' });
274
+
275
+ const idleResult = makeIdleResult();
276
+ const result = executeNocturnalReflection(workspaceDir, stateDir, { idleCheckOverride: idleResult });
277
+ expect(result.success).toBe(true);
278
+ expect(result.artifact?.boundedAction).toBeDefined();
279
+ expect(result.artifact?.boundedAction?.verb).toBeTruthy();
280
+ expect(result.artifact?.boundedAction?.target).toBeTruthy();
281
+ });
282
+ });
283
+
284
+ // -------------------------------------------------------------------------
285
+ // Tests: executeNocturnalReflection — skip conditions
286
+ // -------------------------------------------------------------------------
287
+
288
+ describe('executeNocturnalReflection — skip conditions', () => {
289
+ it('skips when workspace is not idle', () => {
290
+ // Setup: NO forceIdleWorkspace - workspace has a very recent session
291
+ seedPrinciples();
292
+ clearCooldownsSync();
293
+
294
+ // Create a session that started JUST NOW (non-idle)
295
+ const activeSessionId = 'session-active';
296
+ const justNow = new Date().toISOString();
297
+ seedSession(activeSessionId, justNow, { withToolCalls: 1 });
298
+
299
+ const result = executeNocturnalReflection(workspaceDir, stateDir);
300
+
301
+ // Workspace is NOT idle, so preflight should block
302
+ expect(result.success).toBe(false);
303
+ expect(result.noTargetSelected).toBe(false); // preflight blocked
304
+ });
305
+
306
+ it('skips when no evaluable principles exist', () => {
307
+ forceIdleWorkspace();
308
+ clearCooldownsSync();
309
+ // Don't seed any principles
310
+
311
+ const recentSessionId = 'session-recent';
312
+ const now = new Date().toISOString();
313
+ seedSession(recentSessionId, now, { withToolCalls: 3, withPain: true, outcome: 'failure' });
314
+
315
+ const idleResult = makeIdleResult();
316
+ const result = executeNocturnalReflection(workspaceDir, stateDir, { idleCheckOverride: idleResult });
317
+ expect(result.success).toBe(false);
318
+ expect(result.noTargetSelected).toBe(true);
319
+ expect(result.skipReason).toBe('no_evaluable_principles');
320
+ });
321
+
322
+ it('skips when no violating sessions found (only successful sessions)', () => {
323
+ forceIdleWorkspace();
324
+ seedPrinciples();
325
+ clearCooldownsSync();
326
+
327
+ // Only successful sessions (no violations)
328
+ const sessionId = 'session-success-only';
329
+ const now = new Date().toISOString();
330
+ seedSession(sessionId, now, { withToolCalls: 3, outcome: 'success' });
331
+
332
+ const idleResult = makeIdleResult();
333
+ const result = executeNocturnalReflection(workspaceDir, stateDir, { idleCheckOverride: idleResult });
334
+ expect(result.success).toBe(false);
335
+ expect(result.noTargetSelected).toBe(true);
336
+ expect(result.skipReason).toBe('no_violating_sessions');
337
+ });
338
+
339
+ it('returns failure when snapshot extraction fails', () => {
340
+ forceIdleWorkspace();
341
+ seedPrinciples();
342
+ clearCooldownsSync();
343
+
344
+ // Create a session with no tool calls - will fail snapshot extraction
345
+ const sessionId = 'session-no-toolcalls';
346
+ const now = new Date().toISOString();
347
+ seedSession(sessionId, now, { withToolCalls: 0, withPain: false, outcome: 'success' });
348
+
349
+ const idleResult = makeIdleResult();
350
+ const result = executeNocturnalReflection(workspaceDir, stateDir, { idleCheckOverride: idleResult });
351
+ // Session has no tool calls, so minToolCalls=1 filter removes it
352
+ // No sessions available → selection skips with insufficient_snapshot_data
353
+ expect(result.success).toBe(false);
354
+ expect(result.noTargetSelected).toBe(true);
355
+ });
356
+ });
357
+
358
+ // -------------------------------------------------------------------------
359
+ // Tests: executeNocturnalReflection — reflector override
360
+ // -------------------------------------------------------------------------
361
+
362
+ describe('executeNocturnalReflection — reflector override', () => {
363
+ it('uses reflectorOutputOverride when skipReflector is true', () => {
364
+ forceIdleWorkspace();
365
+ seedPrinciples();
366
+ clearCooldownsSync();
367
+
368
+ const recentSessionId = 'session-override-test';
369
+ const now = new Date().toISOString();
370
+ seedSession(recentSessionId, now, { withToolCalls: 3, withPain: true, outcome: 'failure' });
371
+
372
+ const overrideArtifact = {
373
+ artifactId: '11111111-2222-4333-aaaa-bbbbbbbbbbbb',
374
+ sessionId: recentSessionId,
375
+ principleId: 'T-08',
376
+ sourceSnapshotRef: 'snapshot-override',
377
+ badDecision: 'Overridden bad decision',
378
+ betterDecision: 'Read the error message before retrying the bash command',
379
+ rationale: 'Overridden rationale for testing purposes',
380
+ createdAt: new Date().toISOString(),
381
+ };
382
+
383
+ const idleResult = makeIdleResult();
384
+ const result = executeNocturnalReflection(workspaceDir, stateDir, {
385
+ skipReflector: true,
386
+ reflectorOutputOverride: JSON.stringify(overrideArtifact),
387
+ idleCheckOverride: idleResult,
388
+ });
389
+
390
+ expect(result.success).toBe(true);
391
+ expect(result.artifact?.artifactId).toBe('11111111-2222-4333-aaaa-bbbbbbbbbbbb');
392
+ expect(result.artifact?.badDecision).toBe('Overridden bad decision');
393
+ });
394
+
395
+ it('fails if skipReflector is true but no override provided', () => {
396
+ forceIdleWorkspace();
397
+ seedPrinciples();
398
+ clearCooldownsSync();
399
+
400
+ // Seed a session so selector finds a target, then validation fails because no override
401
+ const recentSessionId = 'session-no-override';
402
+ const now = new Date().toISOString();
403
+ seedSession(recentSessionId, now, { withToolCalls: 3, withPain: true, outcome: 'failure' });
404
+
405
+ const idleResult = makeIdleResult();
406
+ const result = executeNocturnalReflection(workspaceDir, stateDir, {
407
+ skipReflector: true,
408
+ idleCheckOverride: idleResult,
409
+ // no reflectorOutputOverride
410
+ });
411
+
412
+ expect(result.success).toBe(false);
413
+ expect(result.validationFailed).toBe(true);
414
+ expect(result.validationFailures.some(f => f.includes('reflectorOutputOverride'))).toBe(true);
415
+ });
416
+
417
+ it('rejects invalid JSON in reflectorOutputOverride', () => {
418
+ forceIdleWorkspace();
419
+ seedPrinciples();
420
+ clearCooldownsSync();
421
+
422
+ const recentSessionId = 'session-bad-override';
423
+ const now = new Date().toISOString();
424
+ seedSession(recentSessionId, now, { withToolCalls: 3, withPain: true, outcome: 'failure' });
425
+
426
+ const idleResult = makeIdleResult();
427
+ const result = executeNocturnalReflection(workspaceDir, stateDir, {
428
+ skipReflector: true,
429
+ reflectorOutputOverride: 'not valid json',
430
+ idleCheckOverride: idleResult,
431
+ });
432
+
433
+ expect(result.success).toBe(false);
434
+ expect(result.validationFailed).toBe(true);
435
+ expect(result.validationFailures.some(f => f.includes('parse'))).toBe(true);
436
+ });
437
+ });
438
+
439
+ // -------------------------------------------------------------------------
440
+ // Tests: executeNocturnalReflection — arbiter/executability rejection
441
+ // -------------------------------------------------------------------------
442
+
443
+ describe('executeNocturnalReflection — validation rejection', () => {
444
+ it('rejects artifact with vague verb in betterDecision', () => {
445
+ forceIdleWorkspace();
446
+ seedPrinciples();
447
+ clearCooldownsSync();
448
+
449
+ const recentSessionId = 'session-vague';
450
+ const now = new Date().toISOString();
451
+ seedSession(recentSessionId, now, { withToolCalls: 3, withPain: true, outcome: 'failure' });
452
+
453
+ const overrideArtifact = {
454
+ artifactId: '22222222-3333-4444-aaaa-cccccccccccc',
455
+ sessionId: recentSessionId,
456
+ principleId: 'T-08',
457
+ sourceSnapshotRef: 'snapshot-vague',
458
+ badDecision: 'Made a bad decision',
459
+ betterDecision: 'Understand the error first',
460
+ rationale: 'Testing executability rejection for vague verbs',
461
+ createdAt: new Date().toISOString(),
462
+ };
463
+
464
+ const idleResult = makeIdleResult();
465
+ const result = executeNocturnalReflection(workspaceDir, stateDir, {
466
+ skipReflector: true,
467
+ reflectorOutputOverride: JSON.stringify(overrideArtifact),
468
+ idleCheckOverride: idleResult,
469
+ });
470
+
471
+ expect(result.success).toBe(false);
472
+ expect(result.validationFailed).toBe(true);
473
+ expect(result.validationFailures.some(f => f.includes('vague verb'))).toBe(true);
474
+ });
475
+ });
476
+
477
+ // -------------------------------------------------------------------------
478
+ // Tests: listApprovedNocturnalArtifacts
479
+ // -------------------------------------------------------------------------
480
+
481
+ describe('listApprovedNocturnalArtifacts', () => {
482
+ it('returns empty array when no artifacts exist', () => {
483
+ const artifacts = listApprovedNocturnalArtifacts(workspaceDir);
484
+ expect(artifacts).toHaveLength(0);
485
+ });
486
+
487
+ it('returns approved artifacts sorted by createdAt descending', () => {
488
+ // Create some sample artifacts
489
+ const samples = [
490
+ {
491
+ artifactId: 'older-artifact',
492
+ sessionId: 'session-1',
493
+ principleId: 'T-08',
494
+ sourceSnapshotRef: 'snap-1',
495
+ badDecision: 'Bad decision 1',
496
+ betterDecision: 'Better decision 1',
497
+ rationale: 'Rationale 1',
498
+ createdAt: '2026-03-27T10:00:00.000Z',
499
+ persistedAt: '2026-03-27T10:00:00.000Z',
500
+ status: 'approved',
501
+ },
502
+ {
503
+ artifactId: 'newer-artifact',
504
+ sessionId: 'session-2',
505
+ principleId: 'T-08',
506
+ sourceSnapshotRef: 'snap-2',
507
+ badDecision: 'Bad decision 2',
508
+ betterDecision: 'Better decision 2',
509
+ rationale: 'Rationale 2',
510
+ createdAt: '2026-03-27T12:00:00.000Z',
511
+ persistedAt: '2026-03-27T12:00:00.000Z',
512
+ status: 'approved',
513
+ },
514
+ {
515
+ artifactId: 'rejected-artifact',
516
+ sessionId: 'session-3',
517
+ principleId: 'T-08',
518
+ sourceSnapshotRef: 'snap-3',
519
+ badDecision: 'Bad decision 3',
520
+ betterDecision: 'Better decision 3',
521
+ rationale: 'Rationale 3',
522
+ createdAt: '2026-03-27T14:00:00.000Z',
523
+ persistedAt: '2026-03-27T14:00:00.000Z',
524
+ status: 'rejected', // Should be filtered out
525
+ },
526
+ ];
527
+
528
+ for (const sample of samples) {
529
+ const samplePath = NocturnalPathResolver.samplePath(workspaceDir, sample.artifactId);
530
+ const sampleDir = path.dirname(samplePath);
531
+ if (!fs.existsSync(sampleDir)) {
532
+ fs.mkdirSync(sampleDir, { recursive: true });
533
+ }
534
+ fs.writeFileSync(samplePath, JSON.stringify(sample), 'utf-8');
535
+ }
536
+
537
+ const artifacts = listApprovedNocturnalArtifacts(workspaceDir);
538
+ expect(artifacts).toHaveLength(2);
539
+ // Should be sorted by createdAt descending (newer first)
540
+ expect(artifacts[0].artifactId).toBe('newer-artifact');
541
+ expect(artifacts[1].artifactId).toBe('older-artifact');
542
+ });
543
+
544
+ it('skips malformed JSON files', () => {
545
+ const samplePath = NocturnalPathResolver.samplePath(workspaceDir, 'malformed');
546
+ const sampleDir = path.dirname(samplePath);
547
+ if (!fs.existsSync(sampleDir)) {
548
+ fs.mkdirSync(sampleDir, { recursive: true });
549
+ }
550
+ fs.writeFileSync(samplePath, 'not valid json {{{', 'utf-8');
551
+
552
+ const artifacts = listApprovedNocturnalArtifacts(workspaceDir);
553
+ expect(artifacts).toHaveLength(0);
554
+ });
555
+ });
556
+
557
+ // -------------------------------------------------------------------------
558
+ // Tests: executeNocturnalReflectionAsync
559
+ // -------------------------------------------------------------------------
560
+
561
+ describe('executeNocturnalReflectionAsync', () => {
562
+ it('returns a Promise that resolves to the same result as sync version', async () => {
563
+ forceIdleWorkspace();
564
+ seedPrinciples();
565
+ clearCooldownsSync();
566
+
567
+ const recentSessionId = 'session-async-test';
568
+ const now = new Date().toISOString();
569
+ seedSession(recentSessionId, now, { withToolCalls: 3, withPain: true, outcome: 'failure' });
570
+
571
+ const idleResult = makeIdleResult();
572
+ const result = await executeNocturnalReflectionAsync(workspaceDir, stateDir, { idleCheckOverride: idleResult });
573
+ expect(result.success).toBe(true);
574
+ expect(result.artifact).toBeDefined();
575
+ });
576
+ });
577
+ });