principles-disciple 1.8.1 → 1.8.3

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 (508) 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 +4 -4
  10. package/package.json +11 -13
  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} +175 -62
  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} +160 -80
  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} +235 -79
  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/{dist/service/subagent-workflow/empathy-observer-workflow-manager.js → src/service/subagent-workflow/empathy-observer-workflow-manager.ts} +240 -117
  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/{dist/service/subagent-workflow/types.d.ts → src/service/subagent-workflow/types.ts} +137 -18
  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/en/skills/ai-sprint-orchestration/EXAMPLES.md +63 -0
  138. package/templates/langs/en/skills/ai-sprint-orchestration/REFERENCE.md +136 -0
  139. package/templates/langs/en/skills/ai-sprint-orchestration/SKILL.md +67 -0
  140. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +214 -0
  141. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +107 -0
  142. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +107 -0
  143. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +105 -0
  144. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +108 -0
  145. package/templates/langs/en/skills/ai-sprint-orchestration/references/workflow-v1-acceptance-checklist.md +58 -0
  146. package/templates/langs/en/skills/ai-sprint-orchestration/references/workflow-v1.4-work-unit-handoff.md +190 -0
  147. package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -0
  148. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/archive.mjs +310 -0
  149. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs +683 -0
  150. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/decision.mjs +604 -0
  151. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/state-store.mjs +32 -0
  152. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +707 -0
  153. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +3419 -0
  154. package/templates/langs/zh/skills/ai-sprint-orchestration/EXAMPLES.md +63 -0
  155. package/templates/langs/zh/skills/ai-sprint-orchestration/REFERENCE.md +136 -0
  156. package/templates/langs/zh/skills/ai-sprint-orchestration/SKILL.md +67 -0
  157. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +214 -0
  158. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +107 -0
  159. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +107 -0
  160. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +105 -0
  161. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +108 -0
  162. package/templates/langs/zh/skills/ai-sprint-orchestration/references/workflow-v1-acceptance-checklist.md +58 -0
  163. package/templates/langs/zh/skills/ai-sprint-orchestration/references/workflow-v1.4-work-unit-handoff.md +190 -0
  164. package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -0
  165. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/archive.mjs +310 -0
  166. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs +683 -0
  167. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/decision.mjs +604 -0
  168. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/state-store.mjs +32 -0
  169. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +707 -0
  170. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +3419 -0
  171. package/templates/langs/zh/skills/ai-sprint-orchestration/test/archive.test.mjs +230 -0
  172. package/templates/langs/zh/skills/ai-sprint-orchestration/test/contract-enforcement.test.mjs +672 -0
  173. package/templates/langs/zh/skills/ai-sprint-orchestration/test/decision.test.mjs +1321 -0
  174. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +1419 -0
  175. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +70 -1
  176. package/templates/pain_settings.json +2 -1
  177. package/tests/README.md +120 -0
  178. package/tests/build-artifacts.test.ts +111 -0
  179. package/tests/commands/evolution-status.test.ts +222 -0
  180. package/tests/commands/evolver.test.ts +22 -0
  181. package/tests/commands/export.test.ts +78 -0
  182. package/tests/commands/nocturnal-review.test.ts +448 -0
  183. package/tests/commands/nocturnal-train.test.ts +97 -0
  184. package/tests/commands/pain.test.ts +108 -0
  185. package/tests/commands/samples.test.ts +65 -0
  186. package/tests/commands/strategy.test.ts +34 -0
  187. package/tests/commands/thinking-os.test.ts +88 -0
  188. package/tests/core/adaptive-thresholds.test.ts +261 -0
  189. package/tests/core/config-service.test.ts +89 -0
  190. package/tests/core/config.test.ts +90 -0
  191. package/tests/core/control-ui-db.test.ts +75 -0
  192. package/tests/core/core-template-guidance.test.ts +21 -0
  193. package/tests/core/detection-funnel.test.ts +63 -0
  194. package/tests/core/detection-service.test.ts +50 -0
  195. package/tests/core/dictionary-service.test.ts +116 -0
  196. package/tests/core/dictionary.test.ts +168 -0
  197. package/tests/core/empathy-keyword-matcher.test.ts +209 -0
  198. package/tests/core/event-log.test.ts +181 -0
  199. package/tests/core/evolution-e2e.test.ts +58 -0
  200. package/tests/core/evolution-engine-gate-integration.test.ts +543 -0
  201. package/tests/core/evolution-engine.test.ts +562 -0
  202. package/tests/core/evolution-logger.test.ts +148 -0
  203. package/tests/core/evolution-migration.test.ts +50 -0
  204. package/tests/core/evolution-paths.test.ts +21 -0
  205. package/tests/core/evolution-reducer.detector-metadata.test.ts +602 -0
  206. package/tests/core/evolution-reducer.test.ts +180 -0
  207. package/tests/core/evolution-types-loop.test.ts +48 -0
  208. package/tests/core/evolution-user-stories.e2e.test.ts +249 -0
  209. package/tests/core/external-training-contract.test.ts +463 -0
  210. package/tests/core/focus-history.test.ts +682 -0
  211. package/tests/core/init-flatten.test.ts +69 -0
  212. package/tests/core/init-refactor.test.ts +87 -0
  213. package/tests/core/init-v1.3.test.ts +46 -0
  214. package/tests/core/init.test.ts +190 -0
  215. package/tests/core/local-worker-routing.test.ts +757 -0
  216. package/tests/core/migration.test.ts +84 -0
  217. package/tests/core/model-deployment-registry.test.ts +845 -0
  218. package/tests/core/model-training-registry.test.ts +889 -0
  219. package/tests/core/nocturnal-arbiter.test.ts +494 -0
  220. package/tests/core/nocturnal-candidate-scoring.test.ts +400 -0
  221. package/tests/core/nocturnal-compliance.test.ts +646 -0
  222. package/tests/core/nocturnal-dataset.test.ts +892 -0
  223. package/tests/core/nocturnal-executability.test.ts +357 -0
  224. package/tests/core/nocturnal-export.test.ts +462 -0
  225. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +428 -0
  226. package/tests/core/nocturnal-trajectory-extractor.test.ts +634 -0
  227. package/tests/core/nocturnal-trinity.test.ts +953 -0
  228. package/tests/core/pain.test.ts +33 -0
  229. package/tests/core/path-resolver.test.ts +57 -0
  230. package/tests/core/paths-refactor.test.ts +42 -0
  231. package/tests/core/phase7-rollout-integration.test.ts +477 -0
  232. package/tests/core/principle-training-state.test.ts +712 -0
  233. package/tests/core/profile.test.ts +56 -0
  234. package/tests/core/promotion-gate.test.ts +556 -0
  235. package/tests/core/risk-calculator.test.ts +168 -0
  236. package/tests/core/session-tracker.test.ts +191 -0
  237. package/tests/core/training-program.test.ts +472 -0
  238. package/tests/core/trajectory.test.ts +265 -0
  239. package/tests/core/workspace-context-factory.test.ts +18 -0
  240. package/tests/core/workspace-context.test.ts +134 -0
  241. package/tests/fixtures/nocturnal-reviewed-subset.json +183 -0
  242. package/tests/fixtures/production-compatibility.test.ts +147 -0
  243. package/tests/fixtures/production-mock-generator.ts +282 -0
  244. package/tests/hooks/bash-risk-integration.test.ts +137 -0
  245. package/tests/hooks/bash-risk.test.ts +81 -0
  246. package/tests/hooks/edit-verification.test.ts +678 -0
  247. package/tests/hooks/gate-edit-verification-p1.test.ts +632 -0
  248. package/tests/hooks/gate-edit-verification.test.ts +435 -0
  249. package/tests/hooks/gate-pipeline-integration.test.ts +404 -0
  250. package/tests/hooks/gate.test.ts +271 -0
  251. package/tests/hooks/gfi-gate-unit.test.ts +422 -0
  252. package/tests/hooks/gfi-gate.test.ts +669 -0
  253. package/tests/hooks/lifecycle.test.ts +248 -0
  254. package/tests/hooks/llm.test.ts +308 -0
  255. package/tests/hooks/message-sanitize.test.ts +36 -0
  256. package/tests/hooks/pain.test.ts +141 -0
  257. package/tests/hooks/progressive-trust-gate.test.ts +277 -0
  258. package/tests/hooks/prompt.test.ts +1411 -0
  259. package/tests/hooks/subagent.test.ts +467 -0
  260. package/tests/hooks/thinking-gate.test.ts +313 -0
  261. package/tests/http/principles-console-route.test.ts +140 -0
  262. package/tests/hygiene-tracker.test.ts +77 -0
  263. package/tests/index.integration.test.ts +179 -0
  264. package/tests/index.shadow-routing.integration.test.ts +140 -0
  265. package/tests/index.test.ts +9 -0
  266. package/tests/integration/empathy-workflow-integration.test.ts +627 -0
  267. package/tests/service/control-ui-query-service.test.ts +121 -0
  268. package/tests/service/empathy-observer-workflow-manager.test.ts +176 -0
  269. package/tests/service/evolution-worker.test.ts +585 -0
  270. package/tests/service/nocturnal-runtime.test.ts +470 -0
  271. package/tests/service/nocturnal-service.test.ts +577 -0
  272. package/tests/service/nocturnal-target-selector.test.ts +615 -0
  273. package/tests/service/nocturnal-workflow-manager.test.ts +439 -0
  274. package/tests/service/phase3-input-filter.test.ts +289 -0
  275. package/tests/service/runtime-summary-service.test.ts +919 -0
  276. package/tests/task-compliance.test.ts +166 -0
  277. package/tests/test-utils.ts +48 -0
  278. package/tests/tools/critique-prompt.test.ts +260 -0
  279. package/tests/tools/deep-reflect.test.ts +232 -0
  280. package/tests/tools/model-index.test.ts +246 -0
  281. package/tests/ui/app.test.tsx +114 -0
  282. package/tests/utils/file-lock.test.ts +407 -0
  283. package/tests/utils/hashing.test.ts +32 -0
  284. package/tests/utils/io.test.ts +39 -0
  285. package/tests/utils/nlp.test.ts +53 -0
  286. package/tests/utils/plugin-logger.test.ts +156 -0
  287. package/tsconfig.json +16 -0
  288. package/tsconfig.tsbuildinfo +1 -0
  289. package/ui/src/App.tsx +45 -0
  290. package/ui/src/api.ts +216 -0
  291. package/ui/src/charts.tsx +586 -0
  292. package/ui/src/components/ErrorState.tsx +6 -0
  293. package/ui/src/components/Loading.tsx +13 -0
  294. package/ui/src/components/ProtectedRoute.tsx +12 -0
  295. package/ui/src/components/Shell.tsx +91 -0
  296. package/ui/src/components/WorkspaceConfig.tsx +146 -0
  297. package/ui/src/components/index.ts +5 -0
  298. package/ui/src/context/auth.tsx +80 -0
  299. package/ui/src/context/theme.tsx +66 -0
  300. package/ui/src/hooks/useAutoRefresh.ts +39 -0
  301. package/ui/src/i18n/ui.ts +363 -0
  302. package/ui/src/main.tsx +16 -0
  303. package/ui/src/pages/EvolutionPage.tsx +352 -0
  304. package/ui/src/pages/FeedbackPage.tsx +140 -0
  305. package/ui/src/pages/GateMonitorPage.tsx +136 -0
  306. package/ui/src/pages/LoginPage.tsx +88 -0
  307. package/ui/src/pages/OverviewPage.tsx +238 -0
  308. package/ui/src/pages/SamplesPage.tsx +174 -0
  309. package/ui/src/pages/ThinkingModelsPage.tsx +127 -0
  310. package/ui/src/styles.css +1661 -0
  311. package/ui/src/types.ts +368 -0
  312. package/ui/src/utils/format.ts +15 -0
  313. package/vitest.config.ts +23 -0
  314. package/dist/commands/capabilities.d.ts +0 -3
  315. package/dist/commands/capabilities.js +0 -73
  316. package/dist/commands/context.d.ts +0 -5
  317. package/dist/commands/evolution-status.d.ts +0 -4
  318. package/dist/commands/evolution-status.js +0 -117
  319. package/dist/commands/evolver.d.ts +0 -9
  320. package/dist/commands/evolver.js +0 -26
  321. package/dist/commands/export.d.ts +0 -2
  322. package/dist/commands/export.js +0 -98
  323. package/dist/commands/focus.d.ts +0 -14
  324. package/dist/commands/focus.js +0 -457
  325. package/dist/commands/nocturnal-review.d.ts +0 -24
  326. package/dist/commands/nocturnal-review.js +0 -265
  327. package/dist/commands/nocturnal-rollout.d.ts +0 -27
  328. package/dist/commands/nocturnal-rollout.js +0 -671
  329. package/dist/commands/nocturnal-train.d.ts +0 -25
  330. package/dist/commands/nocturnal-train.js +0 -919
  331. package/dist/commands/pain.d.ts +0 -5
  332. package/dist/commands/principle-rollback.d.ts +0 -4
  333. package/dist/commands/principle-rollback.js +0 -22
  334. package/dist/commands/rollback.d.ts +0 -19
  335. package/dist/commands/samples.d.ts +0 -2
  336. package/dist/commands/samples.js +0 -55
  337. package/dist/commands/strategy.d.ts +0 -3
  338. package/dist/commands/strategy.js +0 -29
  339. package/dist/commands/thinking-os.d.ts +0 -2
  340. package/dist/config/defaults/runtime.d.ts +0 -40
  341. package/dist/config/errors.d.ts +0 -84
  342. package/dist/config/errors.js +0 -94
  343. package/dist/config/index.js +0 -7
  344. package/dist/constants/diagnostician.d.ts +0 -12
  345. package/dist/constants/diagnostician.js +0 -56
  346. package/dist/constants/tools.d.ts +0 -17
  347. package/dist/constants/tools.js +0 -54
  348. package/dist/core/adaptive-thresholds.d.ts +0 -186
  349. package/dist/core/adaptive-thresholds.js +0 -300
  350. package/dist/core/config-service.d.ts +0 -15
  351. package/dist/core/config.d.ts +0 -129
  352. package/dist/core/control-ui-db.d.ts +0 -95
  353. package/dist/core/control-ui-db.js +0 -292
  354. package/dist/core/detection-funnel.d.ts +0 -33
  355. package/dist/core/detection-service.d.ts +0 -15
  356. package/dist/core/dictionary-service.d.ts +0 -15
  357. package/dist/core/dictionary.d.ts +0 -38
  358. package/dist/core/event-log.d.ts +0 -82
  359. package/dist/core/event-log.js +0 -463
  360. package/dist/core/evolution-engine.d.ts +0 -118
  361. package/dist/core/evolution-engine.js +0 -464
  362. package/dist/core/evolution-logger.d.ts +0 -137
  363. package/dist/core/evolution-logger.js +0 -256
  364. package/dist/core/evolution-migration.d.ts +0 -5
  365. package/dist/core/evolution-migration.js +0 -65
  366. package/dist/core/evolution-reducer.d.ts +0 -98
  367. package/dist/core/evolution-reducer.js +0 -465
  368. package/dist/core/evolution-types.d.ts +0 -287
  369. package/dist/core/evolution-types.js +0 -78
  370. package/dist/core/external-training-contract.d.ts +0 -276
  371. package/dist/core/external-training-contract.js +0 -269
  372. package/dist/core/focus-history.d.ts +0 -210
  373. package/dist/core/focus-history.js +0 -1185
  374. package/dist/core/hygiene/tracker.d.ts +0 -22
  375. package/dist/core/hygiene/tracker.js +0 -106
  376. package/dist/core/init.d.ts +0 -12
  377. package/dist/core/local-worker-routing.d.ts +0 -175
  378. package/dist/core/local-worker-routing.js +0 -525
  379. package/dist/core/migration.d.ts +0 -6
  380. package/dist/core/model-deployment-registry.d.ts +0 -218
  381. package/dist/core/model-deployment-registry.js +0 -503
  382. package/dist/core/model-training-registry.d.ts +0 -295
  383. package/dist/core/model-training-registry.js +0 -475
  384. package/dist/core/nocturnal-arbiter.d.ts +0 -159
  385. package/dist/core/nocturnal-arbiter.js +0 -534
  386. package/dist/core/nocturnal-candidate-scoring.d.ts +0 -137
  387. package/dist/core/nocturnal-candidate-scoring.js +0 -266
  388. package/dist/core/nocturnal-compliance.d.ts +0 -175
  389. package/dist/core/nocturnal-compliance.js +0 -824
  390. package/dist/core/nocturnal-dataset.d.ts +0 -224
  391. package/dist/core/nocturnal-dataset.js +0 -443
  392. package/dist/core/nocturnal-executability.d.ts +0 -85
  393. package/dist/core/nocturnal-executability.js +0 -331
  394. package/dist/core/nocturnal-export.d.ts +0 -124
  395. package/dist/core/nocturnal-export.js +0 -275
  396. package/dist/core/nocturnal-paths.d.ts +0 -124
  397. package/dist/core/nocturnal-trajectory-extractor.d.ts +0 -242
  398. package/dist/core/nocturnal-trajectory-extractor.js +0 -307
  399. package/dist/core/nocturnal-trinity.d.ts +0 -311
  400. package/dist/core/nocturnal-trinity.js +0 -880
  401. package/dist/core/pain.d.ts +0 -4
  402. package/dist/core/pain.js +0 -70
  403. package/dist/core/path-resolver.d.ts +0 -46
  404. package/dist/core/paths.d.ts +0 -65
  405. package/dist/core/principle-training-state.d.ts +0 -121
  406. package/dist/core/principle-training-state.js +0 -321
  407. package/dist/core/profile.d.ts +0 -62
  408. package/dist/core/profile.js +0 -210
  409. package/dist/core/promotion-gate.d.ts +0 -238
  410. package/dist/core/promotion-gate.js +0 -529
  411. package/dist/core/risk-calculator.d.ts +0 -22
  412. package/dist/core/session-tracker.d.ts +0 -101
  413. package/dist/core/shadow-observation-registry.d.ts +0 -217
  414. package/dist/core/shadow-observation-registry.js +0 -308
  415. package/dist/core/system-logger.d.ts +0 -8
  416. package/dist/core/thinking-models.d.ts +0 -38
  417. package/dist/core/thinking-models.js +0 -170
  418. package/dist/core/training-program.d.ts +0 -233
  419. package/dist/core/training-program.js +0 -433
  420. package/dist/core/trajectory.d.ts +0 -411
  421. package/dist/core/trajectory.js +0 -1307
  422. package/dist/core/workspace-context.d.ts +0 -71
  423. package/dist/hooks/bash-risk.d.ts +0 -57
  424. package/dist/hooks/bash-risk.js +0 -137
  425. package/dist/hooks/edit-verification.d.ts +0 -62
  426. package/dist/hooks/edit-verification.js +0 -256
  427. package/dist/hooks/gate-block-helper.d.ts +0 -44
  428. package/dist/hooks/gate-block-helper.js +0 -119
  429. package/dist/hooks/gate.d.ts +0 -24
  430. package/dist/hooks/gate.js +0 -173
  431. package/dist/hooks/gfi-gate.d.ts +0 -40
  432. package/dist/hooks/gfi-gate.js +0 -113
  433. package/dist/hooks/lifecycle.d.ts +0 -5
  434. package/dist/hooks/lifecycle.js +0 -284
  435. package/dist/hooks/llm.d.ts +0 -13
  436. package/dist/hooks/message-sanitize.d.ts +0 -3
  437. package/dist/hooks/message-sanitize.js +0 -37
  438. package/dist/hooks/pain.d.ts +0 -5
  439. package/dist/hooks/pain.js +0 -301
  440. package/dist/hooks/progressive-trust-gate.d.ts +0 -52
  441. package/dist/hooks/progressive-trust-gate.js +0 -134
  442. package/dist/hooks/prompt.d.ts +0 -49
  443. package/dist/hooks/prompt.js +0 -905
  444. package/dist/hooks/subagent.d.ts +0 -10
  445. package/dist/hooks/subagent.js +0 -387
  446. package/dist/hooks/thinking-checkpoint.d.ts +0 -37
  447. package/dist/hooks/thinking-checkpoint.js +0 -51
  448. package/dist/hooks/trajectory-collector.d.ts +0 -32
  449. package/dist/hooks/trajectory-collector.js +0 -256
  450. package/dist/http/principles-console-route.d.ts +0 -9
  451. package/dist/http/principles-console-route.js +0 -681
  452. package/dist/i18n/commands.d.ts +0 -26
  453. package/dist/i18n/commands.js +0 -116
  454. package/dist/index.d.ts +0 -7
  455. package/dist/index.js +0 -581
  456. package/dist/service/central-database.d.ts +0 -104
  457. package/dist/service/central-database.js +0 -649
  458. package/dist/service/control-ui-query-service.d.ts +0 -221
  459. package/dist/service/control-ui-query-service.js +0 -543
  460. package/dist/service/empathy-observer-manager.d.ts +0 -88
  461. package/dist/service/empathy-observer-manager.js +0 -414
  462. package/dist/service/evolution-query-service.d.ts +0 -155
  463. package/dist/service/evolution-query-service.js +0 -258
  464. package/dist/service/evolution-worker.d.ts +0 -101
  465. package/dist/service/evolution-worker.js +0 -975
  466. package/dist/service/health-query-service.d.ts +0 -170
  467. package/dist/service/health-query-service.js +0 -662
  468. package/dist/service/nocturnal-runtime.d.ts +0 -183
  469. package/dist/service/nocturnal-service.d.ts +0 -163
  470. package/dist/service/nocturnal-service.js +0 -787
  471. package/dist/service/nocturnal-target-selector.d.ts +0 -145
  472. package/dist/service/nocturnal-target-selector.js +0 -315
  473. package/dist/service/phase3-input-filter.d.ts +0 -73
  474. package/dist/service/phase3-input-filter.js +0 -172
  475. package/dist/service/runtime-summary-service.d.ts +0 -122
  476. package/dist/service/runtime-summary-service.js +0 -485
  477. package/dist/service/subagent-workflow/empathy-observer-workflow-manager.d.ts +0 -48
  478. package/dist/service/subagent-workflow/index.d.ts +0 -4
  479. package/dist/service/subagent-workflow/index.js +0 -3
  480. package/dist/service/subagent-workflow/runtime-direct-driver.d.ts +0 -77
  481. package/dist/service/subagent-workflow/runtime-direct-driver.js +0 -75
  482. package/dist/service/subagent-workflow/types.js +0 -11
  483. package/dist/service/subagent-workflow/workflow-store.d.ts +0 -26
  484. package/dist/service/subagent-workflow/workflow-store.js +0 -165
  485. package/dist/service/trajectory-service.d.ts +0 -2
  486. package/dist/service/trajectory-service.js +0 -15
  487. package/dist/tools/critique-prompt.d.ts +0 -14
  488. package/dist/tools/deep-reflect.d.ts +0 -39
  489. package/dist/tools/deep-reflect.js +0 -350
  490. package/dist/tools/model-index.d.ts +0 -9
  491. package/dist/types/event-types.d.ts +0 -306
  492. package/dist/types/event-types.js +0 -106
  493. package/dist/types/hygiene-types.d.ts +0 -20
  494. package/dist/types/hygiene-types.js +0 -12
  495. package/dist/types/runtime-summary.d.ts +0 -47
  496. package/dist/types/runtime-summary.js +0 -1
  497. package/dist/types.d.ts +0 -50
  498. package/dist/types.js +0 -22
  499. package/dist/utils/file-lock.d.ts +0 -71
  500. package/dist/utils/file-lock.js +0 -309
  501. package/dist/utils/glob-match.d.ts +0 -28
  502. package/dist/utils/hashing.d.ts +0 -9
  503. package/dist/utils/io.d.ts +0 -6
  504. package/dist/utils/io.js +0 -106
  505. package/dist/utils/nlp.d.ts +0 -9
  506. package/dist/utils/plugin-logger.d.ts +0 -39
  507. package/dist/utils/subagent-probe.d.ts +0 -34
  508. package/dist/utils/subagent-probe.js +0 -81
