principles-disciple 1.8.1 → 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 (470) 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/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 -129
  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 -101
  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 -13
  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 -52
  403. package/dist/hooks/progressive-trust-gate.js +0 -134
  404. package/dist/hooks/prompt.d.ts +0 -49
  405. package/dist/hooks/prompt.js +0 -905
  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 -681
  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 -88
  423. package/dist/service/empathy-observer-manager.js +0 -414
  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 -975
  428. package/dist/service/health-query-service.d.ts +0 -170
  429. package/dist/service/health-query-service.js +0 -662
  430. package/dist/service/nocturnal-runtime.d.ts +0 -183
  431. package/dist/service/nocturnal-service.d.ts +0 -163
  432. package/dist/service/nocturnal-service.js +0 -787
  433. package/dist/service/nocturnal-target-selector.d.ts +0 -145
  434. package/dist/service/nocturnal-target-selector.js +0 -315
  435. package/dist/service/phase3-input-filter.d.ts +0 -73
  436. package/dist/service/phase3-input-filter.js +0 -172
  437. package/dist/service/runtime-summary-service.d.ts +0 -122
  438. package/dist/service/runtime-summary-service.js +0 -485
  439. package/dist/service/subagent-workflow/empathy-observer-workflow-manager.d.ts +0 -48
  440. package/dist/service/subagent-workflow/index.d.ts +0 -4
  441. package/dist/service/subagent-workflow/index.js +0 -3
  442. package/dist/service/subagent-workflow/runtime-direct-driver.d.ts +0 -77
  443. package/dist/service/subagent-workflow/runtime-direct-driver.js +0 -75
  444. package/dist/service/subagent-workflow/types.js +0 -11
  445. package/dist/service/subagent-workflow/workflow-store.d.ts +0 -26
  446. package/dist/service/subagent-workflow/workflow-store.js +0 -165
  447. package/dist/service/trajectory-service.d.ts +0 -2
  448. package/dist/service/trajectory-service.js +0 -15
  449. package/dist/tools/critique-prompt.d.ts +0 -14
  450. package/dist/tools/deep-reflect.d.ts +0 -39
  451. package/dist/tools/deep-reflect.js +0 -350
  452. package/dist/tools/model-index.d.ts +0 -9
  453. package/dist/types/event-types.d.ts +0 -306
  454. package/dist/types/event-types.js +0 -106
  455. package/dist/types/hygiene-types.d.ts +0 -20
  456. package/dist/types/hygiene-types.js +0 -12
  457. package/dist/types/runtime-summary.d.ts +0 -47
  458. package/dist/types/runtime-summary.js +0 -1
  459. package/dist/types.d.ts +0 -50
  460. package/dist/types.js +0 -22
  461. package/dist/utils/file-lock.d.ts +0 -71
  462. package/dist/utils/file-lock.js +0 -309
  463. package/dist/utils/glob-match.d.ts +0 -28
  464. package/dist/utils/hashing.d.ts +0 -9
  465. package/dist/utils/io.d.ts +0 -6
  466. package/dist/utils/io.js +0 -106
  467. package/dist/utils/nlp.d.ts +0 -9
  468. package/dist/utils/plugin-logger.d.ts +0 -39
  469. package/dist/utils/subagent-probe.d.ts +0 -34
  470. package/dist/utils/subagent-probe.js +0 -81
