principles-disciple 1.126.0 → 1.128.0

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 (593) hide show
  1. package/dist/commands/archive-impl.d.ts +18 -0
  2. package/dist/commands/archive-impl.js +105 -0
  3. package/dist/commands/capabilities.d.ts +3 -0
  4. package/dist/commands/capabilities.js +76 -0
  5. package/dist/commands/context.d.ts +5 -0
  6. package/{src/commands/context.ts → dist/commands/context.js} +34 -83
  7. package/dist/commands/disable-impl.d.ts +22 -0
  8. package/dist/commands/disable-impl.js +118 -0
  9. package/dist/commands/evolution-status.d.ts +4 -0
  10. package/dist/commands/evolution-status.js +187 -0
  11. package/dist/commands/export.d.ts +2 -0
  12. package/dist/commands/export.js +53 -0
  13. package/dist/commands/focus.d.ts +14 -0
  14. package/dist/commands/focus.js +443 -0
  15. package/dist/commands/pain.d.ts +6 -0
  16. package/{src/commands/pain.ts → dist/commands/pain.js} +137 -188
  17. package/dist/commands/principle-rollback.d.ts +4 -0
  18. package/dist/commands/principle-rollback.js +26 -0
  19. package/dist/commands/promote-impl.d.ts +22 -0
  20. package/dist/commands/promote-impl.js +194 -0
  21. package/dist/commands/rollback-impl.d.ts +32 -0
  22. package/dist/commands/rollback-impl.js +180 -0
  23. package/dist/commands/rollback.d.ts +19 -0
  24. package/{src/commands/rollback.ts → dist/commands/rollback.js} +10 -53
  25. package/dist/commands/samples.d.ts +2 -0
  26. package/dist/commands/samples.js +58 -0
  27. package/dist/commands/strategy.d.ts +2 -0
  28. package/dist/commands/strategy.js +16 -0
  29. package/dist/commands/thinking-os.d.ts +2 -0
  30. package/{src/commands/thinking-os.ts → dist/commands/thinking-os.js} +35 -58
  31. package/dist/commands/workflow-debug.d.ts +6 -0
  32. package/{src/commands/workflow-debug.ts → dist/commands/workflow-debug.js} +13 -33
  33. package/dist/config/defaults/runtime.d.ts +58 -0
  34. package/{src/config/defaults/runtime.ts → dist/config/defaults/runtime.js} +4 -41
  35. package/dist/config/errors.d.ts +86 -0
  36. package/dist/config/errors.js +107 -0
  37. package/{src/config/index.ts → dist/config/index.d.ts} +1 -2
  38. package/dist/config/index.js +7 -0
  39. package/dist/constants/diagnostician.d.ts +12 -0
  40. package/{src/constants/diagnostician.ts → dist/constants/diagnostician.js} +0 -1
  41. package/dist/constants/tools.d.ts +17 -0
  42. package/dist/constants/tools.js +53 -0
  43. package/dist/core/__tests__/focus-history.test.d.ts +1 -0
  44. package/dist/core/__tests__/focus-history.test.js +160 -0
  45. package/dist/core/bootstrap-rules.d.ts +50 -0
  46. package/dist/core/bootstrap-rules.js +187 -0
  47. package/dist/core/code-implementation-storage.d.ts +71 -0
  48. package/dist/core/code-implementation-storage.js +139 -0
  49. package/dist/core/config-health.d.ts +27 -0
  50. package/dist/core/config-health.js +60 -0
  51. package/dist/core/config-service.d.ts +15 -0
  52. package/{src/core/config-service.ts → dist/core/config-service.js} +4 -7
  53. package/dist/core/config.d.ts +111 -0
  54. package/{src/core/config.ts → dist/core/config.js} +46 -146
  55. package/dist/core/control-ui-db.d.ts +106 -0
  56. package/dist/core/control-ui-db.js +309 -0
  57. package/dist/core/correction-cue-learner.d.ts +33 -0
  58. package/dist/core/correction-cue-learner.js +184 -0
  59. package/dist/core/correction-types.d.ts +3 -0
  60. package/dist/core/correction-types.js +1 -0
  61. package/dist/core/detection-funnel.d.ts +33 -0
  62. package/{src/core/detection-funnel.ts → dist/core/detection-funnel.js} +17 -37
  63. package/dist/core/detection-service.d.ts +15 -0
  64. package/{src/core/detection-service.ts → dist/core/detection-service.js} +4 -7
  65. package/dist/core/dictionary-service.d.ts +15 -0
  66. package/{src/core/dictionary-service.ts → dist/core/dictionary-service.js} +4 -7
  67. package/dist/core/dictionary.d.ts +38 -0
  68. package/{src/core/dictionary.ts → dist/core/dictionary.js} +33 -59
  69. package/dist/core/empathy-keyword-matcher.d.ts +5 -0
  70. package/dist/core/empathy-keyword-matcher.js +40 -0
  71. package/dist/core/empathy-types.d.ts +2 -0
  72. package/dist/core/empathy-types.js +1 -0
  73. package/dist/core/event-log.d.ts +90 -0
  74. package/dist/core/event-log.js +702 -0
  75. package/dist/core/evolution-engine.d.ts +119 -0
  76. package/dist/core/evolution-engine.js +460 -0
  77. package/dist/core/evolution-logger.d.ts +137 -0
  78. package/dist/core/evolution-logger.js +261 -0
  79. package/dist/core/evolution-migration.d.ts +5 -0
  80. package/dist/core/evolution-migration.js +65 -0
  81. package/dist/core/evolution-reducer.d.ts +124 -0
  82. package/dist/core/evolution-reducer.js +730 -0
  83. package/dist/core/evolution-types.d.ts +50 -0
  84. package/dist/core/evolution-types.js +11 -0
  85. package/dist/core/external-training-contract.d.ts +276 -0
  86. package/dist/core/external-training-contract.js +269 -0
  87. package/dist/core/file-store.d.ts +41 -0
  88. package/dist/core/file-store.js +46 -0
  89. package/dist/core/focus-history.d.ts +50 -0
  90. package/dist/core/focus-history.js +574 -0
  91. package/dist/core/hygiene/tracker.d.ts +22 -0
  92. package/dist/core/hygiene/tracker.js +110 -0
  93. package/dist/core/init.d.ts +30 -0
  94. package/{src/core/init.ts → dist/core/init.js} +87 -118
  95. package/dist/core/local-worker-routing.d.ts +124 -0
  96. package/dist/core/local-worker-routing.js +216 -0
  97. package/dist/core/migration.d.ts +6 -0
  98. package/{src/core/migration.ts → dist/core/migration.js} +10 -18
  99. package/dist/core/model-deployment-registry.d.ts +218 -0
  100. package/dist/core/model-deployment-registry.js +502 -0
  101. package/dist/core/model-training-registry.d.ts +295 -0
  102. package/dist/core/model-training-registry.js +474 -0
  103. package/dist/core/observability.d.ts +40 -0
  104. package/dist/core/observability.js +164 -0
  105. package/dist/core/pain-diagnostic-gate.d.ts +36 -0
  106. package/dist/core/pain-diagnostic-gate.js +105 -0
  107. package/dist/core/pain.d.ts +87 -0
  108. package/dist/core/pain.js +223 -0
  109. package/dist/core/path-resolver.d.ts +60 -0
  110. package/{src/core/path-resolver.ts → dist/core/path-resolver.js} +41 -131
  111. package/dist/core/paths.d.ts +60 -0
  112. package/{src/core/paths.ts → dist/core/paths.js} +4 -13
  113. package/dist/core/pd-config-loader.d.ts +96 -0
  114. package/dist/core/pd-config-loader.js +307 -0
  115. package/dist/core/pd-task-reconciler.d.ts +89 -0
  116. package/dist/core/pd-task-reconciler.js +262 -0
  117. package/dist/core/pd-task-service.d.ts +2 -0
  118. package/dist/core/pd-task-service.js +32 -0
  119. package/dist/core/pd-task-store.d.ts +5 -0
  120. package/dist/core/pd-task-store.js +62 -0
  121. package/dist/core/pd-task-types.d.ts +8 -0
  122. package/dist/core/pd-task-types.js +2 -0
  123. package/dist/core/principle-compiler/__tests__/compiler-replay-gate.test.d.ts +1 -0
  124. package/dist/core/principle-compiler/__tests__/compiler-replay-gate.test.js +132 -0
  125. package/dist/core/principle-compiler/code-validator.d.ts +14 -0
  126. package/dist/core/principle-compiler/code-validator.js +86 -0
  127. package/dist/core/principle-compiler/compiler.d.ts +45 -0
  128. package/dist/core/principle-compiler/compiler.js +287 -0
  129. package/{src/core/principle-compiler/index.ts → dist/core/principle-compiler/index.d.ts} +0 -1
  130. package/dist/core/principle-compiler/index.js +10 -0
  131. package/dist/core/principle-compiler/ledger-registrar.d.ts +28 -0
  132. package/dist/core/principle-compiler/ledger-registrar.js +95 -0
  133. package/{src/core/principle-compiler/template-generator.ts → dist/core/principle-compiler/template-generator.d.ts} +0 -1
  134. package/dist/core/principle-compiler/template-generator.js +7 -0
  135. package/{src/core/principle-injection.ts → dist/core/principle-injection.d.ts} +1 -7
  136. package/dist/core/principle-injection.js +10 -0
  137. package/dist/core/principle-internalization/filesystem-lifecycle-datasource.d.ts +15 -0
  138. package/dist/core/principle-internalization/filesystem-lifecycle-datasource.js +34 -0
  139. package/dist/core/principle-internalization/lifecycle-read-model.d.ts +17 -0
  140. package/{src/core/principle-internalization/lifecycle-read-model.ts → dist/core/principle-internalization/lifecycle-read-model.js} +2 -21
  141. package/dist/core/principle-internalization/lifecycle-refresh.d.ts +2 -0
  142. package/dist/core/principle-internalization/lifecycle-refresh.js +4 -0
  143. package/dist/core/principle-internalization/principle-lifecycle-service.d.ts +28 -0
  144. package/dist/core/principle-internalization/principle-lifecycle-service.js +89 -0
  145. package/dist/core/principle-training-state.d.ts +47 -0
  146. package/dist/core/principle-training-state.js +113 -0
  147. package/dist/core/principle-tree-ledger-adapter.d.ts +32 -0
  148. package/dist/core/principle-tree-ledger-adapter.js +125 -0
  149. package/dist/core/principle-tree-ledger.d.ts +82 -0
  150. package/dist/core/principle-tree-ledger.js +527 -0
  151. package/dist/core/profile.d.ts +62 -0
  152. package/dist/core/profile.js +211 -0
  153. package/dist/core/promotion-gate.d.ts +243 -0
  154. package/dist/core/promotion-gate.js +554 -0
  155. package/dist/core/reflection/reflection-context.d.ts +31 -0
  156. package/dist/core/reflection/reflection-context.js +91 -0
  157. package/dist/core/replay-engine.d.ts +12 -0
  158. package/dist/core/replay-engine.js +82 -0
  159. package/dist/core/risk-calculator.d.ts +22 -0
  160. package/{src/core/risk-calculator.ts → dist/core/risk-calculator.js} +19 -49
  161. package/{src/core/rule-host-helpers.ts → dist/core/rule-host-helpers.d.ts} +0 -1
  162. package/dist/core/rule-host-helpers.js +7 -0
  163. package/{src/core/rule-host-types.ts → dist/core/rule-host-types.d.ts} +1 -8
  164. package/dist/core/rule-host-types.js +7 -0
  165. package/dist/core/rule-host.d.ts +86 -0
  166. package/dist/core/rule-host.js +311 -0
  167. package/dist/core/rule-implementation-runtime.d.ts +18 -0
  168. package/dist/core/rule-implementation-runtime.js +112 -0
  169. package/dist/core/runtime-v2-prompt-activation-reader.d.ts +22 -0
  170. package/dist/core/runtime-v2-prompt-activation-reader.js +99 -0
  171. package/{src/core/schema/db-types.ts → dist/core/schema/db-types.d.ts} +4 -9
  172. package/dist/core/schema/db-types.js +5 -0
  173. package/{src/core/schema/index.ts → dist/core/schema/index.d.ts} +1 -10
  174. package/dist/core/schema/index.js +15 -0
  175. package/dist/core/schema/migration-runner.d.ts +53 -0
  176. package/dist/core/schema/migration-runner.js +170 -0
  177. package/dist/core/schema/migrations/001-init-trajectory.d.ts +6 -0
  178. package/{src/core/schema/migrations/001-init-trajectory.ts → dist/core/schema/migrations/001-init-trajectory.js} +38 -56
  179. package/dist/core/schema/migrations/002-init-central.d.ts +5 -0
  180. package/{src/core/schema/migrations/002-init-central.ts → dist/core/schema/migrations/002-init-central.js} +27 -41
  181. package/dist/core/schema/migrations/003-init-workflow.d.ts +5 -0
  182. package/{src/core/schema/migrations/003-init-workflow.ts → dist/core/schema/migrations/003-init-workflow.js} +16 -21
  183. package/dist/core/schema/migrations/004-add-thinking-and-gfi.d.ts +8 -0
  184. package/{src/core/schema/migrations/004-add-thinking-and-gfi.ts → dist/core/schema/migrations/004-add-thinking-and-gfi.js} +19 -27
  185. package/{src/core/schema/migrations/index.ts → dist/core/schema/migrations/index.d.ts} +1 -16
  186. package/dist/core/schema/migrations/index.js +27 -0
  187. package/dist/core/schema/schema-definitions.d.ts +46 -0
  188. package/{src/core/schema/schema-definitions.ts → dist/core/schema/schema-definitions.js} +242 -291
  189. package/dist/core/session-tracker.d.ts +119 -0
  190. package/{src/core/session-tracker.ts → dist/core/session-tracker.js} +96 -239
  191. package/dist/core/shadow-observation-registry.d.ts +217 -0
  192. package/dist/core/shadow-observation-registry.js +280 -0
  193. package/dist/core/surface-guard.d.ts +31 -0
  194. package/dist/core/surface-guard.js +131 -0
  195. package/dist/core/system-logger.d.ts +8 -0
  196. package/{src/core/system-logger.ts → dist/core/system-logger.js} +20 -34
  197. package/dist/core/thinking-models.d.ts +70 -0
  198. package/dist/core/thinking-models.js +300 -0
  199. package/dist/core/thinking-os-parser.d.ts +35 -0
  200. package/dist/core/thinking-os-parser.js +123 -0
  201. package/dist/core/trajectory-types.d.ts +220 -0
  202. package/dist/core/trajectory-types.js +8 -0
  203. package/dist/core/trajectory.d.ts +242 -0
  204. package/dist/core/trajectory.js +1500 -0
  205. package/dist/core/workflow-funnel-loader.d.ts +72 -0
  206. package/dist/core/workflow-funnel-loader.js +135 -0
  207. package/dist/core/workspace-context.d.ts +111 -0
  208. package/{src/core/workspace-context.ts → dist/core/workspace-context.js} +50 -105
  209. package/dist/core/workspace-dir-validation.d.ts +11 -0
  210. package/dist/core/workspace-dir-validation.js +37 -0
  211. package/dist/core/workspace-guidance-migrator.d.ts +12 -0
  212. package/{src/core/workspace-guidance-migrator.ts → dist/core/workspace-guidance-migrator.js} +21 -59
  213. package/dist/hooks/after-tool-call-helpers.d.ts +89 -0
  214. package/dist/hooks/after-tool-call-helpers.js +475 -0
  215. package/dist/hooks/after-tool-call-types.d.ts +86 -0
  216. package/dist/hooks/after-tool-call-types.js +13 -0
  217. package/dist/hooks/gate-block-helper.d.ts +44 -0
  218. package/dist/hooks/gate-block-helper.js +209 -0
  219. package/dist/hooks/gate.d.ts +16 -0
  220. package/dist/hooks/gate.js +381 -0
  221. package/dist/hooks/lifecycle.d.ts +5 -0
  222. package/dist/hooks/lifecycle.js +287 -0
  223. package/dist/hooks/llm.d.ts +13 -0
  224. package/{src/hooks/llm.ts → dist/hooks/llm.js} +63 -117
  225. package/dist/hooks/message-sanitize.d.ts +24 -0
  226. package/dist/hooks/message-sanitize.js +77 -0
  227. package/dist/hooks/pain.d.ts +33 -0
  228. package/dist/hooks/pain.js +294 -0
  229. package/dist/hooks/prompt.d.ts +52 -0
  230. package/dist/hooks/prompt.js +930 -0
  231. package/dist/hooks/raw-observation-adapter.d.ts +57 -0
  232. package/dist/hooks/raw-observation-adapter.js +170 -0
  233. package/dist/hooks/raw-observation-types.d.ts +62 -0
  234. package/dist/hooks/raw-observation-types.js +15 -0
  235. package/dist/hooks/subagent.d.ts +9 -0
  236. package/{src/hooks/subagent.ts → dist/hooks/subagent.js} +20 -60
  237. package/dist/hooks/trajectory-collector.d.ts +25 -0
  238. package/dist/hooks/trajectory-collector.js +225 -0
  239. package/dist/hooks/trajectory-evidence.d.ts +11 -0
  240. package/dist/hooks/trajectory-evidence.js +88 -0
  241. package/dist/hooks/triage-adapter.d.ts +58 -0
  242. package/{src/hooks/triage-adapter.ts → dist/hooks/triage-adapter.js} +36 -68
  243. package/dist/hooks/trigger-cooldown-tracker.d.ts +30 -0
  244. package/dist/hooks/trigger-cooldown-tracker.js +55 -0
  245. package/dist/i18n/commands.d.ts +26 -0
  246. package/dist/i18n/commands.js +96 -0
  247. package/dist/index.d.ts +50 -0
  248. package/dist/index.js +829 -0
  249. package/dist/openclaw-sdk.d.ts +293 -0
  250. package/dist/openclaw-sdk.js +4 -0
  251. package/dist/service/central-database.d.ts +138 -0
  252. package/dist/service/central-database.js +731 -0
  253. package/dist/service/central-sync-service.d.ts +8 -0
  254. package/dist/service/central-sync-service.js +71 -0
  255. package/dist/service/correction-observer-service.d.ts +19 -0
  256. package/{src/service/correction-observer-service.ts → dist/service/correction-observer-service.js} +36 -76
  257. package/dist/service/evolution-dedup.d.ts +27 -0
  258. package/{src/service/evolution-dedup.ts → dist/service/evolution-dedup.js} +11 -31
  259. package/{src/service/evolution-queue-migration.ts → dist/service/evolution-queue-migration.d.ts} +15 -62
  260. package/dist/service/evolution-queue-migration.js +53 -0
  261. package/dist/service/evolution-worker.d.ts +103 -0
  262. package/{src/service/evolution-worker.ts → dist/service/evolution-worker.js} +169 -331
  263. package/dist/service/internalization-auto-consumer-service.d.ts +8 -0
  264. package/dist/service/internalization-auto-consumer-service.js +322 -0
  265. package/dist/service/keyword-optimization-service.d.ts +44 -0
  266. package/dist/service/keyword-optimization-service.js +137 -0
  267. package/dist/service/phase3-input-filter.d.ts +73 -0
  268. package/dist/service/phase3-input-filter.js +172 -0
  269. package/dist/service/queue-io.d.ts +30 -0
  270. package/dist/service/queue-io.js +79 -0
  271. package/{src/service/queue-migration.ts → dist/service/queue-migration.d.ts} +6 -45
  272. package/dist/service/queue-migration.js +56 -0
  273. package/dist/service/runtime-summary-service.d.ts +158 -0
  274. package/dist/service/runtime-summary-service.js +636 -0
  275. package/dist/service/subagent-workflow/subagent-error-utils.d.ts +8 -0
  276. package/{src/service/subagent-workflow/subagent-error-utils.ts → dist/service/subagent-workflow/subagent-error-utils.js} +3 -6
  277. package/{src/service/subagent-workflow/types.ts → dist/service/subagent-workflow/types.d.ts} +17 -80
  278. package/dist/service/subagent-workflow/types.js +11 -0
  279. package/dist/service/subagent-workflow/workflow-store.d.ts +30 -0
  280. package/{src/service/subagent-workflow/workflow-store.ts → dist/service/subagent-workflow/workflow-store.js} +43 -108
  281. package/dist/service/trajectory-service.d.ts +2 -0
  282. package/dist/service/trajectory-service.js +16 -0
  283. package/dist/service/workflow-watchdog.d.ts +23 -0
  284. package/dist/service/workflow-watchdog.js +103 -0
  285. package/dist/types/event-types.d.ts +2 -0
  286. package/dist/types/event-types.js +1 -0
  287. package/dist/types/hygiene-types.d.ts +2 -0
  288. package/dist/types/hygiene-types.js +1 -0
  289. package/{src/types/principle-tree-schema.ts → dist/types/principle-tree-schema.d.ts} +2 -25
  290. package/dist/types/principle-tree-schema.js +24 -0
  291. package/dist/types/queue.d.ts +2 -0
  292. package/dist/types/queue.js +1 -0
  293. package/dist/types/runtime-summary.d.ts +1 -0
  294. package/dist/types/runtime-summary.js +1 -0
  295. package/dist/types.d.ts +33 -0
  296. package/dist/types.js +16 -0
  297. package/dist/utils/file-lock.d.ts +68 -0
  298. package/dist/utils/file-lock.js +310 -0
  299. package/dist/utils/hashing.d.ts +9 -0
  300. package/{src/utils/hashing.ts → dist/utils/hashing.js} +4 -6
  301. package/dist/utils/io.d.ts +11 -0
  302. package/dist/utils/io.js +174 -0
  303. package/dist/utils/node-vm-polyfill.d.ts +9 -0
  304. package/{src/utils/node-vm-polyfill.ts → dist/utils/node-vm-polyfill.js} +0 -2
  305. package/dist/utils/retry.d.ts +210 -0
  306. package/dist/utils/retry.js +375 -0
  307. package/dist/utils/session-key.d.ts +10 -0
  308. package/{src/utils/session-key.ts → dist/utils/session-key.js} +5 -4
  309. package/dist/utils/shadow-fingerprint.d.ts +16 -0
  310. package/dist/utils/shadow-fingerprint.js +34 -0
  311. package/dist/utils/workspace-resolver.d.ts +82 -0
  312. package/dist/utils/workspace-resolver.js +288 -0
  313. package/openclaw.plugin.json +1 -1
  314. package/package.json +8 -2
  315. package/templates/workspace/.principles/DECISION_POLICY.json +44 -0
  316. package/templates/workspace/.principles/PRINCIPLES.md +20 -0
  317. package/templates/workspace/.principles/PROFILE.json +54 -0
  318. package/templates/workspace/.principles/PROFILE.schema.json +56 -0
  319. package/templates/workspace/.principles/THINKING_OS.md +65 -0
  320. package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +7 -0
  321. package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +9 -0
  322. package/templates/workspace/.principles/models/_INDEX.md +27 -0
  323. package/templates/workspace/.principles/models/first_principles.md +62 -0
  324. package/templates/workspace/.principles/models/marketing_4p.md +52 -0
  325. package/templates/workspace/.principles/models/porter_five.md +63 -0
  326. package/templates/workspace/.principles/models/swot.md +60 -0
  327. package/templates/workspace/.principles/models/user_story_map.md +63 -0
  328. package/templates/workspace/.state/WORKBOARD.json +4 -0
  329. package/.dependency-cruiser.json +0 -19
  330. package/ADVANCED_CONFIG_ZH.md +0 -93
  331. package/AGENT_INSTALL.md +0 -173
  332. package/AGENT_INSTALL_EN.md +0 -173
  333. package/CHANGELOG.md +0 -26
  334. package/INSTALL.md +0 -254
  335. package/SKILL.md +0 -63
  336. package/esbuild.config.js +0 -104
  337. package/src/commands/archive-impl.ts +0 -147
  338. package/src/commands/capabilities.ts +0 -89
  339. package/src/commands/disable-impl.ts +0 -159
  340. package/src/commands/evolution-status.ts +0 -247
  341. package/src/commands/export.ts +0 -59
  342. package/src/commands/focus.ts +0 -523
  343. package/src/commands/principle-rollback.ts +0 -32
  344. package/src/commands/promote-impl.ts +0 -254
  345. package/src/commands/rollback-impl.ts +0 -246
  346. package/src/commands/samples.ts +0 -65
  347. package/src/commands/strategy.ts +0 -21
  348. package/src/config/errors.ts +0 -180
  349. package/src/constants/tools.ts +0 -62
  350. package/src/core/AGENTS.md +0 -34
  351. package/src/core/__tests__/focus-history.test.ts +0 -210
  352. package/src/core/bootstrap-rules.ts +0 -216
  353. package/src/core/code-implementation-storage.ts +0 -213
  354. package/src/core/config-health.ts +0 -72
  355. package/src/core/control-ui-db.ts +0 -454
  356. package/src/core/correction-cue-learner.ts +0 -239
  357. package/src/core/correction-types.ts +0 -16
  358. package/src/core/empathy-keyword-matcher.ts +0 -64
  359. package/src/core/empathy-types.ts +0 -18
  360. package/src/core/event-log.ts +0 -787
  361. package/src/core/evolution-engine.ts +0 -613
  362. package/src/core/evolution-logger.ts +0 -357
  363. package/src/core/evolution-migration.ts +0 -77
  364. package/src/core/evolution-reducer.ts +0 -891
  365. package/src/core/evolution-types.ts +0 -110
  366. package/src/core/external-training-contract.ts +0 -530
  367. package/src/core/file-store.ts +0 -80
  368. package/src/core/focus-history.ts +0 -763
  369. package/src/core/hygiene/tracker.ts +0 -122
  370. package/src/core/local-worker-routing.ts +0 -337
  371. package/src/core/model-deployment-registry.ts +0 -724
  372. package/src/core/model-training-registry.ts +0 -815
  373. package/src/core/observability.ts +0 -242
  374. package/src/core/pain-diagnostic-gate.ts +0 -169
  375. package/src/core/pain.ts +0 -309
  376. package/src/core/pd-config-loader.ts +0 -400
  377. package/src/core/pd-task-reconciler.ts +0 -380
  378. package/src/core/pd-task-service.ts +0 -43
  379. package/src/core/pd-task-store.ts +0 -78
  380. package/src/core/pd-task-types.ts +0 -24
  381. package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +0 -174
  382. package/src/core/principle-compiler/code-validator.ts +0 -93
  383. package/src/core/principle-compiler/compiler.ts +0 -327
  384. package/src/core/principle-compiler/ledger-registrar.ts +0 -116
  385. package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +0 -42
  386. package/src/core/principle-internalization/lifecycle-refresh.ts +0 -11
  387. package/src/core/principle-internalization/principle-lifecycle-service.ts +0 -170
  388. package/src/core/principle-training-state.ts +0 -177
  389. package/src/core/principle-tree-ledger-adapter.ts +0 -145
  390. package/src/core/principle-tree-ledger.ts +0 -725
  391. package/src/core/profile.ts +0 -228
  392. package/src/core/promotion-gate.ts +0 -854
  393. package/src/core/reflection/reflection-context.ts +0 -133
  394. package/src/core/replay-engine.ts +0 -106
  395. package/src/core/rule-host.ts +0 -386
  396. package/src/core/rule-implementation-runtime.ts +0 -145
  397. package/src/core/runtime-v2-prompt-activation-reader.ts +0 -114
  398. package/src/core/schema/migration-runner.ts +0 -207
  399. package/src/core/shadow-observation-registry.ts +0 -508
  400. package/src/core/surface-guard.ts +0 -188
  401. package/src/core/thinking-models.ts +0 -377
  402. package/src/core/thinking-os-parser.ts +0 -165
  403. package/src/core/trajectory-types.ts +0 -247
  404. package/src/core/trajectory.ts +0 -1831
  405. package/src/core/workflow-funnel-loader.ts +0 -195
  406. package/src/core/workspace-dir-validation.ts +0 -52
  407. package/src/hooks/AGENTS.md +0 -31
  408. package/src/hooks/after-tool-call-helpers.ts +0 -601
  409. package/src/hooks/after-tool-call-types.ts +0 -99
  410. package/src/hooks/gate-block-helper.ts +0 -265
  411. package/src/hooks/gate.ts +0 -394
  412. package/src/hooks/lifecycle.ts +0 -334
  413. package/src/hooks/message-sanitize.ts +0 -102
  414. package/src/hooks/pain.ts +0 -358
  415. package/src/hooks/prompt.ts +0 -1102
  416. package/src/hooks/raw-observation-adapter.ts +0 -215
  417. package/src/hooks/raw-observation-types.ts +0 -77
  418. package/src/hooks/trajectory-collector.ts +0 -253
  419. package/src/hooks/trajectory-evidence.ts +0 -97
  420. package/src/hooks/trigger-cooldown-tracker.ts +0 -82
  421. package/src/i18n/commands.ts +0 -101
  422. package/src/index.ts +0 -939
  423. package/src/openclaw-sdk.ts +0 -312
  424. package/src/service/central-database.ts +0 -936
  425. package/src/service/central-sync-service.ts +0 -85
  426. package/src/service/internalization-auto-consumer-service.ts +0 -397
  427. package/src/service/keyword-optimization-service.ts +0 -169
  428. package/src/service/phase3-input-filter.ts +0 -237
  429. package/src/service/queue-io.ts +0 -108
  430. package/src/service/runtime-summary-service.ts +0 -990
  431. package/src/service/trajectory-service.ts +0 -16
  432. package/src/service/workflow-watchdog.ts +0 -125
  433. package/src/types/event-types.ts +0 -39
  434. package/src/types/hygiene-types.ts +0 -8
  435. package/src/types/queue.ts +0 -15
  436. package/src/types/runtime-summary.ts +0 -5
  437. package/src/types.ts +0 -53
  438. package/src/utils/file-lock.ts +0 -394
  439. package/src/utils/io.ts +0 -176
  440. package/src/utils/retry.ts +0 -573
  441. package/src/utils/shadow-fingerprint.ts +0 -42
  442. package/src/utils/workspace-resolver.ts +0 -393
  443. package/tests/README.md +0 -120
  444. package/tests/build-artifacts.test.ts +0 -55
  445. package/tests/commands/evolution-status.test.ts +0 -511
  446. package/tests/commands/export.test.ts +0 -78
  447. package/tests/commands/pain.test.ts +0 -287
  448. package/tests/commands/samples.test.ts +0 -65
  449. package/tests/commands/strategy.test.ts +0 -19
  450. package/tests/commands/thinking-os.test.ts +0 -88
  451. package/tests/core/bootstrap-rules.test.ts +0 -596
  452. package/tests/core/code-implementation-storage.test.ts +0 -300
  453. package/tests/core/code-validator.test.ts +0 -197
  454. package/tests/core/config-service.test.ts +0 -89
  455. package/tests/core/config.test.ts +0 -90
  456. package/tests/core/control-ui-db.test.ts +0 -75
  457. package/tests/core/core-principle-registry-drift.test.ts +0 -39
  458. package/tests/core/core-template-guidance.test.ts +0 -21
  459. package/tests/core/correction-cue-learner.test.ts +0 -345
  460. package/tests/core/detection-service.test.ts +0 -50
  461. package/tests/core/dictionary-service.test.ts +0 -116
  462. package/tests/core/dictionary.test.ts +0 -171
  463. package/tests/core/empathy-keyword-matcher.test.ts +0 -209
  464. package/tests/core/event-log.test.ts +0 -484
  465. package/tests/core/evolution-engine.test.ts +0 -610
  466. package/tests/core/evolution-logger.test.ts +0 -148
  467. package/tests/core/evolution-migration.test.ts +0 -374
  468. package/tests/core/evolution-paths.test.ts +0 -21
  469. package/tests/core/evolution-reducer.compilation-retry.test.ts +0 -189
  470. package/tests/core/evolution-reducer.detector-metadata.test.ts +0 -602
  471. package/tests/core/evolution-types-loop.test.ts +0 -48
  472. package/tests/core/external-training-contract.test.ts +0 -463
  473. package/tests/core/file-store.test.ts +0 -102
  474. package/tests/core/focus-history.test.ts +0 -874
  475. package/tests/core/init-flatten.test.ts +0 -69
  476. package/tests/core/init-refactor.test.ts +0 -87
  477. package/tests/core/init-v1.3.test.ts +0 -46
  478. package/tests/core/init.test.ts +0 -190
  479. package/tests/core/ledger-registrar.test.ts +0 -235
  480. package/tests/core/migration.test.ts +0 -84
  481. package/tests/core/model-deployment-registry.test.ts +0 -858
  482. package/tests/core/model-training-registry.test.ts +0 -908
  483. package/tests/core/observability.test.ts +0 -382
  484. package/tests/core/pain-diagnostic-gate.test.ts +0 -614
  485. package/tests/core/pain-score.property.test.ts +0 -19
  486. package/tests/core/pain.test.ts +0 -132
  487. package/tests/core/path-resolver.test.ts +0 -57
  488. package/tests/core/paths-refactor.test.ts +0 -20
  489. package/tests/core/pd-config-loader.test.ts +0 -407
  490. package/tests/core/pd-task-store.test.ts +0 -126
  491. package/tests/core/phase7-rollout-integration.test.ts +0 -477
  492. package/tests/core/principle-compiler.test.ts +0 -350
  493. package/tests/core/principle-injection.test.ts +0 -223
  494. package/tests/core/principle-internalization/deprecated-readiness.test.ts +0 -193
  495. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +0 -350
  496. package/tests/core/principle-internalization/lifecycle-routing-policy.test.ts +0 -212
  497. package/tests/core/principle-internalization/lineage-source-retired.test.ts +0 -56
  498. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +0 -189
  499. package/tests/core/principle-training-state.test.ts +0 -939
  500. package/tests/core/principle-tree-ledger-adapter.test.ts +0 -253
  501. package/tests/core/principle-tree-ledger.test.ts +0 -423
  502. package/tests/core/profile.test.ts +0 -56
  503. package/tests/core/promotion-gate.test.ts +0 -561
  504. package/tests/core/queue-purge.test.ts +0 -337
  505. package/tests/core/reflection-context.test.ts +0 -342
  506. package/tests/core/regression-v1-9-1.test.ts +0 -266
  507. package/tests/core/replay-engine.test.ts +0 -165
  508. package/tests/core/risk-calculator.test.ts +0 -168
  509. package/tests/core/rule-host-adversarial-output.test.ts +0 -242
  510. package/tests/core/rule-host-autocorrect-vm.test.ts +0 -163
  511. package/tests/core/rule-host-helpers.test.ts +0 -120
  512. package/tests/core/rule-host-resource-bounds.test.ts +0 -231
  513. package/tests/core/rule-host-sqlite-source.test.ts +0 -720
  514. package/tests/core/rule-host-unhealthy-visibility.test.ts +0 -261
  515. package/tests/core/rule-host-validation.test.ts +0 -315
  516. package/tests/core/rule-implementation-runtime.test.ts +0 -49
  517. package/tests/core/session-tracker.test.ts +0 -191
  518. package/tests/core/surface-guard.test.ts +0 -338
  519. package/tests/core/template-generator.test.ts +0 -101
  520. package/tests/core/trajectory.test.ts +0 -427
  521. package/tests/core/workflow-funnel-loader.test.ts +0 -1028
  522. package/tests/core/workspace-context-factory.test.ts +0 -18
  523. package/tests/core/workspace-context.test.ts +0 -187
  524. package/tests/core/workspace-guidance-migrator.test.ts +0 -248
  525. package/tests/core-anti-growth.test.ts +0 -182
  526. package/tests/evolution-worker-quarantine.test.ts +0 -400
  527. package/tests/evolution-worker-slimming.test.ts +0 -380
  528. package/tests/fixtures/legacy-queue-v1.json +0 -74
  529. package/tests/fixtures/production-compatibility.test.ts +0 -147
  530. package/tests/fixtures/production-mock-generator.ts +0 -282
  531. package/tests/globalSetup.ts +0 -38
  532. package/tests/hook-workspace-nextaction-contract.test.ts +0 -50
  533. package/tests/hooks/confirm-first-removal.test.ts +0 -188
  534. package/tests/hooks/gate-auto-correct-shadow.test.ts +0 -309
  535. package/tests/hooks/gate-auto-correct.test.ts +0 -664
  536. package/tests/hooks/gate-block-helper-profile.test.ts +0 -186
  537. package/tests/hooks/gate-no-path-write-tool.test.ts +0 -171
  538. package/tests/hooks/gate-rule-host-pipeline.test.ts +0 -230
  539. package/tests/hooks/gate-rule-host-real-pipeline.test.ts +0 -190
  540. package/tests/hooks/lifecycle.test.ts +0 -248
  541. package/tests/hooks/message-sanitize.test.ts +0 -244
  542. package/tests/hooks/pain-emission-characterization.test.ts +0 -420
  543. package/tests/hooks/pain.test.ts +0 -795
  544. package/tests/hooks/prompt-characterization.test.ts +0 -476
  545. package/tests/hooks/prompt-diet.test.ts +0 -335
  546. package/tests/hooks/prompt-size-guard.test.ts +0 -319
  547. package/tests/hooks/raw-observation-adapter.test.ts +0 -312
  548. package/tests/hooks/runtime-v2-prompt-activation.test.ts +0 -895
  549. package/tests/hooks/single-gate-pain-admission.test.ts +0 -258
  550. package/tests/hooks/subagent.test.ts +0 -469
  551. package/tests/hooks/trajectory-collector.test.ts +0 -269
  552. package/tests/hooks/trajectory-evidence.test.ts +0 -280
  553. package/tests/hooks/triage-adapter.test.ts +0 -331
  554. package/tests/hygiene-tracker.test.ts +0 -77
  555. package/tests/index.test.ts +0 -142
  556. package/tests/integration/auto-entry-gate.test.ts +0 -256
  557. package/tests/integration/gate-real-io.e2e.test.ts +0 -248
  558. package/tests/integration/m8-legacy-paths.test.ts +0 -63
  559. package/tests/integration/mvp-surface-registry-guard.test.ts +0 -536
  560. package/tests/integration/pain-id-chain-e2e.test.ts +0 -313
  561. package/tests/integration/principle-compiler-e2e.test.ts +0 -403
  562. package/tests/integration/principle-lifecycle.e2e.test.ts +0 -198
  563. package/tests/integration/runtime-v2-pain-guard.test.ts +0 -125
  564. package/tests/integration/trajectory-lifecycle.e2e.test.ts +0 -523
  565. package/tests/internalization-auto-consumer-gate.test.ts +0 -158
  566. package/tests/internalization-auto-consumer-service.test.ts +0 -219
  567. package/tests/plugin-config-resolution-cutover.test.ts +0 -359
  568. package/tests/queue/async-lock.test.ts +0 -200
  569. package/tests/runtime-v2-discovery-guard.test.ts +0 -153
  570. package/tests/scripts/validate-live-path.test.ts +0 -286
  571. package/tests/service/central-database.test.ts +0 -457
  572. package/tests/service/correction-observer-service.test.ts +0 -505
  573. package/tests/service/evolution-worker.compilation-backfill.test.ts +0 -203
  574. package/tests/service/evolution-worker.correction-observer.test.ts +0 -50
  575. package/tests/service/evolution-worker.queue.test.ts +0 -296
  576. package/tests/service/evolution-worker.timeout.test.ts +0 -233
  577. package/tests/service/keyword-optimization-service.test.ts +0 -121
  578. package/tests/service/phase3-input-filter.test.ts +0 -289
  579. package/tests/service/queue-io.test.ts +0 -168
  580. package/tests/service/queue-migration.test.ts +0 -147
  581. package/tests/service/runtime-summary-service.test.ts +0 -1100
  582. package/tests/service/workflow-watchdog.test.ts +0 -281
  583. package/tests/task-compliance.test.ts +0 -166
  584. package/tests/test-utils.ts +0 -48
  585. package/tests/utils/file-lock.test.ts +0 -409
  586. package/tests/utils/hashing.test.ts +0 -32
  587. package/tests/utils/hook-workspace-resolver.test.ts +0 -524
  588. package/tests/utils/io.test.ts +0 -39
  589. package/tests/utils/retry.test.ts +0 -327
  590. package/tests/utils/session-key.test.ts +0 -52
  591. package/tsconfig.json +0 -16
  592. package/vitest.config.ts +0 -61
  593. package/vitest.unit.config.ts +0 -12
