principles-disciple 1.8.0 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (460) hide show
  1. package/ADVANCED_CONFIG_ZH.md +97 -0
  2. package/AGENT_INSTALL.md +173 -0
  3. package/AGENT_INSTALL_EN.md +173 -0
  4. package/INSTALL.md +256 -0
  5. package/SKILL.md +63 -0
  6. package/docs/COMMAND_REFERENCE.md +76 -0
  7. package/docs/COMMAND_REFERENCE_EN.md +79 -0
  8. package/esbuild.config.js +75 -0
  9. package/openclaw.plugin.json +6 -1
  10. package/package.json +13 -15
  11. package/scripts/build-web.mjs +46 -0
  12. package/scripts/install-dependencies.cjs +47 -0
  13. package/scripts/sync-plugin.mjs +802 -0
  14. package/scripts/verify-build.mjs +109 -0
  15. package/src/agents/nocturnal-dreamer.md +152 -0
  16. package/src/agents/nocturnal-philosopher.md +138 -0
  17. package/src/agents/nocturnal-reflector.md +126 -0
  18. package/src/agents/nocturnal-scribe.md +164 -0
  19. package/src/commands/capabilities.ts +85 -0
  20. package/{dist/commands/context.js → src/commands/context.ts} +78 -38
  21. package/src/commands/evolution-status.ts +146 -0
  22. package/src/commands/export.ts +111 -0
  23. package/src/commands/focus.ts +533 -0
  24. package/src/commands/nocturnal-review.ts +311 -0
  25. package/src/commands/nocturnal-rollout.ts +763 -0
  26. package/src/commands/nocturnal-train.ts +1002 -0
  27. package/{dist/commands/pain.js → src/commands/pain.ts} +68 -49
  28. package/src/commands/principle-rollback.ts +27 -0
  29. package/{dist/commands/rollback.js → src/commands/rollback.ts} +44 -12
  30. package/src/commands/samples.ts +60 -0
  31. package/src/commands/strategy.ts +38 -0
  32. package/{dist/commands/thinking-os.js → src/commands/thinking-os.ts} +59 -36
  33. package/src/commands/workflow-debug.ts +128 -0
  34. package/{dist/config/defaults/runtime.js → src/config/defaults/runtime.ts} +12 -5
  35. package/src/config/errors.ts +163 -0
  36. package/{dist/config/index.d.ts → src/config/index.ts} +2 -1
  37. package/src/constants/diagnostician.ts +66 -0
  38. package/src/constants/tools.ts +62 -0
  39. package/src/core/adaptive-thresholds.ts +476 -0
  40. package/{dist/core/config-service.js → src/core/config-service.ts} +7 -4
  41. package/{dist/core/config.js → src/core/config.ts} +158 -46
  42. package/src/core/control-ui-db.ts +435 -0
  43. package/{dist/core/detection-funnel.js → src/core/detection-funnel.ts} +36 -21
  44. package/{dist/core/detection-service.js → src/core/detection-service.ts} +7 -4
  45. package/{dist/core/dictionary-service.js → src/core/dictionary-service.ts} +7 -4
  46. package/{dist/core/dictionary.js → src/core/dictionary.ts} +57 -34
  47. package/src/core/empathy-keyword-matcher.ts +327 -0
  48. package/src/core/empathy-types.ts +218 -0
  49. package/src/core/event-log.ts +544 -0
  50. package/src/core/evolution-engine.ts +612 -0
  51. package/src/core/evolution-logger.ts +353 -0
  52. package/src/core/evolution-migration.ts +77 -0
  53. package/src/core/evolution-reducer.ts +731 -0
  54. package/src/core/evolution-types.ts +456 -0
  55. package/src/core/external-training-contract.ts +527 -0
  56. package/src/core/focus-history.ts +1458 -0
  57. package/src/core/hygiene/tracker.ts +117 -0
  58. package/{dist/core/init.js → src/core/init.ts} +39 -26
  59. package/src/core/local-worker-routing.ts +617 -0
  60. package/{dist/core/migration.js → src/core/migration.ts} +18 -11
  61. package/src/core/model-deployment-registry.ts +722 -0
  62. package/src/core/model-training-registry.ts +813 -0
  63. package/src/core/nocturnal-arbiter.ts +706 -0
  64. package/src/core/nocturnal-candidate-scoring.ts +392 -0
  65. package/src/core/nocturnal-compliance.ts +1075 -0
  66. package/src/core/nocturnal-dataset.ts +668 -0
  67. package/src/core/nocturnal-executability.ts +428 -0
  68. package/src/core/nocturnal-export.ts +390 -0
  69. package/{dist/core/nocturnal-paths.js → src/core/nocturnal-paths.ts} +49 -23
  70. package/src/core/nocturnal-trajectory-extractor.ts +484 -0
  71. package/src/core/nocturnal-trinity.ts +1384 -0
  72. package/src/core/pain.ts +122 -0
  73. package/{dist/core/path-resolver.js → src/core/path-resolver.ts} +157 -36
  74. package/{dist/core/paths.js → src/core/paths.ts} +13 -4
  75. package/src/core/principle-training-state.ts +450 -0
  76. package/src/core/profile.ts +226 -0
  77. package/src/core/promotion-gate.ts +822 -0
  78. package/{dist/core/risk-calculator.js → src/core/risk-calculator.ts} +42 -16
  79. package/{dist/core/session-tracker.js → src/core/session-tracker.ts} +185 -63
  80. package/src/core/shadow-observation-registry.ts +534 -0
  81. package/{dist/core/system-logger.js → src/core/system-logger.ts} +9 -5
  82. package/src/core/thinking-models.ts +217 -0
  83. package/src/core/training-program.ts +630 -0
  84. package/src/core/trajectory-types.ts +243 -0
  85. package/src/core/trajectory.ts +1673 -0
  86. package/{dist/core/workspace-context.js → src/core/workspace-context.ts} +57 -32
  87. package/src/hooks/bash-risk.ts +171 -0
  88. package/src/hooks/edit-verification.ts +295 -0
  89. package/src/hooks/gate-block-helper.ts +160 -0
  90. package/src/hooks/gate.ts +210 -0
  91. package/src/hooks/gfi-gate.ts +177 -0
  92. package/src/hooks/lifecycle.ts +326 -0
  93. package/{dist/hooks/llm.js → src/hooks/llm.ts} +166 -139
  94. package/src/hooks/message-sanitize.ts +45 -0
  95. package/src/hooks/pain.ts +384 -0
  96. package/src/hooks/progressive-trust-gate.ts +174 -0
  97. package/src/hooks/prompt.ts +920 -0
  98. package/src/hooks/subagent.ts +207 -0
  99. package/src/hooks/thinking-checkpoint.ts +73 -0
  100. package/src/hooks/trajectory-collector.ts +290 -0
  101. package/src/http/principles-console-route.ts +716 -0
  102. package/src/i18n/commands.ts +117 -0
  103. package/src/index.ts +694 -0
  104. package/src/service/central-database.ts +831 -0
  105. package/src/service/control-ui-query-service.ts +888 -0
  106. package/src/service/evolution-query-service.ts +405 -0
  107. package/src/service/evolution-worker.ts +1646 -0
  108. package/src/service/health-query-service.ts +836 -0
  109. package/{dist/service/nocturnal-runtime.js → src/service/nocturnal-runtime.ts} +263 -36
  110. package/src/service/nocturnal-service.ts +1015 -0
  111. package/src/service/nocturnal-target-selector.ts +532 -0
  112. package/src/service/phase3-input-filter.ts +237 -0
  113. package/src/service/runtime-summary-service.ts +757 -0
  114. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +513 -0
  115. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +603 -0
  116. package/src/service/subagent-workflow/index.ts +51 -0
  117. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +856 -0
  118. package/src/service/subagent-workflow/runtime-direct-driver.ts +166 -0
  119. package/src/service/subagent-workflow/types.ts +378 -0
  120. package/src/service/subagent-workflow/workflow-store.ts +328 -0
  121. package/src/service/trajectory-service.ts +15 -0
  122. package/{dist/tools/critique-prompt.js → src/tools/critique-prompt.ts} +25 -8
  123. package/src/tools/deep-reflect.ts +349 -0
  124. package/{dist/tools/model-index.js → src/tools/model-index.ts} +33 -17
  125. package/src/types/event-types.ts +453 -0
  126. package/src/types/hygiene-types.ts +31 -0
  127. package/src/types/principle-tree-schema.ts +244 -0
  128. package/src/types/runtime-summary.ts +49 -0
  129. package/src/types.ts +74 -0
  130. package/src/utils/file-lock.ts +391 -0
  131. package/{dist/utils/glob-match.js → src/utils/glob-match.ts} +21 -20
  132. package/{dist/utils/hashing.js → src/utils/hashing.ts} +6 -4
  133. package/src/utils/io.ts +110 -0
  134. package/{dist/utils/nlp.js → src/utils/nlp.ts} +19 -12
  135. package/{dist/utils/plugin-logger.js → src/utils/plugin-logger.ts} +33 -8
  136. package/src/utils/subagent-probe.ts +94 -0
  137. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +70 -1
  138. package/templates/pain_settings.json +2 -1
  139. package/tests/README.md +120 -0
  140. package/tests/build-artifacts.test.ts +111 -0
  141. package/tests/commands/evolution-status.test.ts +222 -0
  142. package/tests/commands/evolver.test.ts +22 -0
  143. package/tests/commands/export.test.ts +78 -0
  144. package/tests/commands/nocturnal-review.test.ts +448 -0
  145. package/tests/commands/nocturnal-train.test.ts +97 -0
  146. package/tests/commands/pain.test.ts +108 -0
  147. package/tests/commands/samples.test.ts +65 -0
  148. package/tests/commands/strategy.test.ts +34 -0
  149. package/tests/commands/thinking-os.test.ts +88 -0
  150. package/tests/core/adaptive-thresholds.test.ts +261 -0
  151. package/tests/core/config-service.test.ts +89 -0
  152. package/tests/core/config.test.ts +90 -0
  153. package/tests/core/control-ui-db.test.ts +75 -0
  154. package/tests/core/core-template-guidance.test.ts +21 -0
  155. package/tests/core/detection-funnel.test.ts +63 -0
  156. package/tests/core/detection-service.test.ts +50 -0
  157. package/tests/core/dictionary-service.test.ts +116 -0
  158. package/tests/core/dictionary.test.ts +168 -0
  159. package/tests/core/empathy-keyword-matcher.test.ts +209 -0
  160. package/tests/core/event-log.test.ts +181 -0
  161. package/tests/core/evolution-e2e.test.ts +58 -0
  162. package/tests/core/evolution-engine-gate-integration.test.ts +543 -0
  163. package/tests/core/evolution-engine.test.ts +562 -0
  164. package/tests/core/evolution-logger.test.ts +148 -0
  165. package/tests/core/evolution-migration.test.ts +50 -0
  166. package/tests/core/evolution-paths.test.ts +21 -0
  167. package/tests/core/evolution-reducer.detector-metadata.test.ts +602 -0
  168. package/tests/core/evolution-reducer.test.ts +180 -0
  169. package/tests/core/evolution-types-loop.test.ts +48 -0
  170. package/tests/core/evolution-user-stories.e2e.test.ts +249 -0
  171. package/tests/core/external-training-contract.test.ts +463 -0
  172. package/tests/core/focus-history.test.ts +682 -0
  173. package/tests/core/init-flatten.test.ts +69 -0
  174. package/tests/core/init-refactor.test.ts +87 -0
  175. package/tests/core/init-v1.3.test.ts +46 -0
  176. package/tests/core/init.test.ts +190 -0
  177. package/tests/core/local-worker-routing.test.ts +757 -0
  178. package/tests/core/migration.test.ts +84 -0
  179. package/tests/core/model-deployment-registry.test.ts +845 -0
  180. package/tests/core/model-training-registry.test.ts +889 -0
  181. package/tests/core/nocturnal-arbiter.test.ts +494 -0
  182. package/tests/core/nocturnal-candidate-scoring.test.ts +400 -0
  183. package/tests/core/nocturnal-compliance.test.ts +646 -0
  184. package/tests/core/nocturnal-dataset.test.ts +892 -0
  185. package/tests/core/nocturnal-executability.test.ts +357 -0
  186. package/tests/core/nocturnal-export.test.ts +462 -0
  187. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +428 -0
  188. package/tests/core/nocturnal-trajectory-extractor.test.ts +634 -0
  189. package/tests/core/nocturnal-trinity.test.ts +953 -0
  190. package/tests/core/pain.test.ts +33 -0
  191. package/tests/core/path-resolver.test.ts +57 -0
  192. package/tests/core/paths-refactor.test.ts +42 -0
  193. package/tests/core/phase7-rollout-integration.test.ts +477 -0
  194. package/tests/core/principle-training-state.test.ts +712 -0
  195. package/tests/core/profile.test.ts +56 -0
  196. package/tests/core/promotion-gate.test.ts +556 -0
  197. package/tests/core/risk-calculator.test.ts +168 -0
  198. package/tests/core/session-tracker.test.ts +191 -0
  199. package/tests/core/training-program.test.ts +472 -0
  200. package/tests/core/trajectory.test.ts +265 -0
  201. package/tests/core/workspace-context-factory.test.ts +18 -0
  202. package/tests/core/workspace-context.test.ts +134 -0
  203. package/tests/fixtures/nocturnal-reviewed-subset.json +183 -0
  204. package/tests/fixtures/production-compatibility.test.ts +147 -0
  205. package/tests/fixtures/production-mock-generator.ts +282 -0
  206. package/tests/hooks/bash-risk-integration.test.ts +137 -0
  207. package/tests/hooks/bash-risk.test.ts +81 -0
  208. package/tests/hooks/edit-verification.test.ts +678 -0
  209. package/tests/hooks/gate-edit-verification-p1.test.ts +632 -0
  210. package/tests/hooks/gate-edit-verification.test.ts +435 -0
  211. package/tests/hooks/gate-pipeline-integration.test.ts +404 -0
  212. package/tests/hooks/gate.test.ts +271 -0
  213. package/tests/hooks/gfi-gate-unit.test.ts +422 -0
  214. package/tests/hooks/gfi-gate.test.ts +669 -0
  215. package/tests/hooks/lifecycle.test.ts +248 -0
  216. package/tests/hooks/llm.test.ts +308 -0
  217. package/tests/hooks/message-sanitize.test.ts +36 -0
  218. package/tests/hooks/pain.test.ts +141 -0
  219. package/tests/hooks/progressive-trust-gate.test.ts +277 -0
  220. package/tests/hooks/prompt.test.ts +1411 -0
  221. package/tests/hooks/subagent.test.ts +467 -0
  222. package/tests/hooks/thinking-gate.test.ts +313 -0
  223. package/tests/http/principles-console-route.test.ts +140 -0
  224. package/tests/hygiene-tracker.test.ts +77 -0
  225. package/tests/index.integration.test.ts +179 -0
  226. package/tests/index.shadow-routing.integration.test.ts +140 -0
  227. package/tests/index.test.ts +9 -0
  228. package/tests/integration/empathy-workflow-integration.test.ts +627 -0
  229. package/tests/service/control-ui-query-service.test.ts +121 -0
  230. package/tests/service/empathy-observer-workflow-manager.test.ts +176 -0
  231. package/tests/service/evolution-worker.test.ts +585 -0
  232. package/tests/service/nocturnal-runtime.test.ts +470 -0
  233. package/tests/service/nocturnal-service.test.ts +577 -0
  234. package/tests/service/nocturnal-target-selector.test.ts +615 -0
  235. package/tests/service/nocturnal-workflow-manager.test.ts +439 -0
  236. package/tests/service/phase3-input-filter.test.ts +289 -0
  237. package/tests/service/runtime-summary-service.test.ts +919 -0
  238. package/tests/task-compliance.test.ts +166 -0
  239. package/tests/test-utils.ts +48 -0
  240. package/tests/tools/critique-prompt.test.ts +260 -0
  241. package/tests/tools/deep-reflect.test.ts +232 -0
  242. package/tests/tools/model-index.test.ts +246 -0
  243. package/tests/ui/app.test.tsx +114 -0
  244. package/tests/utils/file-lock.test.ts +407 -0
  245. package/tests/utils/hashing.test.ts +32 -0
  246. package/tests/utils/io.test.ts +39 -0
  247. package/tests/utils/nlp.test.ts +53 -0
  248. package/tests/utils/plugin-logger.test.ts +156 -0
  249. package/tsconfig.json +16 -0
  250. package/tsconfig.tsbuildinfo +1 -0
  251. package/ui/src/App.tsx +45 -0
  252. package/ui/src/api.ts +216 -0
  253. package/ui/src/charts.tsx +586 -0
  254. package/ui/src/components/ErrorState.tsx +6 -0
  255. package/ui/src/components/Loading.tsx +13 -0
  256. package/ui/src/components/ProtectedRoute.tsx +12 -0
  257. package/ui/src/components/Shell.tsx +91 -0
  258. package/ui/src/components/WorkspaceConfig.tsx +146 -0
  259. package/ui/src/components/index.ts +5 -0
  260. package/ui/src/context/auth.tsx +80 -0
  261. package/ui/src/context/theme.tsx +66 -0
  262. package/ui/src/hooks/useAutoRefresh.ts +39 -0
  263. package/ui/src/i18n/ui.ts +363 -0
  264. package/ui/src/main.tsx +16 -0
  265. package/ui/src/pages/EvolutionPage.tsx +352 -0
  266. package/ui/src/pages/FeedbackPage.tsx +140 -0
  267. package/ui/src/pages/GateMonitorPage.tsx +136 -0
  268. package/ui/src/pages/LoginPage.tsx +88 -0
  269. package/ui/src/pages/OverviewPage.tsx +238 -0
  270. package/ui/src/pages/SamplesPage.tsx +174 -0
  271. package/ui/src/pages/ThinkingModelsPage.tsx +127 -0
  272. package/ui/src/styles.css +1661 -0
  273. package/ui/src/types.ts +368 -0
  274. package/ui/src/utils/format.ts +15 -0
  275. package/vitest.config.ts +23 -0
  276. package/dist/commands/capabilities.d.ts +0 -3
  277. package/dist/commands/capabilities.js +0 -73
  278. package/dist/commands/context.d.ts +0 -5
  279. package/dist/commands/evolution-status.d.ts +0 -4
  280. package/dist/commands/evolution-status.js +0 -117
  281. package/dist/commands/evolver.d.ts +0 -9
  282. package/dist/commands/evolver.js +0 -26
  283. package/dist/commands/export.d.ts +0 -2
  284. package/dist/commands/export.js +0 -98
  285. package/dist/commands/focus.d.ts +0 -14
  286. package/dist/commands/focus.js +0 -457
  287. package/dist/commands/nocturnal-review.d.ts +0 -24
  288. package/dist/commands/nocturnal-review.js +0 -265
  289. package/dist/commands/nocturnal-rollout.d.ts +0 -27
  290. package/dist/commands/nocturnal-rollout.js +0 -671
  291. package/dist/commands/nocturnal-train.d.ts +0 -25
  292. package/dist/commands/nocturnal-train.js +0 -919
  293. package/dist/commands/pain.d.ts +0 -5
  294. package/dist/commands/principle-rollback.d.ts +0 -4
  295. package/dist/commands/principle-rollback.js +0 -22
  296. package/dist/commands/rollback.d.ts +0 -19
  297. package/dist/commands/samples.d.ts +0 -2
  298. package/dist/commands/samples.js +0 -55
  299. package/dist/commands/strategy.d.ts +0 -3
  300. package/dist/commands/strategy.js +0 -29
  301. package/dist/commands/thinking-os.d.ts +0 -2
  302. package/dist/config/defaults/runtime.d.ts +0 -40
  303. package/dist/config/errors.d.ts +0 -84
  304. package/dist/config/errors.js +0 -94
  305. package/dist/config/index.js +0 -7
  306. package/dist/constants/diagnostician.d.ts +0 -12
  307. package/dist/constants/diagnostician.js +0 -56
  308. package/dist/constants/tools.d.ts +0 -17
  309. package/dist/constants/tools.js +0 -54
  310. package/dist/core/adaptive-thresholds.d.ts +0 -186
  311. package/dist/core/adaptive-thresholds.js +0 -300
  312. package/dist/core/config-service.d.ts +0 -15
  313. package/dist/core/config.d.ts +0 -127
  314. package/dist/core/control-ui-db.d.ts +0 -95
  315. package/dist/core/control-ui-db.js +0 -292
  316. package/dist/core/detection-funnel.d.ts +0 -33
  317. package/dist/core/detection-service.d.ts +0 -15
  318. package/dist/core/dictionary-service.d.ts +0 -15
  319. package/dist/core/dictionary.d.ts +0 -38
  320. package/dist/core/event-log.d.ts +0 -82
  321. package/dist/core/event-log.js +0 -463
  322. package/dist/core/evolution-engine.d.ts +0 -118
  323. package/dist/core/evolution-engine.js +0 -464
  324. package/dist/core/evolution-logger.d.ts +0 -137
  325. package/dist/core/evolution-logger.js +0 -256
  326. package/dist/core/evolution-migration.d.ts +0 -5
  327. package/dist/core/evolution-migration.js +0 -65
  328. package/dist/core/evolution-reducer.d.ts +0 -98
  329. package/dist/core/evolution-reducer.js +0 -465
  330. package/dist/core/evolution-types.d.ts +0 -287
  331. package/dist/core/evolution-types.js +0 -78
  332. package/dist/core/external-training-contract.d.ts +0 -276
  333. package/dist/core/external-training-contract.js +0 -269
  334. package/dist/core/focus-history.d.ts +0 -210
  335. package/dist/core/focus-history.js +0 -1185
  336. package/dist/core/hygiene/tracker.d.ts +0 -22
  337. package/dist/core/hygiene/tracker.js +0 -106
  338. package/dist/core/init.d.ts +0 -12
  339. package/dist/core/local-worker-routing.d.ts +0 -175
  340. package/dist/core/local-worker-routing.js +0 -525
  341. package/dist/core/migration.d.ts +0 -6
  342. package/dist/core/model-deployment-registry.d.ts +0 -218
  343. package/dist/core/model-deployment-registry.js +0 -503
  344. package/dist/core/model-training-registry.d.ts +0 -295
  345. package/dist/core/model-training-registry.js +0 -475
  346. package/dist/core/nocturnal-arbiter.d.ts +0 -159
  347. package/dist/core/nocturnal-arbiter.js +0 -534
  348. package/dist/core/nocturnal-candidate-scoring.d.ts +0 -137
  349. package/dist/core/nocturnal-candidate-scoring.js +0 -266
  350. package/dist/core/nocturnal-compliance.d.ts +0 -175
  351. package/dist/core/nocturnal-compliance.js +0 -824
  352. package/dist/core/nocturnal-dataset.d.ts +0 -224
  353. package/dist/core/nocturnal-dataset.js +0 -443
  354. package/dist/core/nocturnal-executability.d.ts +0 -85
  355. package/dist/core/nocturnal-executability.js +0 -331
  356. package/dist/core/nocturnal-export.d.ts +0 -124
  357. package/dist/core/nocturnal-export.js +0 -275
  358. package/dist/core/nocturnal-paths.d.ts +0 -124
  359. package/dist/core/nocturnal-trajectory-extractor.d.ts +0 -242
  360. package/dist/core/nocturnal-trajectory-extractor.js +0 -307
  361. package/dist/core/nocturnal-trinity.d.ts +0 -311
  362. package/dist/core/nocturnal-trinity.js +0 -880
  363. package/dist/core/pain.d.ts +0 -4
  364. package/dist/core/pain.js +0 -70
  365. package/dist/core/path-resolver.d.ts +0 -46
  366. package/dist/core/paths.d.ts +0 -65
  367. package/dist/core/principle-training-state.d.ts +0 -121
  368. package/dist/core/principle-training-state.js +0 -321
  369. package/dist/core/profile.d.ts +0 -62
  370. package/dist/core/profile.js +0 -210
  371. package/dist/core/promotion-gate.d.ts +0 -238
  372. package/dist/core/promotion-gate.js +0 -529
  373. package/dist/core/risk-calculator.d.ts +0 -22
  374. package/dist/core/session-tracker.d.ts +0 -99
  375. package/dist/core/shadow-observation-registry.d.ts +0 -217
  376. package/dist/core/shadow-observation-registry.js +0 -308
  377. package/dist/core/system-logger.d.ts +0 -8
  378. package/dist/core/thinking-models.d.ts +0 -38
  379. package/dist/core/thinking-models.js +0 -170
  380. package/dist/core/training-program.d.ts +0 -233
  381. package/dist/core/training-program.js +0 -433
  382. package/dist/core/trajectory.d.ts +0 -411
  383. package/dist/core/trajectory.js +0 -1307
  384. package/dist/core/workspace-context.d.ts +0 -71
  385. package/dist/hooks/bash-risk.d.ts +0 -57
  386. package/dist/hooks/bash-risk.js +0 -137
  387. package/dist/hooks/edit-verification.d.ts +0 -62
  388. package/dist/hooks/edit-verification.js +0 -256
  389. package/dist/hooks/gate-block-helper.d.ts +0 -44
  390. package/dist/hooks/gate-block-helper.js +0 -119
  391. package/dist/hooks/gate.d.ts +0 -24
  392. package/dist/hooks/gate.js +0 -173
  393. package/dist/hooks/gfi-gate.d.ts +0 -40
  394. package/dist/hooks/gfi-gate.js +0 -113
  395. package/dist/hooks/lifecycle.d.ts +0 -5
  396. package/dist/hooks/lifecycle.js +0 -284
  397. package/dist/hooks/llm.d.ts +0 -12
  398. package/dist/hooks/message-sanitize.d.ts +0 -3
  399. package/dist/hooks/message-sanitize.js +0 -37
  400. package/dist/hooks/pain.d.ts +0 -5
  401. package/dist/hooks/pain.js +0 -301
  402. package/dist/hooks/progressive-trust-gate.d.ts +0 -51
  403. package/dist/hooks/progressive-trust-gate.js +0 -89
  404. package/dist/hooks/prompt.d.ts +0 -47
  405. package/dist/hooks/prompt.js +0 -884
  406. package/dist/hooks/subagent.d.ts +0 -10
  407. package/dist/hooks/subagent.js +0 -387
  408. package/dist/hooks/thinking-checkpoint.d.ts +0 -37
  409. package/dist/hooks/thinking-checkpoint.js +0 -51
  410. package/dist/hooks/trajectory-collector.d.ts +0 -32
  411. package/dist/hooks/trajectory-collector.js +0 -256
  412. package/dist/http/principles-console-route.d.ts +0 -9
  413. package/dist/http/principles-console-route.js +0 -567
  414. package/dist/i18n/commands.d.ts +0 -26
  415. package/dist/i18n/commands.js +0 -116
  416. package/dist/index.d.ts +0 -7
  417. package/dist/index.js +0 -581
  418. package/dist/service/central-database.d.ts +0 -104
  419. package/dist/service/central-database.js +0 -649
  420. package/dist/service/control-ui-query-service.d.ts +0 -221
  421. package/dist/service/control-ui-query-service.js +0 -543
  422. package/dist/service/empathy-observer-manager.d.ts +0 -52
  423. package/dist/service/empathy-observer-manager.js +0 -229
  424. package/dist/service/evolution-query-service.d.ts +0 -155
  425. package/dist/service/evolution-query-service.js +0 -258
  426. package/dist/service/evolution-worker.d.ts +0 -101
  427. package/dist/service/evolution-worker.js +0 -974
  428. package/dist/service/nocturnal-runtime.d.ts +0 -183
  429. package/dist/service/nocturnal-service.d.ts +0 -163
  430. package/dist/service/nocturnal-service.js +0 -787
  431. package/dist/service/nocturnal-target-selector.d.ts +0 -145
  432. package/dist/service/nocturnal-target-selector.js +0 -315
  433. package/dist/service/phase3-input-filter.d.ts +0 -73
  434. package/dist/service/phase3-input-filter.js +0 -172
  435. package/dist/service/runtime-summary-service.d.ts +0 -122
  436. package/dist/service/runtime-summary-service.js +0 -485
  437. package/dist/service/trajectory-service.d.ts +0 -2
  438. package/dist/service/trajectory-service.js +0 -15
  439. package/dist/tools/critique-prompt.d.ts +0 -14
  440. package/dist/tools/deep-reflect.d.ts +0 -39
  441. package/dist/tools/deep-reflect.js +0 -350
  442. package/dist/tools/model-index.d.ts +0 -9
  443. package/dist/types/event-types.d.ts +0 -306
  444. package/dist/types/event-types.js +0 -106
  445. package/dist/types/hygiene-types.d.ts +0 -20
  446. package/dist/types/hygiene-types.js +0 -12
  447. package/dist/types/runtime-summary.d.ts +0 -47
  448. package/dist/types/runtime-summary.js +0 -1
  449. package/dist/types.d.ts +0 -50
  450. package/dist/types.js +0 -22
  451. package/dist/utils/file-lock.d.ts +0 -71
  452. package/dist/utils/file-lock.js +0 -309
  453. package/dist/utils/glob-match.d.ts +0 -28
  454. package/dist/utils/hashing.d.ts +0 -9
  455. package/dist/utils/io.d.ts +0 -6
  456. package/dist/utils/io.js +0 -106
  457. package/dist/utils/nlp.d.ts +0 -9
  458. package/dist/utils/plugin-logger.d.ts +0 -39
  459. package/dist/utils/subagent-probe.d.ts +0 -34
  460. package/dist/utils/subagent-probe.js +0 -81