@@ -0,0 +1,646 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ detectOpportunity,
4
+ detectViolation,
5
+ computeCompliance,
6
+ computeAllCompliance,
7
+ groupEventsIntoSessions,
8
+ type SessionEvents,
9
+ type RawEventEntry,
10
+ } from '../../src/core/nocturnal-compliance.js';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Test Utilities
14
+ // ---------------------------------------------------------------------------
15
+
16
+ function makeSession(overrides: Partial<SessionEvents> = {}): SessionEvents {
17
+ return {
18
+ sessionId: overrides.sessionId ?? 'session-1',
19
+ toolCalls: overrides.toolCalls ?? [],
20
+ painSignals: overrides.painSignals ?? [],
21
+ gateBlocks: overrides.gateBlocks ?? [],
22
+ userCorrections: overrides.userCorrections ?? [],
23
+ planApprovals: overrides.planApprovals ?? [],
24
+ };
25
+ }
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // detectOpportunity — T-01
29
+ // ---------------------------------------------------------------------------
30
+
31
+ describe('detectOpportunity — T-01', () => {
32
+ it('returns applicable on edit operations', () => {
33
+ const session = makeSession({
34
+ toolCalls: [{ toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'success' }],
35
+ });
36
+ const result = detectOpportunity('T-01', session);
37
+ expect(result.applicable).toBe(true);
38
+ });
39
+
40
+ it('returns applicable on write_to_file', () => {
41
+ const session = makeSession({
42
+ toolCalls: [{ toolName: 'write_to_file', filePath: 'src/new.ts', outcome: 'success' }],
43
+ });
44
+ expect(detectOpportunity('T-01', session).applicable).toBe(true);
45
+ });
46
+
47
+ it('returns not applicable when only read operations', () => {
48
+ const session = makeSession({
49
+ toolCalls: [{ toolName: 'read_file', filePath: 'src/main.ts', outcome: 'success' }],
50
+ });
51
+ const result = detectOpportunity('T-01', session);
52
+ expect(result.applicable).toBe(false);
53
+ });
54
+
55
+ it('returns not applicable on empty session', () => {
56
+ const session = makeSession({ toolCalls: [] });
57
+ expect(detectOpportunity('T-01', session).applicable).toBe(false);
58
+ });
59
+ });
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // detectOpportunity — T-05
63
+ // ---------------------------------------------------------------------------
64
+
65
+ describe('detectOpportunity — T-05', () => {
66
+ it('returns applicable when gate block fires', () => {
67
+ const session = makeSession({
68
+ gateBlocks: [{ toolName: 'delete_file', filePath: 'src/main.ts', reason: 'risky operation' }],
69
+ });
70
+ const result = detectOpportunity('T-05', session);
71
+ expect(result.applicable).toBe(true);
72
+ });
73
+
74
+ it('returns applicable when risky tool is attempted', () => {
75
+ const session = makeSession({
76
+ toolCalls: [{ toolName: 'delete_file', outcome: 'blocked' }],
77
+ });
78
+ expect(detectOpportunity('T-05', session).applicable).toBe(true);
79
+ });
80
+
81
+ it('returns applicable for dangerous bash command', () => {
82
+ const session = makeSession({
83
+ toolCalls: [{ toolName: 'bash', outcome: 'failure', errorMessage: 'rm -rf /home' }],
84
+ });
85
+ expect(detectOpportunity('T-05', session).applicable).toBe(true);
86
+ });
87
+
88
+ it('returns not applicable when no risky operations', () => {
89
+ const session = makeSession({
90
+ toolCalls: [{ toolName: 'read_file', outcome: 'success' }],
91
+ });
92
+ const result = detectOpportunity('T-05', session);
93
+ expect(result.applicable).toBe(false);
94
+ });
95
+ });
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // detectOpportunity — T-09
99
+ // ---------------------------------------------------------------------------
100
+
101
+ describe('detectOpportunity — T-09', () => {
102
+ it('returns applicable when session has 5+ tool calls', () => {
103
+ const calls = Array.from({ length: 6 }, (_, i) => ({
104
+ toolName: 'read_file' as const,
105
+ filePath: `src/file${i}.ts`,
106
+ outcome: 'success' as const,
107
+ }));
108
+ const session = makeSession({ toolCalls: calls });
109
+ expect(detectOpportunity('T-09', session).applicable).toBe(true);
110
+ });
111
+
112
+ it('returns applicable when 3+ files touched', () => {
113
+ const session = makeSession({
114
+ toolCalls: [
115
+ { toolName: 'read_file', filePath: 'src/a.ts', outcome: 'success' },
116
+ { toolName: 'read_file', filePath: 'src/b.ts', outcome: 'success' },
117
+ { toolName: 'edit_file', filePath: 'src/c.ts', outcome: 'success' },
118
+ ],
119
+ });
120
+ expect(detectOpportunity('T-09', session).applicable).toBe(true);
121
+ });
122
+
123
+ it('returns applicable when pain present on complex task', () => {
124
+ const session = makeSession({
125
+ toolCalls: [
126
+ { toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'failure' },
127
+ ],
128
+ painSignals: [{ source: 'edit', score: 60 }],
129
+ });
130
+ expect(detectOpportunity('T-09', session).applicable).toBe(true);
131
+ });
132
+
133
+ it('returns not applicable for short sessions', () => {
134
+ const session = makeSession({
135
+ toolCalls: [
136
+ { toolName: 'read_file', filePath: 'src/main.ts', outcome: 'success' },
137
+ { toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'success' },
138
+ ],
139
+ });
140
+ expect(detectOpportunity('T-09', session).applicable).toBe(false);
141
+ });
142
+ });
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // detectViolation — T-01
146
+ // ---------------------------------------------------------------------------
147
+
148
+ describe('detectViolation — T-01', () => {
149
+ it('returns violated when editing unread file followed by pain', () => {
150
+ const session = makeSession({
151
+ toolCalls: [
152
+ { toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'failure' },
153
+ ],
154
+ painSignals: [
155
+ { source: 'src/main.ts', score: 50, reason: 'edit without understanding structure' },
156
+ ],
157
+ });
158
+ const result = detectViolation('T-01', session);
159
+ expect(result.violated).toBe(true);
160
+ });
161
+
162
+ it('returns NOT violated when file was read before edit', () => {
163
+ const session = makeSession({
164
+ toolCalls: [
165
+ { toolName: 'read_file', filePath: 'src/main.ts', outcome: 'success' },
166
+ { toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'success' },
167
+ ],
168
+ });
169
+ const result = detectViolation('T-01', session);
170
+ expect(result.violated).toBe(false);
171
+ });
172
+
173
+ it('returns violated when editing unread file followed by tool failure', () => {
174
+ const session = makeSession({
175
+ toolCalls: [
176
+ { toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'failure' },
177
+ ],
178
+ });
179
+ const result = detectViolation('T-01', session);
180
+ expect(result.violated).toBe(true);
181
+ expect(result.reason).toContain('without understanding');
182
+ });
183
+
184
+ it('returns NOT violated when edit succeeds without prior read (but no pain)', () => {
185
+ // No pain signal, no failure → can't confirm violation
186
+ const session = makeSession({
187
+ toolCalls: [
188
+ { toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'success' },
189
+ ],
190
+ });
191
+ const result = detectViolation('T-01', session);
192
+ expect(result.violated).toBe(false);
193
+ });
194
+ });
195
+
196
+ // ---------------------------------------------------------------------------
197
+ // detectViolation — T-05
198
+ // ---------------------------------------------------------------------------
199
+
200
+ describe('detectViolation — T-05', () => {
201
+ it('returns violated when gate block fires on risky operation', () => {
202
+ const session = makeSession({
203
+ gateBlocks: [{ toolName: 'bash', reason: 'rm -rf attempted', filePath: '/' }],
204
+ });
205
+ const result = detectViolation('T-05', session);
206
+ expect(result.violated).toBe(true);
207
+ expect(result.reason).toContain('safety rail not');
208
+ });
209
+
210
+ it('returns violated when gate block fires on delete_file', () => {
211
+ const session = makeSession({
212
+ gateBlocks: [{ toolName: 'delete_file', reason: 'risky', filePath: 'src/old.ts' }],
213
+ });
214
+ expect(detectViolation('T-05', session).violated).toBe(true);
215
+ });
216
+
217
+ it('returns NOT violated when no gate blocks', () => {
218
+ const session = makeSession({
219
+ toolCalls: [{ toolName: 'delete_file', outcome: 'success' }],
220
+ });
221
+ expect(detectViolation('T-05', session).violated).toBe(false);
222
+ });
223
+ });
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // detectViolation — T-09
227
+ // ---------------------------------------------------------------------------
228
+
229
+ describe('detectViolation — T-09', () => {
230
+ it('returns violated on complex task with failure and no planning', () => {
231
+ const calls = Array.from({ length: 6 }, (_, i) => ({
232
+ toolName: 'edit_file' as const,
233
+ filePath: `src/file${i}.ts`,
234
+ outcome: 'failure' as const,
235
+ }));
236
+ const session = makeSession({ toolCalls: calls });
237
+ const result = detectViolation('T-09', session);
238
+ expect(result.violated).toBe(true);
239
+ });
240
+
241
+ it('returns NOT violated on complex task that has plan approval', () => {
242
+ const calls = Array.from({ length: 6 }, (_, i) => ({
243
+ toolName: 'edit_file' as const,
244
+ filePath: `src/file${i}.ts`,
245
+ outcome: 'failure' as const,
246
+ }));
247
+ const session = makeSession({
248
+ toolCalls: calls,
249
+ planApprovals: [{ toolName: 'edit_file', filePath: 'src/main.ts' }],
250
+ });
251
+ expect(detectViolation('T-09', session).violated).toBe(false);
252
+ });
253
+
254
+ it('returns NOT violated on non-complex session', () => {
255
+ const session = makeSession({
256
+ toolCalls: [
257
+ { toolName: 'read_file', filePath: 'src/main.ts', outcome: 'success' },
258
+ { toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'success' },
259
+ ],
260
+ });
261
+ expect(detectViolation('T-09', session).violated).toBe(false);
262
+ });
263
+ });
264
+
265
+ // ---------------------------------------------------------------------------
266
+ // computeCompliance — basic
267
+ // ---------------------------------------------------------------------------
268
+
269
+ describe('computeCompliance — basic', () => {
270
+ it('returns zero compliance when no sessions provided', () => {
271
+ const result = computeCompliance('T-01', []);
272
+ expect(result.principleId).toBe('T-01');
273
+ expect(result.applicableOpportunityCount).toBe(0);
274
+ expect(result.observedViolationCount).toBe(0);
275
+ expect(result.complianceRate).toBe(0);
276
+ expect(result.violationTrend).toBe(0);
277
+ });
278
+
279
+ it('returns compliance 1.0 when all opportunities compliant', () => {
280
+ // T-01 applicable (has edit) but no violation (file was read first)
281
+ const session = makeSession({
282
+ toolCalls: [
283
+ { toolName: 'read_file', filePath: 'src/main.ts', outcome: 'success' },
284
+ { toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'success' },
285
+ ],
286
+ });
287
+ const result = computeCompliance('T-01', [session]);
288
+ expect(result.complianceRate).toBe(1.0);
289
+ expect(result.applicableOpportunityCount).toBe(1);
290
+ expect(result.observedViolationCount).toBe(0);
291
+ });
292
+
293
+ it('returns compliance 0.0 when all opportunities violated', () => {
294
+ // T-05: gate block fires (applicable) AND violated
295
+ const session = makeSession({
296
+ gateBlocks: [{ toolName: 'delete_file', reason: 'risky', filePath: 'src/old.ts' }],
297
+ });
298
+ const result = computeCompliance('T-05', [session]);
299
+ expect(result.complianceRate).toBe(0);
300
+ expect(result.applicableOpportunityCount).toBe(1);
301
+ expect(result.observedViolationCount).toBe(1);
302
+ });
303
+
304
+ it('computes partial compliance correctly', () => {
305
+ const compliant = makeSession({
306
+ toolCalls: [
307
+ { toolName: 'read_file', filePath: 'src/main.ts', outcome: 'success' },
308
+ { toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'success' },
309
+ ],
310
+ });
311
+ const violated = makeSession({
312
+ sessionId: 'session-2',
313
+ toolCalls: [
314
+ { toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'failure' },
315
+ ],
316
+ });
317
+ const result = computeCompliance('T-01', [compliant, violated]);
318
+ // 2 applicable, 1 violated → compliance = (2-1)/2 = 0.5
319
+ expect(result.complianceRate).toBe(0.5);
320
+ expect(result.applicableOpportunityCount).toBe(2);
321
+ expect(result.observedViolationCount).toBe(1);
322
+ });
323
+ });
324
+
325
+ // ---------------------------------------------------------------------------
326
+ // computeCompliance — dilution prevention
327
+ // ---------------------------------------------------------------------------
328
+
329
+ describe('computeCompliance — dilution prevention', () => {
330
+ /**
331
+ * The dilution prevention scenario:
332
+ * T-05 is a LOW-frequency, HIGH-severity principle.
333
+ * If we compute compliance over ALL sessions (including ones with no risky ops),
334
+ * the compliance rate would be inflated because non-applicable sessions
335
+ * count as "compliant by default" — which is WRONG.
336
+ *
337
+ * Our engine ONLY counts sessions where T-05 was applicable.
338
+ */
339
+ it('T-05 compliance ignores sessions with no risky operations', () => {
340
+ // Session A: T-05 violated (gate block on delete)
341
+ const sessionA = makeSession({
342
+ sessionId: 'A',
343
+ gateBlocks: [{ toolName: 'delete_file', reason: 'risky', filePath: 'src/old.ts' }],
344
+ });
345
+
346
+ // Session B: No gate blocks, no risky ops — T-05 NOT APPLICABLE
347
+ const sessionB = makeSession({
348
+ sessionId: 'B',
349
+ toolCalls: [{ toolName: 'read_file', outcome: 'success' }],
350
+ });
351
+
352
+ // Session C: Another non-applicable session (only read ops)
353
+ const sessionC = makeSession({
354
+ sessionId: 'C',
355
+ toolCalls: [{ toolName: 'grep', outcome: 'success' }],
356
+ });
357
+
358
+ // WRONG approach (session-average): (1 + 0 + 0) / 3 = 33% compliance
359
+ // CORRECT approach (opportunity-based): only session A counts
360
+ // Session A: applicable + violated → 0% compliance
361
+ const result = computeCompliance('T-05', [sessionA, sessionB, sessionC]);
362
+
363
+ expect(result.applicableOpportunityCount).toBe(1); // Only session A
364
+ expect(result.observedViolationCount).toBe(1); // Session A violated
365
+ expect(result.complianceRate).toBe(0); // 0% — not diluted by B and C
366
+
367
+ // Explanation must mention dilution prevention
368
+ expect(result.explanation).toContain('applicable opportunities');
369
+ });
370
+
371
+ it('T-01 compliance ignores sessions with no edit operations', () => {
372
+ // Session A: T-01 applicable + violated
373
+ const sessionA = makeSession({
374
+ sessionId: 'A',
375
+ toolCalls: [{ toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'failure' }],
376
+ });
377
+
378
+ // Session B: No edits — T-01 NOT APPLICABLE
379
+ const sessionB = makeSession({
380
+ sessionId: 'B',
381
+ toolCalls: [{ toolName: 'read_file', outcome: 'success' }],
382
+ });
383
+
384
+ // T-01 compliance = 0% (1 applicable, 1 violated), session B doesn't dilute
385
+ const result = computeCompliance('T-01', [sessionA, sessionB]);
386
+
387
+ expect(result.applicableOpportunityCount).toBe(1);
388
+ expect(result.observedViolationCount).toBe(1);
389
+ expect(result.complianceRate).toBe(0);
390
+ });
391
+
392
+ it('high-frequency principle (T-01) still gets high opportunity count across diverse sessions', () => {
393
+ // Sessions with edit ops — all T-01 applicable
394
+ const sessions = [
395
+ makeSession({ sessionId: '1', toolCalls: [{ toolName: 'edit_file', filePath: 'a.ts', outcome: 'success' }] }),
396
+ makeSession({ sessionId: '2', toolCalls: [{ toolName: 'edit_file', filePath: 'b.ts', outcome: 'failure' }] }),
397
+ makeSession({ sessionId: '3', toolCalls: [{ toolName: 'write_to_file', filePath: 'c.ts', outcome: 'success' }] }),
398
+ ];
399
+
400
+ // Session with no edits — T-01 not applicable
401
+ const noEdit = makeSession({ sessionId: '4', toolCalls: [{ toolName: 'grep', outcome: 'success' }] });
402
+
403
+ const result = computeCompliance('T-01', [...sessions, noEdit]);
404
+ expect(result.applicableOpportunityCount).toBe(3); // Only sessions with edits
405
+ });
406
+ });
407
+
408
+ // ---------------------------------------------------------------------------
409
+ // computeCompliance — violationTrend
410
+ // ---------------------------------------------------------------------------
411
+
412
+ describe('computeCompliance — violationTrend', () => {
413
+ function t01(opportunity: 'violated' | 'compliant'): SessionEvents {
414
+ if (opportunity === 'violated') {
415
+ return makeSession({
416
+ sessionId: `s-${opportunity}`,
417
+ toolCalls: [{ toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'failure' }],
418
+ });
419
+ }
420
+ return makeSession({
421
+ sessionId: `s-${opportunity}`,
422
+ toolCalls: [
423
+ { toolName: 'read_file', filePath: 'src/main.ts', outcome: 'success' },
424
+ { toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'success' },
425
+ ],
426
+ });
427
+ }
428
+
429
+ it('returns trend = +1 (improving) when recent violations decrease', () => {
430
+ // Most recent: compliant, compliant
431
+ // Previous: violated, violated, violated
432
+ // Recent rate = 0/2 = 0, Previous rate = 3/3 = 1
433
+ // delta = 1 - 0 > 0.1 → improving (+1)
434
+ const sessions = [
435
+ t01('compliant'), // index 0 (most recent in input order)
436
+ t01('compliant'), // index 1
437
+ t01('violated'), // index 2
438
+ t01('violated'), // index 3
439
+ t01('violated'), // index 4
440
+ ];
441
+ const result = computeCompliance('T-01', sessions, { trendWindowSize: 2 });
442
+ expect(result.violationTrend).toBe(1);
443
+ });
444
+
445
+ it('returns trend = -1 (worsening) when recent violations increase', () => {
446
+ // Most recent: violated, violated
447
+ // Previous: compliant, compliant
448
+ // Recent rate = 2/2 = 1, Previous rate = 0/2 = 0
449
+ // delta = 0 - 1 = -1 < -0.1 → worsening (-1)
450
+ const sessions = [
451
+ t01('violated'),
452
+ t01('violated'),
453
+ t01('compliant'),
454
+ t01('compliant'),
455
+ ];
456
+ const result = computeCompliance('T-01', sessions, { trendWindowSize: 2 });
457
+ expect(result.violationTrend).toBe(-1);
458
+ });
459
+
460
+ it('returns trend = 0 when stable', () => {
461
+ // 2 compliant, then 2 compliant
462
+ const sessions = [t01('compliant'), t01('compliant'), t01('compliant'), t01('compliant')];
463
+ const result = computeCompliance('T-01', sessions, { trendWindowSize: 2 });
464
+ expect(result.violationTrend).toBe(0);
465
+ });
466
+
467
+ it('returns trend = 0 when only 1 applicable session', () => {
468
+ const sessions = [t01('violated')];
469
+ const result = computeCompliance('T-01', sessions);
470
+ expect(result.violationTrend).toBe(0);
471
+ });
472
+ });
473
+
474
+ // ---------------------------------------------------------------------------
475
+ // computeAllCompliance
476
+ // ---------------------------------------------------------------------------
477
+
478
+ describe('computeAllCompliance', () => {
479
+ it('returns results for all T-01 through T-09', () => {
480
+ const results = computeAllCompliance([]);
481
+ const ids = results.map((r) => r.principleId);
482
+ expect(ids).toEqual(['T-01', 'T-02', 'T-03', 'T-04', 'T-05', 'T-06', 'T-07', 'T-08', 'T-09']);
483
+ });
484
+
485
+ it('each result has all required fields', () => {
486
+ const results = computeAllCompliance([]);
487
+ for (const result of results) {
488
+ expect(result.principleId).toBeDefined();
489
+ expect(result.applicableOpportunityCount).toBe(0);
490
+ expect(result.observedViolationCount).toBe(0);
491
+ expect(result.complianceRate).toBe(0);
492
+ expect(result.violationTrend).toBe(0);
493
+ expect(result.explanation).toBeDefined();
494
+ }
495
+ });
496
+ });
497
+
498
+ // ---------------------------------------------------------------------------
499
+ // groupEventsIntoSessions
500
+ // ---------------------------------------------------------------------------
501
+
502
+ describe('groupEventsIntoSessions', () => {
503
+ function event(type: string, sessionId: string, data: Record<string, unknown> = {}): RawEventEntry {
504
+ return { ts: '2026-03-27T12:00:00Z', type, sessionId, data };
505
+ }
506
+
507
+ it('groups events by sessionId', () => {
508
+ const events: RawEventEntry[] = [
509
+ event('tool_call', 's1', { toolName: 'read_file', filePath: 'a.ts' }),
510
+ event('tool_call', 's1', { toolName: 'edit_file', filePath: 'b.ts' }),
511
+ event('tool_call', 's2', { toolName: 'read_file', filePath: 'c.ts' }),
512
+ ];
513
+ const sessions = groupEventsIntoSessions(events);
514
+ expect(sessions.get('s1')!.toolCalls).toHaveLength(2);
515
+ expect(sessions.get('s2')!.toolCalls).toHaveLength(1);
516
+ });
517
+
518
+ it('maps pain_signal events', () => {
519
+ const events: RawEventEntry[] = [
520
+ event('pain_signal', 's1', { source: 'edit', score: 50, severity: 'moderate' }),
521
+ ];
522
+ const sessions = groupEventsIntoSessions(events);
523
+ expect(sessions.get('s1')!.painSignals).toHaveLength(1);
524
+ expect(sessions.get('s1')!.painSignals[0].source).toBe('edit');
525
+ expect(sessions.get('s1')!.painSignals[0].severity).toBe('moderate');
526
+ });
527
+
528
+ it('maps gate_block events', () => {
529
+ const events: RawEventEntry[] = [
530
+ event('gate_block', 's1', { toolName: 'bash', reason: 'dangerous command' }),
531
+ ];
532
+ const sessions = groupEventsIntoSessions(events);
533
+ expect(sessions.get('s1')!.gateBlocks).toHaveLength(1);
534
+ expect(sessions.get('s1')!.gateBlocks[0].toolName).toBe('bash');
535
+ });
536
+
537
+ it('maps plan_approval events', () => {
538
+ const events: RawEventEntry[] = [
539
+ event('plan_approval', 's1', { toolName: 'edit_file', filePath: 'src/main.ts' }),
540
+ ];
541
+ const sessions = groupEventsIntoSessions(events);
542
+ expect(sessions.get('s1')!.planApprovals).toHaveLength(1);
543
+ });
544
+
545
+ it('groups events without sessionId into "unknown"', () => {
546
+ const events: RawEventEntry[] = [
547
+ { ts: '2026-03-27T12:00:00Z', type: 'tool_call', data: { toolName: 'read_file' } },
548
+ ];
549
+ const sessions = groupEventsIntoSessions(events);
550
+ expect(sessions.get('unknown')!.toolCalls).toHaveLength(1);
551
+ });
552
+
553
+ it('correctly maps error field to outcome', () => {
554
+ const events: RawEventEntry[] = [
555
+ event('tool_call', 's1', { toolName: 'edit_file', error: 'file not found' }),
556
+ event('tool_call', 's2', { toolName: 'read_file' }), // no error → success
557
+ ];
558
+ const sessions = groupEventsIntoSessions(events);
559
+ expect(sessions.get('s1')!.toolCalls[0].outcome).toBe('failure');
560
+ expect(sessions.get('s2')!.toolCalls[0].outcome).toBe('success');
561
+ });
562
+ });
563
+
564
+ // ---------------------------------------------------------------------------
565
+ // Explanation is human-readable
566
+ // ---------------------------------------------------------------------------
567
+
568
+ describe('ComplianceResult — explanation', () => {
569
+ it('explanation includes compliance rate and trend', () => {
570
+ const session = makeSession({
571
+ toolCalls: [
572
+ { toolName: 'read_file', filePath: 'src/main.ts', outcome: 'success' },
573
+ { toolName: 'edit_file', filePath: 'src/main.ts', outcome: 'success' },
574
+ ],
575
+ });
576
+ const result = computeCompliance('T-01', [session]);
577
+ expect(result.explanation).toContain('T-01');
578
+ expect(result.explanation).toContain('applicable opportunities');
579
+ expect(result.explanation).toContain('100.0%'); // compliance rate
580
+ });
581
+
582
+ it('explanation notes when no opportunities exist', () => {
583
+ const result = computeCompliance('T-05', [
584
+ makeSession({ toolCalls: [{ toolName: 'read_file', outcome: 'success' }] }),
585
+ ]);
586
+ expect(result.explanation).toContain('No applicable opportunities');
587
+ });
588
+
589
+ it('explanation includes sample violation reasons', () => {
590
+ const session = makeSession({
591
+ gateBlocks: [{ toolName: 'delete_file', reason: 'risky', filePath: 'src/old.ts' }],
592
+ });
593
+ const result = computeCompliance('T-05', [session]);
594
+ expect(result.explanation).toContain('violation');
595
+ expect(result.explanation).toContain('safety rail not');
596
+ });
597
+ });
598
+
599
+ // ---------------------------------------------------------------------------
600
+ // Integration: full session list
601
+ // ---------------------------------------------------------------------------
602
+
603
+ describe('Full session integration — T-05 dilution scenario', () => {
604
+ /**
605
+ * Real-world scenario: 20 sessions in a day.
606
+ * Only 2 sessions involve risky operations (T-05 applicable).
607
+ * Both had gate blocks (violations).
608
+ * 18 sessions had no risky operations (T-05 not applicable).
609
+ *
610
+ * If we averaged all 20 sessions: 2 violations / 20 = 90% compliance (wrong!)
611
+ * With opportunity-based: 2 applicable / 2 violated = 0% compliance (correct!)
612
+ */
613
+ it('does not dilute low-frequency high-severity principle compliance', () => {
614
+ function gateBlockSession(id: string): SessionEvents {
615
+ return makeSession({
616
+ sessionId: id,
617
+ gateBlocks: [{ toolName: 'bash', reason: 'rm -rf attempted', filePath: '/' }],
618
+ });
619
+ }
620
+
621
+ function safeSession(id: string): SessionEvents {
622
+ return makeSession({
623
+ sessionId: id,
624
+ toolCalls: [{ toolName: 'read_file', outcome: 'success' }],
625
+ });
626
+ }
627
+
628
+ const sessions: SessionEvents[] = [
629
+ // Only 2 sessions where T-05 is applicable
630
+ gateBlockSession('risky-1'),
631
+ gateBlockSession('risky-2'),
632
+ // 18 sessions where T-05 is NOT applicable (safe operations)
633
+ ...Array.from({ length: 18 }, (_, i) => safeSession(`safe-${i}`)),
634
+ ];
635
+
636
+ const result = computeCompliance('T-05', sessions);
637
+
638
+ // Only 2 applicable opportunities
639
+ expect(result.applicableOpportunityCount).toBe(2);
640
+ // Both were violated
641
+ expect(result.observedViolationCount).toBe(2);
642
+ // Compliance = 0% — NOT 90%
643
+ expect(result.complianceRate).toBe(0);
644
+ expect(result.explanation).toContain('applicable opportunities');
645
+ });
646
+ });