@@ -1,1831 +0,0 @@
1
-
2
-
3
- import Database from 'better-sqlite3';
4
- import fs from 'fs';
5
- import path from 'path';
6
- import crypto from 'crypto';
7
- import { withLock } from '../utils/file-lock.js';
8
- import { atomicWriteFileSync } from '../utils/io.js';
9
- import { resolvePdPath } from './paths.js';
10
- import { SampleNotFoundError } from '../config/index.js';
11
- import type {
12
- CorrectionSampleReviewStatus,
13
- CorrectionExportMode,
14
- TrajectoryDataStats,
15
- TrajectoryAssistantTurnInput,
16
- TrajectoryUserTurnInput,
17
- TrajectoryToolCallInput,
18
- TrajectoryPainEventInput,
19
- TrajectoryGateBlockInput,
20
- DailyMetricRow,
21
- TrajectoryTrustChangeInput,
22
- TrajectoryPrincipleEventInput,
23
- TrajectoryTaskOutcomeInput,
24
- TrajectorySessionInput,
25
- TaskKind,
26
- TaskPriority,
27
- EvolutionTaskInput,
28
- EvolutionEventInput,
29
- EvolutionTaskRecord,
30
- EvolutionEventRecord,
31
- EvolutionTaskFilters,
32
- AssistantTurnRecord,
33
- CorrectionSampleRecord,
34
- TrajectoryExportResult,
35
- TrajectoryDatabaseOptions,
36
- } from './trajectory-types.js';
37
-
38
- export type {
39
- CorrectionSampleReviewStatus,
40
- CorrectionExportMode,
41
- TrajectoryDataStats,
42
- TrajectoryAssistantTurnInput,
43
- TrajectoryUserTurnInput,
44
- TrajectoryToolCallInput,
45
- TrajectoryPainEventInput,
46
- TrajectoryGateBlockInput,
47
- DailyMetricRow,
48
- TrajectoryTrustChangeInput,
49
- TrajectoryPrincipleEventInput,
50
- TrajectoryTaskOutcomeInput,
51
- TrajectorySessionInput,
52
- TaskKind,
53
- TaskPriority,
54
- EvolutionTaskInput,
55
- EvolutionEventInput,
56
- EvolutionTaskRecord,
57
- EvolutionEventRecord,
58
- EvolutionTaskFilters,
59
- AssistantTurnRecord,
60
- CorrectionSampleRecord,
61
- TrajectoryExportResult,
62
- TrajectoryDatabaseOptions,
63
- } from './trajectory-types.js';
64
-
65
- /**
66
- * Trajectory database stores HISTORICAL and ANALYTICS data.
67
- *
68
- * PURPOSE: Track task outcomes, trust changes, and evolution progress over time.
69
- * USAGE: Insights, trends, and Phase 3 supporting evidence (where explicitly allowed).
70
- * NOT FOR: Control decisions, Phase 3 eligibility, or real-time operations.
71
- *
72
- * Runtime truth comes from: queue state, workspace trust scorecard, active sessions
73
- */
74
-
75
- const DEFAULT_INLINE_THRESHOLD = 16 * 1024;
76
- const DEFAULT_BUSY_TIMEOUT_MS = 5000;
77
- const DEFAULT_ORPHAN_BLOB_GRACE_DAYS = 7;
78
- const SCHEMA_VERSION = 1;
79
-
80
- function nowIso(): string {
81
- return new Date().toISOString();
82
- }
83
-
84
- function safeJson(value: unknown): string {
85
- return JSON.stringify(value ?? {});
86
- }
87
-
88
- function fileSizeIfExists(filePath: string): number {
89
- try {
90
- return fs.existsSync(filePath) ? fs.statSync(filePath).size : 0;
91
- } catch {
92
- return 0;
93
- }
94
- }
95
-
96
- function summarizeForDiff(text: string): string {
97
- return text.length > 240 ? `${text.slice(0, 237)}...` : text;
98
- }
99
-
100
- function redactText(text: string): string {
101
- return text
102
- .replace(/[A-Za-z]:\\[^\s"'`]+/g, '<WINDOWS_PATH>')
103
- .replace(/\/[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+/g, '<PATH>')
104
- .replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, '<EMAIL>')
105
- .replace(/\b(sk|rk|pk)_[A-Za-z0-9]+\b/g, '<TOKEN>');
106
- }
107
-
108
- export class TrajectoryDatabase {
109
- private readonly workspaceDir: string;
110
- private readonly stateDir: string;
111
- private readonly dbPath: string;
112
- private readonly blobDir: string;
113
- private readonly exportDir: string;
114
- private readonly blobInlineThresholdBytes: number;
115
- private readonly orphanBlobGraceMs: number;
116
- private readonly db: Database.Database;
117
-
118
- constructor(opts: TrajectoryDatabaseOptions) {
119
- this.workspaceDir = path.resolve(opts.workspaceDir);
120
- this.stateDir = resolvePdPath(this.workspaceDir, 'STATE_DIR');
121
- this.dbPath = resolvePdPath(this.workspaceDir, 'TRAJECTORY_DB');
122
- this.blobDir = resolvePdPath(this.workspaceDir, 'TRAJECTORY_BLOBS_DIR');
123
- this.exportDir = resolvePdPath(this.workspaceDir, 'EXPORTS_DIR');
124
- this.blobInlineThresholdBytes = opts.blobInlineThresholdBytes ?? DEFAULT_INLINE_THRESHOLD;
125
- this.orphanBlobGraceMs = Math.max(0, (opts.orphanBlobGraceDays ?? DEFAULT_ORPHAN_BLOB_GRACE_DAYS) * 24 * 60 * 60 * 1000);
126
-
127
- fs.mkdirSync(this.stateDir, { recursive: true });
128
- fs.mkdirSync(this.blobDir, { recursive: true });
129
- fs.mkdirSync(this.exportDir, { recursive: true });
130
-
131
- this.db = new Database(this.dbPath);
132
- this.db.pragma('journal_mode = WAL');
133
- this.db.pragma('foreign_keys = ON');
134
- this.db.pragma('synchronous = NORMAL');
135
- this.db.pragma(`busy_timeout = ${Math.max(0, opts.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS)}`);
136
- this.initSchema();
137
- this.importLegacyArtifacts();
138
- this.pruneUnreferencedBlobs();
139
- }
140
-
141
- dispose(): void {
142
- this.db.close();
143
- }
144
-
145
- recordSession(input: TrajectorySessionInput): void {
146
- const startedAt = input.startedAt ?? nowIso();
147
- this.withWrite(() => {
148
- this.db.prepare(`
149
- INSERT INTO sessions (session_id, started_at, updated_at)
150
- VALUES (?, ?, ?)
151
- ON CONFLICT(session_id) DO UPDATE SET updated_at = excluded.updated_at
152
- `).run(input.sessionId, startedAt, nowIso());
153
- });
154
- }
155
-
156
- recordAssistantTurn(input: TrajectoryAssistantTurnInput): number {
157
- this.recordSession({ sessionId: input.sessionId, startedAt: input.createdAt });
158
- const rawStorage = this.storeRawText('assistant', input.rawText);
159
- const createdAt = input.createdAt ?? nowIso();
160
-
161
- return this.withWrite(() => {
162
- const result = this.db.prepare(`
163
- INSERT INTO assistant_turns (
164
- session_id, run_id, provider, model, raw_text, sanitized_text, usage_json,
165
- empathy_signal_json, blob_ref, raw_excerpt, created_at
166
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
167
- `).run(
168
- input.sessionId,
169
- input.runId,
170
- input.provider,
171
- input.model,
172
- rawStorage.inlineText,
173
- input.sanitizedText,
174
- safeJson(input.usageJson),
175
- safeJson(input.empathySignalJson),
176
- rawStorage.blobRef,
177
- rawStorage.excerpt,
178
- createdAt,
179
- );
180
- return Number(result.lastInsertRowid);
181
- });
182
- }
183
-
184
- recordUserTurn(input: TrajectoryUserTurnInput): number {
185
- this.recordSession({ sessionId: input.sessionId, startedAt: input.createdAt });
186
- const rawStorage = this.storeRawText('user', input.rawText);
187
- const createdAt = input.createdAt ?? nowIso();
188
- return this.withWrite(() => {
189
- const result = this.db.prepare(`
190
- INSERT INTO user_turns (
191
- session_id, turn_index, raw_text, blob_ref, raw_excerpt,
192
- correction_detected, correction_cue, references_assistant_turn_id, created_at
193
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
194
- `).run(
195
- input.sessionId,
196
- input.turnIndex,
197
- rawStorage.inlineText,
198
- rawStorage.blobRef,
199
- rawStorage.excerpt,
200
- input.correctionDetected ? 1 : 0,
201
- input.correctionCue ?? null,
202
- input.referencesAssistantTurnId ?? null,
203
- createdAt,
204
- );
205
- return Number(result.lastInsertRowid);
206
- });
207
- }
208
-
209
- recordToolCall(input: TrajectoryToolCallInput): number {
210
- this.recordSession({ sessionId: input.sessionId, startedAt: input.createdAt });
211
- const createdAt = input.createdAt ?? nowIso();
212
- const rowId = this.withWrite(() => {
213
- const result = this.db.prepare(`
214
- INSERT INTO tool_calls (
215
- session_id, tool_name, outcome, duration_ms, exit_code, error_type, error_message,
216
- gfi_before, gfi_after, params_json, created_at
217
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
218
- `).run(
219
- input.sessionId,
220
- input.toolName,
221
- input.outcome,
222
- input.durationMs ?? null,
223
- input.exitCode ?? null,
224
- input.errorType ?? null,
225
- input.errorMessage ?? null,
226
- input.gfiBefore ?? null,
227
- input.gfiAfter ?? null,
228
- safeJson(input.paramsJson),
229
- createdAt,
230
- );
231
- return Number(result.lastInsertRowid);
232
- });
233
-
234
- if (input.outcome === 'success') {
235
- this.maybeCreateCorrectionSample(input.sessionId);
236
- }
237
- return rowId;
238
- }
239
-
240
- recordPainEvent(input: TrajectoryPainEventInput): number {
241
- this.recordSession({ sessionId: input.sessionId, startedAt: input.createdAt });
242
- let insertedId = -1;
243
- this.withWrite(() => {
244
- // Try INSERT; on UNIQUE constraint violation for canonical_pain_id, do UPDATE instead.
245
- // SQLite UPSERT (ON CONFLICT) does not support partial unique indexes, so we
246
- // handle the conflict manually.
247
- try {
248
- const runResult = this.db.prepare(`
249
- INSERT INTO pain_events (
250
- session_id, source, score, reason, severity, origin, confidence, text, created_at,
251
- canonical_pain_id, runtime_task_id
252
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
253
- `).run(
254
- input.sessionId,
255
- input.source,
256
- input.score,
257
- input.reason ?? null,
258
- input.severity ?? null,
259
- input.origin ?? null,
260
- input.confidence ?? null,
261
- input.text ?? null,
262
- input.createdAt ?? nowIso(),
263
- input.canonicalPainId ?? null,
264
- input.runtimeTaskId ?? null,
265
- );
266
- insertedId = Number(runResult.lastInsertRowid);
267
- } catch (insertErr: unknown) {
268
- if (
269
- input.canonicalPainId &&
270
- insertErr instanceof Error &&
271
- insertErr.message.includes('UNIQUE constraint failed') &&
272
- insertErr.message.includes('canonical_pain_id')
273
- ) {
274
- this.db.prepare(`
275
- UPDATE pain_events
276
- SET runtime_task_id = COALESCE(?, runtime_task_id)
277
- WHERE canonical_pain_id = ?
278
- `).run(input.runtimeTaskId ?? null, input.canonicalPainId);
279
- const rawRow = this.db.prepare('SELECT id FROM pain_events WHERE canonical_pain_id = ?').get(input.canonicalPainId);
280
- // Runtime Contract #1/#2: validate DB row instead of `as` cast
281
- if (rawRow && typeof rawRow === 'object' && Object.hasOwn(rawRow, 'id') && typeof (rawRow as Record<string, unknown>).id === 'number') {
282
- insertedId = (rawRow as { id: number }).id;
283
- } else {
284
- insertedId = -1;
285
- }
286
- } else {
287
- throw insertErr;
288
- }
289
- }
290
- });
291
- // FTS indexing is best-effort — run outside the transaction so it cannot
292
- // roll back the committed pain event row (MEM-03, MEM-04).
293
- if (input.text && insertedId > 0) {
294
- try {
295
- this.db.prepare(`
296
- INSERT INTO pain_events_fts (text, pain_event_id) VALUES (?, ?)
297
- `).run(input.text, insertedId);
298
- } catch (err) {
299
- // Non-fatal: FTS index is for search convenience, not correctness.
300
- // Log but do not re-throw — the pain event itself is already committed.
301
- console.warn(`[trajectory] FTS index insert failed for pain_event ${insertedId}: ${String(err)}`);
302
- }
303
- }
304
- return insertedId;
305
- }
306
-
307
- /**
308
- * Search pain_events using FTS5 full-text search (MEM-04).
309
- * Returns pain events matching the query, ordered by relevance.
310
- */
311
- searchPainEvents(query: string, limit = 10): {
312
- id: number;
313
- sessionId: string;
314
- source: string;
315
- score: number;
316
- reason: string | null;
317
- severity: string | null;
318
- origin: string | null;
319
- confidence: number | null;
320
- text: string | null;
321
- createdAt: string;
322
- }[] {
323
- if (!query || query.trim().length === 0) {
324
- return [];
325
- }
326
-
327
- // Escape FTS5 special characters and format query for porter tokenizer
328
- const ftsQuery = query.trim().split(/\s+/).map(term => `"${term.replace(/"/g, '""')}"`).join(' ');
329
-
330
- try {
331
- const results = this.db.prepare(`
332
- SELECT pe.*
333
- FROM pain_events_fts pf
334
- JOIN pain_events pe ON pe.id = pf.pain_event_id
335
- WHERE pain_events_fts MATCH ?
336
- ORDER BY bm25(pain_events_fts) DESC
337
- LIMIT ?
338
- `).all(ftsQuery, limit) as {
339
- id: number;
340
- session_id: string;
341
- source: string;
342
- score: number;
343
- reason: string | null;
344
- severity: string | null;
345
- origin: string | null;
346
- confidence: number | null;
347
- text: string | null;
348
- created_at: string;
349
- }[];
350
-
351
- return results.map(row => ({
352
- id: row.id,
353
- sessionId: row.session_id,
354
- source: row.source,
355
- score: row.score,
356
- reason: row.reason,
357
- severity: row.severity,
358
- origin: row.origin,
359
- confidence: row.confidence,
360
- text: row.text,
361
- createdAt: row.created_at,
362
- }));
363
- } catch (err) {
364
- // If FTS5 query fails (e.g., syntax error), return empty results
365
- console.warn(`[PD:TrajectoryDatabase] FTS5 search failed: ${String(err)}`);
366
- return [];
367
- }
368
- }
369
-
370
- recordGateBlock(input: TrajectoryGateBlockInput): void {
371
- this.withWrite(() => {
372
- this.db.prepare(`
373
- INSERT INTO gate_blocks (session_id, tool_name, file_path, reason, plan_status, created_at)
374
- VALUES (?, ?, ?, ?, ?, ?)
375
- `).run(
376
- input.sessionId ?? null,
377
- input.toolName,
378
- input.filePath ?? null,
379
- input.reason,
380
- input.planStatus ?? null,
381
- input.createdAt ?? nowIso(),
382
- );
383
- });
384
- }
385
-
386
- recordTrustChange(input: TrajectoryTrustChangeInput): void {
387
- this.withWrite(() => {
388
- this.db.prepare(`
389
- INSERT INTO trust_changes (session_id, previous_score, new_score, delta, reason, created_at)
390
- VALUES (?, ?, ?, ?, ?, ?)
391
- `).run(
392
- input.sessionId ?? null,
393
- input.previousScore,
394
- input.newScore,
395
- input.delta,
396
- input.reason,
397
- input.createdAt ?? nowIso(),
398
- );
399
- });
400
- }
401
-
402
- recordPrincipleEvent(input: TrajectoryPrincipleEventInput): void {
403
- this.withWrite(() => {
404
- this.db.prepare(`
405
- INSERT INTO principle_events (principle_id, event_type, payload_json, created_at)
406
- VALUES (?, ?, ?, ?)
407
- `).run(
408
- input.principleId ?? null,
409
- input.eventType,
410
- safeJson(input.payload),
411
- input.createdAt ?? nowIso(),
412
- );
413
- });
414
- }
415
-
416
- recordTaskOutcome(input: TrajectoryTaskOutcomeInput): void {
417
- this.withWrite(() => {
418
- this.db.prepare(`
419
- INSERT INTO task_outcomes (session_id, task_id, outcome, summary, principle_ids_json, created_at)
420
- VALUES (?, ?, ?, ?, ?, ?)
421
- `).run(
422
- input.sessionId,
423
- input.taskId ?? null,
424
- input.outcome,
425
- input.summary ?? null,
426
- safeJson(input.principleIdsJson),
427
- input.createdAt ?? nowIso(),
428
- );
429
- });
430
- }
431
-
432
- recordEvolutionTask(input: EvolutionTaskInput): void {
433
- const now = nowIso();
434
- // Cast to V2 to access new fields
435
- const v2 = input;
436
- this.withWrite(() => {
437
- this.db.prepare(`
438
- INSERT INTO evolution_tasks (
439
- task_id, trace_id, source, reason, score, status,
440
- enqueued_at, started_at, completed_at, resolution, created_at, updated_at,
441
- task_kind, priority, retry_count, max_retries, last_error, result_ref
442
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
443
- ON CONFLICT(task_id) DO UPDATE SET
444
- status = excluded.status,
445
- started_at = excluded.started_at,
446
- completed_at = excluded.completed_at,
447
- resolution = excluded.resolution,
448
- updated_at = excluded.updated_at,
449
- task_kind = excluded.task_kind,
450
- priority = excluded.priority,
451
- retry_count = excluded.retry_count,
452
- max_retries = excluded.max_retries,
453
- last_error = excluded.last_error,
454
- result_ref = excluded.result_ref
455
- `).run(
456
- input.taskId,
457
- input.traceId,
458
- input.source,
459
- input.reason ?? null,
460
- input.score ?? 0,
461
- input.status ?? 'pending',
462
- input.enqueuedAt ?? null,
463
- input.startedAt ?? null,
464
- input.completedAt ?? null,
465
- input.resolution ?? null,
466
- input.createdAt ?? now,
467
- input.updatedAt ?? now,
468
- v2.taskKind ?? null,
469
- v2.priority ?? null,
470
- v2.retryCount ?? null,
471
- v2.maxRetries ?? null,
472
- v2.lastError ?? null,
473
- v2.resultRef ?? null,
474
- );
475
- });
476
- }
477
-
478
- updateEvolutionTask(taskId: string, updates: Partial<Omit<EvolutionTaskInput, 'taskId' | 'traceId' | 'source'>>): void {
479
- const now = nowIso();
480
- // Cast to V2 to access new fields
481
- const v2Updates = updates;
482
-
483
- this.withWrite(() => {
484
- const setClauses: string[] = ['updated_at = ?'];
485
- const values: unknown[] = [now];
486
-
487
- if (updates.status !== undefined) {
488
- setClauses.push('status = ?');
489
- values.push(updates.status);
490
- }
491
- if (updates.startedAt !== undefined) {
492
- setClauses.push('started_at = ?');
493
- values.push(updates.startedAt);
494
- }
495
- if (updates.completedAt !== undefined) {
496
- setClauses.push('completed_at = ?');
497
- values.push(updates.completedAt);
498
- }
499
- if (updates.resolution !== undefined) {
500
- setClauses.push('resolution = ?');
501
- values.push(updates.resolution);
502
- }
503
- if (updates.score !== undefined) {
504
- setClauses.push('score = ?');
505
- values.push(updates.score);
506
- }
507
- // V2 fields
508
- if (v2Updates.taskKind !== undefined) {
509
- setClauses.push('task_kind = ?');
510
- values.push(v2Updates.taskKind);
511
- }
512
- if (v2Updates.priority !== undefined) {
513
- setClauses.push('priority = ?');
514
- values.push(v2Updates.priority);
515
- }
516
- if (v2Updates.retryCount !== undefined) {
517
- setClauses.push('retry_count = ?');
518
- values.push(v2Updates.retryCount);
519
- }
520
- if (v2Updates.maxRetries !== undefined) {
521
- setClauses.push('max_retries = ?');
522
- values.push(v2Updates.maxRetries);
523
- }
524
- if (v2Updates.lastError !== undefined) {
525
- setClauses.push('last_error = ?');
526
- values.push(v2Updates.lastError);
527
- }
528
- if (v2Updates.resultRef !== undefined) {
529
- setClauses.push('result_ref = ?');
530
- values.push(v2Updates.resultRef);
531
- }
532
-
533
- values.push(taskId);
534
- this.db.prepare(`
535
- UPDATE evolution_tasks SET ${setClauses.join(', ')} WHERE task_id = ?
536
- `).run(...values);
537
- });
538
- }
539
-
540
- recordEvolutionEvent(input: EvolutionEventInput): void {
541
- this.withWrite(() => {
542
- this.db.prepare(`
543
- INSERT INTO evolution_events (trace_id, task_id, stage, level, message, summary, metadata_json, created_at)
544
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
545
- `).run(
546
- input.traceId,
547
- input.taskId ?? null,
548
- input.stage,
549
- input.level ?? 'info',
550
- input.message,
551
- input.summary ?? null,
552
- safeJson(input.metadata),
553
- input.createdAt ?? nowIso(),
554
- );
555
- });
556
- }
557
-
558
- /**
559
- * List evolution tasks with optional filtering.
560
- *
561
- * Returns: Analytics data aggregated from trajectory database.
562
- * Not: Runtime truth or real-time queue state.
563
- */
564
- listEvolutionTasks(filters: EvolutionTaskFilters = {}): EvolutionTaskRecord[] {
565
- const conditions: string[] = [];
566
- const values: unknown[] = [];
567
-
568
- if (filters.status) {
569
- conditions.push('status = ?');
570
- values.push(filters.status);
571
- }
572
- if (filters.dateFrom) {
573
- conditions.push('created_at >= ?');
574
- values.push(filters.dateFrom);
575
- }
576
- if (filters.dateTo) {
577
- conditions.push('created_at <= ?');
578
- values.push(filters.dateTo);
579
- }
580
-
581
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
582
- const limit = filters.limit ?? 50;
583
- const offset = filters.offset ?? 0;
584
-
585
- const rows = this.db.prepare(`
586
- SELECT id, task_id, trace_id, source, reason, score, status,
587
- enqueued_at, started_at, completed_at, resolution, created_at, updated_at,
588
- task_kind, priority, retry_count, max_retries, last_error, result_ref
589
- FROM evolution_tasks
590
- ${whereClause}
591
- ORDER BY created_at DESC
592
- LIMIT ? OFFSET ?
593
- `).all(...values, limit, offset) as Record<string, unknown>[];
594
-
595
-
596
- return rows.map((row) => ({
597
- id: Number(row.id),
598
- taskId: String(row.task_id),
599
- traceId: String(row.trace_id),
600
- source: String(row.source),
601
- reason: row.reason ? String(row.reason) : null,
602
- score: Number(row.score ?? 0),
603
- status: String(row.status),
604
- enqueuedAt: row.enqueued_at ? String(row.enqueued_at) : null,
605
- startedAt: row.started_at ? String(row.started_at) : null,
606
- completedAt: row.completed_at ? String(row.completed_at) : null,
607
- resolution: row.resolution ? String(row.resolution) : null,
608
- createdAt: String(row.created_at),
609
- updatedAt: String(row.updated_at),
610
- taskKind: row.task_kind ? (row.task_kind as TaskKind) : null,
611
- priority: row.priority ? (row.priority as TaskPriority) : null,
612
- retryCount: row.retry_count != null ? Number(row.retry_count) : null,
613
- maxRetries: row.max_retries != null ? Number(row.max_retries) : null,
614
- lastError: row.last_error ? String(row.last_error) : null,
615
- resultRef: row.result_ref ? String(row.result_ref) : null,
616
- }));
617
- }
618
-
619
- /**
620
- * List evolution events for a trace or globally.
621
- *
622
- * Returns: Analytics data aggregated from trajectory database.
623
- * Not: Runtime truth or real-time queue state.
624
- */
625
- listEvolutionEvents(traceId?: string, filters: { limit?: number; offset?: number } = {}): EvolutionEventRecord[] {
626
- const limit = filters.limit ?? 100;
627
- const offset = filters.offset ?? 0;
628
-
629
-
630
-
631
- let rows: Record<string, unknown>[];
632
- if (traceId) {
633
- rows = this.db.prepare(`
634
- SELECT id, trace_id, task_id, stage, level, message, summary, metadata_json, created_at
635
- FROM evolution_events
636
- WHERE trace_id = ?
637
- ORDER BY created_at ASC
638
- LIMIT ? OFFSET ?
639
- `).all(traceId, limit, offset) as Record<string, unknown>[];
640
- } else {
641
- rows = this.db.prepare(`
642
- SELECT id, trace_id, task_id, stage, level, message, summary, metadata_json, created_at
643
- FROM evolution_events
644
- ORDER BY created_at DESC
645
- LIMIT ? OFFSET ?
646
- `).all(limit, offset) as Record<string, unknown>[];
647
- }
648
-
649
- return rows.map((row) => ({
650
- id: Number(row.id),
651
- traceId: String(row.trace_id),
652
- taskId: row.task_id ? String(row.task_id) : null,
653
- stage: String(row.stage),
654
- level: String(row.level ?? 'info'),
655
- message: String(row.message),
656
- summary: row.summary ? String(row.summary) : null,
657
- metadata: JSON.parse(String(row.metadata_json ?? '{}')),
658
- createdAt: String(row.created_at),
659
- }));
660
- }
661
-
662
- /**
663
- * Get evolution task by trace ID.
664
- *
665
- * Returns: Analytics data aggregated from trajectory database.
666
- * Not: Runtime truth or real-time queue state.
667
- */
668
-
669
- getEvolutionTaskByTraceId(traceId: string): EvolutionTaskRecord | null {
670
- const row = this.db.prepare(`
671
- SELECT id, task_id, trace_id, source, reason, score, status,
672
- enqueued_at, started_at, completed_at, resolution, created_at, updated_at,
673
- task_kind, priority, retry_count, max_retries, last_error, result_ref
674
- FROM evolution_tasks
675
- WHERE trace_id = ?
676
- LIMIT 1
677
- `).get(traceId) as Record<string, unknown> | undefined;
678
-
679
- if (!row) return null;
680
-
681
- return {
682
- id: Number(row.id),
683
- taskId: String(row.task_id),
684
- traceId: String(row.trace_id),
685
- source: String(row.source),
686
- reason: row.reason ? String(row.reason) : null,
687
- score: Number(row.score ?? 0),
688
- status: String(row.status),
689
- enqueuedAt: row.enqueued_at ? String(row.enqueued_at) : null,
690
- startedAt: row.started_at ? String(row.started_at) : null,
691
- completedAt: row.completed_at ? String(row.completed_at) : null,
692
- resolution: row.resolution ? String(row.resolution) : null,
693
- createdAt: String(row.created_at),
694
- updatedAt: String(row.updated_at),
695
- taskKind: row.task_kind ? (row.task_kind as TaskKind) : null,
696
- priority: row.priority ? (row.priority as TaskPriority) : null,
697
- retryCount: row.retry_count != null ? Number(row.retry_count) : null,
698
- maxRetries: row.max_retries != null ? Number(row.max_retries) : null,
699
- lastError: row.last_error ? String(row.last_error) : null,
700
- resultRef: row.result_ref ? String(row.result_ref) : null,
701
- };
702
- }
703
-
704
- /**
705
- * Get evolution task statistics.
706
- *
707
- * Returns: Analytics data aggregated from trajectory database.
708
- * Not: Runtime truth or real-time queue state.
709
- */
710
- getEvolutionStats(): { total: number; pending: number; inProgress: number; completed: number; failed: number } {
711
- const rows = this.db.prepare(`
712
- SELECT status, COUNT(*) as count FROM evolution_tasks GROUP BY status
713
- `).all() as { status: string; count: number }[];
714
-
715
- const stats = { total: 0, pending: 0, inProgress: 0, completed: 0, failed: 0 };
716
- for (const row of rows) {
717
- stats.total += row.count;
718
- if (row.status === 'pending') stats.pending = row.count;
719
- else if (row.status === 'in_progress') stats.inProgress = row.count;
720
- else if (row.status === 'completed') stats.completed = row.count;
721
- else if (row.status === 'failed') stats.failed = row.count;
722
- }
723
- return stats;
724
- }
725
-
726
- /**
727
- * List recent sessions from the trajectory database.
728
- *
729
- * Returns: Recent session records ordered by most recently updated.
730
- *
731
- * @param options.limit - Maximum number of sessions to return (default: 20)
732
- * @param options.dateFrom - Only return sessions updated after this date
733
- * @param options.dateTo - Only return sessions updated before this date
734
- */
735
- listRecentSessions(options: { limit?: number; dateFrom?: string; dateTo?: string } = {}): { sessionId: string; startedAt: string; updatedAt: string }[] {
736
- const conditions: string[] = [];
737
- const values: unknown[] = [];
738
-
739
- if (options.dateFrom) {
740
- conditions.push('updated_at >= ?');
741
- values.push(options.dateFrom);
742
- }
743
- if (options.dateTo) {
744
- conditions.push('updated_at <= ?');
745
- values.push(options.dateTo);
746
- }
747
-
748
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
749
- const limit = options.limit ?? 20;
750
-
751
- const rows = this.db.prepare(`
752
- SELECT session_id, started_at, updated_at
753
- FROM sessions
754
- ${whereClause}
755
- ORDER BY updated_at DESC
756
- LIMIT ?
757
- `).all(...values, limit) as Record<string, unknown>[];
758
-
759
- return rows.map((row) => ({
760
- sessionId: String(row.session_id),
761
- startedAt: String(row.started_at),
762
- updatedAt: String(row.updated_at),
763
- }));
764
- }
765
-
766
- /**
767
- * List assistant turns for a session.
768
- *
769
- * Returns: Analytics data aggregated from trajectory database.
770
- * Not: Runtime truth or real-time queue state.
771
- */
772
- listAssistantTurns(sessionId: string): AssistantTurnRecord[] {
773
- const rows = this.db.prepare(`
774
- SELECT id, session_id, run_id, provider, model, raw_text, sanitized_text, blob_ref, created_at
775
- FROM assistant_turns
776
- WHERE session_id = ?
777
- ORDER BY id ASC
778
- `).all(sessionId) as Record<string, unknown>[];
779
-
780
- return rows.map((row) => ({
781
- id: Number(row.id),
782
- sessionId: String(row.session_id),
783
- runId: String(row.run_id),
784
- provider: String(row.provider),
785
- model: String(row.model),
786
- rawText: this.restoreRawText(row.raw_text as string | null, row.blob_ref as string | null),
787
- sanitizedText: String(row.sanitized_text ?? ''),
788
- blobRef: row.blob_ref ? String(row.blob_ref) : null,
789
- createdAt: String(row.created_at),
790
- }));
791
- }
792
-
793
- /**
794
- * List tool calls for a session.
795
- *
796
- * Returns: Analytics data aggregated from trajectory database.
797
- * Not: Runtime truth or real-time queue state.
798
- */
799
- listToolCallsForSession(sessionId: string): {
800
- id: number;
801
- toolName: string;
802
- outcome: string;
803
- filePath: string | null;
804
- durationMs: number | null;
805
- exitCode: number | null;
806
- errorType: string | null;
807
- errorMessage: string | null;
808
- gfiBefore: number | null;
809
- gfiAfter: number | null;
810
- createdAt: string;
811
- }[] {
812
- const rows = this.db.prepare(`
813
- SELECT id, tool_name, outcome, params_json, duration_ms, exit_code, error_type, error_message,
814
- gfi_before, gfi_after, created_at
815
- FROM tool_calls
816
- WHERE session_id = ?
817
- ORDER BY id ASC
818
- `).all(sessionId) as Record<string, unknown>[];
819
-
820
-
821
- return rows.map((row) => {
822
- // Extract filePath from params_json if present
823
- let filePath: string | null = null;
824
- if (row.params_json && typeof row.params_json === 'string') {
825
- try {
826
- const params = JSON.parse(row.params_json);
827
- if (params && typeof params.filePath === 'string') {
828
-
829
-
830
- filePath = params.filePath;
831
- }
832
- } catch {
833
- // Ignore malformed JSON
834
- }
835
- }
836
- return {
837
- id: Number(row.id),
838
- toolName: String(row.tool_name),
839
- outcome: String(row.outcome),
840
- filePath,
841
- durationMs: row.duration_ms != null ? Number(row.duration_ms) : null,
842
- exitCode: row.exit_code != null ? Number(row.exit_code) : null,
843
- errorType: row.error_type ? String(row.error_type) : null,
844
- errorMessage: row.error_message ? String(row.error_message) : null,
845
- gfiBefore: row.gfi_before != null ? Number(row.gfi_before) : null,
846
- gfiAfter: row.gfi_after != null ? Number(row.gfi_after) : null,
847
- createdAt: String(row.created_at),
848
- };
849
- });
850
- }
851
-
852
- /**
853
- * List pain events for a session.
854
- *
855
- * Returns: Analytics data aggregated from trajectory database.
856
- * Not: Runtime truth or real-time queue state.
857
- */
858
- listPainEventsForSession(sessionId: string): {
859
- id: number;
860
- source: string;
861
- score: number;
862
- reason: string | null;
863
- severity: string | null;
864
- origin: string | null;
865
- confidence: number | null;
866
- createdAt: string;
867
- }[] {
868
- const rows = this.db.prepare(`
869
- SELECT id, source, score, reason, severity, origin, confidence, created_at
870
- FROM pain_events
871
- WHERE session_id = ?
872
- ORDER BY created_at ASC
873
- `).all(sessionId) as Record<string, unknown>[];
874
-
875
- return rows.map((row) => ({
876
- id: Number(row.id),
877
- source: String(row.source),
878
- score: Number(row.score),
879
- reason: row.reason ? String(row.reason) : null,
880
- severity: row.severity ? String(row.severity) : null,
881
- origin: row.origin ? String(row.origin) : null,
882
- confidence: row.confidence != null ? Number(row.confidence) : null,
883
- createdAt: String(row.created_at),
884
- }));
885
- }
886
-
887
- /**
888
- * List user turns for a session.
889
- * Returns sanitized/reduced fields for nocturnal use — NO raw text.
890
- */
891
- listUserTurnsForSession(sessionId: string): {
892
- id: number;
893
- turnIndex: number;
894
- rawExcerpt: string;
895
- correctionDetected: boolean;
896
- correctionCue: string | null;
897
- createdAt: string;
898
- }[] {
899
- const rows = this.db.prepare(`
900
- SELECT id, turn_index, raw_excerpt, correction_detected, correction_cue, created_at
901
- FROM user_turns
902
- WHERE session_id = ?
903
- ORDER BY turn_index ASC
904
- `).all(sessionId) as Record<string, unknown>[];
905
-
906
- return rows.map((row) => ({
907
- id: Number(row.id),
908
- turnIndex: Number(row.turn_index),
909
- rawExcerpt: String(row.raw_excerpt ?? ''),
910
- correctionDetected: Boolean(row.correction_detected),
911
- correctionCue: row.correction_cue ? String(row.correction_cue) : null,
912
- createdAt: String(row.created_at),
913
- }));
914
- }
915
-
916
- /**
917
- * List correction samples with optional review status filter.
918
- *
919
- * Returns: Analytics data aggregated from trajectory database.
920
- * Not: Runtime truth or real-time queue state.
921
- */
922
- listCorrectionSamples(status: CorrectionSampleReviewStatus = 'pending'): CorrectionSampleRecord[] {
923
- const rows = this.db.prepare(`
924
- SELECT sample_id, session_id, bad_assistant_turn_id, user_correction_turn_id,
925
- recovery_tool_span_json, diff_excerpt, principle_ids_json, quality_score,
926
- review_status, export_mode, created_at, updated_at
927
- FROM correction_samples
928
- WHERE review_status = ?
929
- ORDER BY created_at DESC
930
- `).all(status) as Record<string, unknown>[];
931
-
932
- return rows.map((row) => ({
933
- sampleId: String(row.sample_id),
934
- sessionId: String(row.session_id),
935
- badAssistantTurnId: Number(row.bad_assistant_turn_id),
936
- userCorrectionTurnId: Number(row.user_correction_turn_id),
937
- recoveryToolSpanJson: String(row.recovery_tool_span_json),
938
- diffExcerpt: String(row.diff_excerpt ?? ''),
939
- principleIdsJson: String(row.principle_ids_json ?? '[]'),
940
- qualityScore: Number(row.quality_score),
941
- reviewStatus: row.review_status as CorrectionSampleReviewStatus,
942
- exportMode: row.export_mode as CorrectionExportMode,
943
- createdAt: String(row.created_at),
944
- updatedAt: String(row.updated_at),
945
- }));
946
- }
947
-
948
- /**
949
- * List gate blocks for a session.
950
- * Returns minimal fields for nocturnal use — no raw text.
951
- */
952
- listGateBlocksForSession(sessionId: string): {
953
- id: number;
954
- toolName: string;
955
- filePath: string | null;
956
- reason: string;
957
- planStatus: string | null;
958
- createdAt: string;
959
- }[] {
960
- const rows = this.db.prepare(`
961
- SELECT id, tool_name, file_path, reason, plan_status, created_at
962
- FROM gate_blocks
963
- WHERE session_id = ?
964
- ORDER BY id ASC
965
- `).all(sessionId) as Record<string, unknown>[];
966
-
967
- return rows.map((row) => ({
968
- id: Number(row.id),
969
- toolName: String(row.tool_name),
970
- filePath: row.file_path ? String(row.file_path) : null,
971
- reason: String(row.reason),
972
- planStatus: row.plan_status ? String(row.plan_status) : null,
973
- createdAt: String(row.created_at),
974
- }));
975
- }
976
-
977
- /**
978
- * List correction samples for a specific session.
979
- * Returns minimal fields for nocturnal use — correction cue only.
980
- * #268: Wire correction_samples into nocturnal pipeline.
981
- */
982
- listCorrectionSamplesForSession(sessionId: string): { correctionCue: string | null }[] {
983
- const rows = this.db.prepare(`
984
- SELECT cs.sample_id, ut.correction_cue
985
- FROM correction_samples cs
986
- LEFT JOIN user_turns ut ON ut.id = cs.user_correction_turn_id
987
- WHERE cs.session_id = ?
988
- ORDER BY cs.created_at DESC
989
- `).all(sessionId) as Record<string, unknown>[];
990
-
991
- return rows.map((row) => ({
992
- correctionCue: row.correction_cue ? String(row.correction_cue) : null,
993
- }));
994
- }
995
-
996
- reviewCorrectionSample(sampleId: string, status: Exclude<CorrectionSampleReviewStatus, 'pending'>, note?: string): CorrectionSampleRecord {
997
- const updatedAt = nowIso();
998
- const updated = this.withWrite(() => {
999
- const updateResult = this.db.prepare(`
1000
- UPDATE correction_samples
1001
- SET review_status = ?, updated_at = ?
1002
- WHERE sample_id = ?
1003
- `).run(status, updatedAt, sampleId);
1004
- if (updateResult.changes === 0) {
1005
- return false;
1006
- }
1007
- this.db.prepare(`
1008
- INSERT INTO sample_reviews (sample_id, review_status, note, created_at)
1009
- VALUES (?, ?, ?, ?)
1010
- `).run(sampleId, status, note ?? null, updatedAt);
1011
- return true;
1012
- });
1013
- if (!updated) {
1014
- throw new SampleNotFoundError(sampleId);
1015
- }
1016
-
1017
- const record = this.db.prepare(`
1018
- SELECT sample_id, session_id, bad_assistant_turn_id, user_correction_turn_id,
1019
- recovery_tool_span_json, diff_excerpt, principle_ids_json, quality_score,
1020
- review_status, export_mode, created_at, updated_at
1021
- FROM correction_samples
1022
- WHERE sample_id = ?
1023
- `).get(sampleId) as Record<string, unknown>;
1024
- if (!record) {
1025
- throw new SampleNotFoundError(`${sampleId} (after update)`);
1026
- }
1027
-
1028
- // #Phase2b: Emit pain event for rejected corrections
1029
- if (status === 'rejected') {
1030
- this.recordCorrectionRejectedPain({
1031
- session_id: record.session_id,
1032
- quality_score: record.quality_score,
1033
- diff_excerpt: record.diff_excerpt,
1034
- principle_ids_json: record.principle_ids_json,
1035
- created_at: record.created_at,
1036
- });
1037
- }
1038
-
1039
- return {
1040
- sampleId: String(record.sample_id),
1041
- sessionId: String(record.session_id),
1042
- badAssistantTurnId: Number(record.bad_assistant_turn_id),
1043
- userCorrectionTurnId: Number(record.user_correction_turn_id),
1044
- recoveryToolSpanJson: String(record.recovery_tool_span_json),
1045
- diffExcerpt: String(record.diff_excerpt ?? ''),
1046
- principleIdsJson: String(record.principle_ids_json ?? '[]'),
1047
- qualityScore: Number(record.quality_score),
1048
- reviewStatus: record.review_status as CorrectionSampleReviewStatus,
1049
- exportMode: record.export_mode as CorrectionExportMode,
1050
- createdAt: String(record.created_at),
1051
- updatedAt: String(record.updated_at),
1052
- };
1053
- }
1054
-
1055
- /**
1056
- * When a correction sample is rejected, emit a pain event to the trajectory.
1057
- * This feeds rejected corrections into the nocturnal pipeline as a high-fidelity
1058
- * violation signal (human-verified, unlike heuristic pain detection).
1059
- */
1060
- private recordCorrectionRejectedPain(record: {
1061
- session_id: unknown;
1062
- quality_score: unknown;
1063
- diff_excerpt: unknown;
1064
- principle_ids_json: unknown;
1065
- created_at: unknown;
1066
- }): void {
1067
- const sessionId = String(record.session_id);
1068
- const qualityScore = Number(record.quality_score);
1069
- const diffExcerpt = String(record.diff_excerpt ?? '');
1070
- const principleIds = String(record.principle_ids_json ?? '[]');
1071
- // quality_score (0-100) from correction sample → pain score (0-100), clamped
1072
- const painScore = Math.max(0, Math.min(100, Math.round(Number(qualityScore) || 0)));
1073
- const reason = `Correction rejected (quality ${qualityScore.toFixed(2)}). Principles: ${principleIds}${diffExcerpt ? ` — ${diffExcerpt.slice(0, 120)}` : ''}`;
1074
-
1075
- try {
1076
- this.recordPainEvent({
1077
- sessionId,
1078
- source: 'correction_rejected',
1079
- score: painScore,
1080
- reason,
1081
- severity: painScore >= 70 ? 'severe' : painScore >= 40 ? 'moderate' : 'mild',
1082
- origin: 'system_infer',
1083
- text: diffExcerpt || undefined,
1084
- createdAt: String(record.created_at),
1085
- });
1086
- } catch (err) {
1087
- // Non-fatal: pain event recording should not break the review flow
1088
- console.warn(`[Trajectory] Failed to record correction_rejected pain event: ${String(err)}`);
1089
- }
1090
- }
1091
-
1092
- /**
1093
- * Export correction samples to JSONL file.
1094
- *
1095
- * Returns: Analytics data aggregated from trajectory database.
1096
- * Not: Runtime truth or real-time queue state.
1097
- */
1098
- exportCorrections(opts: { mode: CorrectionExportMode; approvedOnly: boolean }): TrajectoryExportResult {
1099
- const rows = this.db.prepare(`
1100
- SELECT cs.sample_id, cs.session_id, cs.recovery_tool_span_json, cs.diff_excerpt, cs.quality_score,
1101
- at.raw_text AS assistant_raw_text, at.blob_ref AS assistant_blob_ref, at.sanitized_text,
1102
- ut.raw_text AS user_raw_text, ut.blob_ref AS user_blob_ref, ut.correction_cue
1103
- FROM correction_samples cs
1104
- JOIN assistant_turns at ON at.id = cs.bad_assistant_turn_id
1105
- JOIN user_turns ut ON ut.id = cs.user_correction_turn_id
1106
- WHERE (? = 0 OR cs.review_status = 'approved')
1107
- ORDER BY cs.created_at ASC
1108
- `).all(opts.approvedOnly ? 1 : 0) as Record<string, unknown>[];
1109
-
1110
- const exportPath = path.join(this.exportDir, `corrections-${Date.now()}-${opts.mode}.jsonl`);
1111
- const lines = rows.map((row) => {
1112
- const assistantRaw = this.restoreRawText(row.assistant_raw_text as string | null, row.assistant_blob_ref as string | null);
1113
- const userRaw = this.restoreRawText(row.user_raw_text as string | null, row.user_blob_ref as string | null);
1114
- const assistantText = opts.mode === 'redacted' ? redactText(assistantRaw) : assistantRaw;
1115
- const userText = opts.mode === 'redacted' ? redactText(userRaw) : userRaw;
1116
- return JSON.stringify({
1117
- sample_id: row.sample_id,
1118
- session_id: row.session_id,
1119
- instruction: userText,
1120
- input_context: assistantText,
1121
- bad_attempt_summary: String(row.diff_excerpt ?? ''),
1122
- preferred_response: userText,
1123
- labels: {
1124
- correction_cue: row.correction_cue,
1125
- quality_score: row.quality_score,
1126
- },
1127
- metadata: {
1128
- mode: opts.mode,
1129
- recovery_tool_span_json: row.recovery_tool_span_json,
1130
- },
1131
- });
1132
- });
1133
-
1134
- atomicWriteFileSync(exportPath, `${lines.join('\n')}${lines.length > 0 ? '\n' : ''}`);
1135
- this.recordExportAudit('corrections', opts.mode, opts.approvedOnly, exportPath, rows.length);
1136
- return { filePath: exportPath, count: rows.length, mode: opts.mode };
1137
- }
1138
-
1139
- /**
1140
- * Export analytics data to JSON file.
1141
- *
1142
- * Returns: Analytics data aggregated from trajectory database.
1143
- * Not: Runtime truth or real-time queue state.
1144
- */
1145
- exportAnalytics(): TrajectoryExportResult {
1146
- const payload = {
1147
- generatedAt: nowIso(),
1148
- stats: this.getDataStats(),
1149
- dailyMetrics: this.dailyMetrics(),
1150
- errorClusters: this.db.prepare('SELECT * FROM v_error_clusters').all(),
1151
- principleEffectiveness: this.db.prepare('SELECT * FROM v_principle_effectiveness').all(),
1152
- sampleQueue: this.db.prepare('SELECT * FROM v_sample_queue').all(),
1153
- };
1154
- const exportPath = path.join(this.exportDir, `analytics-${Date.now()}.json`);
1155
- atomicWriteFileSync(exportPath, JSON.stringify(payload, null, 2));
1156
- this.recordExportAudit('analytics', 'raw', true, exportPath, Array.isArray(payload.dailyMetrics) ? payload.dailyMetrics.length : 0);
1157
- return { filePath: exportPath, count: Array.isArray(payload.dailyMetrics) ? payload.dailyMetrics.length : 0 };
1158
- }
1159
-
1160
- /**
1161
- * Get trajectory database statistics.
1162
- *
1163
- * Returns: Analytics data aggregated from trajectory database.
1164
- * Not: Runtime truth or real-time queue state.
1165
- */
1166
- getDataStats(): TrajectoryDataStats {
1167
- const getCount = (table: string, where?: string) => {
1168
- const sql = where ? `SELECT COUNT(*) as count FROM ${table} WHERE ${where}` : `SELECT COUNT(*) as count FROM ${table}`;
1169
- return Number((this.db.prepare(sql).get() as { count: number }).count);
1170
- };
1171
- const lastIngest = this.db.prepare(`
1172
- SELECT MAX(ts) AS ts FROM (
1173
- SELECT MAX(created_at) AS ts FROM assistant_turns
1174
- UNION ALL SELECT MAX(created_at) AS ts FROM user_turns
1175
- UNION ALL SELECT MAX(created_at) AS ts FROM tool_calls
1176
- UNION ALL SELECT MAX(created_at) AS ts FROM pain_events
1177
- )
1178
- `).get() as { ts: string | null };
1179
- return {
1180
- dbPath: this.dbPath,
1181
- dbSizeBytes: fileSizeIfExists(this.dbPath),
1182
- assistantTurns: getCount('assistant_turns'),
1183
- userTurns: getCount('user_turns'),
1184
- toolCalls: getCount('tool_calls'),
1185
- painEvents: getCount('pain_events'),
1186
- pendingSamples: getCount('correction_samples', `review_status = 'pending'`),
1187
- approvedSamples: getCount('correction_samples', `review_status = 'approved'`),
1188
- blobBytes: this.computeBlobBytes(),
1189
- lastIngestAt: lastIngest.ts ?? null,
1190
- };
1191
- }
1192
-
1193
- cleanupBlobStorage(): { removedFiles: number; reclaimedBytes: number } {
1194
- return this.pruneUnreferencedBlobs();
1195
- }
1196
-
1197
- private initSchema(): void {
1198
- this.db.exec(`
1199
- CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL);
1200
- CREATE TABLE IF NOT EXISTS ingest_checkpoint (
1201
- source_key TEXT PRIMARY KEY,
1202
- imported_at TEXT NOT NULL
1203
- );
1204
- CREATE TABLE IF NOT EXISTS sessions (
1205
- session_id TEXT PRIMARY KEY,
1206
- started_at TEXT NOT NULL,
1207
- updated_at TEXT NOT NULL
1208
- );
1209
- CREATE TABLE IF NOT EXISTS assistant_turns (
1210
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1211
- session_id TEXT NOT NULL,
1212
- run_id TEXT NOT NULL,
1213
- provider TEXT NOT NULL,
1214
- model TEXT NOT NULL,
1215
- raw_text TEXT,
1216
- sanitized_text TEXT NOT NULL,
1217
- usage_json TEXT NOT NULL,
1218
- empathy_signal_json TEXT NOT NULL,
1219
- blob_ref TEXT,
1220
- raw_excerpt TEXT,
1221
- created_at TEXT NOT NULL
1222
- );
1223
- CREATE TABLE IF NOT EXISTS user_turns (
1224
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1225
- session_id TEXT NOT NULL,
1226
- turn_index INTEGER NOT NULL,
1227
- raw_text TEXT,
1228
- blob_ref TEXT,
1229
- raw_excerpt TEXT,
1230
- correction_detected INTEGER NOT NULL DEFAULT 0,
1231
- correction_cue TEXT,
1232
- references_assistant_turn_id INTEGER,
1233
- created_at TEXT NOT NULL
1234
- );
1235
- CREATE TABLE IF NOT EXISTS tool_calls (
1236
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1237
- session_id TEXT NOT NULL,
1238
- tool_name TEXT NOT NULL,
1239
- outcome TEXT NOT NULL,
1240
- duration_ms INTEGER,
1241
- exit_code INTEGER,
1242
- error_type TEXT,
1243
- error_message TEXT,
1244
- gfi_before REAL,
1245
- gfi_after REAL,
1246
- params_json TEXT NOT NULL,
1247
- created_at TEXT NOT NULL
1248
- );
1249
- CREATE TABLE IF NOT EXISTS pain_events (
1250
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1251
- session_id TEXT NOT NULL,
1252
- source TEXT NOT NULL,
1253
- score REAL NOT NULL,
1254
- reason TEXT,
1255
- severity TEXT,
1256
- origin TEXT,
1257
- confidence REAL,
1258
- text TEXT,
1259
- created_at TEXT NOT NULL
1260
- );
1261
- CREATE TABLE IF NOT EXISTS gate_blocks (
1262
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1263
- session_id TEXT,
1264
- tool_name TEXT NOT NULL,
1265
- file_path TEXT,
1266
- reason TEXT NOT NULL,
1267
- plan_status TEXT,
1268
- created_at TEXT NOT NULL
1269
- );
1270
- CREATE TABLE IF NOT EXISTS trust_changes (
1271
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1272
- session_id TEXT,
1273
- previous_score REAL NOT NULL,
1274
- new_score REAL NOT NULL,
1275
- delta REAL NOT NULL,
1276
- reason TEXT NOT NULL,
1277
- created_at TEXT NOT NULL
1278
- );
1279
- CREATE TABLE IF NOT EXISTS principle_events (
1280
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1281
- principle_id TEXT,
1282
- event_type TEXT NOT NULL,
1283
- payload_json TEXT NOT NULL,
1284
- created_at TEXT NOT NULL
1285
- );
1286
- CREATE TABLE IF NOT EXISTS task_outcomes (
1287
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1288
- session_id TEXT NOT NULL,
1289
- task_id TEXT,
1290
- outcome TEXT NOT NULL,
1291
- summary TEXT,
1292
- principle_ids_json TEXT NOT NULL,
1293
- created_at TEXT NOT NULL
1294
- );
1295
- CREATE TABLE IF NOT EXISTS correction_samples (
1296
- sample_id TEXT PRIMARY KEY,
1297
- session_id TEXT NOT NULL,
1298
- bad_assistant_turn_id INTEGER NOT NULL,
1299
- user_correction_turn_id INTEGER NOT NULL,
1300
- recovery_tool_span_json TEXT NOT NULL,
1301
- diff_excerpt TEXT NOT NULL,
1302
- principle_ids_json TEXT NOT NULL,
1303
- quality_score REAL NOT NULL,
1304
- review_status TEXT NOT NULL,
1305
- export_mode TEXT NOT NULL,
1306
- created_at TEXT NOT NULL,
1307
- updated_at TEXT NOT NULL
1308
- );
1309
- CREATE TABLE IF NOT EXISTS sample_reviews (
1310
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1311
- sample_id TEXT NOT NULL,
1312
- review_status TEXT NOT NULL,
1313
- note TEXT,
1314
- created_at TEXT NOT NULL
1315
- );
1316
- CREATE TABLE IF NOT EXISTS exports_audit (
1317
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1318
- export_kind TEXT NOT NULL,
1319
- mode TEXT NOT NULL,
1320
- approved_only INTEGER NOT NULL,
1321
- file_path TEXT NOT NULL,
1322
- row_count INTEGER NOT NULL,
1323
- created_at TEXT NOT NULL
1324
- );
1325
- CREATE TABLE IF NOT EXISTS evolution_tasks (
1326
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1327
- task_id TEXT UNIQUE NOT NULL,
1328
- trace_id TEXT NOT NULL,
1329
- source TEXT NOT NULL,
1330
- reason TEXT,
1331
- score INTEGER DEFAULT 0,
1332
- status TEXT DEFAULT 'pending',
1333
- enqueued_at TEXT,
1334
- started_at TEXT,
1335
- completed_at TEXT,
1336
- resolution TEXT,
1337
- created_at TEXT NOT NULL,
1338
- updated_at TEXT NOT NULL
1339
- );
1340
- CREATE TABLE IF NOT EXISTS evolution_events (
1341
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1342
- trace_id TEXT NOT NULL,
1343
- task_id TEXT,
1344
- stage TEXT NOT NULL,
1345
- level TEXT DEFAULT 'info',
1346
- message TEXT NOT NULL,
1347
- summary TEXT,
1348
- metadata_json TEXT,
1349
- created_at TEXT NOT NULL
1350
- );
1351
- `);
1352
-
1353
- // Migration: Add text column to pain_events if it doesn't exist (MEM-01)
1354
- // SQLite doesn't support IF NOT EXISTS for ADD COLUMN, so we use try/catch
1355
- try {
1356
- this.db.exec(`ALTER TABLE pain_events ADD COLUMN text TEXT`);
1357
- } catch (err: unknown) {
1358
- const message = err instanceof Error ? err.message : String(err);
1359
- if (!message.includes('duplicate column name') && !message.includes('no column named')) {
1360
- // Re-throw unexpected errors — silently swallowing migration failures is dangerous
1361
- throw err;
1362
- }
1363
- }
1364
-
1365
- // PRI-406: Add canonical_pain_id and runtime_task_id columns to pain_events
1366
- for (const col of [
1367
- { name: 'canonical_pain_id', type: 'TEXT' },
1368
- { name: 'runtime_task_id', type: 'TEXT' },
1369
- ]) {
1370
- try {
1371
- this.db.exec(`ALTER TABLE pain_events ADD COLUMN ${col.name} ${col.type}`);
1372
- } catch (err: unknown) {
1373
- const message = err instanceof Error ? err.message : String(err);
1374
- if (!message.includes('duplicate column name') && !message.includes('no column named')) {
1375
- throw err;
1376
- }
1377
- }
1378
- }
1379
-
1380
- // PRI-406: Partial unique index on canonical_pain_id (non-null only) for dedup
1381
- this.db.exec(`
1382
- CREATE UNIQUE INDEX IF NOT EXISTS idx_pain_events_canonical_pain_id
1383
- ON pain_events(canonical_pain_id)
1384
- WHERE canonical_pain_id IS NOT NULL
1385
- `);
1386
-
1387
- // Create FTS5 virtual table for pain_events text search (MEM-04)
1388
- this.db.exec(`
1389
- CREATE VIRTUAL TABLE IF NOT EXISTS pain_events_fts USING fts5(
1390
- text,
1391
- pain_event_id UNINDEXED,
1392
- tokenize='porter unicode61'
1393
- )
1394
- `);
1395
-
1396
- // V2 migration: Add V2 columns to evolution_tasks if they don't exist
1397
- // SQLite does not support IF NOT EXISTS for ADD COLUMN, so we must check manually
1398
- // before each ALTER to avoid "duplicate column name" errors on existing DBs
1399
- const v2Columns = [
1400
- { name: 'task_kind', type: 'TEXT' },
1401
- { name: 'priority', type: 'TEXT' },
1402
- { name: 'retry_count', type: 'INTEGER' },
1403
- { name: 'max_retries', type: 'INTEGER' },
1404
- { name: 'last_error', type: 'TEXT' },
1405
- { name: 'result_ref', type: 'TEXT' },
1406
- ];
1407
- for (const col of v2Columns) {
1408
- const exists = this.db.prepare(`PRAGMA table_info(evolution_tasks)`).all()
1409
- .some((row) => (row as Record<string, unknown>).name === col.name);
1410
- if (!exists) {
1411
- this.db.exec(`ALTER TABLE evolution_tasks ADD COLUMN ${col.name} ${col.type}`);
1412
- }
1413
- }
1414
-
1415
- this.db.exec(`
1416
- CREATE VIEW IF NOT EXISTS v_error_clusters AS
1417
- SELECT tool_name, COALESCE(error_type, 'unknown') AS error_type, COUNT(*) AS occurrences
1418
- FROM tool_calls
1419
- WHERE outcome = 'failure'
1420
- GROUP BY tool_name, COALESCE(error_type, 'unknown')
1421
- ORDER BY occurrences DESC;
1422
- CREATE VIEW IF NOT EXISTS v_principle_effectiveness AS
1423
- SELECT event_type, COUNT(*) AS total
1424
- FROM principle_events
1425
- GROUP BY event_type
1426
- ORDER BY total DESC;
1427
- CREATE VIEW IF NOT EXISTS v_sample_queue AS
1428
- SELECT review_status, COUNT(*) AS total
1429
- FROM correction_samples
1430
- GROUP BY review_status;
1431
- CREATE INDEX IF NOT EXISTS idx_assistant_turns_session_id ON assistant_turns(session_id);
1432
- CREATE INDEX IF NOT EXISTS idx_assistant_turns_created_at ON assistant_turns(created_at);
1433
- CREATE INDEX IF NOT EXISTS idx_assistant_turns_provider_model ON assistant_turns(provider, model);
1434
- CREATE INDEX IF NOT EXISTS idx_user_turns_session_id ON user_turns(session_id);
1435
- CREATE INDEX IF NOT EXISTS idx_tool_calls_session_id ON tool_calls(session_id);
1436
- CREATE INDEX IF NOT EXISTS idx_tool_calls_created_at ON tool_calls(created_at);
1437
- CREATE INDEX IF NOT EXISTS idx_pain_events_session_id ON pain_events(session_id);
1438
- CREATE INDEX IF NOT EXISTS idx_correction_samples_review_status ON correction_samples(review_status);
1439
- CREATE INDEX IF NOT EXISTS idx_evolution_tasks_trace_id ON evolution_tasks(trace_id);
1440
- CREATE INDEX IF NOT EXISTS idx_evolution_tasks_status ON evolution_tasks(status);
1441
- CREATE INDEX IF NOT EXISTS idx_evolution_tasks_created_at ON evolution_tasks(created_at);
1442
- CREATE INDEX IF NOT EXISTS idx_evolution_events_trace_id ON evolution_events(trace_id);
1443
- CREATE INDEX IF NOT EXISTS idx_evolution_events_created_at ON evolution_events(created_at);
1444
- `);
1445
-
1446
- const row = this.db.prepare('SELECT version FROM schema_version LIMIT 1').get() as { version?: number } | undefined;
1447
- this.migrateSchema(row?.version);
1448
- if (!row) {
1449
- this.db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(SCHEMA_VERSION);
1450
- } else if (row.version !== SCHEMA_VERSION) {
1451
- this.db.prepare('UPDATE schema_version SET version = ?').run(SCHEMA_VERSION);
1452
- }
1453
- }
1454
-
1455
- private importLegacyArtifacts(): void {
1456
- this.importLegacySessions();
1457
- this.importLegacyEvents();
1458
- this.importLegacyEvolution();
1459
- }
1460
-
1461
-
1462
- private migrateSchema(_fromVersion?: number): void {
1463
- this.db.exec(`
1464
- DROP VIEW IF EXISTS v_daily_metrics;
1465
- CREATE VIEW IF NOT EXISTS v_daily_metrics AS
1466
- WITH tool_daily AS (
1467
- SELECT
1468
- substr(created_at, 1, 10) AS day,
1469
- COUNT(*) AS tool_calls,
1470
- SUM(CASE WHEN outcome = 'failure' THEN 1 ELSE 0 END) AS failures
1471
- FROM tool_calls
1472
- GROUP BY substr(created_at, 1, 10)
1473
- ),
1474
- correction_daily AS (
1475
- SELECT
1476
- substr(created_at, 1, 10) AS day,
1477
- SUM(CASE WHEN correction_detected = 1 THEN 1 ELSE 0 END) AS user_corrections
1478
- FROM user_turns
1479
- GROUP BY substr(created_at, 1, 10)
1480
- )
1481
- SELECT
1482
- tool_daily.day AS day,
1483
- tool_daily.tool_calls AS tool_calls,
1484
- tool_daily.failures AS failures,
1485
- COALESCE(correction_daily.user_corrections, 0) AS user_corrections
1486
- FROM tool_daily
1487
- LEFT JOIN correction_daily ON correction_daily.day = tool_daily.day;
1488
- `);
1489
- }
1490
-
1491
- /**
1492
- * Get daily metrics for analytics.
1493
- *
1494
- * Returns: Analytics data aggregated from trajectory database.
1495
- * Not: Runtime truth or real-time queue state.
1496
- */
1497
- private dailyMetrics(): DailyMetricRow[] {
1498
- return this.db.prepare('SELECT * FROM v_daily_metrics ORDER BY day ASC').all() as DailyMetricRow[];
1499
- }
1500
-
1501
- private importLegacySessions(): void {
1502
- const key = 'legacy:sessions';
1503
- if (this.isImported(key)) return;
1504
- const sessionDir = resolvePdPath(this.workspaceDir, 'SESSION_DIR');
1505
- if (!fs.existsSync(sessionDir)) return;
1506
- for (const file of fs.readdirSync(sessionDir).filter((entry) => entry.endsWith('.json'))) {
1507
- try {
1508
- const content = JSON.parse(fs.readFileSync(path.join(sessionDir, file), 'utf8')) as { sessionId?: string; lastActivityAt?: number };
1509
- if (content.sessionId) {
1510
- const startedAt = typeof content.lastActivityAt === 'number'
1511
- ? new Date(content.lastActivityAt).toISOString()
1512
- : nowIso();
1513
- this.recordSession({ sessionId: content.sessionId, startedAt });
1514
- }
1515
- } catch {
1516
- // Ignore malformed legacy sessions.
1517
- }
1518
- }
1519
- this.markImported(key);
1520
- }
1521
-
1522
- private importLegacyEvents(): void {
1523
- const key = 'legacy:events';
1524
- if (this.isImported(key)) return;
1525
- const eventsPath = path.join(this.stateDir, 'logs', 'events.jsonl');
1526
- if (!fs.existsSync(eventsPath)) return;
1527
- const raw = fs.readFileSync(eventsPath, 'utf8').trim();
1528
- if (!raw) {
1529
- this.markImported(key);
1530
- return;
1531
- }
1532
- for (const line of raw.split('\n')) {
1533
- try {
1534
- const event = JSON.parse(line) as { type?: string; sessionId?: string; data?: Record<string, unknown>; ts?: string };
1535
- if (event.type === 'pain_signal' && event.sessionId) {
1536
- this.recordPainEvent({
1537
- sessionId: event.sessionId,
1538
- source: String(event.data?.source ?? 'legacy'),
1539
- score: Number(event.data?.score ?? 0),
1540
- reason: typeof event.data?.reason === 'string' ? event.data.reason : null,
1541
- severity: typeof event.data?.severity === 'string' ? event.data.severity : null,
1542
- origin: typeof event.data?.origin === 'string' ? event.data.origin : null,
1543
- confidence: typeof event.data?.confidence === 'number' ? event.data.confidence : null,
1544
- createdAt: event.ts,
1545
- });
1546
- }
1547
- if (event.type === 'trust_change') {
1548
- this.recordTrustChange({
1549
- sessionId: event.sessionId,
1550
- previousScore: Number(event.data?.previousScore ?? 0),
1551
- newScore: Number(event.data?.newScore ?? 0),
1552
- delta: Number(event.data?.delta ?? 0),
1553
- reason: String(event.data?.reason ?? 'legacy'),
1554
- createdAt: event.ts,
1555
- });
1556
- }
1557
- if (event.type === 'gate_block') {
1558
- this.recordGateBlock({
1559
- sessionId: event.sessionId,
1560
- toolName: String(event.data?.toolName ?? 'unknown'),
1561
- filePath: typeof event.data?.filePath === 'string' ? event.data.filePath : null,
1562
- reason: String(event.data?.reason ?? 'legacy'),
1563
- planStatus: typeof event.data?.planStatus === 'string' ? event.data.planStatus : null,
1564
- createdAt: event.ts,
1565
- });
1566
- }
1567
- } catch {
1568
- // Ignore malformed legacy events.
1569
- }
1570
- }
1571
- this.markImported(key);
1572
- }
1573
-
1574
- private importLegacyEvolution(): void {
1575
- const key = 'legacy:evolution';
1576
- if (this.isImported(key)) return;
1577
- const evolutionPath = resolvePdPath(this.workspaceDir, 'EVOLUTION_STREAM');
1578
- if (!fs.existsSync(evolutionPath)) return;
1579
- const raw = fs.readFileSync(evolutionPath, 'utf8').trim();
1580
- if (!raw) {
1581
- this.markImported(key);
1582
- return;
1583
- }
1584
- for (const line of raw.split('\n')) {
1585
- try {
1586
- const event = JSON.parse(line) as { type?: string; data?: Record<string, unknown>; ts?: string };
1587
- this.recordPrincipleEvent({
1588
- principleId: typeof event.data?.principleId === 'string' ? event.data.principleId : null,
1589
- eventType: String(event.type ?? 'legacy'),
1590
- payload: event.data ?? {},
1591
- createdAt: event.ts,
1592
- });
1593
- } catch {
1594
- // Ignore malformed legacy evolution events.
1595
- }
1596
- }
1597
- this.markImported(key);
1598
- }
1599
-
1600
- private markImported(sourceKey: string): void {
1601
- this.withWrite(() => {
1602
- this.db.prepare(`
1603
- INSERT INTO ingest_checkpoint (source_key, imported_at)
1604
- VALUES (?, ?)
1605
- ON CONFLICT(source_key) DO UPDATE SET imported_at = excluded.imported_at
1606
- `).run(sourceKey, nowIso());
1607
- });
1608
- }
1609
-
1610
- private isImported(sourceKey: string): boolean {
1611
- const row = this.db.prepare('SELECT source_key FROM ingest_checkpoint WHERE source_key = ?').get(sourceKey);
1612
- return Boolean(row);
1613
- }
1614
-
1615
- private maybeCreateCorrectionSample(sessionId: string): void {
1616
- const pending = this.db.prepare(`
1617
- SELECT sample_id FROM correction_samples
1618
- WHERE session_id = ? AND review_status = 'pending'
1619
- ORDER BY created_at DESC
1620
- LIMIT 1
1621
- `).get(sessionId) as { sample_id?: string } | undefined;
1622
- if (pending?.sample_id) return;
1623
-
1624
- const correctionTurn = this.db.prepare(`
1625
- SELECT id, references_assistant_turn_id, correction_cue, raw_text, blob_ref
1626
- FROM user_turns
1627
- WHERE session_id = ? AND correction_detected = 1
1628
- ORDER BY id DESC
1629
- LIMIT 1
1630
- `).get(sessionId) as Record<string, unknown> | undefined;
1631
- if (!correctionTurn || !correctionTurn.references_assistant_turn_id) return;
1632
-
1633
- // #Phase2b-fix: Tool failure is NOT required for correction samples.
1634
- // User corrections are the highest-fidelity signal — they indicate the agent
1635
- // said or did something wrong, regardless of whether tool calls succeeded.
1636
- // Requiring tool failure excluded the most valuable cases: "agent did something
1637
- // that technically worked but violated a principle or was logically wrong."
1638
- const failedCall = this.db.prepare(`
1639
- SELECT id, tool_name, error_type, error_message
1640
- FROM tool_calls
1641
- WHERE session_id = ? AND outcome = 'failure'
1642
- ORDER BY id DESC
1643
- LIMIT 1
1644
- `).get(sessionId) as Record<string, unknown> | undefined;
1645
-
1646
- const recentCalls = this.db.prepare(`
1647
- SELECT id, tool_name, outcome
1648
- FROM tool_calls
1649
- WHERE session_id = ?
1650
- ORDER BY id DESC
1651
- LIMIT 5
1652
- `).all(sessionId) as Record<string, unknown>[];
1653
-
1654
- const successfulCalls = recentCalls.filter(c => c.outcome === 'success');
1655
-
1656
- // Generate sample ID from correction turn + first recent call (or correction id if no calls)
1657
- const refForHash = successfulCalls[0]?.id ?? correctionTurn.id;
1658
- const sampleId = `sample_${crypto.createHash('md5').update(`${sessionId}:${correctionTurn.id}:${refForHash}`).digest('hex').slice(0, 12)}`;
1659
- const userRawText = this.restoreRawText(correctionTurn.raw_text as string | null, correctionTurn.blob_ref as string | null);
1660
-
1661
- // Quality scoring: correction cue is always valuable
1662
- // Tool failure adds context (20pts), successful calls add context (up to 15pts)
1663
- // Pure conversation corrections still score 55-75 (high enough to review)
1664
- const qualityScore = [
1665
- correctionTurn.references_assistant_turn_id ? 35 : 0,
1666
- correctionTurn.correction_cue ? 20 : 0,
1667
- failedCall ? 20 : 0,
1668
- Math.min(successfulCalls.length, 3) * 5,
1669
- ].reduce((sum, value) => sum + value, 0);
1670
-
1671
- // Diff excerpt: prefer user correction text, fallback to error info, fallback to cue
1672
- const diffText = userRawText
1673
- || (failedCall ? String(failedCall.error_message ?? failedCall.error_type ?? failedCall.tool_name) : '')
1674
- || String(correctionTurn.correction_cue ?? 'user correction');
1675
-
1676
- this.withWrite(() => {
1677
- this.db.prepare(`
1678
- INSERT OR IGNORE INTO correction_samples (
1679
- sample_id, session_id, bad_assistant_turn_id, user_correction_turn_id,
1680
- recovery_tool_span_json, diff_excerpt, principle_ids_json, quality_score,
1681
- review_status, export_mode, created_at, updated_at
1682
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', 'raw', ?, ?)
1683
- `).run(
1684
- sampleId,
1685
- sessionId,
1686
- Number(correctionTurn.references_assistant_turn_id),
1687
- Number(correctionTurn.id),
1688
- safeJson(successfulCalls.map((call) => ({ id: call.id, toolName: call.tool_name, outcome: call.outcome }))),
1689
- summarizeForDiff(diffText),
1690
- '[]',
1691
- qualityScore,
1692
- nowIso(),
1693
- nowIso(),
1694
- );
1695
- });
1696
- }
1697
-
1698
-
1699
-
1700
- private recordExportAudit(
1701
- exportKind: string,
1702
- mode: CorrectionExportMode,
1703
- approvedOnly: boolean,
1704
- filePath: string,
1705
- rowCount: number,
1706
- ): void {
1707
- this.withWrite(() => {
1708
- this.db.prepare(`
1709
- INSERT INTO exports_audit (export_kind, mode, approved_only, file_path, row_count, created_at)
1710
- VALUES (?, ?, ?, ?, ?, ?)
1711
- `).run(exportKind, mode, approvedOnly ? 1 : 0, filePath, rowCount, nowIso());
1712
- });
1713
- }
1714
-
1715
- private storeRawText(kind: 'assistant' | 'user', text: string): { inlineText: string | null; blobRef: string | null; excerpt: string } {
1716
- const excerpt = text.length > 200 ? `${text.slice(0, 197)}...` : text;
1717
- const bytes = Buffer.byteLength(text, 'utf8');
1718
- if (bytes <= this.blobInlineThresholdBytes) {
1719
- return { inlineText: text, blobRef: null, excerpt };
1720
- }
1721
- const hash = crypto.createHash('sha256').update(text).digest('hex');
1722
- const relativePath = `${kind}-${hash}.txt`;
1723
- const fullPath = path.join(this.blobDir, relativePath);
1724
- if (!fs.existsSync(fullPath)) {
1725
- atomicWriteFileSync(fullPath, text);
1726
- }
1727
- return { inlineText: null, blobRef: relativePath, excerpt };
1728
- }
1729
-
1730
- private restoreRawText(inlineText: string | null, blobRef: string | null): string {
1731
- if (inlineText) return inlineText;
1732
- if (!blobRef) return '';
1733
- const fullPath = path.join(this.blobDir, blobRef);
1734
- return fs.existsSync(fullPath) ? fs.readFileSync(fullPath, 'utf8') : '';
1735
- }
1736
-
1737
- private computeBlobBytes(): number {
1738
- if (!fs.existsSync(this.blobDir)) return 0;
1739
- return fs.readdirSync(this.blobDir).reduce((sum, file) => sum + fileSizeIfExists(path.join(this.blobDir, file)), 0);
1740
- }
1741
-
1742
- private pruneUnreferencedBlobs(): { removedFiles: number; reclaimedBytes: number } {
1743
- if (!fs.existsSync(this.blobDir)) {
1744
- return { removedFiles: 0, reclaimedBytes: 0 };
1745
- }
1746
-
1747
- const referenced = new Set<string>();
1748
- const rows = this.db.prepare(`
1749
- SELECT blob_ref FROM assistant_turns WHERE blob_ref IS NOT NULL
1750
- UNION
1751
- SELECT blob_ref FROM user_turns WHERE blob_ref IS NOT NULL
1752
- `).all() as { blob_ref?: string | null }[];
1753
- for (const row of rows) {
1754
- if (row.blob_ref) referenced.add(String(row.blob_ref));
1755
- }
1756
-
1757
- const now = Date.now();
1758
- let removedFiles = 0;
1759
- let reclaimedBytes = 0;
1760
-
1761
- for (const entry of fs.readdirSync(this.blobDir)) {
1762
- if (referenced.has(entry)) continue;
1763
- const fullPath = path.join(this.blobDir, entry);
1764
-
1765
-
1766
- let stat: fs.Stats;
1767
- try {
1768
- stat = fs.statSync(fullPath);
1769
- } catch {
1770
- continue;
1771
- }
1772
- if (!stat.isFile()) continue;
1773
- if (this.orphanBlobGraceMs > 0 && now - stat.mtimeMs < this.orphanBlobGraceMs) continue;
1774
- reclaimedBytes += stat.size;
1775
- removedFiles += 1;
1776
- fs.rmSync(fullPath, { force: true });
1777
- }
1778
-
1779
- return { removedFiles, reclaimedBytes };
1780
- }
1781
-
1782
- private withWrite<T>(fn: () => T): T {
1783
- return withLock(this.dbPath, fn, { lockSuffix: '.trajectory.lock', lockStaleMs: 30000 });
1784
- }
1785
- }
1786
-
1787
- export class TrajectoryRegistry {
1788
- private static readonly instances = new Map<string, TrajectoryDatabase>();
1789
-
1790
- static get(workspaceDir: string, opts: Omit<TrajectoryDatabaseOptions, 'workspaceDir'> = {}): TrajectoryDatabase {
1791
- const normalized = path.resolve(workspaceDir);
1792
- const existing = this.instances.get(normalized);
1793
- if (existing) return existing;
1794
- const created = new TrajectoryDatabase({ workspaceDir: normalized, ...opts });
1795
- this.instances.set(normalized, created);
1796
- return created;
1797
- }
1798
-
1799
- static dispose(workspaceDir: string): void {
1800
- const normalized = path.resolve(workspaceDir);
1801
- const instance = this.instances.get(normalized);
1802
- if (instance) {
1803
- instance.dispose();
1804
- this.instances.delete(normalized);
1805
- }
1806
- }
1807
-
1808
- static clear(): void {
1809
- for (const instance of this.instances.values()) {
1810
- instance.dispose();
1811
- }
1812
- this.instances.clear();
1813
- }
1814
-
1815
-
1816
- static use<T>(workspaceDir: string, fn: (_db: TrajectoryDatabase) => T, opts: Omit<TrajectoryDatabaseOptions, 'workspaceDir'> = {}): T {
1817
-
1818
- const normalized = path.resolve(workspaceDir);
1819
- const existing = this.instances.get(normalized);
1820
- if (existing) {
1821
- return fn(existing);
1822
- }
1823
-
1824
- const transient = new TrajectoryDatabase({ workspaceDir: normalized, ...opts });
1825
- try {
1826
- return fn(transient);
1827
- } finally {
1828
- transient.dispose();
1829
- }
1830
- }
1831
- }