@@ -0,0 +1,1075 @@
1
+ /**
2
+ * Nocturnal Compliance Engine — Opportunity-Based Principle Evaluation
3
+ * =====================================================================
4
+ *
5
+ * Replaces session-average compliance with opportunity-based compliance.
6
+ *
7
+ * CORE CONCEPTS:
8
+ *
9
+ * Opportunity — a session context where a principle COULD have been applied.
10
+ * An opportunity exists when the agent's action (or planned action)
11
+ * falls within the principle's applicability scope.
12
+ *
13
+ * Compliance — the principle was followed in an opportunity.
14
+ * Determined by absence of violation signals, not presence of
15
+ * positive confirmation (avoids LLM scoring).
16
+ *
17
+ * Violation — strong evidence the principle was NOT followed.
18
+ * Detected through deterministic event signals (pain, tool failures,
19
+ * gate blocks) — no LLM involved.
20
+ *
21
+ * Dilution prevention — compliance is computed ONLY over sessions where the
22
+ * principle had an opportunity. Unrelated sessions
23
+ * (where T-05's risky operations never occurred) do NOT
24
+ * dilute the compliance rate.
25
+ *
26
+ * DESIGN CONSTRAINTS (Phase 1):
27
+ * - T-xx principles only (deterministic / weak-heuristic evaluability)
28
+ * - No P_xxx automation (requires detector metadata — Task 1.3 scope)
29
+ * - No LLM-based scoring
30
+ * - No training logic
31
+ *
32
+ * FILE: No file persistence — stateless computation over event stream.
33
+ * Caller is responsible for writing results to principle-training-state.ts.
34
+ */
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Types
38
+ // ---------------------------------------------------------------------------
39
+
40
+ /**
41
+ * Session events extracted from the event log.
42
+ * Compatible with EventLogEntry from event-types.ts.
43
+ */
44
+ export interface SessionEvents {
45
+ sessionId: string;
46
+ toolCalls: ToolCallRecord[];
47
+ painSignals: PainSignalRecord[];
48
+ gateBlocks: GateBlockRecord[];
49
+ userCorrections: UserCorrectionRecord[];
50
+ planApprovals: PlanApprovalRecord[];
51
+ }
52
+
53
+ export interface ToolCallRecord {
54
+ toolName: string;
55
+ filePath?: string;
56
+ outcome: 'success' | 'failure' | 'blocked';
57
+ errorType?: string;
58
+ errorMessage?: string;
59
+ }
60
+
61
+ export interface PainSignalRecord {
62
+ source: string;
63
+ score: number;
64
+ severity?: 'mild' | 'moderate' | 'severe';
65
+ reason?: string;
66
+ }
67
+
68
+ export interface GateBlockRecord {
69
+ toolName: string;
70
+ filePath?: string;
71
+ reason: string;
72
+ }
73
+
74
+ export interface UserCorrectionRecord {
75
+ correctionCue?: string;
76
+ }
77
+
78
+ export interface PlanApprovalRecord {
79
+ toolName: string;
80
+ filePath?: string;
81
+ }
82
+
83
+ /**
84
+ * The result of compliance computation for one principle.
85
+ */
86
+ export interface ComplianceResult {
87
+ principleId: string;
88
+ /** Number of sessions/events where this principle had an applicable opportunity */
89
+ applicableOpportunityCount: number;
90
+ /** Number of opportunities where violation signals were detected */
91
+ observedViolationCount: number;
92
+ /** complianceRate = (opportunities - violations) / opportunities; 0 if none */
93
+ complianceRate: number;
94
+ /**
95
+ * Violation trend:
96
+ * +1 = violations increasing (worsening)
97
+ * 0 = stable
98
+ * -1 = violations decreasing (improving)
99
+ */
100
+ violationTrend: number;
101
+ /**
102
+ * Explanation of why the result is what it is.
103
+ * For debugging, observability, and reviewer verification.
104
+ */
105
+ explanation: string;
106
+ }
107
+
108
+ /**
109
+ * Opportunity detection result for a single session.
110
+ */
111
+ interface OpportunityMatch {
112
+ applicable: boolean;
113
+ reason: string;
114
+ }
115
+
116
+ /**
117
+ * Violation detection result for a session with applicable opportunity.
118
+ */
119
+ interface ViolationMatch {
120
+ violated: boolean;
121
+ reason: string;
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Risky Operation Registry
126
+ // ---------------------------------------------------------------------------
127
+
128
+ /**
129
+ * Tools and operations that constitute risky actions.
130
+ * Gate blocks on these map to T-05 (Safety Rails) violations.
131
+ */
132
+ const RISKY_TOOLS: Set<string> = new Set([
133
+ 'delete_file',
134
+ 'move_file',
135
+ 'rename_file',
136
+ 'delete_directory',
137
+ 'bash',
138
+ 'MultiExec',
139
+ ]);
140
+
141
+ /**
142
+ * Bash command patterns that constitute dangerous operations.
143
+ * Matched against bash command text in tool_call events.
144
+ */
145
+ const DANGEROUS_BASH_PATTERNS: RegExp[] = [
146
+ /rm\s+(-[a-z]*r[a-z]*f?|-rf)/i, // rm -rf / rm -r
147
+ /del\s+\/[s/q]/i, // Windows del /s
148
+ /rmdir\s+\/s/i, // rmdir /s
149
+ /git\s+push\s+.*--force/i, // git push --force
150
+ /git\s+reset\s+--hard/i, // git reset --hard
151
+ /git\s+clean\s+-f[dx]/i, // git clean -fd
152
+ /npm\s+publish/i, // npm publish
153
+ /pip\s+upload/i, // pip upload
154
+ /docker\s+push/i, // docker push
155
+ /curl.+\|\s*(ba)?sh/i, // curl | bash
156
+ /wget.+\|\s*(ba)?sh/i, // wget | bash
157
+ /^make\s+[^-|]+$/i, // bare make (destructive)
158
+ ];
159
+
160
+ /**
161
+ * Keywords in gate block reason that indicate a dangerous/risky operation.
162
+ * Used as a fallback when the tool itself is risky but the reason is free text.
163
+ */
164
+ const RISKY_KEYWORDS_IN_REASON: RegExp[] = [
165
+ /delete|remove|destroy|drop/i,
166
+ /force|unsafe|dangerous/i,
167
+ /format|truncate|overwrite/i,
168
+ /exec|eval|shell|command/i,
169
+ /credential|secret|password|token/i,
170
+ ];
171
+
172
+ /**
173
+ * Edit/write tool names.
174
+ */
175
+ const EDIT_TOOLS: Set<string> = new Set([
176
+ 'edit_file',
177
+ 'edit_file_batch',
178
+ 'write_to_file',
179
+ 'create_file',
180
+ 'apply_patch',
181
+ ]);
182
+
183
+ /**
184
+ * Read tool names.
185
+ */
186
+ const READ_TOOLS: Set<string> = new Set([
187
+ 'read_file',
188
+ 'read_multiple_files',
189
+ 'grep',
190
+ 'search_files',
191
+ 'list_directory',
192
+ 'glob',
193
+ ]);
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // Path Normalization (cross-platform)
197
+ // ---------------------------------------------------------------------------
198
+
199
+ /**
200
+ * Normalizes a file path to POSIX forward-slash format for consistent matching.
201
+ * Handles Windows backslash paths on any platform.
202
+ */
203
+ function normalizePath(filePath: string): string {
204
+ return filePath.replace(/\\/g, '/');
205
+ }
206
+
207
+ /**
208
+ * Returns true if the file path matches any of the given patterns when normalized.
209
+ */
210
+ function pathMatches(filePath: string | undefined, patterns: RegExp[]): boolean {
211
+ if (!filePath) return false;
212
+ const normalized = normalizePath(filePath);
213
+ return patterns.some((p) => p.test(normalized));
214
+ }
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // Opportunity Detection
218
+ // ---------------------------------------------------------------------------
219
+
220
+ /**
221
+ * Detects whether a given session presents an APPLICABLE OPPORTUNITY
222
+ * for a specific T-xx principle.
223
+ *
224
+ * An opportunity exists when the session context falls within the
225
+ * principle's applicability scope — regardless of whether the agent
226
+ * followed the principle.
227
+ *
228
+ * IMPORTANT: This does NOT assess compliance. It only answers:
229
+ * "Could the principle have applied here?"
230
+ */
231
+ export function detectOpportunity(principleId: string, session: SessionEvents): OpportunityMatch {
232
+ switch (principleId) {
233
+ case 'T-01':
234
+ return detectT01Opportunity(session);
235
+ case 'T-02':
236
+ return detectT02Opportunity(session);
237
+ case 'T-03':
238
+ return detectT03Opportunity(session);
239
+ case 'T-04':
240
+ return detectT04Opportunity(session);
241
+ case 'T-05':
242
+ return detectT05Opportunity(session);
243
+ case 'T-06':
244
+ return detectT06Opportunity(session);
245
+ case 'T-07':
246
+ return detectT07Opportunity(session);
247
+ case 'T-08':
248
+ return detectT08Opportunity(session);
249
+ case 'T-09':
250
+ return detectT09Opportunity(session);
251
+ default:
252
+ return { applicable: false, reason: `Unknown principle: ${principleId}` };
253
+ }
254
+ }
255
+
256
+ /**
257
+ * T-01 "Survey Before Acting" — Understand the structure first before making changes.
258
+ *
259
+ * APPLICABLE when: Agent performs edit/write operations.
260
+ * Rationale: Any edit to code is an opportunity to survey first.
261
+ * Excluded: Read-only sessions (no applicable opportunity).
262
+ */
263
+ function detectT01Opportunity(session: SessionEvents): OpportunityMatch {
264
+ const hasEdit = session.toolCalls.some((call) => EDIT_TOOLS.has(call.toolName));
265
+ if (hasEdit) {
266
+ return { applicable: true, reason: 'Edit operations present — opportunity to survey before acting' };
267
+ }
268
+ return { applicable: false, reason: 'No edit operations in session — T-01 not applicable' };
269
+ }
270
+
271
+ /**
272
+ * T-02 "Respect Constraints" — Explicitly reason about contracts, tests, schemas.
273
+ *
274
+ * APPLICABLE when: Agent interacts with type/test/schema/contract files.
275
+ */
276
+ function detectT02Opportunity(session: SessionEvents): OpportunityMatch {
277
+ const hasConstraintInteraction = session.toolCalls.some((call) => {
278
+ if (!call.filePath) return false;
279
+ const normalized = normalizePath(call.filePath);
280
+ return (
281
+ /\.(ts|tsx|js|jsx)$/.test(normalized) || // type-aware files
282
+ /\b(test|spec|contract|schema|interface|type)\b/i.test(normalized)
283
+ );
284
+ });
285
+ if (hasConstraintInteraction) {
286
+ return { applicable: true, reason: 'Type/test/contract interaction — opportunity to respect constraints' };
287
+ }
288
+ return { applicable: false, reason: 'No type/test/contract interaction — T-02 not applicable' };
289
+ }
290
+
291
+ /**
292
+ * T-03 "Evidence Over Assumption" — Use logs, code, and outputs before inferring.
293
+ *
294
+ * APPLICABLE when: Pain signals or tool failures follow an edit/write operation.
295
+ * Rationale: When a change causes something to go wrong, there's an opportunity
296
+ * to gather evidence instead of assuming. Read-only failures are less relevant.
297
+ * Narrowed: requires an edit/write in the session before the failure/pain signal.
298
+ */
299
+ function detectT03Opportunity(session: SessionEvents): OpportunityMatch {
300
+ const hasWriteBeforeFailure = session.toolCalls.some(
301
+ (call, i) => {
302
+ if (call.outcome !== 'failure') return false;
303
+ // Check that at least one prior call was an edit/write
304
+ const priorCalls = session.toolCalls.slice(0, i);
305
+ return priorCalls.some((c) => EDIT_TOOLS.has(c.toolName));
306
+ }
307
+ );
308
+
309
+ if (hasWriteBeforeFailure) {
310
+ return { applicable: true, reason: 'Write operation followed by failure — opportunity to gather evidence before retry' };
311
+ }
312
+
313
+ // Also applicable: pain signal with severity moderate+ (indicating something went wrong after a change)
314
+ const hasSignificantPain = session.painSignals.some(
315
+ (p) => p.severity === 'moderate' || p.severity === 'severe'
316
+ );
317
+ if (hasSignificantPain) {
318
+ return { applicable: true, reason: 'Significant pain signal — opportunity to use evidence over assumption' };
319
+ }
320
+
321
+ return { applicable: false, reason: 'No pain or failure on write operations — T-03 not applicable' };
322
+ }
323
+
324
+ /**
325
+ * T-04 "Reversible First" — Prefer changes that are safe to roll back.
326
+ *
327
+ * APPLICABLE when: Risky or destructive operations are attempted.
328
+ */
329
+ function detectT04Opportunity(session: SessionEvents): OpportunityMatch {
330
+ const hasRisky = session.toolCalls.some(
331
+ (call) => RISKY_TOOLS.has(call.toolName) || call.toolName === 'bash'
332
+ );
333
+ if (hasRisky) {
334
+ return { applicable: true, reason: 'Risky/destructive operations — opportunity to prefer reversible changes' };
335
+ }
336
+ return { applicable: false, reason: 'No risky operations — T-04 not applicable' };
337
+ }
338
+
339
+ /**
340
+ * T-05 "Safety Rails" — Call out guardrails, prohibitions, failure-prevention constraints.
341
+ *
342
+ * APPLICABLE when: A gate block fires on a risky operation.
343
+ * Rationale: The gate block IS the safety rail being tested. An opportunity
344
+ * exists when the system judged an operation risky enough to block.
345
+ * This makes T-05 applicable ONLY when gate blocks fire — preventing dilution
346
+ * by unrelated sessions.
347
+ *
348
+ * IMPORTANT: T-05's compliance is tied to gate blocks specifically.
349
+ * A risky operation without a gate block may still be a T-05 opportunity
350
+ * if the reason mentions safety-relevant terms.
351
+ */
352
+ function detectT05Opportunity(session: SessionEvents): OpportunityMatch {
353
+ const hasGateBlock = session.gateBlocks.length > 0;
354
+ if (hasGateBlock) {
355
+ return {
356
+ applicable: true,
357
+ reason: 'Gate block present — opportunity to call out safety rails',
358
+ };
359
+ }
360
+
361
+ // Also applicable when a risky operation is attempted
362
+ // (even if not yet blocked — the agent should self-censor)
363
+ const hasRisky = session.toolCalls.some((call) => {
364
+ if (RISKY_TOOLS.has(call.toolName)) return true;
365
+ // Check bash for dangerous patterns
366
+ if (call.toolName === 'bash' && call.errorMessage) {
367
+ return DANGEROUS_BASH_PATTERNS.some((p) => p.test(call.errorMessage!));
368
+ }
369
+ return false;
370
+ });
371
+
372
+ if (hasRisky) {
373
+ return {
374
+ applicable: true,
375
+ reason: 'Risky operation attempted — opportunity to apply safety rails',
376
+ };
377
+ }
378
+
379
+ return {
380
+ applicable: false,
381
+ reason: 'No gate blocks or risky operations — T-05 not applicable in this session',
382
+ };
383
+ }
384
+
385
+ /**
386
+ * T-06 "Simplicity First" — Prefer the smallest understandable solution.
387
+ *
388
+ * APPLICABLE when: The task involves non-trivial code creation or refactoring.
389
+ */
390
+ function detectT06Opportunity(session: SessionEvents): OpportunityMatch {
391
+ const hasNonTrivialWrite = session.toolCalls.some(
392
+ (call) =>
393
+ call.toolName === 'create_file' ||
394
+ call.toolName === 'write_to_file' ||
395
+ (call.toolName === 'bash' && /\b(refactor|rewrite|overhaul)\b/i.test(call.errorMessage ?? ''))
396
+ );
397
+ if (hasNonTrivialWrite) {
398
+ return {
399
+ applicable: true,
400
+ reason: 'Non-trivial code creation — opportunity to prefer simplicity',
401
+ };
402
+ }
403
+ return { applicable: false, reason: 'No non-trivial writes — T-06 not applicable' };
404
+ }
405
+
406
+ /**
407
+ * T-07 "Minimal Change Surface" — Limit the blast radius.
408
+ *
409
+ * APPLICABLE when: Multiple files are touched in a single session.
410
+ */
411
+ function detectT07Opportunity(session: SessionEvents): OpportunityMatch {
412
+ const filePaths = session.toolCalls
413
+ .filter((call) => call.filePath !== undefined)
414
+ .map((call) => normalizePath(call.filePath!));
415
+ const uniqueFiles = new Set(filePaths);
416
+ if (uniqueFiles.size >= 3) {
417
+ return {
418
+ applicable: true,
419
+ reason: `Multiple files touched (${uniqueFiles.size}) — opportunity to minimize change surface`,
420
+ };
421
+ }
422
+ return { applicable: false, reason: 'Few files touched — T-07 not applicable' };
423
+ }
424
+
425
+ /**
426
+ * T-08 "Pain As Signal" — Treat failures and friction as clues.
427
+ *
428
+ * APPLICABLE when: Pain signals are present after a failure.
429
+ */
430
+ function detectT08Opportunity(session: SessionEvents): OpportunityMatch {
431
+ const hasPain = session.painSignals.length > 0;
432
+ const hasFailure = session.toolCalls.some((call) => call.outcome === 'failure');
433
+ if (hasPain && hasFailure) {
434
+ return {
435
+ applicable: true,
436
+ reason: 'Pain signals following failures — opportunity to treat pain as signal',
437
+ };
438
+ }
439
+ return { applicable: false, reason: 'No pain-after-failure — T-08 not applicable' };
440
+ }
441
+
442
+ /**
443
+ * T-09 "Divide And Conquer" — Split the task into smaller phases before execution.
444
+ *
445
+ * APPLICABLE when: Complex operations are attempted (multi-file edits, refactors,
446
+ * architecture changes) OR when pain events occur on complex tasks.
447
+ *
448
+ * COMPLEXITY INDICATORS:
449
+ * - 5+ tool calls in a session (indicates multi-step task)
450
+ * - Multiple file paths touched
451
+ * - Pain events on multi-step tasks
452
+ * - Explicit "complex" or "refactor" or "architecture" in operations
453
+ */
454
+ function detectT09Opportunity(session: SessionEvents): OpportunityMatch {
455
+ const toolCallCount = session.toolCalls.length;
456
+ const uniqueFiles = new Set(
457
+ session.toolCalls
458
+ .filter((call) => call.filePath !== undefined)
459
+ .map((call) => normalizePath(call.filePath!))
460
+ );
461
+ const hasComplexity = toolCallCount >= 5 || uniqueFiles.size >= 3;
462
+
463
+ const hasPain = session.painSignals.length > 0;
464
+ const hasFailure = session.toolCalls.some((call) => call.outcome === 'failure');
465
+
466
+ if (hasComplexity) {
467
+ return {
468
+ applicable: true,
469
+ reason: `Complex task detected (${toolCallCount} calls, ${uniqueFiles.size} files) — opportunity to decompose`,
470
+ };
471
+ }
472
+
473
+ if (hasPain || hasFailure) {
474
+ // Pain/failure may indicate the task was too complex without decomposition
475
+ return {
476
+ applicable: true,
477
+ reason: 'Pain or failure present — opportunity to decompose before retry',
478
+ };
479
+ }
480
+
481
+ return {
482
+ applicable: false,
483
+ reason: 'No complexity indicators — T-09 not applicable in this session',
484
+ };
485
+ }
486
+
487
+ // ---------------------------------------------------------------------------
488
+ // Violation Detection
489
+ // ---------------------------------------------------------------------------
490
+
491
+ /**
492
+ * Detects whether a principle was VIOLATED in a session where an
493
+ * opportunity was applicable.
494
+ *
495
+ * Returns a ViolationMatch with violated=true if violation signals are present.
496
+ */
497
+ export function detectViolation(principleId: string, session: SessionEvents): ViolationMatch {
498
+ switch (principleId) {
499
+ case 'T-01':
500
+ return detectT01Violation(session);
501
+ case 'T-02':
502
+ return detectT02Violation(session);
503
+ case 'T-03':
504
+ return detectT03Violation(session);
505
+ case 'T-04':
506
+ return detectT04Violation(session);
507
+ case 'T-05':
508
+ return detectT05Violation(session);
509
+ case 'T-06':
510
+ return detectT06Violation(session);
511
+ case 'T-07':
512
+ return detectT07Violation(session);
513
+ case 'T-08':
514
+ return detectT08Violation(session);
515
+ case 'T-09':
516
+ return detectT09Violation(session);
517
+ default:
518
+ return { violated: false, reason: `Unknown principle: ${principleId}` };
519
+ }
520
+ }
521
+
522
+ /**
523
+ * T-01 violation:
524
+ * - Pain signal or tool failure on an edit where the file was NOT read first
525
+ * - Pain signal with source indicating structural misunderstanding
526
+ */
527
+ function detectT01Violation(session: SessionEvents): ViolationMatch {
528
+ // Build set of files that were read (normalized for cross-platform consistency)
529
+ const readFiles = new Set(
530
+ session.toolCalls
531
+ .filter((call) => READ_TOOLS.has(call.toolName) && call.filePath !== undefined)
532
+ .map((call) => normalizePath(call.filePath!))
533
+ );
534
+
535
+ // Find edits to files that were NOT read first
536
+ const unreadEdits = session.toolCalls.filter(
537
+ (call) =>
538
+ EDIT_TOOLS.has(call.toolName) &&
539
+ call.filePath !== undefined &&
540
+ !readFiles.has(normalizePath(call.filePath!))
541
+ );
542
+
543
+ // If there were edits to unread files AND pain/failure followed → T-01 likely violated
544
+ if (unreadEdits.length > 0) {
545
+ const painOnUnreadEdit = session.painSignals.some(
546
+ (p) =>
547
+ unreadEdits.some((e) => e.filePath !== undefined && p.source.includes(e.filePath!)) ||
548
+ /structure|architecture|dependency|context|before.*edit|survey/i.test(p.reason ?? '')
549
+ );
550
+
551
+ if (painOnUnreadEdit) {
552
+ return {
553
+ violated: true,
554
+ reason: `Edits to unread files (${unreadEdits.length}) followed by pain — T-01 violated: agent acted without surveying first`,
555
+ };
556
+ }
557
+
558
+ // If edits to unread files AND tool failures → likely violated
559
+ const failuresOnUnread = unreadEdits.some((e) => e.outcome === 'failure');
560
+ if (failuresOnUnread) {
561
+ return {
562
+ violated: true,
563
+ reason: `Edits to unread files (${unreadEdits.length}) followed by failures — T-01 violated: agent acted without understanding`,
564
+ };
565
+ }
566
+ }
567
+
568
+ // Also check for pain signals specifically mentioning T-01-relevant themes
569
+ // without any prior read
570
+ const hasPainTheme =
571
+ /structure|architecture|context|before.*acting|didn't.*survey|didn't.*read.*first/i.test(
572
+ session.painSignals.map((p) => p.reason ?? '').join(' ')
573
+ );
574
+ if (hasPainTheme && unreadEdits.length > 0) {
575
+ return {
576
+ violated: true,
577
+ reason: 'Pain signals mentioning structure/context themes after edits to unread files — T-01 violated',
578
+ };
579
+ }
580
+
581
+ return {
582
+ violated: false,
583
+ reason: 'No violation signals detected for T-01',
584
+ };
585
+ }
586
+
587
+ /**
588
+ * T-02 violation:
589
+ * - Tool failures on type/test/contract interactions without prior verification
590
+ */
591
+ function detectT02Violation(session: SessionEvents): ViolationMatch {
592
+ const constraintFailures = session.toolCalls.filter(
593
+ (call) =>
594
+ call.outcome === 'failure' &&
595
+ call.filePath !== undefined &&
596
+ (/\b(test|spec|contract|schema|interface|type)\b/i.test(call.filePath) ||
597
+ /\b(type|test|contract)\b/i.test(call.errorMessage ?? ''))
598
+ );
599
+
600
+ if (constraintFailures.length > 0) {
601
+ return {
602
+ violated: true,
603
+ reason: `Tool failures on type/test/contract interactions (${constraintFailures.length}) — T-02 violated: constraints not verified`,
604
+ };
605
+ }
606
+
607
+ return { violated: false, reason: 'No violation signals for T-02' };
608
+ }
609
+
610
+ /**
611
+ * T-03 violation:
612
+ * - Tool failures without prior evidence gathering (no read calls before failure)
613
+ */
614
+ function detectT03Violation(session: SessionEvents): ViolationMatch {
615
+ const failureIndices = session.toolCalls
616
+ .map((call, i) => (call.outcome === 'failure' ? i : -1))
617
+ .filter((i) => i >= 0);
618
+
619
+ for (const failIdx of failureIndices) {
620
+ const priorCalls = session.toolCalls.slice(0, failIdx);
621
+ const hasPriorRead = priorCalls.some(
622
+ (call) => READ_TOOLS.has(call.toolName) && call.filePath !== undefined
623
+ );
624
+ if (!hasPriorRead) {
625
+ return {
626
+ violated: true,
627
+ reason: `Tool failure at index ${failIdx} without prior read operations — T-03 violated: assumption made without evidence`,
628
+ };
629
+ }
630
+ }
631
+
632
+ return { violated: false, reason: 'No violation signals for T-03' };
633
+ }
634
+
635
+ /**
636
+ * T-04 violation:
637
+ * - Pain signals following risky operations (the operation succeeded but caused issues)
638
+ */
639
+ function detectT04Violation(session: SessionEvents): ViolationMatch {
640
+ const riskyIndices = session.toolCalls
641
+ .map((call, i) => (RISKY_TOOLS.has(call.toolName) || call.toolName === 'bash' ? i : -1))
642
+ .filter((i) => i >= 0);
643
+
644
+ if (riskyIndices.length === 0) return { violated: false, reason: 'No risky operations — T-04 not violated' };
645
+
646
+ // If risky operations AND pain signals are present in the same session,
647
+ // that indicates the risky operation caused negative consequences.
648
+ const hasPain = session.painSignals.length > 0;
649
+ if (hasPain) {
650
+ return {
651
+ violated: true,
652
+ reason: 'Pain signals present alongside risky operations — T-04 violated: irreversible consequences',
653
+ };
654
+ }
655
+
656
+ return { violated: false, reason: 'No violation signals for T-04' };
657
+ }
658
+
659
+ /**
660
+ * T-05 violation:
661
+ * - Gate block fires → the agent tried a risky operation without first applying
662
+ * safety reasoning. The gate block IS the violation signal.
663
+ * - Gate block on a dangerous bash command is an explicit violation.
664
+ */
665
+ function detectT05Violation(session: SessionEvents): ViolationMatch {
666
+ if (session.gateBlocks.length > 0) {
667
+ // Check if any gate block was on a dangerous operation.
668
+ // A block is dangerous if:
669
+ // 1. The tool is in RISKY_TOOLS (delete_file, bash, MultiExec, etc.)
670
+ // 2. The tool is 'bash' AND the reason mentions a dangerous pattern
671
+ // 3. The reason contains risky keywords (delete, force, credential, exec, etc.)
672
+ const dangerousBlocks = session.gateBlocks.filter((block) => {
673
+ if (RISKY_TOOLS.has(block.toolName)) return true;
674
+ if (block.toolName === 'bash' && DANGEROUS_BASH_PATTERNS.some((p) => p.test(block.reason))) return true;
675
+ // Fallback: scan reason for risky keywords
676
+ if (RISKY_KEYWORDS_IN_REASON.some((p) => p.test(block.reason))) return true;
677
+ return false;
678
+ });
679
+
680
+ if (dangerousBlocks.length > 0) {
681
+ return {
682
+ violated: true,
683
+ reason: `Gate blocks on dangerous operations (${dangerousBlocks.length}) — T-05 violated: safety rail not called out`,
684
+ };
685
+ }
686
+
687
+ return {
688
+ violated: true,
689
+ reason: `Gate blocks present (${session.gateBlocks.length}) — T-05 violated: safety rail not respected`,
690
+ };
691
+ }
692
+
693
+ return { violated: false, reason: 'No gate blocks — T-05 not violated' };
694
+ }
695
+
696
+ /**
697
+ * T-06 violation:
698
+ * - Over-engineering signals: pain from overly complex solutions
699
+ */
700
+ function detectT06Violation(session: SessionEvents): ViolationMatch {
701
+ const hasOverEngineerPain = session.painSignals.some(
702
+ (p) =>
703
+ /over.*engineer|over.*complicat|too.*complex|unnecessarily.*complex/i.test(p.reason ?? '') &&
704
+ p.severity === 'severe'
705
+ );
706
+
707
+ if (hasOverEngineerPain) {
708
+ return {
709
+ violated: true,
710
+ reason: 'Severe pain from over-engineering — T-06 violated: simplicity not preferred',
711
+ };
712
+ }
713
+
714
+ return { violated: false, reason: 'No over-engineering signals — T-06 not violated' };
715
+ }
716
+
717
+ /**
718
+ * T-07 violation:
719
+ * - Pain from wide blast radius: many files modified, cascading failures
720
+ */
721
+ function detectT07Violation(session: SessionEvents): ViolationMatch {
722
+ const modifiedFiles = new Set(
723
+ session.toolCalls
724
+ .filter((call) => EDIT_TOOLS.has(call.toolName) && call.filePath !== undefined)
725
+ .map((call) => normalizePath(call.filePath!))
726
+ );
727
+
728
+ const failures = session.toolCalls.filter((call) => call.outcome === 'failure');
729
+
730
+ if (modifiedFiles.size >= 5 && failures.length >= 2) {
731
+ return {
732
+ violated: true,
733
+ reason: `Wide blast radius (${modifiedFiles.size} files, ${failures.length} failures) — T-07 violated: change surface not minimized`,
734
+ };
735
+ }
736
+
737
+ return { violated: false, reason: 'No blast radius violations — T-07 not violated' };
738
+ }
739
+
740
+ /**
741
+ * T-08 violation:
742
+ * - Pain signal present but no reflection/self-correction behavior
743
+ * (This is harder to detect without explicit reflection events.
744
+ * We use pain-without-correction as a proxy.)
745
+ */
746
+ function detectT08Violation(session: SessionEvents): ViolationMatch {
747
+ const hasPain = session.painSignals.length > 0;
748
+ const hasFailure = session.toolCalls.some((call) => call.outcome === 'failure');
749
+
750
+ // If pain and failure, but the agent immediately retries without pause/reflect
751
+ if (hasPain && hasFailure) {
752
+ // Find the first failure index and check if the agent continued without reflecting
753
+ const failureIdx = session.toolCalls.findIndex((c) => c.outcome === 'failure');
754
+ if (failureIdx >= 0) {
755
+ const postFailure = session.toolCalls.slice(failureIdx + 1, failureIdx + 4);
756
+ // If the agent immediately continues without a read/reflect call, T-08 may be violated
757
+ const continuesImmediately =
758
+ postFailure.length > 0 && !postFailure.some((c) => READ_TOOLS.has(c.toolName));
759
+ if (continuesImmediately) {
760
+ return {
761
+ violated: true,
762
+ reason: 'Failure followed immediately by continued operations without pause/reflect — T-08 violated: pain not treated as signal',
763
+ };
764
+ }
765
+ }
766
+ }
767
+
768
+ return { violated: false, reason: 'No T-08 violation signals detected' };
769
+ }
770
+
771
+ /**
772
+ * T-09 violation:
773
+ * - Pain or failures on complex tasks that should have been decomposed.
774
+ * Signal: pain/failure on multi-step task without prior planning calls.
775
+ */
776
+ function detectT09Violation(session: SessionEvents): ViolationMatch {
777
+ const toolCallCount = session.toolCalls.length;
778
+ const uniqueFiles = new Set(
779
+ session.toolCalls
780
+ .filter((call) => call.filePath !== undefined)
781
+ .map((call) => normalizePath(call.filePath!))
782
+ );
783
+
784
+ // Only applies if the session was complex
785
+ if (toolCallCount < 5 && uniqueFiles.size < 3) {
786
+ return { violated: false, reason: 'Session not complex enough for T-09 applicability' };
787
+ }
788
+
789
+ // Check: failures on complex task without prior planning
790
+ const hasFailures = session.toolCalls.some((call) => call.outcome === 'failure');
791
+ const hasPain = session.painSignals.length > 0;
792
+
793
+ if (hasFailures || hasPain) {
794
+ // Check if the agent showed decomposition/planning behavior
795
+ const hasPlanApproval = session.planApprovals.length > 0;
796
+ const hasReadFirst = session.toolCalls.some((call) => READ_TOOLS.has(call.toolName));
797
+
798
+ if (!hasPlanApproval && !hasReadFirst) {
799
+ return {
800
+ violated: true,
801
+ reason: `Complex task with failures/pain but no planning or decomposition signals — T-09 violated: task not divided`,
802
+ };
803
+ }
804
+ }
805
+
806
+ return { violated: false, reason: 'No T-09 violation signals' };
807
+ }
808
+
809
+ // ---------------------------------------------------------------------------
810
+ // Compliance Computation
811
+ // ---------------------------------------------------------------------------
812
+
813
+ /**
814
+ * Computes compliance metrics for a single T-xx principle across a batch of sessions.
815
+ *
816
+ * DILUTION PREVENTION:
817
+ * - Sessions where the principle had NO opportunity are EXCLUDED from
818
+ * applicableOpportunityCount and do not affect complianceRate.
819
+ * - Example: T-05 sessions with no risky operations do not dilute
820
+ * the compliance rate computed from T-05 sessions with gate blocks.
821
+ *
822
+ * TREND COMPUTATION:
823
+ * - Sessions are ordered chronologically (most recent first).
824
+ * - Current window: last 3 applicable sessions.
825
+ * - Previous window: sessions 4-6 (if available).
826
+ * - If either window has < 1 applicable session, trend = 0 (insufficient data).
827
+ * - Otherwise: trend = prevViolationRate - currentViolationRate
828
+ * (+1 = improving, 0 = stable, -1 = worsening).
829
+ */
830
+ export function computeCompliance(
831
+ principleId: string,
832
+ sessions: SessionEvents[],
833
+ options: { trendWindowSize?: number } = {}
834
+ ): ComplianceResult {
835
+ const windowSize = options.trendWindowSize ?? 3;
836
+
837
+ let applicableOpportunityCount = 0;
838
+ let observedViolationCount = 0;
839
+
840
+ const applicableSessions: { session: SessionEvents; violated: boolean; reason: string }[] = [];
841
+
842
+ for (const session of sessions) {
843
+ const opp = detectOpportunity(principleId, session);
844
+ if (!opp.applicable) {
845
+ // Principle had no opportunity in this session — skip entirely.
846
+ // This is the key dilution-prevention mechanism.
847
+ continue;
848
+ }
849
+
850
+ applicableOpportunityCount++;
851
+ const violation = detectViolation(principleId, session);
852
+ if (violation.violated) {
853
+ observedViolationCount++;
854
+ }
855
+
856
+ applicableSessions.push({
857
+ session,
858
+ violated: violation.violated,
859
+ reason: violation.reason,
860
+ });
861
+ }
862
+
863
+ // Compute complianceRate
864
+ const complianceRate =
865
+ applicableOpportunityCount > 0
866
+ ? (applicableOpportunityCount - observedViolationCount) / applicableOpportunityCount
867
+ : 0;
868
+
869
+ // Compute violationTrend using windows
870
+ const violationTrend = computeViolationTrend(applicableSessions, windowSize);
871
+
872
+ // Build explanation
873
+ const explanation = buildExplanation(
874
+ principleId,
875
+ applicableOpportunityCount,
876
+ observedViolationCount,
877
+ complianceRate,
878
+ violationTrend,
879
+ applicableSessions
880
+ );
881
+
882
+ return {
883
+ principleId,
884
+ applicableOpportunityCount,
885
+ observedViolationCount,
886
+ complianceRate,
887
+ violationTrend,
888
+ explanation,
889
+ };
890
+ }
891
+
892
+ /**
893
+ * Computes violation trend across the applicable session list.
894
+ *
895
+ * Trend is positive (+1) when violations are DECREASING (improving).
896
+ * Trend is negative (-1) when violations are INCREASING (worsening).
897
+ *
898
+ * Sessions are ordered most-recent-first.
899
+ * currentWindow = first windowSize sessions (most recent)
900
+ * previousWindow = next windowSize sessions
901
+ */
902
+ function computeViolationTrend(
903
+ applicableSessions: { violated: boolean }[],
904
+ windowSize: number
905
+ ): number {
906
+ if (applicableSessions.length < 2) {
907
+ // Not enough data for trend
908
+ return 0;
909
+ }
910
+
911
+ // Sessions are ordered most-recent-first in the input array.
912
+ // currentWindow = most recent N sessions
913
+ // previousWindow = N sessions before that (older)
914
+ const currentWindow = applicableSessions.slice(0, windowSize);
915
+ const previousWindow = applicableSessions.slice(windowSize, windowSize * 2);
916
+
917
+ if (currentWindow.length === 0) return 0;
918
+
919
+ const currentViolationRate =
920
+ currentWindow.filter((s) => s.violated).length / currentWindow.length;
921
+
922
+ if (previousWindow.length === 0) {
923
+ // No previous window — compare to overall rate
924
+ const overallRate = applicableSessions.filter((s) => s.violated).length / applicableSessions.length;
925
+ if (currentViolationRate < overallRate - 0.1) return 1; // improving
926
+ if (currentViolationRate > overallRate + 0.1) return -1; // worsening
927
+ return 0;
928
+ }
929
+
930
+ const previousViolationRate =
931
+ previousWindow.filter((s) => s.violated).length / previousWindow.length;
932
+
933
+ const delta = previousViolationRate - currentViolationRate;
934
+
935
+ if (delta > 0.1) return 1; // violations decreasing → improving
936
+ if (delta < -0.1) return -1; // violations increasing → worsening
937
+ return 0; // stable
938
+ }
939
+
940
+ /**
941
+ * Builds a human-readable explanation for the compliance result.
942
+ */
943
+ function buildExplanation(
944
+ principleId: string,
945
+ applicableOpportunityCount: number,
946
+ observedViolationCount: number,
947
+ complianceRate: number,
948
+ violationTrend: number,
949
+ applicableSessions: { violated: boolean; reason: string }[]
950
+ ): string {
951
+ const trendStr =
952
+ violationTrend === 1
953
+ ? '↑ improving'
954
+ : violationTrend === -1
955
+ ? '↓ worsening'
956
+ : '→ stable';
957
+
958
+ if (applicableOpportunityCount === 0) {
959
+ return `${principleId}: No applicable opportunities in provided sessions — compliance cannot be assessed.`;
960
+ }
961
+
962
+ const violationExamples = applicableSessions
963
+ .filter((s) => s.violated)
964
+ .slice(0, 2)
965
+ .map((s) => ` • ${s.reason}`)
966
+ .join('\n');
967
+
968
+ return [
969
+ `${principleId}: ${applicableOpportunityCount} applicable opportunities, ${observedViolationCount} violations.`,
970
+ `Compliance rate: ${(complianceRate * 100).toFixed(1)}%. Trend: ${trendStr}.`,
971
+ violationExamples ? `Sample violation signals:\n${violationExamples}` : 'No violations detected in recent sessions.',
972
+ ].join('\n');
973
+ }
974
+
975
+ // ---------------------------------------------------------------------------
976
+ // Batch Update Helpers
977
+ // ---------------------------------------------------------------------------
978
+
979
+ /**
980
+ * Computes compliance results for all T-01 through T-09 principles
981
+ * across the provided sessions.
982
+ *
983
+ * Sessions are assumed to be ordered most-recent-first.
984
+ */
985
+ export function computeAllCompliance(
986
+ sessions: SessionEvents[],
987
+ options: { trendWindowSize?: number } = {}
988
+ ): ComplianceResult[] {
989
+ const results: ComplianceResult[] = [];
990
+ for (const id of ['T-01', 'T-02', 'T-03', 'T-04', 'T-05', 'T-06', 'T-07', 'T-08', 'T-09']) {
991
+ results.push(computeCompliance(id, sessions, options));
992
+ }
993
+ return results;
994
+ }
995
+
996
+ /**
997
+ * Converts raw EventLogEntry[] from event-types.ts into SessionEvents.
998
+ *
999
+ * Groups events by sessionId and maps to the SessionEvents interface.
1000
+ * Events with no sessionId are grouped under sessionId = 'unknown'.
1001
+ */
1002
+ export function groupEventsIntoSessions(events: RawEventEntry[]): Map<string, SessionEvents> {
1003
+ const sessionMap = new Map<string, SessionEvents>();
1004
+
1005
+ for (const event of events) {
1006
+ const sessionId = event.sessionId ?? 'unknown';
1007
+
1008
+ if (!sessionMap.has(sessionId)) {
1009
+ sessionMap.set(sessionId, {
1010
+ sessionId,
1011
+ toolCalls: [],
1012
+ painSignals: [],
1013
+ gateBlocks: [],
1014
+ userCorrections: [],
1015
+ planApprovals: [],
1016
+ });
1017
+ }
1018
+
1019
+ const session = sessionMap.get(sessionId)!;
1020
+
1021
+ switch (event.type) {
1022
+ case 'tool_call':
1023
+ if (event.data.toolName) {
1024
+ session.toolCalls.push({
1025
+ toolName: event.data.toolName as string,
1026
+ filePath: event.data.filePath as string | undefined,
1027
+ outcome: (event.data.error ? 'failure' : 'success') as 'success' | 'failure' | 'blocked',
1028
+ errorType: event.data.errorType as string | undefined,
1029
+ errorMessage: event.data.error as string | undefined,
1030
+ });
1031
+ }
1032
+ break;
1033
+ case 'pain_signal':
1034
+ session.painSignals.push({
1035
+ source: (event.data.source as string) ?? 'unknown',
1036
+ score: (event.data.score as number) ?? 0,
1037
+ severity: event.data.severity as 'mild' | 'moderate' | 'severe' | undefined,
1038
+ reason: event.data.reason as string | undefined,
1039
+ });
1040
+ break;
1041
+ case 'gate_block':
1042
+ session.gateBlocks.push({
1043
+ toolName: (event.data.toolName as string) ?? 'unknown',
1044
+ filePath: event.data.filePath as string | undefined,
1045
+ reason: (event.data.reason as string) ?? '',
1046
+ });
1047
+ break;
1048
+ case 'empathy_rollback':
1049
+ // User corrections are flagged via empathy rollback
1050
+ session.userCorrections.push({
1051
+ correctionCue: event.data.reason as string | undefined,
1052
+ });
1053
+ break;
1054
+ case 'plan_approval':
1055
+ session.planApprovals.push({
1056
+ toolName: (event.data.toolName as string) ?? 'unknown',
1057
+ filePath: event.data.filePath as string | undefined,
1058
+ });
1059
+ break;
1060
+ }
1061
+ }
1062
+
1063
+ return sessionMap;
1064
+ }
1065
+
1066
+ /**
1067
+ * Raw event entry from the events.jsonl log.
1068
+ * Compatible with EventLogEntry from event-types.ts.
1069
+ */
1070
+ export interface RawEventEntry {
1071
+ ts: string;
1072
+ type: string;
1073
+ sessionId?: string;
1074
+ data: Record<string, unknown>;
1075
+ }