@@ -0,0 +1,470 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+ import {
6
+ checkWorkspaceIdle,
7
+ checkCooldown,
8
+ checkPreflight,
9
+ recordRunStart,
10
+ recordRunEnd,
11
+ clearAllCooldowns,
12
+ getRuntimeState,
13
+ DEFAULT_IDLE_THRESHOLD_MS,
14
+ DEFAULT_GLOBAL_COOLDOWN_MS,
15
+ DEFAULT_PRINCIPLE_COOLDOWN_MS,
16
+ DEFAULT_ABANDONED_THRESHOLD_MS,
17
+ NOCTURNAL_RUNTIME_FILE,
18
+ } from '../../src/service/nocturnal-runtime.js';
19
+ import { initPersistence, trackToolRead, clearSession, listSessions } from '../../src/core/session-tracker.js';
20
+ import { safeRmDir } from '../test-utils.js';
21
+
22
+ describe('NocturnalRuntime', () => {
23
+ let tempDir: string;
24
+ let workspaceDir: string;
25
+
26
+ beforeEach(() => {
27
+ vi.useFakeTimers();
28
+ // Use a fixed "now" for deterministic testing
29
+ vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
30
+
31
+ workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-nocturnal-ws-'));
32
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-nocturnal-'));
33
+
34
+ // Initialize session tracker persistence for the temp workspace
35
+ initPersistence(tempDir);
36
+ });
37
+
38
+ afterEach(() => {
39
+ vi.useRealTimers();
40
+ vi.clearAllMocks();
41
+ safeRmDir(workspaceDir);
42
+ safeRmDir(tempDir);
43
+ clearSession('session-active');
44
+ clearSession('session-stale');
45
+ clearSession('session-abandoned');
46
+ clearSession('session-ancient');
47
+ });
48
+
49
+ // -------------------------------------------------------------------------
50
+ // Idle Detection Tests
51
+ // -------------------------------------------------------------------------
52
+
53
+ describe('checkWorkspaceIdle', () => {
54
+ it('should return isIdle=true when no sessions exist', () => {
55
+ const result = checkWorkspaceIdle(workspaceDir);
56
+ expect(result.isIdle).toBe(true);
57
+ expect(result.userActiveSessions).toBe(0);
58
+ expect(result.abandonedSessionIds).toEqual([]);
59
+ expect(result.reason).toContain('No active sessions');
60
+ });
61
+
62
+ it('should return isIdle=false when a session is recent (within threshold)', () => {
63
+ // Create an active session with recent activity
64
+ trackToolRead('session-active', 'src/main.ts', workspaceDir);
65
+
66
+ const result = checkWorkspaceIdle(workspaceDir, { idleThresholdMs: 30 * 60 * 1000 });
67
+ expect(result.isIdle).toBe(false);
68
+ expect(result.userActiveSessions).toBe(1);
69
+ expect(result.abandonedSessionIds).toEqual([]);
70
+ });
71
+
72
+ it('should return isIdle=true when session is older than idle threshold', () => {
73
+ // Create a stale session (activity 45 min ago)
74
+ vi.setSystemTime(new Date('2026-03-27T11:15:00.000Z')); // 45 min before "now"
75
+ trackToolRead('session-stale', 'src/main.ts', workspaceDir);
76
+ vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z')); // reset to "now"
77
+
78
+ const result = checkWorkspaceIdle(workspaceDir, { idleThresholdMs: 30 * 60 * 1000 });
79
+ expect(result.isIdle).toBe(true);
80
+ expect(result.idleForMs).toBeGreaterThan(30 * 60 * 1000);
81
+ });
82
+
83
+ it('should treat abandoned sessions as not contributing to idle check', () => {
84
+ // Session active 3 hours ago — should be treated as abandoned
85
+ vi.setSystemTime(new Date('2026-03-27T09:00:00.000Z')); // 3 hours before "now"
86
+ trackToolRead('session-abandoned', 'src/main.ts', workspaceDir);
87
+ vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z')); // reset to "now"
88
+
89
+ const result = checkWorkspaceIdle(workspaceDir, {
90
+ idleThresholdMs: 30 * 60 * 1000,
91
+ abandonedThresholdMs: 2 * 60 * 60 * 1000,
92
+ });
93
+
94
+ expect(result.isIdle).toBe(true); // No active sessions, so idle
95
+ expect(result.abandonedSessionIds).toContain('session-abandoned');
96
+ expect(result.userActiveSessions).toBe(0);
97
+ expect(result.reason).toContain('abandoned session(s) ignored');
98
+ });
99
+
100
+ it('should ignore ancient sessions but still detect recent activity from other sessions', () => {
101
+ // Ancient session (4 hours ago — abandoned)
102
+ vi.setSystemTime(new Date('2026-03-27T08:00:00.000Z'));
103
+ trackToolRead('session-ancient', 'src/main.ts', workspaceDir);
104
+ vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
105
+
106
+ // Recent session (5 minutes ago — still active)
107
+ vi.setSystemTime(new Date('2026-03-27T11:55:00.000Z'));
108
+ trackToolRead('session-active', 'src/main.ts', workspaceDir);
109
+ vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
110
+
111
+ const result = checkWorkspaceIdle(workspaceDir, {
112
+ idleThresholdMs: 30 * 60 * 1000,
113
+ abandonedThresholdMs: 2 * 60 * 60 * 1000,
114
+ });
115
+
116
+ expect(result.isIdle).toBe(false); // Recent activity 5 min ago
117
+ expect(result.abandonedSessionIds).toContain('session-ancient');
118
+ expect(result.userActiveSessions).toBe(1);
119
+ });
120
+
121
+ it('should use trajectory timestamp as secondary guardrail', () => {
122
+ // No sessions, trajectory shows recent activity
123
+ const trajectoryRecent = Date.now() - 5 * 60 * 1000; // 5 min ago
124
+
125
+ const result = checkWorkspaceIdle(workspaceDir, {}, trajectoryRecent);
126
+ expect(result.isIdle).toBe(true); // Still idle (no sessions is primary)
127
+ expect(result.trajectoryGuardrailConfirmsIdle).toBe(false); // But trajectory disagrees
128
+ });
129
+
130
+ it('should confirm idle when both session state and trajectory agree', () => {
131
+ // No sessions, trajectory also shows idle (>80% of threshold)
132
+ const trajectoryOld = Date.now() - 40 * 60 * 1000; // 40 min ago (>24 min = 80% of 30min)
133
+
134
+ const result = checkWorkspaceIdle(workspaceDir, {}, trajectoryOld);
135
+ expect(result.isIdle).toBe(true);
136
+ expect(result.trajectoryGuardrailConfirmsIdle).toBe(true);
137
+ });
138
+
139
+ it('should report idleForMs correctly', () => {
140
+ // Session active 15 min ago
141
+ vi.setSystemTime(new Date('2026-03-27T11:45:00.000Z'));
142
+ trackToolRead('session-active', 'src/main.ts', workspaceDir);
143
+ vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
144
+
145
+ const result = checkWorkspaceIdle(workspaceDir, { idleThresholdMs: 30 * 60 * 1000 });
146
+ expect(result.idleForMs).toBe(15 * 60 * 1000);
147
+ expect(result.isIdle).toBe(false);
148
+ });
149
+ });
150
+
151
+ // -------------------------------------------------------------------------
152
+ // Cooldown Management Tests
153
+ // -------------------------------------------------------------------------
154
+
155
+ describe('checkCooldown', () => {
156
+ it('should return no active cooldowns when state is empty', () => {
157
+ const result = checkCooldown(tempDir);
158
+ expect(result.globalCooldownActive).toBe(false);
159
+ expect(result.principleCooldownActive).toBe(false);
160
+ expect(result.quotaExhausted).toBe(false);
161
+ expect(result.runsRemaining).toBe(3); // DEFAULT_MAX_RUNS_PER_WINDOW
162
+ });
163
+
164
+ it('should detect active global cooldown', async () => {
165
+ await recordRunStart(tempDir, 'T-01');
166
+
167
+ const result = checkCooldown(tempDir);
168
+ expect(result.globalCooldownActive).toBe(true);
169
+ expect(result.globalCooldownRemainingMs).toBe(DEFAULT_GLOBAL_COOLDOWN_MS);
170
+ expect(result.globalCooldownUntil).toBeTruthy();
171
+ });
172
+
173
+ it('should detect expired global cooldown', async () => {
174
+ await recordRunStart(tempDir, 'T-01');
175
+
176
+ // Advance time past the global cooldown
177
+ vi.advanceTimersByTime(DEFAULT_GLOBAL_COOLDOWN_MS + 1000);
178
+
179
+ const result = checkCooldown(tempDir);
180
+ expect(result.globalCooldownActive).toBe(false);
181
+ expect(result.globalCooldownRemainingMs).toBe(0);
182
+ });
183
+
184
+ it('should detect principle-specific cooldown after successful run', async () => {
185
+ await recordRunStart(tempDir, 'T-01');
186
+ await recordRunEnd(tempDir, 'success', { sampleCount: 5 });
187
+
188
+ const result = checkCooldown(tempDir, 'T-01');
189
+ expect(result.principleCooldownActive).toBe(true);
190
+ expect(result.principleCooldownRemainingMs).toBe(DEFAULT_PRINCIPLE_COOLDOWN_MS);
191
+ });
192
+
193
+ it('should not trigger principle cooldown on failed run', async () => {
194
+ await recordRunStart(tempDir, 'T-01');
195
+ await recordRunEnd(tempDir, 'failed', { reason: 'No violating sessions' });
196
+
197
+ // Global cooldown still active, but no principle cooldown
198
+ const result = checkCooldown(tempDir, 'T-01');
199
+ expect(result.globalCooldownActive).toBe(true);
200
+ expect(result.principleCooldownActive).toBe(false);
201
+ });
202
+
203
+ it('should not trigger principle cooldown on skipped run', async () => {
204
+ await recordRunStart(tempDir, 'T-01');
205
+ await recordRunEnd(tempDir, 'skipped', { reason: 'Idle check failed' });
206
+
207
+ const result = checkCooldown(tempDir, 'T-01');
208
+ expect(result.principleCooldownActive).toBe(false);
209
+ });
210
+
211
+ it('should enforce quota limit', async () => {
212
+ // Run max number of times
213
+ for (let i = 0; i < 3; i++) {
214
+ await recordRunStart(tempDir, 'T-01');
215
+ await recordRunEnd(tempDir, 'success', { sampleCount: 1 });
216
+ // Advance past global cooldown for each run
217
+ vi.advanceTimersByTime(DEFAULT_GLOBAL_COOLDOWN_MS + 1000);
218
+ }
219
+
220
+ const result = checkCooldown(tempDir);
221
+ expect(result.quotaExhausted).toBe(true);
222
+ expect(result.runsRemaining).toBe(0);
223
+ });
224
+
225
+ it('should reset quota after window expires', async () => {
226
+ // Run max times
227
+ for (let i = 0; i < 3; i++) {
228
+ await recordRunStart(tempDir, 'T-01');
229
+ await recordRunEnd(tempDir, 'success', { sampleCount: 1 });
230
+ vi.advanceTimersByTime(DEFAULT_GLOBAL_COOLDOWN_MS + 1000);
231
+ }
232
+
233
+ // Advance past the quota window
234
+ vi.advanceTimersByTime(24 * 60 * 60 * 1000 + 1000);
235
+
236
+ const result = checkCooldown(tempDir);
237
+ expect(result.quotaExhausted).toBe(false);
238
+ expect(result.runsRemaining).toBe(3);
239
+ });
240
+
241
+ it('should only cooldown specific principles, not others', async () => {
242
+ await recordRunStart(tempDir, 'T-01');
243
+ await recordRunEnd(tempDir, 'success');
244
+
245
+ const resultT01 = checkCooldown(tempDir, 'T-01');
246
+ const resultT02 = checkCooldown(tempDir, 'T-02');
247
+
248
+ expect(resultT01.principleCooldownActive).toBe(true);
249
+ expect(resultT02.principleCooldownActive).toBe(false);
250
+ });
251
+ });
252
+
253
+ // -------------------------------------------------------------------------
254
+ // Run Recording Tests
255
+ // -------------------------------------------------------------------------
256
+
257
+ describe('recordRunStart / recordRunEnd', () => {
258
+ it('should record run start timestamp', async () => {
259
+ await recordRunStart(tempDir, 'T-01');
260
+ const state = await getRuntimeState(tempDir);
261
+
262
+ expect(state.lastRunAt).toBeTruthy();
263
+ expect(state.lastRunMeta?.targetPrincipleId).toBe('T-01');
264
+ expect(state.lastRunMeta?.status).toBe('skipped');
265
+ expect(state.globalCooldownUntil).toBeTruthy();
266
+ });
267
+
268
+ it('should record successful run with sample count', async () => {
269
+ await recordRunStart(tempDir, 'T-01');
270
+ await recordRunEnd(tempDir, 'success', { sampleCount: 7 });
271
+
272
+ const state = await getRuntimeState(tempDir);
273
+ expect(state.lastSuccessfulRunAt).toBeTruthy();
274
+ expect(state.lastRunMeta?.status).toBe('success');
275
+ expect(state.lastRunMeta?.sampleCount).toBe(7);
276
+ expect(state.principleCooldowns['T-01']).toBeTruthy();
277
+ });
278
+
279
+ it('should preserve failed run reason without setting successful timestamp', async () => {
280
+ await recordRunStart(tempDir, 'T-02');
281
+ await recordRunEnd(tempDir, 'failed', { reason: 'No violating sessions found' });
282
+
283
+ const state = await getRuntimeState(tempDir);
284
+ expect(state.lastSuccessfulRunAt).toBeUndefined();
285
+ expect(state.lastRunMeta?.status).toBe('failed');
286
+ expect(state.lastRunMeta?.reason).toBe('No violating sessions found');
287
+ expect(state.principleCooldowns['T-02']).toBeUndefined(); // No principle cooldown on failure
288
+ });
289
+
290
+ it('should add timestamp to recentRunTimestamps for quota tracking', async () => {
291
+ await recordRunStart(tempDir, 'T-01');
292
+ const state = await getRuntimeState(tempDir);
293
+
294
+ expect(state.recentRunTimestamps.length).toBe(1);
295
+ });
296
+ });
297
+
298
+ // -------------------------------------------------------------------------
299
+ // clearAllCooldowns Tests
300
+ // -------------------------------------------------------------------------
301
+
302
+ describe('clearAllCooldowns', () => {
303
+ it('should clear all cooldown state', async () => {
304
+ await recordRunStart(tempDir, 'T-01');
305
+ await recordRunEnd(tempDir, 'success');
306
+
307
+ await clearAllCooldowns(tempDir);
308
+
309
+ const state = await getRuntimeState(tempDir);
310
+ expect(state.globalCooldownUntil).toBeUndefined();
311
+ expect(state.principleCooldowns).toEqual({});
312
+ expect(state.recentRunTimestamps).toEqual([]);
313
+ expect(state.lastRunMeta).toBeUndefined();
314
+ });
315
+ });
316
+
317
+ // -------------------------------------------------------------------------
318
+ // Preflight Check Tests
319
+ // -------------------------------------------------------------------------
320
+
321
+ describe('checkPreflight', () => {
322
+ it('should return canRun=true when workspace is idle and no cooldowns', () => {
323
+ // No sessions = idle
324
+ const result = checkPreflight(workspaceDir, tempDir, 'T-01');
325
+ expect(result.canRun).toBe(true);
326
+ expect(result.blockers).toEqual([]);
327
+ });
328
+
329
+ it('should block when workspace is not idle', () => {
330
+ // Create recent session
331
+ trackToolRead('session-active', 'src/main.ts', workspaceDir);
332
+
333
+ const result = checkPreflight(workspaceDir, tempDir, 'T-01');
334
+ expect(result.canRun).toBe(false);
335
+ expect(result.blockers.some(b => b.includes('not idle'))).toBe(true);
336
+ });
337
+
338
+ it('should block when global cooldown is active', async () => {
339
+ await recordRunStart(tempDir, 'T-01');
340
+
341
+ const result = checkPreflight(workspaceDir, tempDir, 'T-01');
342
+ expect(result.canRun).toBe(false);
343
+ expect(result.blockers.some(b => b.includes('Global cooldown'))).toBe(true);
344
+ });
345
+
346
+ it('should block when quota is exhausted', async () => {
347
+ // Exhaust quota
348
+ for (let i = 0; i < 3; i++) {
349
+ await recordRunStart(tempDir, 'T-01');
350
+ await recordRunEnd(tempDir, 'success', { sampleCount: 1 });
351
+ vi.advanceTimersByTime(DEFAULT_GLOBAL_COOLDOWN_MS + 1000);
352
+ }
353
+
354
+ const result = checkPreflight(workspaceDir, tempDir, 'T-01');
355
+ expect(result.canRun).toBe(false);
356
+ expect(result.blockers.some(b => b.includes('Quota exhausted'))).toBe(true);
357
+ });
358
+
359
+ it('should report all blockers when multiple conditions block', async () => {
360
+ // Create recent session AND set global cooldown
361
+ trackToolRead('session-active', 'src/main.ts', workspaceDir);
362
+ await recordRunStart(tempDir, 'T-01');
363
+
364
+ const result = checkPreflight(workspaceDir, tempDir, 'T-01');
365
+ expect(result.canRun).toBe(false);
366
+ expect(result.blockers.length).toBeGreaterThanOrEqual(2);
367
+ });
368
+
369
+ it('should include idle info in preflight result', () => {
370
+ const result = checkPreflight(workspaceDir, tempDir, 'T-01');
371
+ expect(result.idle).toBeDefined();
372
+ expect(result.idle.isIdle).toBe(true); // No sessions
373
+ });
374
+
375
+ it('should include cooldown info in preflight result', () => {
376
+ const result = checkPreflight(workspaceDir, tempDir, 'T-01');
377
+ expect(result.cooldown).toBeDefined();
378
+ expect(result.cooldown.globalCooldownActive).toBe(false);
379
+ });
380
+ });
381
+
382
+ // -------------------------------------------------------------------------
383
+ // Abandoned Session Tests (dedicated)
384
+ // -------------------------------------------------------------------------
385
+
386
+ describe('abandoned sessions', () => {
387
+ it('should not block nocturnal flow when all sessions are abandoned but workspace otherwise empty', () => {
388
+ // Create only abandoned sessions (no recent activity)
389
+ vi.setSystemTime(new Date('2026-03-27T09:00:00.000Z')); // 3 hours ago
390
+ trackToolRead('session-abandoned', 'src/main.ts', workspaceDir);
391
+ vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
392
+
393
+ const result = checkWorkspaceIdle(workspaceDir, {
394
+ idleThresholdMs: 30 * 60 * 1000,
395
+ abandonedThresholdMs: 2 * 60 * 60 * 1000,
396
+ });
397
+
398
+ // Workspace should be considered idle (all sessions abandoned = effectively no sessions)
399
+ expect(result.isIdle).toBe(true);
400
+ expect(result.userActiveSessions).toBe(0);
401
+ });
402
+
403
+ it('should not incorrectly block when there are abandoned AND active sessions', () => {
404
+ // Abandoned session (3 hours ago)
405
+ vi.setSystemTime(new Date('2026-03-27T09:00:00.000Z'));
406
+ trackToolRead('session-abandoned', 'src/main.ts', workspaceDir);
407
+
408
+ // Recent session (5 min ago)
409
+ vi.setSystemTime(new Date('2026-03-27T11:55:00.000Z'));
410
+ trackToolRead('session-active', 'src/main.ts', workspaceDir);
411
+ vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
412
+
413
+ const idleResult = checkWorkspaceIdle(workspaceDir, {
414
+ idleThresholdMs: 30 * 60 * 1000,
415
+ abandonedThresholdMs: 2 * 60 * 60 * 1000,
416
+ });
417
+
418
+ // Should NOT be idle because there's a recent active session
419
+ expect(idleResult.isIdle).toBe(false);
420
+ expect(idleResult.userActiveSessions).toBe(1);
421
+ expect(idleResult.abandonedSessionIds).toContain('session-abandoned');
422
+ });
423
+
424
+ it('should persist abandoned sessions do not create cooldown state', async () => {
425
+ // Create abandoned session
426
+ vi.setSystemTime(new Date('2026-03-27T09:00:00.000Z'));
427
+ trackToolRead('session-abandoned', 'src/main.ts', workspaceDir);
428
+ vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
429
+
430
+ // Workspace is idle, preflight should pass
431
+ const result = checkPreflight(workspaceDir, tempDir, 'T-01');
432
+ expect(result.canRun).toBe(true);
433
+ });
434
+ });
435
+
436
+ // -------------------------------------------------------------------------
437
+ // File Persistence Tests
438
+ // -------------------------------------------------------------------------
439
+
440
+ describe('file persistence', () => {
441
+ it('should create nocturnal-runtime.json on first write', async () => {
442
+ const filePath = path.join(tempDir, NOCTURNAL_RUNTIME_FILE);
443
+ expect(fs.existsSync(filePath)).toBe(false);
444
+
445
+ await recordRunStart(tempDir, 'T-01');
446
+ expect(fs.existsSync(filePath)).toBe(true);
447
+ });
448
+
449
+ it('should survive corrupted JSON (start fresh)', async () => {
450
+ const filePath = path.join(tempDir, NOCTURNAL_RUNTIME_FILE);
451
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
452
+ fs.writeFileSync(filePath, '{ corrupted json }', 'utf-8');
453
+
454
+ const state = await getRuntimeState(tempDir);
455
+ // Should return default state, not throw
456
+ expect(state.principleCooldowns).toEqual({});
457
+ expect(state.recentRunTimestamps).toEqual([]);
458
+ });
459
+
460
+ it('should read persisted cooldown on restart', async () => {
461
+ await recordRunStart(tempDir, 'T-01');
462
+ await recordRunEnd(tempDir, 'success');
463
+
464
+ // Simulate restart by re-reading
465
+ const state = await getRuntimeState(tempDir);
466
+ expect(state.lastSuccessfulRunAt).toBeTruthy();
467
+ expect(state.principleCooldowns['T-01']).toBeTruthy();
468
+ });
469
+ });
470
+ });