@winspan/claude-forge 8.54.4 → 9.2.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 (880) hide show
  1. package/DEVELOPMENT.md +649 -33
  2. package/README.md +154 -16
  3. package/dist/catalogs/agents.json +72 -0
  4. package/dist/catalogs/skills.json +194 -0
  5. package/dist/claudemd/claudemd-generator.d.ts +45 -45
  6. package/dist/claudemd/claudemd-generator.d.ts.map +1 -1
  7. package/dist/claudemd/claudemd-generator.js +128 -449
  8. package/dist/claudemd/claudemd-generator.js.map +1 -1
  9. package/dist/claudemd/index.d.ts +14 -4
  10. package/dist/claudemd/index.d.ts.map +1 -1
  11. package/dist/claudemd/index.js +15 -4
  12. package/dist/claudemd/index.js.map +1 -1
  13. package/dist/claudemd/resume-manager.d.ts.map +1 -1
  14. package/dist/claudemd/resume-manager.js +37 -9
  15. package/dist/claudemd/resume-manager.js.map +1 -1
  16. package/dist/claudemd/templates/swarm-protocol.md +35 -186
  17. package/dist/claudemd/violations-manager.d.ts +40 -0
  18. package/dist/claudemd/violations-manager.d.ts.map +1 -0
  19. package/dist/claudemd/violations-manager.js +106 -0
  20. package/dist/claudemd/violations-manager.js.map +1 -0
  21. package/dist/cli/commands/admin.d.ts +15 -0
  22. package/dist/cli/commands/admin.d.ts.map +1 -0
  23. package/dist/cli/commands/admin.js +177 -0
  24. package/dist/cli/commands/admin.js.map +1 -0
  25. package/dist/cli/commands/agents.d.ts +18 -0
  26. package/dist/cli/commands/agents.d.ts.map +1 -0
  27. package/dist/cli/commands/agents.js +160 -0
  28. package/dist/cli/commands/agents.js.map +1 -0
  29. package/dist/cli/commands/bypass.d.ts +18 -0
  30. package/dist/cli/commands/bypass.d.ts.map +1 -0
  31. package/dist/cli/commands/bypass.js +87 -0
  32. package/dist/cli/commands/bypass.js.map +1 -0
  33. package/dist/cli/commands/claudemd.d.ts +60 -0
  34. package/dist/cli/commands/claudemd.d.ts.map +1 -1
  35. package/dist/cli/commands/claudemd.js +174 -37
  36. package/dist/cli/commands/claudemd.js.map +1 -1
  37. package/dist/cli/commands/config.d.ts.map +1 -1
  38. package/dist/cli/commands/config.js +94 -1
  39. package/dist/cli/commands/config.js.map +1 -1
  40. package/dist/cli/commands/daemon.d.ts +39 -0
  41. package/dist/cli/commands/daemon.d.ts.map +1 -1
  42. package/dist/cli/commands/daemon.js +167 -20
  43. package/dist/cli/commands/daemon.js.map +1 -1
  44. package/dist/cli/commands/decisions.d.ts +129 -0
  45. package/dist/cli/commands/decisions.d.ts.map +1 -0
  46. package/dist/cli/commands/decisions.js +669 -0
  47. package/dist/cli/commands/decisions.js.map +1 -0
  48. package/dist/cli/commands/doctor.d.ts +29 -0
  49. package/dist/cli/commands/doctor.d.ts.map +1 -0
  50. package/dist/cli/commands/doctor.js +124 -0
  51. package/dist/cli/commands/doctor.js.map +1 -0
  52. package/dist/cli/commands/entropy.d.ts +35 -0
  53. package/dist/cli/commands/entropy.d.ts.map +1 -0
  54. package/dist/cli/commands/entropy.js +121 -0
  55. package/dist/cli/commands/entropy.js.map +1 -0
  56. package/dist/cli/commands/executions.d.ts +1 -0
  57. package/dist/cli/commands/executions.d.ts.map +1 -1
  58. package/dist/cli/commands/executions.js +10 -1
  59. package/dist/cli/commands/executions.js.map +1 -1
  60. package/dist/cli/commands/fix.d.ts +31 -0
  61. package/dist/cli/commands/fix.d.ts.map +1 -0
  62. package/dist/cli/commands/fix.js +108 -0
  63. package/dist/cli/commands/fix.js.map +1 -0
  64. package/dist/cli/commands/governance.d.ts +21 -0
  65. package/dist/cli/commands/governance.d.ts.map +1 -0
  66. package/dist/cli/commands/governance.js +60 -0
  67. package/dist/cli/commands/governance.js.map +1 -0
  68. package/dist/cli/commands/init.d.ts +27 -0
  69. package/dist/cli/commands/init.d.ts.map +1 -1
  70. package/dist/cli/commands/init.js +158 -146
  71. package/dist/cli/commands/init.js.map +1 -1
  72. package/dist/cli/commands/insights-goal-check.d.ts +50 -0
  73. package/dist/cli/commands/insights-goal-check.d.ts.map +1 -0
  74. package/dist/cli/commands/insights-goal-check.js +318 -0
  75. package/dist/cli/commands/insights-goal-check.js.map +1 -0
  76. package/dist/cli/commands/insights.d.ts +15 -0
  77. package/dist/cli/commands/insights.d.ts.map +1 -0
  78. package/dist/cli/commands/insights.js +127 -0
  79. package/dist/cli/commands/insights.js.map +1 -0
  80. package/dist/cli/commands/knowledge.d.ts +66 -0
  81. package/dist/cli/commands/knowledge.d.ts.map +1 -0
  82. package/dist/cli/commands/knowledge.js +897 -0
  83. package/dist/cli/commands/knowledge.js.map +1 -0
  84. package/dist/cli/commands/mcp.d.ts +0 -12
  85. package/dist/cli/commands/mcp.d.ts.map +1 -1
  86. package/dist/cli/commands/mcp.js +11 -5
  87. package/dist/cli/commands/mcp.js.map +1 -1
  88. package/dist/cli/commands/menu.d.ts.map +1 -1
  89. package/dist/cli/commands/menu.js +10 -184
  90. package/dist/cli/commands/menu.js.map +1 -1
  91. package/dist/cli/commands/project.d.ts +98 -0
  92. package/dist/cli/commands/project.d.ts.map +1 -0
  93. package/dist/cli/commands/project.js +382 -0
  94. package/dist/cli/commands/project.js.map +1 -0
  95. package/dist/cli/commands/skills.d.ts.map +1 -1
  96. package/dist/cli/commands/skills.js +14 -128
  97. package/dist/cli/commands/skills.js.map +1 -1
  98. package/dist/cli/commands/spec.d.ts +40 -0
  99. package/dist/cli/commands/spec.d.ts.map +1 -0
  100. package/dist/cli/commands/spec.js +49 -0
  101. package/dist/cli/commands/spec.js.map +1 -0
  102. package/dist/cli/commands/stats.d.ts.map +1 -1
  103. package/dist/cli/commands/stats.js +3 -2
  104. package/dist/cli/commands/stats.js.map +1 -1
  105. package/dist/cli/commands/status.d.ts.map +1 -1
  106. package/dist/cli/commands/status.js +17 -2
  107. package/dist/cli/commands/status.js.map +1 -1
  108. package/dist/cli/commands/trace.d.ts.map +1 -1
  109. package/dist/cli/commands/trace.js +4 -9
  110. package/dist/cli/commands/trace.js.map +1 -1
  111. package/dist/cli/commands/violations.d.ts +14 -0
  112. package/dist/cli/commands/violations.d.ts.map +1 -0
  113. package/dist/cli/commands/violations.js +43 -0
  114. package/dist/cli/commands/violations.js.map +1 -0
  115. package/dist/cli/index.js +26 -0
  116. package/dist/cli/index.js.map +1 -1
  117. package/dist/cli/init/hook-manager.d.ts +1 -1
  118. package/dist/cli/init/hook-manager.d.ts.map +1 -1
  119. package/dist/cli/init/hook-manager.js +6 -0
  120. package/dist/cli/init/hook-manager.js.map +1 -1
  121. package/dist/cli/utils/resolve-session.d.ts +32 -0
  122. package/dist/cli/utils/resolve-session.d.ts.map +1 -0
  123. package/dist/cli/utils/resolve-session.js +39 -0
  124. package/dist/cli/utils/resolve-session.js.map +1 -0
  125. package/dist/core/config.d.ts +4 -1
  126. package/dist/core/config.d.ts.map +1 -1
  127. package/dist/core/config.js +11 -23
  128. package/dist/core/config.js.map +1 -1
  129. package/dist/core/constants.d.ts +14 -13
  130. package/dist/core/constants.d.ts.map +1 -1
  131. package/dist/core/constants.js +20 -13
  132. package/dist/core/constants.js.map +1 -1
  133. package/dist/core/diagnostics/checks.d.ts +151 -0
  134. package/dist/core/diagnostics/checks.d.ts.map +1 -0
  135. package/dist/core/diagnostics/checks.js +765 -0
  136. package/dist/core/diagnostics/checks.js.map +1 -0
  137. package/dist/core/diagnostics/daemon-status.d.ts +77 -0
  138. package/dist/core/diagnostics/daemon-status.d.ts.map +1 -0
  139. package/dist/core/diagnostics/daemon-status.js +113 -0
  140. package/dist/core/diagnostics/daemon-status.js.map +1 -0
  141. package/dist/core/diagnostics/entropy-checks.d.ts +82 -0
  142. package/dist/core/diagnostics/entropy-checks.d.ts.map +1 -0
  143. package/dist/core/diagnostics/entropy-checks.js +395 -0
  144. package/dist/core/diagnostics/entropy-checks.js.map +1 -0
  145. package/dist/core/diagnostics/fix-runner.d.ts +54 -0
  146. package/dist/core/diagnostics/fix-runner.d.ts.map +1 -0
  147. package/dist/core/diagnostics/fix-runner.js +90 -0
  148. package/dist/core/diagnostics/fix-runner.js.map +1 -0
  149. package/dist/core/diagnostics/knip-runner.d.ts +49 -0
  150. package/dist/core/diagnostics/knip-runner.d.ts.map +1 -0
  151. package/dist/core/diagnostics/knip-runner.js +100 -0
  152. package/dist/core/diagnostics/knip-runner.js.map +1 -0
  153. package/dist/core/diagnostics/markers.d.ts +96 -0
  154. package/dist/core/diagnostics/markers.d.ts.map +1 -0
  155. package/dist/core/diagnostics/markers.js +153 -0
  156. package/dist/core/diagnostics/markers.js.map +1 -0
  157. package/dist/core/governance/global-inject.d.ts +60 -0
  158. package/dist/core/governance/global-inject.d.ts.map +1 -0
  159. package/dist/core/governance/global-inject.js +129 -0
  160. package/dist/core/governance/global-inject.js.map +1 -0
  161. package/dist/core/queue/index.d.ts +16 -3
  162. package/dist/core/queue/index.d.ts.map +1 -1
  163. package/dist/core/queue/index.js +14 -3
  164. package/dist/core/queue/index.js.map +1 -1
  165. package/dist/core/storage/base.d.ts +158 -0
  166. package/dist/core/storage/base.d.ts.map +1 -1
  167. package/dist/core/storage/base.js +570 -0
  168. package/dist/core/storage/base.js.map +1 -1
  169. package/dist/core/storage/codec/tool-input-codec.d.ts +93 -0
  170. package/dist/core/storage/codec/tool-input-codec.d.ts.map +1 -0
  171. package/dist/core/storage/codec/tool-input-codec.js +159 -0
  172. package/dist/core/storage/codec/tool-input-codec.js.map +1 -0
  173. package/dist/core/storage/decisions.d.ts +362 -0
  174. package/dist/core/storage/decisions.d.ts.map +1 -0
  175. package/dist/core/storage/decisions.js +502 -0
  176. package/dist/core/storage/decisions.js.map +1 -0
  177. package/dist/core/storage/events.d.ts +112 -8
  178. package/dist/core/storage/events.d.ts.map +1 -1
  179. package/dist/core/storage/events.js +390 -39
  180. package/dist/core/storage/events.js.map +1 -1
  181. package/dist/core/storage/feedback.d.ts +131 -0
  182. package/dist/core/storage/feedback.d.ts.map +1 -0
  183. package/dist/core/storage/feedback.js +187 -0
  184. package/dist/core/storage/feedback.js.map +1 -0
  185. package/dist/core/storage/forge-config.d.ts +40 -0
  186. package/dist/core/storage/forge-config.d.ts.map +1 -0
  187. package/dist/core/storage/forge-config.js +65 -0
  188. package/dist/core/storage/forge-config.js.map +1 -0
  189. package/dist/core/storage/injections.d.ts +28 -0
  190. package/dist/core/storage/injections.d.ts.map +1 -1
  191. package/dist/core/storage/injections.js +62 -5
  192. package/dist/core/storage/injections.js.map +1 -1
  193. package/dist/core/storage/knowledge.d.ts +106 -0
  194. package/dist/core/storage/knowledge.d.ts.map +1 -0
  195. package/dist/core/storage/knowledge.js +202 -0
  196. package/dist/core/storage/knowledge.js.map +1 -0
  197. package/dist/core/storage/maintenance.d.ts +36 -9
  198. package/dist/core/storage/maintenance.d.ts.map +1 -1
  199. package/dist/core/storage/maintenance.js +56 -24
  200. package/dist/core/storage/maintenance.js.map +1 -1
  201. package/dist/core/storage/pipeline-rollup.d.ts +117 -0
  202. package/dist/core/storage/pipeline-rollup.d.ts.map +1 -0
  203. package/dist/core/storage/pipeline-rollup.js +471 -0
  204. package/dist/core/storage/pipeline-rollup.js.map +1 -0
  205. package/dist/core/storage/routing.d.ts +16 -3
  206. package/dist/core/storage/routing.d.ts.map +1 -1
  207. package/dist/core/storage/routing.js +39 -8
  208. package/dist/core/storage/routing.js.map +1 -1
  209. package/dist/core/storage/rows.d.ts +50 -7
  210. package/dist/core/storage/rows.d.ts.map +1 -1
  211. package/dist/core/storage/schema.sql +302 -23
  212. package/dist/core/storage/sessions.d.ts +136 -0
  213. package/dist/core/storage/sessions.d.ts.map +1 -1
  214. package/dist/core/storage/sessions.js +351 -15
  215. package/dist/core/storage/sessions.js.map +1 -1
  216. package/dist/core/storage/skills.d.ts +1 -0
  217. package/dist/core/storage/skills.d.ts.map +1 -1
  218. package/dist/core/storage/skills.js +21 -6
  219. package/dist/core/storage/skills.js.map +1 -1
  220. package/dist/core/storage/sqlite.d.ts +253 -20
  221. package/dist/core/storage/sqlite.d.ts.map +1 -1
  222. package/dist/core/storage/sqlite.js +425 -16
  223. package/dist/core/storage/sqlite.js.map +1 -1
  224. package/dist/core/storage/tasks.d.ts +474 -2
  225. package/dist/core/storage/tasks.d.ts.map +1 -1
  226. package/dist/core/storage/tasks.js +1213 -18
  227. package/dist/core/storage/tasks.js.map +1 -1
  228. package/dist/core/storage/tool-intercepts.d.ts +69 -0
  229. package/dist/core/storage/tool-intercepts.d.ts.map +1 -0
  230. package/dist/core/storage/tool-intercepts.js +116 -0
  231. package/dist/core/storage/tool-intercepts.js.map +1 -0
  232. package/dist/core/storage/workflow-recommendations.d.ts +124 -0
  233. package/dist/core/storage/workflow-recommendations.d.ts.map +1 -0
  234. package/dist/core/storage/workflow-recommendations.js +274 -0
  235. package/dist/core/storage/workflow-recommendations.js.map +1 -0
  236. package/dist/core/types.d.ts +112 -17
  237. package/dist/core/types.d.ts.map +1 -1
  238. package/dist/core/types.js +12 -0
  239. package/dist/core/types.js.map +1 -1
  240. package/dist/core/utils/backup.d.ts +81 -0
  241. package/dist/core/utils/backup.d.ts.map +1 -0
  242. package/dist/core/utils/backup.js +98 -0
  243. package/dist/core/utils/backup.js.map +1 -0
  244. package/dist/core/utils/binary-paths.d.ts +92 -0
  245. package/dist/core/utils/binary-paths.d.ts.map +1 -0
  246. package/dist/core/utils/binary-paths.js +166 -0
  247. package/dist/core/utils/binary-paths.js.map +1 -0
  248. package/dist/core/utils/bypass-token.d.ts +75 -0
  249. package/dist/core/utils/bypass-token.d.ts.map +1 -0
  250. package/dist/core/utils/bypass-token.js +133 -0
  251. package/dist/core/utils/bypass-token.js.map +1 -0
  252. package/dist/core/utils/cc-builtin-agents.d.ts +3 -0
  253. package/dist/core/utils/cc-builtin-agents.d.ts.map +1 -0
  254. package/dist/core/utils/cc-builtin-agents.js +29 -0
  255. package/dist/core/utils/cc-builtin-agents.js.map +1 -0
  256. package/dist/core/utils/claude-cli-spawn.d.ts +106 -0
  257. package/dist/core/utils/claude-cli-spawn.d.ts.map +1 -0
  258. package/dist/core/utils/claude-cli-spawn.js +219 -0
  259. package/dist/core/utils/claude-cli-spawn.js.map +1 -0
  260. package/dist/core/utils/forge-resume-block.d.ts.map +1 -1
  261. package/dist/core/utils/forge-resume-block.js +3 -2
  262. package/dist/core/utils/forge-resume-block.js.map +1 -1
  263. package/dist/core/utils/logger.d.ts +15 -3
  264. package/dist/core/utils/logger.d.ts.map +1 -1
  265. package/dist/core/utils/logger.js +20 -2
  266. package/dist/core/utils/logger.js.map +1 -1
  267. package/dist/core/utils/noise-prompt.d.ts +97 -0
  268. package/dist/core/utils/noise-prompt.d.ts.map +1 -0
  269. package/dist/core/utils/noise-prompt.js +127 -0
  270. package/dist/core/utils/noise-prompt.js.map +1 -0
  271. package/dist/core/utils/path.d.ts +0 -4
  272. package/dist/core/utils/path.d.ts.map +1 -1
  273. package/dist/core/utils/path.js +0 -7
  274. package/dist/core/utils/path.js.map +1 -1
  275. package/dist/core/utils/time.d.ts +41 -0
  276. package/dist/core/utils/time.d.ts.map +1 -1
  277. package/dist/core/utils/time.js +114 -0
  278. package/dist/core/utils/time.js.map +1 -1
  279. package/dist/daemon/agent-sync.d.ts +24 -0
  280. package/dist/daemon/agent-sync.d.ts.map +1 -0
  281. package/dist/daemon/agent-sync.js +114 -0
  282. package/dist/daemon/agent-sync.js.map +1 -0
  283. package/dist/daemon/config-store.d.ts +55 -0
  284. package/dist/daemon/config-store.d.ts.map +1 -0
  285. package/dist/daemon/config-store.js +137 -0
  286. package/dist/daemon/config-store.js.map +1 -0
  287. package/dist/daemon/event-parser.d.ts +22 -0
  288. package/dist/daemon/event-parser.d.ts.map +1 -1
  289. package/dist/daemon/event-parser.js +49 -3
  290. package/dist/daemon/event-parser.js.map +1 -1
  291. package/dist/daemon/handlers/history-exporter.d.ts.map +1 -1
  292. package/dist/daemon/handlers/history-exporter.js +9 -8
  293. package/dist/daemon/handlers/history-exporter.js.map +1 -1
  294. package/dist/daemon/handlers/post-tool-use.d.ts +58 -4
  295. package/dist/daemon/handlers/post-tool-use.d.ts.map +1 -1
  296. package/dist/daemon/handlers/post-tool-use.js +261 -8
  297. package/dist/daemon/handlers/post-tool-use.js.map +1 -1
  298. package/dist/daemon/handlers/pre-tool-use.d.ts +156 -0
  299. package/dist/daemon/handlers/pre-tool-use.d.ts.map +1 -0
  300. package/dist/daemon/handlers/pre-tool-use.js +585 -0
  301. package/dist/daemon/handlers/pre-tool-use.js.map +1 -0
  302. package/dist/daemon/handlers/stop.d.ts +35 -7
  303. package/dist/daemon/handlers/stop.d.ts.map +1 -1
  304. package/dist/daemon/handlers/stop.js +157 -8
  305. package/dist/daemon/handlers/stop.js.map +1 -1
  306. package/dist/daemon/handlers/user-prompt.d.ts +36 -14
  307. package/dist/daemon/handlers/user-prompt.d.ts.map +1 -1
  308. package/dist/daemon/handlers/user-prompt.js +135 -48
  309. package/dist/daemon/handlers/user-prompt.js.map +1 -1
  310. package/dist/daemon/hook-sync.d.ts.map +1 -1
  311. package/dist/daemon/hook-sync.js +2 -1
  312. package/dist/daemon/hook-sync.js.map +1 -1
  313. package/dist/daemon/index.d.ts.map +1 -1
  314. package/dist/daemon/index.js +471 -43
  315. package/dist/daemon/index.js.map +1 -1
  316. package/dist/daemon/lifecycle.d.ts +48 -1
  317. package/dist/daemon/lifecycle.d.ts.map +1 -1
  318. package/dist/daemon/lifecycle.js +98 -2
  319. package/dist/daemon/lifecycle.js.map +1 -1
  320. package/dist/daemon/router.d.ts +4 -1
  321. package/dist/daemon/router.d.ts.map +1 -1
  322. package/dist/daemon/router.js +4 -2
  323. package/dist/daemon/router.js.map +1 -1
  324. package/dist/daemon/rules/defaults.d.ts +20 -0
  325. package/dist/daemon/rules/defaults.d.ts.map +1 -0
  326. package/dist/daemon/rules/defaults.js +692 -0
  327. package/dist/daemon/rules/defaults.js.map +1 -0
  328. package/dist/daemon/rules/registry.d.ts +47 -0
  329. package/dist/daemon/rules/registry.d.ts.map +1 -0
  330. package/dist/daemon/rules/registry.js +84 -0
  331. package/dist/daemon/rules/registry.js.map +1 -0
  332. package/dist/daemon/rules/types.d.ts +170 -0
  333. package/dist/daemon/rules/types.d.ts.map +1 -0
  334. package/dist/daemon/rules/types.js +15 -0
  335. package/dist/daemon/rules/types.js.map +1 -0
  336. package/dist/daemon/rules/whitelist.d.ts +101 -0
  337. package/dist/daemon/rules/whitelist.d.ts.map +1 -0
  338. package/dist/daemon/rules/whitelist.js +210 -0
  339. package/dist/daemon/rules/whitelist.js.map +1 -0
  340. package/dist/daemon/rules/workflow-defaults.d.ts +52 -0
  341. package/dist/daemon/rules/workflow-defaults.d.ts.map +1 -0
  342. package/dist/daemon/rules/workflow-defaults.js +521 -0
  343. package/dist/daemon/rules/workflow-defaults.js.map +1 -0
  344. package/dist/daemon/server.d.ts +11 -1
  345. package/dist/daemon/server.d.ts.map +1 -1
  346. package/dist/daemon/server.js +7 -1
  347. package/dist/daemon/server.js.map +1 -1
  348. package/dist/daemon/services/context-injector.d.ts +34 -0
  349. package/dist/daemon/services/context-injector.d.ts.map +1 -0
  350. package/dist/daemon/services/context-injector.js +61 -0
  351. package/dist/daemon/services/context-injector.js.map +1 -0
  352. package/dist/daemon/services/decision-hint.d.ts +203 -0
  353. package/dist/daemon/services/decision-hint.d.ts.map +1 -0
  354. package/dist/daemon/services/decision-hint.js +487 -0
  355. package/dist/daemon/services/decision-hint.js.map +1 -0
  356. package/dist/daemon/services/event-ttl-sweep.d.ts +86 -0
  357. package/dist/daemon/services/event-ttl-sweep.d.ts.map +1 -0
  358. package/dist/daemon/services/event-ttl-sweep.js +123 -0
  359. package/dist/daemon/services/event-ttl-sweep.js.map +1 -0
  360. package/dist/daemon/services/experience-extractor.d.ts +67 -0
  361. package/dist/daemon/services/experience-extractor.d.ts.map +1 -0
  362. package/dist/daemon/services/experience-extractor.js +323 -0
  363. package/dist/daemon/services/experience-extractor.js.map +1 -0
  364. package/dist/daemon/services/feedback-aggregator.d.ts +179 -0
  365. package/dist/daemon/services/feedback-aggregator.d.ts.map +1 -0
  366. package/dist/daemon/services/feedback-aggregator.js +455 -0
  367. package/dist/daemon/services/feedback-aggregator.js.map +1 -0
  368. package/dist/daemon/services/heartbeat-writer.d.ts +55 -0
  369. package/dist/daemon/services/heartbeat-writer.d.ts.map +1 -0
  370. package/dist/daemon/services/heartbeat-writer.js +111 -0
  371. package/dist/daemon/services/heartbeat-writer.js.map +1 -0
  372. package/dist/daemon/services/idle-session-sweeper.d.ts +61 -0
  373. package/dist/daemon/services/idle-session-sweeper.d.ts.map +1 -0
  374. package/dist/daemon/services/idle-session-sweeper.js +94 -0
  375. package/dist/daemon/services/idle-session-sweeper.js.map +1 -0
  376. package/dist/daemon/services/idle-task-budget.d.ts +50 -0
  377. package/dist/daemon/services/idle-task-budget.d.ts.map +1 -0
  378. package/dist/daemon/services/idle-task-budget.js +72 -0
  379. package/dist/daemon/services/idle-task-budget.js.map +1 -0
  380. package/dist/daemon/services/intercept-revive.d.ts +60 -0
  381. package/dist/daemon/services/intercept-revive.d.ts.map +1 -0
  382. package/dist/daemon/services/intercept-revive.js +86 -0
  383. package/dist/daemon/services/intercept-revive.js.map +1 -0
  384. package/dist/daemon/services/intercept-rollback-guard.d.ts +105 -0
  385. package/dist/daemon/services/intercept-rollback-guard.d.ts.map +1 -0
  386. package/dist/daemon/services/intercept-rollback-guard.js +152 -0
  387. package/dist/daemon/services/intercept-rollback-guard.js.map +1 -0
  388. package/dist/daemon/services/intercept-timeout-sweeper.d.ts +58 -0
  389. package/dist/daemon/services/intercept-timeout-sweeper.d.ts.map +1 -0
  390. package/dist/daemon/services/intercept-timeout-sweeper.js +83 -0
  391. package/dist/daemon/services/intercept-timeout-sweeper.js.map +1 -0
  392. package/dist/daemon/services/kb-injector.d.ts +57 -0
  393. package/dist/daemon/services/kb-injector.d.ts.map +1 -0
  394. package/dist/daemon/services/kb-injector.js +140 -0
  395. package/dist/daemon/services/kb-injector.js.map +1 -0
  396. package/dist/daemon/services/outcome-classification-service.d.ts +49 -0
  397. package/dist/daemon/services/outcome-classification-service.d.ts.map +1 -0
  398. package/dist/daemon/services/outcome-classification-service.js +214 -0
  399. package/dist/daemon/services/outcome-classification-service.js.map +1 -0
  400. package/dist/daemon/services/outcome-classifier.d.ts +136 -0
  401. package/dist/daemon/services/outcome-classifier.d.ts.map +1 -0
  402. package/dist/daemon/services/outcome-classifier.js +178 -0
  403. package/dist/daemon/services/outcome-classifier.js.map +1 -0
  404. package/dist/daemon/services/outcome-nudge.d.ts +107 -0
  405. package/dist/daemon/services/outcome-nudge.d.ts.map +1 -0
  406. package/dist/daemon/services/outcome-nudge.js +242 -0
  407. package/dist/daemon/services/outcome-nudge.js.map +1 -0
  408. package/dist/daemon/services/spec-approval.d.ts +127 -0
  409. package/dist/daemon/services/spec-approval.d.ts.map +1 -0
  410. package/dist/daemon/services/spec-approval.js +216 -0
  411. package/dist/daemon/services/spec-approval.js.map +1 -0
  412. package/dist/daemon/services/spec-gate.d.ts +54 -0
  413. package/dist/daemon/services/spec-gate.d.ts.map +1 -0
  414. package/dist/daemon/services/spec-gate.js +113 -0
  415. package/dist/daemon/services/spec-gate.js.map +1 -0
  416. package/dist/daemon/services/task-boundary-classifier.d.ts +78 -0
  417. package/dist/daemon/services/task-boundary-classifier.d.ts.map +1 -0
  418. package/dist/daemon/services/task-boundary-classifier.js +202 -0
  419. package/dist/daemon/services/task-boundary-classifier.js.map +1 -0
  420. package/dist/daemon/services/task-segmenter.d.ts +219 -1
  421. package/dist/daemon/services/task-segmenter.d.ts.map +1 -1
  422. package/dist/daemon/services/task-segmenter.js +481 -17
  423. package/dist/daemon/services/task-segmenter.js.map +1 -1
  424. package/dist/daemon/services/violation-reporter.d.ts +130 -0
  425. package/dist/daemon/services/violation-reporter.d.ts.map +1 -0
  426. package/dist/daemon/services/violation-reporter.js +339 -0
  427. package/dist/daemon/services/violation-reporter.js.map +1 -0
  428. package/dist/daemon/skill-sync.d.ts +7 -2
  429. package/dist/daemon/skill-sync.d.ts.map +1 -1
  430. package/dist/daemon/skill-sync.js +114 -9
  431. package/dist/daemon/skill-sync.js.map +1 -1
  432. package/dist/daemon/templates/agents/claudemd-writer.md +101 -0
  433. package/dist/daemon/templates/agents/coder.md +105 -0
  434. package/dist/daemon/templates/agents/decision-maker.md +460 -0
  435. package/dist/daemon/templates/agents/doc-reviewer.md +115 -0
  436. package/dist/daemon/templates/agents/harness-debug-full.md +114 -0
  437. package/dist/daemon/templates/agents/harness-hotfix.md +99 -0
  438. package/dist/daemon/templates/agents/hybrid-feature-with-safety.md +104 -0
  439. package/dist/daemon/templates/agents/knowledge-builder.md +119 -0
  440. package/dist/daemon/templates/agents/patch-applier.md +144 -0
  441. package/dist/daemon/templates/agents/planner.md +165 -0
  442. package/dist/daemon/templates/agents/refactor-specialist.md +98 -0
  443. package/dist/daemon/templates/agents/skill-distiller.md +113 -0
  444. package/dist/daemon/templates/agents/task-boundary-classifier.md +64 -0
  445. package/dist/daemon/templates/agents/verify-agent.md +136 -0
  446. package/dist/daemon/utils/inject-block.d.ts +39 -0
  447. package/dist/daemon/utils/inject-block.d.ts.map +1 -0
  448. package/dist/daemon/utils/inject-block.js +25 -0
  449. package/dist/daemon/utils/inject-block.js.map +1 -0
  450. package/dist/hooks/hook-lib.sh +8 -0
  451. package/dist/hooks/notification.sh +19 -8
  452. package/dist/hooks/post-tool-use.sh +41 -23
  453. package/dist/hooks/pre-tool-use.sh +54 -23
  454. package/dist/hooks/session-start.sh +68 -0
  455. package/dist/hooks/stop.sh +24 -10
  456. package/dist/hooks/user-prompt-submit.sh +37 -21
  457. package/dist/knowledge/adapters/go-adapter.d.ts +65 -0
  458. package/dist/knowledge/adapters/go-adapter.d.ts.map +1 -0
  459. package/dist/knowledge/adapters/go-adapter.js +294 -0
  460. package/dist/knowledge/adapters/go-adapter.js.map +1 -0
  461. package/dist/knowledge/adapters/index.d.ts +41 -0
  462. package/dist/knowledge/adapters/index.d.ts.map +1 -0
  463. package/dist/knowledge/adapters/index.js +71 -0
  464. package/dist/knowledge/adapters/index.js.map +1 -0
  465. package/dist/knowledge/adapters/java-adapter.d.ts +66 -0
  466. package/dist/knowledge/adapters/java-adapter.d.ts.map +1 -0
  467. package/dist/knowledge/adapters/java-adapter.js +260 -0
  468. package/dist/knowledge/adapters/java-adapter.js.map +1 -0
  469. package/dist/knowledge/adapters/js-vue-adapter.d.ts +56 -0
  470. package/dist/knowledge/adapters/js-vue-adapter.d.ts.map +1 -0
  471. package/dist/knowledge/adapters/js-vue-adapter.js +203 -0
  472. package/dist/knowledge/adapters/js-vue-adapter.js.map +1 -0
  473. package/dist/knowledge/adapters/kotlin-adapter.d.ts +55 -0
  474. package/dist/knowledge/adapters/kotlin-adapter.d.ts.map +1 -0
  475. package/dist/knowledge/adapters/kotlin-adapter.js +209 -0
  476. package/dist/knowledge/adapters/kotlin-adapter.js.map +1 -0
  477. package/dist/knowledge/adapters/monorepo-adapter.d.ts +77 -0
  478. package/dist/knowledge/adapters/monorepo-adapter.d.ts.map +1 -0
  479. package/dist/knowledge/adapters/monorepo-adapter.js +170 -0
  480. package/dist/knowledge/adapters/monorepo-adapter.js.map +1 -0
  481. package/dist/knowledge/adapters/python-adapter.d.ts +89 -0
  482. package/dist/knowledge/adapters/python-adapter.d.ts.map +1 -0
  483. package/dist/knowledge/adapters/python-adapter.js +358 -0
  484. package/dist/knowledge/adapters/python-adapter.js.map +1 -0
  485. package/dist/knowledge/adapters/rust-adapter.d.ts +73 -0
  486. package/dist/knowledge/adapters/rust-adapter.d.ts.map +1 -0
  487. package/dist/knowledge/adapters/rust-adapter.js +329 -0
  488. package/dist/knowledge/adapters/rust-adapter.js.map +1 -0
  489. package/dist/knowledge/adapters/types.d.ts +99 -0
  490. package/dist/knowledge/adapters/types.d.ts.map +1 -0
  491. package/dist/knowledge/adapters/types.js +17 -0
  492. package/dist/knowledge/adapters/types.js.map +1 -0
  493. package/dist/knowledge/adapters/typescript-adapter.d.ts +57 -0
  494. package/dist/knowledge/adapters/typescript-adapter.d.ts.map +1 -0
  495. package/dist/knowledge/adapters/typescript-adapter.js +171 -0
  496. package/dist/knowledge/adapters/typescript-adapter.js.map +1 -0
  497. package/dist/knowledge/audit-applier.d.ts +70 -0
  498. package/dist/knowledge/audit-applier.d.ts.map +1 -0
  499. package/dist/knowledge/audit-applier.js +251 -0
  500. package/dist/knowledge/audit-applier.js.map +1 -0
  501. package/dist/knowledge/builder.d.ts +261 -0
  502. package/dist/knowledge/builder.d.ts.map +1 -0
  503. package/dist/knowledge/builder.js +937 -0
  504. package/dist/knowledge/builder.js.map +1 -0
  505. package/dist/knowledge/cli-provider.d.ts +151 -0
  506. package/dist/knowledge/cli-provider.d.ts.map +1 -0
  507. package/dist/knowledge/cli-provider.js +313 -0
  508. package/dist/knowledge/cli-provider.js.map +1 -0
  509. package/dist/knowledge/constants.d.ts +73 -0
  510. package/dist/knowledge/constants.d.ts.map +1 -0
  511. package/dist/knowledge/constants.js +93 -0
  512. package/dist/knowledge/constants.js.map +1 -0
  513. package/dist/knowledge/cross-module.d.ts +139 -0
  514. package/dist/knowledge/cross-module.d.ts.map +1 -0
  515. package/dist/knowledge/cross-module.js +370 -0
  516. package/dist/knowledge/cross-module.js.map +1 -0
  517. package/dist/knowledge/git-hooks.d.ts +67 -0
  518. package/dist/knowledge/git-hooks.d.ts.map +1 -0
  519. package/dist/knowledge/git-hooks.js +258 -0
  520. package/dist/knowledge/git-hooks.js.map +1 -0
  521. package/dist/knowledge/module-hash.d.ts +88 -0
  522. package/dist/knowledge/module-hash.d.ts.map +1 -0
  523. package/dist/knowledge/module-hash.js +162 -0
  524. package/dist/knowledge/module-hash.js.map +1 -0
  525. package/dist/knowledge/project-detector.d.ts +101 -0
  526. package/dist/knowledge/project-detector.d.ts.map +1 -0
  527. package/dist/knowledge/project-detector.js +223 -0
  528. package/dist/knowledge/project-detector.js.map +1 -0
  529. package/dist/knowledge/prompt.d.ts +228 -0
  530. package/dist/knowledge/prompt.d.ts.map +1 -0
  531. package/dist/knowledge/prompt.js +404 -0
  532. package/dist/knowledge/prompt.js.map +1 -0
  533. package/dist/knowledge/query.d.ts +105 -0
  534. package/dist/knowledge/query.d.ts.map +1 -0
  535. package/dist/knowledge/query.js +341 -0
  536. package/dist/knowledge/query.js.map +1 -0
  537. package/dist/knowledge/repo-map.d.ts +91 -0
  538. package/dist/knowledge/repo-map.d.ts.map +1 -0
  539. package/dist/knowledge/repo-map.js +408 -0
  540. package/dist/knowledge/repo-map.js.map +1 -0
  541. package/dist/knowledge/tools/index.d.ts +14 -0
  542. package/dist/knowledge/tools/index.d.ts.map +1 -0
  543. package/dist/knowledge/tools/index.js +11 -0
  544. package/dist/knowledge/tools/index.js.map +1 -0
  545. package/dist/knowledge/tools/knowledge-get-page.d.ts +46 -0
  546. package/dist/knowledge/tools/knowledge-get-page.d.ts.map +1 -0
  547. package/dist/knowledge/tools/knowledge-get-page.js +101 -0
  548. package/dist/knowledge/tools/knowledge-get-page.js.map +1 -0
  549. package/dist/knowledge/tools/knowledge-query.d.ts +77 -0
  550. package/dist/knowledge/tools/knowledge-query.d.ts.map +1 -0
  551. package/dist/knowledge/tools/knowledge-query.js +104 -0
  552. package/dist/knowledge/tools/knowledge-query.js.map +1 -0
  553. package/dist/knowledge/tools/repo-map-lookup.d.ts +45 -0
  554. package/dist/knowledge/tools/repo-map-lookup.d.ts.map +1 -0
  555. package/dist/knowledge/tools/repo-map-lookup.js +82 -0
  556. package/dist/knowledge/tools/repo-map-lookup.js.map +1 -0
  557. package/dist/knowledge/types.d.ts +269 -0
  558. package/dist/knowledge/types.d.ts.map +1 -0
  559. package/dist/knowledge/types.js +10 -0
  560. package/dist/knowledge/types.js.map +1 -0
  561. package/dist/knowledge/validator.d.ts +90 -0
  562. package/dist/knowledge/validator.d.ts.map +1 -0
  563. package/dist/knowledge/validator.js +288 -0
  564. package/dist/knowledge/validator.js.map +1 -0
  565. package/dist/mcp/server.d.ts.map +1 -1
  566. package/dist/mcp/server.js +222 -1
  567. package/dist/mcp/server.js.map +1 -1
  568. package/dist/skills/builtin-skills.d.ts +35 -0
  569. package/dist/skills/builtin-skills.d.ts.map +1 -0
  570. package/dist/skills/builtin-skills.js +68 -0
  571. package/dist/skills/builtin-skills.js.map +1 -0
  572. package/dist/skills/distill/attribution.d.ts +59 -0
  573. package/dist/skills/distill/attribution.d.ts.map +1 -0
  574. package/dist/skills/distill/attribution.js +101 -0
  575. package/dist/skills/distill/attribution.js.map +1 -0
  576. package/dist/skills/distill/claude-cli-resolver.d.ts +26 -0
  577. package/dist/skills/distill/claude-cli-resolver.d.ts.map +1 -0
  578. package/dist/skills/distill/claude-cli-resolver.js +115 -0
  579. package/dist/skills/distill/claude-cli-resolver.js.map +1 -0
  580. package/dist/skills/distill/distiller.d.ts +161 -0
  581. package/dist/skills/distill/distiller.d.ts.map +1 -0
  582. package/dist/skills/distill/distiller.js +461 -0
  583. package/dist/skills/distill/distiller.js.map +1 -0
  584. package/dist/skills/distill/index.d.ts +223 -0
  585. package/dist/skills/distill/index.d.ts.map +1 -0
  586. package/dist/skills/distill/index.js +466 -0
  587. package/dist/skills/distill/index.js.map +1 -0
  588. package/dist/skills/distill/project-anchor-guard.d.ts +116 -0
  589. package/dist/skills/distill/project-anchor-guard.d.ts.map +1 -0
  590. package/dist/skills/distill/project-anchor-guard.js +334 -0
  591. package/dist/skills/distill/project-anchor-guard.js.map +1 -0
  592. package/dist/skills/distill/topic-deduper.d.ts +77 -0
  593. package/dist/skills/distill/topic-deduper.d.ts.map +1 -0
  594. package/dist/skills/distill/topic-deduper.js +119 -0
  595. package/dist/skills/distill/topic-deduper.js.map +1 -0
  596. package/dist/skills/distill/upstream-fetcher.d.ts +71 -0
  597. package/dist/skills/distill/upstream-fetcher.d.ts.map +1 -0
  598. package/dist/skills/distill/upstream-fetcher.js +202 -0
  599. package/dist/skills/distill/upstream-fetcher.js.map +1 -0
  600. package/dist/skills/distilled/distilled-api-design.md +491 -0
  601. package/dist/skills/distilled/distilled-architecture-decision.md +173 -0
  602. package/dist/skills/distilled/distilled-creator.md +178 -0
  603. package/dist/skills/distilled/distilled-db-schema-design.md +245 -0
  604. package/dist/skills/distilled/distilled-defi-amm-security.md +293 -0
  605. package/dist/skills/distilled/distilled-executing-plans.md +113 -0
  606. package/dist/skills/distilled/distilled-harness-engineering.md +242 -0
  607. package/dist/skills/distilled/distilled-karpathy-guidelines.md +104 -0
  608. package/dist/skills/distilled/distilled-performance-optimization.md +175 -0
  609. package/dist/skills/distilled/distilled-spec-driven-design.md +193 -0
  610. package/dist/skills/distilled/distilled-systematic-debugging.md +306 -0
  611. package/dist/skills/distilled/distilled-verification-before-completion.md +203 -0
  612. package/dist/skills/keyword-score.d.ts +29 -0
  613. package/dist/skills/keyword-score.d.ts.map +1 -0
  614. package/dist/skills/keyword-score.js +54 -0
  615. package/dist/skills/keyword-score.js.map +1 -0
  616. package/dist/skills/registry.d.ts +64 -20
  617. package/dist/skills/registry.d.ts.map +1 -1
  618. package/dist/skills/registry.js +102 -105
  619. package/dist/skills/registry.js.map +1 -1
  620. package/dist/skills/tools/pipeline-suggest.js +14 -14
  621. package/dist/skills/tools/skill-invoke.d.ts +1 -1
  622. package/dist/skills/tools/skill-invoke.js +1 -1
  623. package/dist/web/routes/agent-content.d.ts +30 -0
  624. package/dist/web/routes/agent-content.d.ts.map +1 -0
  625. package/dist/web/routes/agent-content.js +139 -0
  626. package/dist/web/routes/agent-content.js.map +1 -0
  627. package/dist/web/routes/decisions.d.ts +15 -0
  628. package/dist/web/routes/decisions.d.ts.map +1 -0
  629. package/dist/web/routes/decisions.js +181 -0
  630. package/dist/web/routes/decisions.js.map +1 -0
  631. package/dist/web/routes/diagnostics.d.ts +61 -0
  632. package/dist/web/routes/diagnostics.d.ts.map +1 -0
  633. package/dist/web/routes/diagnostics.js +203 -0
  634. package/dist/web/routes/diagnostics.js.map +1 -0
  635. package/dist/web/routes/events.d.ts.map +1 -1
  636. package/dist/web/routes/events.js +24 -0
  637. package/dist/web/routes/events.js.map +1 -1
  638. package/dist/web/routes/health.d.ts +33 -0
  639. package/dist/web/routes/health.d.ts.map +1 -0
  640. package/dist/web/routes/health.js +37 -0
  641. package/dist/web/routes/health.js.map +1 -0
  642. package/dist/web/routes/insights.d.ts +0 -5
  643. package/dist/web/routes/insights.d.ts.map +1 -1
  644. package/dist/web/routes/insights.js +783 -2
  645. package/dist/web/routes/insights.js.map +1 -1
  646. package/dist/web/routes/knowledge.d.ts +16 -0
  647. package/dist/web/routes/knowledge.d.ts.map +1 -0
  648. package/dist/web/routes/knowledge.js +661 -0
  649. package/dist/web/routes/knowledge.js.map +1 -0
  650. package/dist/web/routes/patch.d.ts +60 -1
  651. package/dist/web/routes/patch.d.ts.map +1 -1
  652. package/dist/web/routes/patch.js +170 -64
  653. package/dist/web/routes/patch.js.map +1 -1
  654. package/dist/web/routes/pipeline.d.ts +37 -0
  655. package/dist/web/routes/pipeline.d.ts.map +1 -0
  656. package/dist/web/routes/pipeline.js +149 -0
  657. package/dist/web/routes/pipeline.js.map +1 -0
  658. package/dist/web/routes/rules.d.ts.map +1 -1
  659. package/dist/web/routes/rules.js +6 -1
  660. package/dist/web/routes/rules.js.map +1 -1
  661. package/dist/web/routes/sessions.d.ts.map +1 -1
  662. package/dist/web/routes/sessions.js +9 -1
  663. package/dist/web/routes/sessions.js.map +1 -1
  664. package/dist/web/routes/skill-content.d.ts +30 -0
  665. package/dist/web/routes/skill-content.d.ts.map +1 -0
  666. package/dist/web/routes/skill-content.js +117 -0
  667. package/dist/web/routes/skill-content.js.map +1 -0
  668. package/dist/web/routes/skills-distill.d.ts +29 -0
  669. package/dist/web/routes/skills-distill.d.ts.map +1 -0
  670. package/dist/web/routes/skills-distill.js +552 -0
  671. package/dist/web/routes/skills-distill.js.map +1 -0
  672. package/dist/web/routes/skills.js +7 -7
  673. package/dist/web/routes/skills.js.map +1 -1
  674. package/dist/web/routes/task-timeline.d.ts +102 -0
  675. package/dist/web/routes/task-timeline.d.ts.map +1 -0
  676. package/dist/web/routes/task-timeline.js +274 -0
  677. package/dist/web/routes/task-timeline.js.map +1 -0
  678. package/dist/web/routes/tasks.d.ts.map +1 -1
  679. package/dist/web/routes/tasks.js +355 -8
  680. package/dist/web/routes/tasks.js.map +1 -1
  681. package/dist/web/routes/trace.d.ts.map +1 -1
  682. package/dist/web/routes/trace.js +3 -2
  683. package/dist/web/routes/trace.js.map +1 -1
  684. package/dist/web/routes/types.d.ts +0 -4
  685. package/dist/web/routes/types.d.ts.map +1 -1
  686. package/dist/web/routes/types.js +1 -1
  687. package/dist/web/routes/types.js.map +1 -1
  688. package/dist/web/routes/violations.d.ts +14 -0
  689. package/dist/web/routes/violations.d.ts.map +1 -0
  690. package/dist/web/routes/violations.js +111 -0
  691. package/dist/web/routes/violations.js.map +1 -0
  692. package/dist/web/server.d.ts.map +1 -1
  693. package/dist/web/server.js +79 -19
  694. package/dist/web/server.js.map +1 -1
  695. package/dist/web/services/build-manager.d.ts +72 -0
  696. package/dist/web/services/build-manager.d.ts.map +1 -0
  697. package/dist/web/services/build-manager.js +189 -0
  698. package/dist/web/services/build-manager.js.map +1 -0
  699. package/dist/web/services/distill-manager.d.ts +125 -0
  700. package/dist/web/services/distill-manager.d.ts.map +1 -0
  701. package/dist/web/services/distill-manager.js +308 -0
  702. package/dist/web/services/distill-manager.js.map +1 -0
  703. package/dist/web/static/assets/AgentContentPage-DkeRNxok.js +2 -0
  704. package/dist/web/static/assets/AgentContentPage-DkeRNxok.js.map +1 -0
  705. package/dist/web/static/assets/AgentDelegationTable-ByBa0x1l.js +2 -0
  706. package/dist/web/static/assets/AgentDelegationTable-ByBa0x1l.js.map +1 -0
  707. package/dist/web/static/assets/ContextInsightsPage-oUk7_I8u.js +3 -0
  708. package/dist/web/static/assets/ContextInsightsPage-oUk7_I8u.js.map +1 -0
  709. package/dist/web/static/assets/DaemonHealthPage-DG2fyOP7.js +2 -0
  710. package/dist/web/static/assets/DaemonHealthPage-DG2fyOP7.js.map +1 -0
  711. package/dist/web/static/assets/DecisionsPage-CMAPEnKb.js +2 -0
  712. package/dist/web/static/assets/DecisionsPage-CMAPEnKb.js.map +1 -0
  713. package/dist/web/static/assets/DiagnosticsPage-DQd-Zm4r.js +2 -0
  714. package/dist/web/static/assets/DiagnosticsPage-DQd-Zm4r.js.map +1 -0
  715. package/dist/web/static/assets/DriftTab-DqpepOhI.js +2 -0
  716. package/dist/web/static/assets/DriftTab-DqpepOhI.js.map +1 -0
  717. package/dist/web/static/assets/HealthHomePage-CN6zNIie.js +3 -0
  718. package/dist/web/static/assets/HealthHomePage-CN6zNIie.js.map +1 -0
  719. package/dist/web/static/assets/KbHitRateTable-ByEIWujF.js +2 -0
  720. package/dist/web/static/assets/KbHitRateTable-ByEIWujF.js.map +1 -0
  721. package/dist/web/static/assets/MarkdownRenderer-DZmTl-8J.js +3 -0
  722. package/dist/web/static/assets/MarkdownRenderer-DZmTl-8J.js.map +1 -0
  723. package/dist/web/static/assets/NotFound-BQPh0vaF.js +2 -0
  724. package/dist/web/static/assets/NotFound-BQPh0vaF.js.map +1 -0
  725. package/dist/web/static/assets/ProjectSwitcher-D3lZMFd3.js +2 -0
  726. package/dist/web/static/assets/ProjectSwitcher-D3lZMFd3.js.map +1 -0
  727. package/dist/web/static/assets/SettingsPage-oLJBNzQj.js +2 -0
  728. package/dist/web/static/assets/SettingsPage-oLJBNzQj.js.map +1 -0
  729. package/dist/web/static/assets/SkillContentPage-DK5rgfgw.js +2 -0
  730. package/dist/web/static/assets/SkillContentPage-DK5rgfgw.js.map +1 -0
  731. package/dist/web/static/assets/SkillStatsTable-DYMzjEUV.js +2 -0
  732. package/dist/web/static/assets/SkillStatsTable-DYMzjEUV.js.map +1 -0
  733. package/dist/web/static/assets/SkillsDistillTab-C7qaG8q3.js +2 -0
  734. package/dist/web/static/assets/SkillsDistillTab-C7qaG8q3.js.map +1 -0
  735. package/dist/web/static/assets/TasksHubPage-03wsRRsJ.js +6 -0
  736. package/dist/web/static/assets/TasksHubPage-03wsRRsJ.js.map +1 -0
  737. package/dist/web/static/assets/ViolationsPage-DSiLr-9O.js +3 -0
  738. package/dist/web/static/assets/ViolationsPage-DSiLr-9O.js.map +1 -0
  739. package/dist/web/static/assets/arco-Bhi3a6Qp.js +14 -0
  740. package/dist/web/static/assets/arco-Bhi3a6Qp.js.map +1 -0
  741. package/dist/web/static/assets/arco-DFQA6dO_.css +1 -0
  742. package/dist/web/static/assets/charts-BuHQWDbQ.js +37 -0
  743. package/dist/web/static/assets/charts-BuHQWDbQ.js.map +1 -0
  744. package/dist/web/static/assets/date-fns-sbWH3_uq.js +2 -0
  745. package/dist/web/static/assets/date-fns-sbWH3_uq.js.map +1 -0
  746. package/dist/web/static/assets/index-7bl3kbcx.css +1 -0
  747. package/dist/web/static/assets/index-BIYnq1Dx.js +4 -0
  748. package/dist/web/static/assets/index-BIYnq1Dx.js.map +1 -0
  749. package/dist/web/static/assets/lucide-CnlPQoG8.js +72 -0
  750. package/dist/web/static/assets/lucide-CnlPQoG8.js.map +1 -0
  751. package/dist/web/static/assets/outcome-DUn1NjlC.js +2 -0
  752. package/dist/web/static/assets/outcome-DUn1NjlC.js.map +1 -0
  753. package/dist/web/static/assets/query-S6X1S7K9.js +2 -0
  754. package/dist/web/static/assets/{query-C99w429o.js.map → query-S6X1S7K9.js.map} +1 -1
  755. package/dist/web/static/assets/{react-router-r79dBVy4.js → react-router-JVUrkhdd.js} +3 -3
  756. package/dist/web/static/assets/{react-router-r79dBVy4.js.map → react-router-JVUrkhdd.js.map} +1 -1
  757. package/dist/web/static/assets/react-vendor-tkvCrao7.js +57 -0
  758. package/dist/web/static/assets/react-vendor-tkvCrao7.js.map +1 -0
  759. package/dist/web/static/assets/syntax-highlighter-BkZfCDsz.js +6 -0
  760. package/dist/web/static/assets/syntax-highlighter-BkZfCDsz.js.map +1 -0
  761. package/dist/web/static/assets/useTabsParam-k8qte_0C.js +2 -0
  762. package/dist/web/static/assets/useTabsParam-k8qte_0C.js.map +1 -0
  763. package/dist/web/static/assets/vendor-DWgdB1eY.js +65 -0
  764. package/dist/web/static/assets/vendor-DWgdB1eY.js.map +1 -0
  765. package/dist/web/static/index.html +12 -8
  766. package/package.json +14 -3
  767. package/dist/core/ai/provider.d.ts +0 -63
  768. package/dist/core/ai/provider.d.ts.map +0 -1
  769. package/dist/core/ai/provider.js +0 -241
  770. package/dist/core/ai/provider.js.map +0 -1
  771. package/dist/core/ai/types.d.ts +0 -43
  772. package/dist/core/ai/types.d.ts.map +0 -1
  773. package/dist/core/ai/types.js +0 -5
  774. package/dist/core/ai/types.js.map +0 -1
  775. package/dist/core/storage/token-usage.d.ts +0 -36
  776. package/dist/core/storage/token-usage.d.ts.map +0 -1
  777. package/dist/core/storage/token-usage.js +0 -59
  778. package/dist/core/storage/token-usage.js.map +0 -1
  779. package/dist/core/utils/token-tracker.d.ts +0 -39
  780. package/dist/core/utils/token-tracker.d.ts.map +0 -1
  781. package/dist/core/utils/token-tracker.js +0 -69
  782. package/dist/core/utils/token-tracker.js.map +0 -1
  783. package/dist/skills/index.d.ts +0 -3
  784. package/dist/skills/index.d.ts.map +0 -1
  785. package/dist/skills/index.js +0 -3
  786. package/dist/skills/index.js.map +0 -1
  787. package/dist/skills/matcher.d.ts +0 -26
  788. package/dist/skills/matcher.d.ts.map +0 -1
  789. package/dist/skills/matcher.js +0 -113
  790. package/dist/skills/matcher.js.map +0 -1
  791. package/dist/skills/official/code-simplifier.md +0 -52
  792. package/dist/skills/official/find-skills.md +0 -142
  793. package/dist/skills/official/official-api-design.md +0 -30
  794. package/dist/skills/official/official-architecture-decision.md +0 -41
  795. package/dist/skills/official/official-bmad.md +0 -118
  796. package/dist/skills/official/official-db-schema-design.md +0 -34
  797. package/dist/skills/official/official-debug.md +0 -25
  798. package/dist/skills/official/official-doc-driven.md +0 -31
  799. package/dist/skills/official/official-harness-engineering.md +0 -108
  800. package/dist/skills/official/official-openspec.md +0 -89
  801. package/dist/skills/official/official-performance-optimization.md +0 -30
  802. package/dist/skills/official/official-pr-review.md +0 -35
  803. package/dist/skills/official/official-release-checklist.md +0 -30
  804. package/dist/skills/official/official-security-hardening.md +0 -32
  805. package/dist/skills/official/official-spec-driven-design.md +0 -31
  806. package/dist/skills/official/planning-with-files.md +0 -241
  807. package/dist/skills/official/ui-ux-pro-max.md +0 -105
  808. package/dist/skills/official/webapp-testing.md +0 -96
  809. package/dist/skills/official-skills.d.ts +0 -26
  810. package/dist/skills/official-skills.d.ts.map +0 -1
  811. package/dist/skills/official-skills.js +0 -74
  812. package/dist/skills/official-skills.js.map +0 -1
  813. package/dist/skills/semantic-matcher.d.ts +0 -60
  814. package/dist/skills/semantic-matcher.d.ts.map +0 -1
  815. package/dist/skills/semantic-matcher.js +0 -192
  816. package/dist/skills/semantic-matcher.js.map +0 -1
  817. package/dist/skills/upgrade-engine.d.ts +0 -93
  818. package/dist/skills/upgrade-engine.d.ts.map +0 -1
  819. package/dist/skills/upgrade-engine.js +0 -447
  820. package/dist/skills/upgrade-engine.js.map +0 -1
  821. package/dist/skills/upgrade-prompt.d.ts +0 -20
  822. package/dist/skills/upgrade-prompt.d.ts.map +0 -1
  823. package/dist/skills/upgrade-prompt.js +0 -75
  824. package/dist/skills/upgrade-prompt.js.map +0 -1
  825. package/dist/web/routes/ai.d.ts +0 -10
  826. package/dist/web/routes/ai.d.ts.map +0 -1
  827. package/dist/web/routes/ai.js +0 -186
  828. package/dist/web/routes/ai.js.map +0 -1
  829. package/dist/web/routes/token-usage.d.ts +0 -7
  830. package/dist/web/routes/token-usage.d.ts.map +0 -1
  831. package/dist/web/routes/token-usage.js +0 -18
  832. package/dist/web/routes/token-usage.js.map +0 -1
  833. package/dist/web/ssrf-guard.d.ts +0 -35
  834. package/dist/web/ssrf-guard.d.ts.map +0 -1
  835. package/dist/web/ssrf-guard.js +0 -93
  836. package/dist/web/ssrf-guard.js.map +0 -1
  837. package/dist/web/static/assets/AIConfig-CdDWzJyO.js +0 -2
  838. package/dist/web/static/assets/AIConfig-CdDWzJyO.js.map +0 -1
  839. package/dist/web/static/assets/Dashboard-CoEmmIDt.js +0 -2
  840. package/dist/web/static/assets/Dashboard-CoEmmIDt.js.map +0 -1
  841. package/dist/web/static/assets/Drawer-DdRTzlLB.js +0 -2
  842. package/dist/web/static/assets/Drawer-DdRTzlLB.js.map +0 -1
  843. package/dist/web/static/assets/Events-DrIq1SUS.js +0 -2
  844. package/dist/web/static/assets/Events-DrIq1SUS.js.map +0 -1
  845. package/dist/web/static/assets/Reports-DFBM3MDK.js +0 -2
  846. package/dist/web/static/assets/Reports-DFBM3MDK.js.map +0 -1
  847. package/dist/web/static/assets/SearchInput-qCj_jAcf.js +0 -2
  848. package/dist/web/static/assets/SearchInput-qCj_jAcf.js.map +0 -1
  849. package/dist/web/static/assets/SessionDetail-CCzwdoT7.js +0 -2
  850. package/dist/web/static/assets/SessionDetail-CCzwdoT7.js.map +0 -1
  851. package/dist/web/static/assets/Sessions-FfLYkAw9.js +0 -2
  852. package/dist/web/static/assets/Sessions-FfLYkAw9.js.map +0 -1
  853. package/dist/web/static/assets/Skills-C8Gvs3Qa.js +0 -2
  854. package/dist/web/static/assets/Skills-C8Gvs3Qa.js.map +0 -1
  855. package/dist/web/static/assets/TaskDetail-BS8pYhaR.js +0 -2
  856. package/dist/web/static/assets/TaskDetail-BS8pYhaR.js.map +0 -1
  857. package/dist/web/static/assets/Tasks-CyuhizG8.js +0 -2
  858. package/dist/web/static/assets/Tasks-CyuhizG8.js.map +0 -1
  859. package/dist/web/static/assets/charts-CLrM0_uM.js +0 -37
  860. package/dist/web/static/assets/charts-CLrM0_uM.js.map +0 -1
  861. package/dist/web/static/assets/date-fns-CZ_bHujz.js +0 -2
  862. package/dist/web/static/assets/date-fns-CZ_bHujz.js.map +0 -1
  863. package/dist/web/static/assets/export-L_VBD2p1.js +0 -4
  864. package/dist/web/static/assets/export-L_VBD2p1.js.map +0 -1
  865. package/dist/web/static/assets/index-CBX47X8l.js +0 -3
  866. package/dist/web/static/assets/index-CBX47X8l.js.map +0 -1
  867. package/dist/web/static/assets/index-DjIoMdoR.css +0 -1
  868. package/dist/web/static/assets/lucide-Bs_edTLa.js +0 -232
  869. package/dist/web/static/assets/lucide-Bs_edTLa.js.map +0 -1
  870. package/dist/web/static/assets/query-C99w429o.js +0 -2
  871. package/dist/web/static/assets/react-vendor-CSp-GLFF.js +0 -49
  872. package/dist/web/static/assets/react-vendor-CSp-GLFF.js.map +0 -1
  873. package/dist/web/static/assets/syntax-highlighter-44FakypI.js +0 -9
  874. package/dist/web/static/assets/syntax-highlighter-44FakypI.js.map +0 -1
  875. package/dist/web/static/assets/task-title-BhOcemuR.js +0 -2
  876. package/dist/web/static/assets/task-title-BhOcemuR.js.map +0 -1
  877. package/dist/web/static/assets/time-Bxuk0M-C.js +0 -2
  878. package/dist/web/static/assets/time-Bxuk0M-C.js.map +0 -1
  879. package/dist/web/static/assets/vendor-CMMjVdZs.js +0 -64
  880. package/dist/web/static/assets/vendor-CMMjVdZs.js.map +0 -1
@@ -3,15 +3,33 @@
3
3
  *
4
4
  * 拆分自 sqlite.ts。
5
5
  */
6
+ import { decodeToolInput, decodeToolOutput } from './codec/tool-input-codec.js';
7
+ const ALL_TASK_KINDS = ['user', 'agent-callback', 'image', 'system'];
6
8
  export class TaskOperations {
7
9
  db;
8
10
  constructor(db) {
9
11
  this.db = db;
10
12
  }
13
+ /**
14
+ * Insert a task row.
15
+ *
16
+ * `is_noise` (deprecated since 2026-06-01, spec b1480935) is still accepted
17
+ * for back-compat — callers that haven't been migrated to `task_kind` will
18
+ * keep working; new writers should pass `task_kind` directly.
19
+ *
20
+ * `task_kind` (spec b1480935 Option A): when omitted, defaults to `'user'`
21
+ * via the schema DEFAULT. TaskSegmenter passes the classified kind from
22
+ * `classifyPromptKind()` so envelope rows surface in Web with a muted
23
+ * `agent-callback`/`image`/`system` chip instead of being hidden.
24
+ */
11
25
  writeTask(task) {
26
+ // Default kind to 'user' — matches the schema CHECK and the read-side
27
+ // assumption that pre-classification rows ARE user-driven work.
28
+ const kind = task.task_kind ?? 'user';
12
29
  this.db.prepare(`
13
- INSERT INTO tasks (id, session_id, title, start_time) VALUES (?, ?, ?, ?)
14
- `).run(task.id, task.session_id, task.title, task.start_time);
30
+ INSERT INTO tasks (id, session_id, title, start_time, is_noise, task_kind)
31
+ VALUES (?, ?, ?, ?, ?, ?)
32
+ `).run(task.id, task.session_id, task.title, task.start_time, task.is_noise ? 1 : 0, kind);
15
33
  }
16
34
  updateTask(taskId, updates) {
17
35
  const sets = [];
@@ -32,6 +50,30 @@ export class TaskOperations {
32
50
  sets.push('title = ?');
33
51
  params.push(updates.title);
34
52
  }
53
+ if (updates.outcome !== undefined) {
54
+ sets.push('outcome = ?');
55
+ params.push(updates.outcome);
56
+ }
57
+ if (updates.outcome_reason !== undefined) {
58
+ sets.push('outcome_reason = ?');
59
+ params.push(updates.outcome_reason);
60
+ }
61
+ if (updates.commit_count !== undefined) {
62
+ sets.push('commit_count = ?');
63
+ params.push(updates.commit_count);
64
+ }
65
+ if (updates.reverted_commits !== undefined) {
66
+ sets.push('reverted_commits = ?');
67
+ params.push(updates.reverted_commits);
68
+ }
69
+ if (updates.user_violation_count !== undefined) {
70
+ sets.push('user_violation_count = ?');
71
+ params.push(updates.user_violation_count);
72
+ }
73
+ if (updates.classified_at !== undefined) {
74
+ sets.push('classified_at = ?');
75
+ params.push(updates.classified_at);
76
+ }
35
77
  if (sets.length === 0)
36
78
  return;
37
79
  params.push(taskId);
@@ -41,6 +83,88 @@ export class TaskOperations {
41
83
  this.db.prepare(`INSERT OR IGNORE INTO task_events (task_id, event_id) VALUES (?, ?)`).run(taskId, eventId);
42
84
  this.db.prepare(`UPDATE tasks SET event_count = event_count + 1, end_time = (SELECT timestamp FROM events WHERE event_id = ?) WHERE id = ?`).run(eventId, taskId);
43
85
  }
86
+ /**
87
+ * Task-tracking overhaul (2026-05-27 spec §3.3 Q3): pure-attachment variant
88
+ * of linkEventToTask. Inserts the task_events row + increments event_count
89
+ * BUT does NOT advance task.end_time. Used by the orphan-recovery fallback
90
+ * in TaskSegmenter.linkEvent so attaching a late-arriving sub-agent event
91
+ * to a recently-completed task doesn't stretch its duration KPI.
92
+ *
93
+ * Tradeoff: KPIs that count events get the orphan back, KPIs that read
94
+ * end_time stay honest. Outcome / commit_count rollups already ran at Stop
95
+ * time so they're unaffected either way.
96
+ */
97
+ attachEventToTask(taskId, eventId) {
98
+ this.db.prepare(`INSERT OR IGNORE INTO task_events (task_id, event_id) VALUES (?, ?)`).run(taskId, eventId);
99
+ this.db.prepare(`UPDATE tasks SET event_count = event_count + 1 WHERE id = ?`).run(taskId);
100
+ }
101
+ /**
102
+ * Orphan-event robustness fix (2026-06-01): direct lookup of a session's
103
+ * single active *user* task.
104
+ *
105
+ * Replaces the old `queryTasks(limit:5).find(...)` heuristic in
106
+ * TaskSegmenter.recoverActiveTask, which broke on high-task-count sessions:
107
+ * the active user task could be pushed out of the most-recent-5 window by a
108
+ * burst of newer agent-callback / completed rows, so `.find()` returned null
109
+ * and every subsequent event orphaned.
110
+ *
111
+ * SQL pins the predicate at the DB layer (no LIMIT truncation):
112
+ * WHERE session_id = ? AND status = 'active'
113
+ * AND (task_kind = 'user' OR (task_kind IS NULL AND is_noise = 0))
114
+ * ORDER BY start_time DESC LIMIT 1
115
+ *
116
+ * The `task_kind IS NULL AND is_noise = 0` arm preserves backward compat for
117
+ * legacy rows written before the task_kind backfill (spec b1480935).
118
+ */
119
+ /**
120
+ * decision c6613590 P0 缺陷 B: cheap PK existence check for status='active'.
121
+ *
122
+ * Used by TaskSegmenter.getTrustedCurrent to PK-recheck a map hit before
123
+ * trusting it, so a task the stale-GC already flipped to 'completed' can't
124
+ * keep absorbing new events via a dangling in-memory pointer. Returns false
125
+ * for unknown / completed / abandoned ids.
126
+ */
127
+ isTaskActive(taskId) {
128
+ const row = this.db.prepare(`SELECT 1 FROM tasks WHERE id = ? AND status = 'active' LIMIT 1`).get(taskId);
129
+ return row !== undefined;
130
+ }
131
+ getActiveUserTask(sessionId) {
132
+ const row = this.db.prepare(`
133
+ SELECT t.*, s.project_path
134
+ FROM tasks t
135
+ LEFT JOIN sessions s ON s.session_id = t.session_id
136
+ WHERE t.session_id = ?
137
+ AND t.status = 'active'
138
+ AND (t.task_kind = 'user' OR (t.task_kind IS NULL AND t.is_noise = 0))
139
+ ORDER BY t.start_time DESC
140
+ LIMIT 1
141
+ `).get(sessionId);
142
+ if (!row)
143
+ return null;
144
+ return this.mapRow(row);
145
+ }
146
+ /**
147
+ * Orphan-event robustness fix (2026-06-01): every session that currently has
148
+ * at least one active *user* task, with that task's id + start_time anchor.
149
+ *
150
+ * Drives TaskSegmenter.hydrateActiveTasks() so a freshly-restarted daemon can
151
+ * re-populate its in-memory `currentTasks` map up-front, instead of relying
152
+ * on per-event lazy recovery (which misses buffered/replayed events whose
153
+ * recovery heuristic doesn't fire).
154
+ *
155
+ * One row per session (the most recently started active user task wins) —
156
+ * mirrors getActiveUserTask's tie-break so hydrate and lazy-recover agree.
157
+ */
158
+ queryActiveUserTaskSessions() {
159
+ const rows = this.db.prepare(`
160
+ SELECT t.session_id AS session_id, t.id AS task_id, MAX(t.start_time) AS start_time
161
+ FROM tasks t
162
+ WHERE t.status = 'active'
163
+ AND (t.task_kind = 'user' OR (t.task_kind IS NULL AND t.is_noise = 0))
164
+ GROUP BY t.session_id
165
+ `).all();
166
+ return rows;
167
+ }
44
168
  /** Legacy simple query — returns bare array (used by detail route internally) */
45
169
  queryTasks(filter = {}) {
46
170
  const conditions = [];
@@ -109,21 +233,229 @@ export class TaskOperations {
109
233
  const rows = this.db.prepare(`SELECT event_id FROM task_events WHERE task_id = ?`).all(taskId);
110
234
  return rows.map(r => r.event_id);
111
235
  }
236
+ /**
237
+ * 批量把 event_id 反查到 task_id(用 task_events 关联表)。
238
+ *
239
+ * 场景:pipeline 视图想为每个 prompt 行附上 task_id(让用户点 prompt 直接
240
+ * 跳 task drawer 看"这个任务实际干了什么",而不是噪音重的 session 视图)。
241
+ *
242
+ * 实现:一条 IN(...) 查询,event_id 通常 ≤ pipeline limit(默认 50,
243
+ * 上限 200)— 远低于 SQLite 单语句参数限制 (999),无需分批。
244
+ */
245
+ queryTaskIdsByEventIds(eventIds) {
246
+ const out = new Map();
247
+ if (eventIds.length === 0)
248
+ return out;
249
+ const placeholders = eventIds.map(() => '?').join(',');
250
+ const rows = this.db
251
+ .prepare(`SELECT event_id, task_id FROM task_events WHERE event_id IN (${placeholders})`)
252
+ .all(...eventIds);
253
+ for (const r of rows) {
254
+ // 一个 event 理论上只 link 到一个 task(PRIMARY KEY (task_id, event_id) +
255
+ // TaskSegmenter 单分配语义)。万一重复以最先看到的为准。
256
+ if (!out.has(r.event_id))
257
+ out.set(r.event_id, r.task_id);
258
+ }
259
+ return out;
260
+ }
261
+ /**
262
+ * Task-tracking overhaul (2026-05-27 spec §3.3): orphan-event recovery.
263
+ *
264
+ * After StopHandler completes the current task, late-arriving sub-agent
265
+ * PostToolUse events (the spawn's Bash/Read/etc tool calls that fire
266
+ * AFTER Stop closes the parent task) used to orphan because
267
+ * TaskSegmenter.linkEvent could not find an active task. Spec § 3.3
268
+ * Option C: when no active task exists, attach the event to the most
269
+ * recently completed task on the same session if it's within
270
+ * `sinceMs` of the event timestamp.
271
+ *
272
+ * Q3 (spec § 11): we do NOT update `task.end_time` here — the fallback
273
+ * is pure attachment (event_count grows by the linkEventToTask path in
274
+ * the caller). Updating end_time would stretch the duration KPI for
275
+ * every orphan that lands inside the recovery window and corrupt the
276
+ * outcome / task duration analytics.
277
+ *
278
+ * Order: most-recently-ended task first. event_time bound via SQLite
279
+ * julianday() — sinceMs is the look-back window in ms (typically 5 min).
280
+ * Returns null if no candidate within the window.
281
+ */
282
+ findRecentCompletedTaskForOrphan(opts) {
283
+ const row = this.db.prepare(`
284
+ SELECT t.*, s.project_path
285
+ FROM tasks t
286
+ LEFT JOIN sessions s ON s.session_id = t.session_id
287
+ WHERE t.session_id = ?
288
+ AND t.status = 'completed'
289
+ AND t.end_time IS NOT NULL
290
+ AND (julianday(?) - julianday(t.end_time)) * 86400000 <= ?
291
+ ORDER BY t.end_time DESC
292
+ LIMIT 1
293
+ `).get(opts.session_id, opts.event_ts_iso, opts.sinceMs);
294
+ if (!row)
295
+ return null;
296
+ return this.mapRow(row);
297
+ }
298
+ /**
299
+ * Task-tracking deep fix (2026-05-27 spec §3.A): smart orphan-recovery helper.
300
+ *
301
+ * Finds the most recently completed task on the same session whose end_time
302
+ * is before the event timestamp AND there is no other task that started
303
+ * between that completed task's end_time and the event timestamp.
304
+ *
305
+ * Algorithm (plain English):
306
+ * - candidate: status='completed', end_time IS NOT NULL, end_time <= event_ts
307
+ * - guard: NOT EXISTS t2 where t2.session_id = t.session_id
308
+ * AND t2.start_time > t.end_time AND t2.start_time <= event_ts
309
+ * - if multiple candidates, take end_time DESC (most recent)
310
+ * - returns null if no qualifying candidate
311
+ *
312
+ * Unlike findRecentCompletedTaskForOrphan (5-min window), this function has
313
+ * NO time-window cap. If the idle gap is 2h and no new task started during
314
+ * that gap, the event still attaches to the completed task. The "newer task
315
+ * block" guard prevents cross-task mis-attribution.
316
+ */
317
+ findCompletedTaskForOrphanSmart(opts) {
318
+ try {
319
+ const row = this.db.prepare(`
320
+ SELECT t.*, s.project_path
321
+ FROM tasks t
322
+ LEFT JOIN sessions s ON s.session_id = t.session_id
323
+ WHERE t.session_id = ?
324
+ AND t.status = 'completed'
325
+ AND t.end_time IS NOT NULL
326
+ AND t.end_time <= ?
327
+ AND NOT EXISTS (
328
+ SELECT 1 FROM tasks t2
329
+ WHERE t2.session_id = t.session_id
330
+ AND t2.start_time > t.end_time
331
+ AND t2.start_time <= ?
332
+ )
333
+ ORDER BY t.end_time DESC
334
+ LIMIT 1
335
+ `).get(opts.session_id, opts.event_ts_iso, opts.event_ts_iso);
336
+ if (!row)
337
+ return null;
338
+ return this.mapRow(row);
339
+ }
340
+ catch (err) {
341
+ // Defensive: log and return null so callers can fall back to legacy window
342
+ // method rather than crashing the daemon event pipeline.
343
+ // eslint-disable-next-line no-console
344
+ console.warn('[TaskOperations] findCompletedTaskForOrphanSmart SQL error:', err);
345
+ return null;
346
+ }
347
+ }
348
+ /**
349
+ * Task-tracking deep fix (2026-05-27 spec §5.C): UNION fallback query.
350
+ *
351
+ * Returns all events associated with the task via two sources:
352
+ * 1. linked (linked=1): events directly linked via task_events JOIN
353
+ * 2. fallback (linked=0): events in the same session+time window that are
354
+ * NOT in task_events for this task AND NOT linked to any OTHER task
355
+ * on the same session (prevents event theft across tasks)
356
+ *
357
+ * The `linked` field lets callers (web timeline) badge unlinked events.
358
+ */
359
+ queryEventsByTaskIdWithFallback(taskId, opts = {}) {
360
+ const limit = opts.limit ?? 5000;
361
+ // We need the task's session_id + time window first
362
+ const taskRow = this.db.prepare(`
363
+ SELECT session_id, start_time, end_time FROM tasks WHERE id = ?
364
+ `).get(taskId);
365
+ if (!taskRow)
366
+ return [];
367
+ const { session_id, start_time, end_time } = taskRow;
368
+ // Active tasks: end_time IS NULL → use current time as upper bound
369
+ const endTimeBound = end_time ?? new Date().toISOString();
370
+ const rows = this.db.prepare(`
371
+ SELECT e.*, 1 AS linked
372
+ FROM events e
373
+ JOIN task_events te ON te.event_id = e.event_id
374
+ WHERE te.task_id = ?
375
+
376
+ UNION
377
+
378
+ SELECT e.*, 0 AS linked
379
+ FROM events e
380
+ WHERE e.session_id = ?
381
+ AND e.timestamp >= ?
382
+ AND e.timestamp <= ?
383
+ AND e.event_id NOT IN (
384
+ SELECT event_id FROM task_events WHERE task_id = ?
385
+ )
386
+ AND e.event_id NOT IN (
387
+ SELECT event_id FROM task_events
388
+ WHERE task_id IN (
389
+ SELECT id FROM tasks
390
+ WHERE session_id = ?
391
+ AND id != ?
392
+ )
393
+ )
394
+
395
+ ORDER BY timestamp ASC
396
+ LIMIT ?
397
+ `).all(taskId, session_id, start_time, endTimeBound, taskId, session_id, taskId, limit);
398
+ return rows.map(r => ({
399
+ ...this.rowToEvent(r),
400
+ linked: r.linked === 1 ? 1 : 0,
401
+ }));
402
+ }
403
+ /**
404
+ * B+ spec: 30min smart window 续命 helper.
405
+ *
406
+ * Reactivates a previously completed task so that a new UserPromptSubmit
407
+ * that arrives within TASK_RESUME_WINDOW_MS of its end_time can continue
408
+ * the same task instead of creating a new one.
409
+ *
410
+ * Mutations:
411
+ * - status → 'active'
412
+ * - end_time → NULL (cleared; duration KPI will re-anchor at Stop time)
413
+ *
414
+ * start_time is intentionally left untouched (spec §B+: "不改 start_time").
415
+ * Outcome / commit_count / classified_at columns are not touched here — they
416
+ * would have been NULL anyway (no outcome had landed in a gap of < 30min).
417
+ */
418
+ resumeTask(taskId) {
419
+ this.db.prepare(`
420
+ UPDATE tasks SET status = 'active', end_time = NULL WHERE id = ?
421
+ `).run(taskId);
422
+ }
112
423
  /**
113
424
  * 将 idle 超过阈值的 active task 批量转为 completed。
114
425
  * 条件:status='active' AND end_time IS NOT NULL
115
426
  * AND (now - end_time) > idleMinutes 分钟。
116
- * 返回:受影响行数。
427
+ *
428
+ * 返回:被关闭的 task id 列表(decision c6613590 P0 缺陷 B)。
429
+ *
430
+ * 此前返回行数(number),调用方拿不到具体被关的 task id,导致
431
+ * daemon 的 GC 把任务在 DB 标 completed 后,TaskSegmenter.currentTasks
432
+ * 内存 map 仍留着指向这些已关任务的悬空指针——后续事件经
433
+ * linkEventToTask 推进其 end_time,骗过 30min resume 窗,把多条独立
434
+ * prompt 错并进陈旧任务。改为返回 id 列表后,daemon 可据此从内存
435
+ * map 驱逐这些 entry,使 GC 与内存 map 保持一致。
436
+ *
437
+ * 实现:先 SELECT 出符合条件的 id(带 task_kind / session 信息以便
438
+ * 调用方按 session 驱逐),再用同一谓词 UPDATE。两步在 better-sqlite3
439
+ * 同步事务语义下原子。
117
440
  */
118
441
  completeStaleActiveTasks(idleMinutes) {
119
- const result = this.db.prepare(`
442
+ const stale = this.db.prepare(`
443
+ SELECT id, session_id
444
+ FROM tasks
445
+ WHERE status = 'active'
446
+ AND end_time IS NOT NULL
447
+ AND (julianday('now') - julianday(end_time)) * 24 * 60 > ?
448
+ `).all(idleMinutes);
449
+ if (stale.length === 0)
450
+ return [];
451
+ this.db.prepare(`
120
452
  UPDATE tasks
121
453
  SET status = 'completed'
122
454
  WHERE status = 'active'
123
455
  AND end_time IS NOT NULL
124
456
  AND (julianday('now') - julianday(end_time)) * 24 * 60 > ?
125
457
  `).run(idleMinutes);
126
- return result.changes;
458
+ return stale;
127
459
  }
128
460
  // ── H1: detail route 用的 PK 直查 / JOIN 接口 ──────────────────────
129
461
  /**
@@ -177,6 +509,7 @@ export class TaskOperations {
177
509
  source_handler: r.source_handler,
178
510
  injection_type: r.injection_type,
179
511
  content: r.content,
512
+ metadata_json: r.metadata_json || undefined,
180
513
  }));
181
514
  }
182
515
  /**
@@ -204,20 +537,810 @@ export class TaskOperations {
204
537
  `).all(taskId, nowMs);
205
538
  return rows;
206
539
  }
207
- /** Map a raw events row to ForgeEvent (parses JSON columns). */
208
- rowToEvent(row) {
209
- const parseJson = (val) => {
210
- if (val == null)
211
- return undefined;
212
- if (typeof val !== 'string')
213
- return val;
540
+ // ── Agent Board v3 (task-centric, 2026-05-22) ─────────────────────────
541
+ //
542
+ // 每张卡片 = 一个 task 行(TaskSegmenter 自动按 user prompt 切分)。
543
+ // 替换 v2 的 session-centric 视图(session 是执行痕迹,不是工作项)。
544
+ //
545
+ // v3.1 (2026-05-25, perf P0.3) — N+1 → batched fetch:
546
+ // - 旧版:1 SELECT × 40 rows × 7 内嵌相关子查询 = ~280 隐式子查询,
547
+ // 线性 scaling(10 行=308ms / 40 行=1.2s / 120 行=3.6s)。
548
+ // - 新版:1 SQL 拿基础行(含 ids),7 个批量聚合 SQL 用 `WHERE ... IN (?,?,..)`
549
+ // 一次性取所有 aggregate,JS 内存 merge 回每行。
550
+ //
551
+ // SQL 设计:
552
+ // - 主表 tasks t JOIN sessions s(取 project_path)
553
+ // - 噪音过滤:title 长度 / task-notification 前缀 / 纯应答词 / 单 word slash command
554
+ // - 批量聚合(按维度):
555
+ // top_tools / spawned / files_changed / last_file → events JOIN task_events on task_id
556
+ // GROUP BY task_id
557
+ // intercepts (deny/warn) → tool_intercepts WHERE session_id IN (...)
558
+ // AND timestamp >= min(start_time)
559
+ // JS 内按 task 时间窗口 bucket
560
+ // kb_meta_concat → injections WHERE session_id IN (...)
561
+ // AND source_handler='UserPromptSubmitHandler:kb'
562
+ // JS 内按 task 时间窗口 bucket
563
+ // - 时间窗口 + project filter 作用在 tasks.start_time
564
+ //
565
+ // Notes:
566
+ // - tool_intercepts / injections 按 session+timestamp 窗口过滤而非 task_events 关联,
567
+ // 因为这两张表不一定 link 到 task_events(intercept 在 PreToolUse 阶段,
568
+ // injection 在 UserPromptSubmit 阶段)。用时间窗口可覆盖 task 范围。
569
+ // - 噪音过滤 case-insensitive:title 经 LOWER() 比较 ('approve','停' 等)。
570
+ // - now_ms 可注入便于测试(mock end_time IS NULL 的情况)。
571
+ queryAgentBoardTasks(opts) {
572
+ const limit = Math.min(Math.max(opts.limit ?? 120, 1), 300);
573
+ const offset = Math.max(opts.offset ?? 0, 0);
574
+ const nowMs = opts.now_ms ?? Date.now();
575
+ const sinceMs = nowMs - opts.windowDays * 86400_000;
576
+ const since = new Date(sinceMs).toISOString();
577
+ const nowIso = new Date(nowMs).toISOString();
578
+ let projectFilter = '';
579
+ if (opts.projectPath) {
580
+ projectFilter = 'AND s.project_path = ?';
581
+ }
582
+ // spec b1480935 (2026-06-01 Option A): SQL filter switched from
583
+ // `t.is_noise = 0` to `t.task_kind IN (...)`. is_noise is deprecated;
584
+ // the kind enum is more expressive (callback / image / system distinct).
585
+ //
586
+ // Filter resolution order:
587
+ // 1) opts.includeKinds explicit → use as-is
588
+ // 2) opts.includeNoise=true (legacy) → all 4 kinds
589
+ // 3) default → ['user'] only
590
+ const effectiveKinds = opts.includeKinds
591
+ ?? (opts.includeNoise ? ALL_TASK_KINDS : ['user']);
592
+ const kindFilter = effectiveKinds.length === ALL_TASK_KINDS.length
593
+ ? '' // no filter needed when caller wants everything
594
+ : `AND t.task_kind IN (${effectiveKinds.map(() => '?').join(',')})`;
595
+ // ── Step 1: 基础查询(无内嵌子查询,毫秒级) ──────────────────────
596
+ const baseSql = `
597
+ SELECT
598
+ t.id AS id,
599
+ t.session_id AS session_id,
600
+ s.project_path AS project_path,
601
+ t.title AS title,
602
+ t.description AS description,
603
+ t.start_time AS start_time,
604
+ t.end_time AS end_time,
605
+ t.status AS status,
606
+ t.event_count AS event_count,
607
+ t.outcome AS outcome,
608
+ t.outcome_reason AS outcome_reason,
609
+ t.commit_count AS commit_count,
610
+ t.reverted_commits AS reverted_commits,
611
+ t.user_violation_count AS user_violation_count,
612
+ t.task_kind AS task_kind,
613
+ (CASE
614
+ WHEN t.end_time IS NOT NULL THEN
615
+ CAST(strftime('%s', t.end_time) AS INTEGER) - CAST(strftime('%s', t.start_time) AS INTEGER)
616
+ ELSE
617
+ CAST(strftime('%s', 'now') AS INTEGER) - CAST(strftime('%s', t.start_time) AS INTEGER)
618
+ END) AS duration_sec
619
+ FROM tasks t
620
+ JOIN sessions s ON s.session_id = t.session_id
621
+ WHERE t.start_time >= ?
622
+ ${projectFilter}
623
+ ${kindFilter}
624
+ ORDER BY t.start_time DESC
625
+ LIMIT ? OFFSET ?
626
+ `;
627
+ const baseParams = [since];
628
+ if (opts.projectPath)
629
+ baseParams.push(opts.projectPath);
630
+ if (kindFilter)
631
+ baseParams.push(...effectiveKinds);
632
+ baseParams.push(limit, offset);
633
+ const baseRows = this.db.prepare(baseSql).all(...baseParams);
634
+ if (baseRows.length === 0)
635
+ return [];
636
+ // (count companion lives in countAgentBoardTasks below — same WHERE clauses
637
+ // minus LIMIT/ORDER, so the KPI "总任务数 (7d)" doesn't get clipped by
638
+ // the lane render cap. Added 2026-05-26 to fix "stuck at 40" bug.)
639
+ const taskIds = baseRows.map(r => r.id);
640
+ const sessionIdSet = new Set(baseRows.map(r => r.session_id));
641
+ const sessionIds = Array.from(sessionIdSet);
642
+ const taskPh = taskIds.map(() => '?').join(',');
643
+ const sessionPh = sessionIds.map(() => '?').join(',');
644
+ const toolRows = this.db.prepare(`
645
+ SELECT te.task_id AS task_id, e.tool_name AS tool, COUNT(*) AS cnt
646
+ FROM task_events te JOIN events e ON e.event_id = te.event_id
647
+ WHERE te.task_id IN (${taskPh})
648
+ AND e.hook_type = 'PostToolUse'
649
+ AND e.tool_name IS NOT NULL
650
+ GROUP BY te.task_id, e.tool_name
651
+ `).all(...taskIds);
652
+ const rawRows = this.db.prepare(`
653
+ SELECT te.task_id AS task_id,
654
+ e.tool_name AS tool_name,
655
+ e.tool_input AS tool_input,
656
+ e.timestamp AS ts
657
+ FROM task_events te JOIN events e ON e.event_id = te.event_id
658
+ WHERE te.task_id IN (${taskPh})
659
+ AND e.hook_type = 'PostToolUse'
660
+ AND e.tool_input IS NOT NULL
661
+ AND e.tool_name IN ('Task', 'Agent', 'Edit', 'Write', 'NotebookEdit')
662
+ `).all(...taskIds);
663
+ const spawnAggMap = new Map(); // task_id → (sub_type → cnt)
664
+ const filesByTaskSet = new Map(); // task_id → distinct file_paths
665
+ const lastFileRowsRaw = [];
666
+ for (const raw of rawRows) {
667
+ let decoded = null;
214
668
  try {
215
- return JSON.parse(val);
669
+ const d = decodeToolInput(raw.tool_input);
670
+ if (d && typeof d === 'object' && !Array.isArray(d)) {
671
+ decoded = d;
672
+ }
216
673
  }
217
674
  catch {
218
- return val;
675
+ continue; // skip malformed rows
219
676
  }
220
- };
677
+ if (!decoded)
678
+ continue;
679
+ if (raw.tool_name === 'Task' || raw.tool_name === 'Agent') {
680
+ const subType = decoded.subagent_type;
681
+ if (typeof subType === 'string' && subType.length > 0) {
682
+ let inner = spawnAggMap.get(raw.task_id);
683
+ if (!inner) {
684
+ inner = new Map();
685
+ spawnAggMap.set(raw.task_id, inner);
686
+ }
687
+ inner.set(subType, (inner.get(subType) ?? 0) + 1);
688
+ }
689
+ }
690
+ else if (raw.tool_name === 'Edit' || raw.tool_name === 'Write' || raw.tool_name === 'NotebookEdit') {
691
+ const filePath = decoded.file_path;
692
+ if (typeof filePath === 'string' && filePath.length > 0) {
693
+ let set = filesByTaskSet.get(raw.task_id);
694
+ if (!set) {
695
+ set = new Set();
696
+ filesByTaskSet.set(raw.task_id, set);
697
+ }
698
+ set.add(filePath);
699
+ lastFileRowsRaw.push({ task_id: raw.task_id, file_path: filePath, ts: raw.ts });
700
+ }
701
+ }
702
+ }
703
+ const spawnRows = [];
704
+ for (const [task_id, subMap] of spawnAggMap) {
705
+ for (const [sub_type, cnt] of subMap) {
706
+ spawnRows.push({ task_id, sub_type, cnt });
707
+ }
708
+ }
709
+ const filesRows = [];
710
+ for (const [task_id, set] of filesByTaskSet) {
711
+ filesRows.push({ task_id, n: set.size });
712
+ }
713
+ // 2e + 2f. intercepts (deny/warn) per session — fetch all rows in window,
714
+ // JS buckets by task time range. We bound the timestamp scan with the
715
+ // earliest task start_time to keep the scan tight.
716
+ const earliestStart = baseRows.reduce((min, r) => (r.start_time < min ? r.start_time : min), baseRows[0].start_time);
717
+ const interceptRows = sessionIds.length === 0 ? [] : this.db.prepare(`
718
+ SELECT ti.session_id AS session_id, ti.timestamp AS timestamp, ti.decision AS decision
719
+ FROM tool_intercepts ti
720
+ WHERE ti.session_id IN (${sessionPh})
721
+ AND ti.timestamp >= ?
722
+ AND ti.decision IN ('deny','warn')
723
+ `).all(...sessionIds, earliestStart);
724
+ const injectionRows = sessionIds.length === 0 ? [] : this.db.prepare(`
725
+ SELECT i.session_id AS session_id, i.timestamp AS timestamp, i.metadata_json AS metadata_json
726
+ FROM injections i
727
+ WHERE i.session_id IN (${sessionPh})
728
+ AND i.source_handler = 'UserPromptSubmitHandler:kb'
729
+ AND i.timestamp >= ?
730
+ `).all(...sessionIds, earliestStart);
731
+ // ── Step 3: JS merge ────────────────────────────────────────────────
732
+ // 3a/b/c. Bucket by task_id
733
+ const toolsByTask = new Map();
734
+ for (const r of toolRows) {
735
+ const list = toolsByTask.get(r.task_id);
736
+ if (list)
737
+ list.push(r);
738
+ else
739
+ toolsByTask.set(r.task_id, [r]);
740
+ }
741
+ const spawnByTask = new Map();
742
+ for (const r of spawnRows) {
743
+ const list = spawnByTask.get(r.task_id);
744
+ if (list)
745
+ list.push(r);
746
+ else
747
+ spawnByTask.set(r.task_id, [r]);
748
+ }
749
+ const filesByTask = new Map();
750
+ for (const r of filesRows)
751
+ filesByTask.set(r.task_id, r.n);
752
+ // 3d. last_file: pick most recent ts per task
753
+ const lastFileByTask = new Map();
754
+ for (const r of lastFileRowsRaw) {
755
+ const cur = lastFileByTask.get(r.task_id);
756
+ if (!cur || r.ts > cur.ts)
757
+ lastFileByTask.set(r.task_id, r);
758
+ }
759
+ // 3e/f/g. For per-task time-window aggregates we group source rows by
760
+ // session first, then scan only that session's rows for each task — keeps
761
+ // it O(per-task scan over session subset) rather than O(tasks * all rows).
762
+ const interceptsBySession = new Map();
763
+ for (const r of interceptRows) {
764
+ const list = interceptsBySession.get(r.session_id);
765
+ if (list)
766
+ list.push(r);
767
+ else
768
+ interceptsBySession.set(r.session_id, [r]);
769
+ }
770
+ const injectionsBySession = new Map();
771
+ for (const r of injectionRows) {
772
+ const list = injectionsBySession.get(r.session_id);
773
+ if (list)
774
+ list.push(r);
775
+ else
776
+ injectionsBySession.set(r.session_id, [r]);
777
+ }
778
+ // ── Step 4: build final rows in AgentBoardTaskRow shape ─────────────
779
+ return baseRows.map(b => {
780
+ // top_tools_json (top 3 desc by cnt, matches old SQL semantic)
781
+ const toolsRaw = (toolsByTask.get(b.id) ?? [])
782
+ .slice()
783
+ .sort((a, c) => c.cnt - a.cnt)
784
+ .slice(0, 3)
785
+ .map(r => ({ tool: r.tool, n: r.cnt }));
786
+ // spawned_json (top 5 desc by cnt)
787
+ const spawnedRaw = (spawnByTask.get(b.id) ?? [])
788
+ .slice()
789
+ .sort((a, c) => c.cnt - a.cnt)
790
+ .slice(0, 5)
791
+ .map(r => ({ type: r.sub_type, n: r.cnt }));
792
+ // intercepts: scan session rows within [start_time, end_time||now]
793
+ const endIso = b.end_time ?? nowIso;
794
+ let deny = 0;
795
+ let warn = 0;
796
+ const sessIntercepts = interceptsBySession.get(b.session_id);
797
+ if (sessIntercepts) {
798
+ for (const r of sessIntercepts) {
799
+ if (r.timestamp < b.start_time || r.timestamp > endIso)
800
+ continue;
801
+ if (r.decision === 'deny')
802
+ deny += 1;
803
+ else if (r.decision === 'warn')
804
+ warn += 1;
805
+ }
806
+ }
807
+ // kb_meta_concat: '||' join the metadata_json fields in window
808
+ const sessInjections = injectionsBySession.get(b.session_id);
809
+ let kbConcat = null;
810
+ if (sessInjections) {
811
+ const parts = [];
812
+ for (const r of sessInjections) {
813
+ if (r.timestamp < b.start_time || r.timestamp > endIso)
814
+ continue;
815
+ if (r.metadata_json)
816
+ parts.push(r.metadata_json);
817
+ }
818
+ if (parts.length > 0)
819
+ kbConcat = parts.join('||');
820
+ }
821
+ return {
822
+ id: b.id,
823
+ session_id: b.session_id,
824
+ project_path: b.project_path,
825
+ title: b.title,
826
+ description: b.description,
827
+ start_time: b.start_time,
828
+ end_time: b.end_time,
829
+ status: b.status,
830
+ event_count: b.event_count,
831
+ outcome: b.outcome,
832
+ outcome_reason: b.outcome_reason,
833
+ commit_count: b.commit_count,
834
+ reverted_commits: b.reverted_commits,
835
+ user_violation_count: b.user_violation_count,
836
+ task_kind: b.task_kind ?? 'user',
837
+ duration_sec: b.duration_sec,
838
+ top_tools_json: toolsRaw.length > 0 ? JSON.stringify(toolsRaw) : '[]',
839
+ spawned_json: spawnedRaw.length > 0 ? JSON.stringify(spawnedRaw) : '[]',
840
+ files_changed: filesByTask.get(b.id) ?? 0,
841
+ last_file: lastFileByTask.get(b.id)?.file_path ?? null,
842
+ deny_count: deny,
843
+ warn_count: warn,
844
+ kb_meta_concat: kbConcat,
845
+ };
846
+ });
847
+ }
848
+ /**
849
+ * Shared task_kind filter resolution (decision b509fd91, 2026-06-08).
850
+ *
851
+ * Extracted so queryAgentBoardTasks / countAgentBoardTasks /
852
+ * queryAgentBoardPromptRows / countAgentBoardPromptRows all resolve the
853
+ * effective kind whitelist identically — no copy-paste drift. Resolution
854
+ * order matches the pre-existing inline logic:
855
+ * 1) explicit includeKinds → use as-is
856
+ * 2) includeNoise=true (legacy) → all 4 kinds
857
+ * 3) default → ['user'] only
858
+ *
859
+ * Returns { effectiveKinds, kindFilter } where kindFilter is the SQL
860
+ * fragment (empty string when the caller wants all kinds — no filter needed).
861
+ */
862
+ resolveKindFilter(opts) {
863
+ const effectiveKinds = opts.includeKinds
864
+ ?? (opts.includeNoise ? ALL_TASK_KINDS : ['user']);
865
+ const kindFilter = effectiveKinds.length === ALL_TASK_KINDS.length
866
+ ? ''
867
+ : `AND t.task_kind IN (${effectiveKinds.map(() => '?').join(',')})`;
868
+ return { effectiveKinds, kindFilter };
869
+ }
870
+ // ── Agent Board prompt-rows projection (decision b509fd91, 2026-06-08) ───
871
+ //
872
+ // "一行 = 一条 user prompt" view (P1 方案 B, zero schema). Each row is one
873
+ // UserPromptSubmit event; its `group_id` reuses the existing `task_id` so a
874
+ // task is the group. No new columns, no migration — events.user_prompt is a
875
+ // plaintext column (only tool_input/tool_output are gzip), so prompt text is
876
+ // read with a bare SQL substr, never decoding gzip.
877
+ //
878
+ // Contrast with queryAgentBoardTasks (one row = one task): this query does NOT
879
+ // run the 7 per-task aggregations (top_tools / spawned / kb / intercepts). Each
880
+ // prompt row carries only lightweight fields + its group(task)'s lane metadata,
881
+ // which is cheap and keeps the (potentially 100+) prompt rows fast to render.
882
+ //
883
+ // SQL: events e JOIN task_events te JOIN tasks t JOIN sessions s, filtered to
884
+ // UserPromptSubmit events with non-null user_prompt, within the window and the
885
+ // same task_kind whitelist as the task-row query. ORDER BY e.timestamp DESC.
886
+ //
887
+ // MUST keep the WHERE filter in sync with countAgentBoardPromptRows below.
888
+ queryAgentBoardPromptRows(opts) {
889
+ const limit = Math.min(Math.max(opts.limit ?? 120, 1), 300);
890
+ const offset = Math.max(opts.offset ?? 0, 0);
891
+ const nowMs = opts.now_ms ?? Date.now();
892
+ const sinceMs = nowMs - opts.windowDays * 86400_000;
893
+ const since = new Date(sinceMs).toISOString();
894
+ const projectFilter = opts.projectPath ? 'AND s.project_path = ?' : '';
895
+ const { effectiveKinds, kindFilter } = this.resolveKindFilter(opts);
896
+ const sql = `
897
+ SELECT
898
+ e.event_id AS event_id,
899
+ substr(e.user_prompt, 1, 300) AS prompt_text,
900
+ e.timestamp AS prompt_ts,
901
+ e.session_id AS session_id,
902
+ s.project_path AS project_path,
903
+ t.id AS group_id,
904
+ t.title AS group_title,
905
+ t.status AS group_status,
906
+ t.outcome AS group_outcome,
907
+ t.task_kind AS group_task_kind,
908
+ t.start_time AS group_start_time,
909
+ t.end_time AS group_end_time,
910
+ t.event_count AS group_event_count,
911
+ (CASE
912
+ WHEN t.end_time IS NOT NULL THEN
913
+ CAST(strftime('%s', t.end_time) AS INTEGER) - CAST(strftime('%s', t.start_time) AS INTEGER)
914
+ ELSE
915
+ CAST(strftime('%s', 'now') AS INTEGER) - CAST(strftime('%s', t.start_time) AS INTEGER)
916
+ END) AS group_duration_sec
917
+ FROM events e
918
+ JOIN task_events te ON te.event_id = e.event_id
919
+ JOIN tasks t ON t.id = te.task_id
920
+ JOIN sessions s ON s.session_id = t.session_id
921
+ WHERE e.hook_type = 'UserPromptSubmit'
922
+ AND e.user_prompt IS NOT NULL
923
+ AND t.start_time >= ?
924
+ ${projectFilter}
925
+ ${kindFilter}
926
+ ORDER BY e.timestamp DESC
927
+ LIMIT ? OFFSET ?
928
+ `;
929
+ const params = [since];
930
+ if (opts.projectPath)
931
+ params.push(opts.projectPath);
932
+ if (kindFilter)
933
+ params.push(...effectiveKinds);
934
+ params.push(limit, offset);
935
+ const rows = this.db.prepare(sql).all(...params);
936
+ return rows.map(r => ({
937
+ event_id: r.event_id,
938
+ prompt_text: r.prompt_text ?? '',
939
+ prompt_ts: r.prompt_ts,
940
+ session_id: r.session_id,
941
+ project_path: r.project_path,
942
+ group_id: r.group_id,
943
+ group_title: r.group_title,
944
+ group_status: r.group_status,
945
+ group_outcome: r.group_outcome,
946
+ group_task_kind: r.group_task_kind ?? 'user',
947
+ group_start_time: r.group_start_time,
948
+ group_end_time: r.group_end_time,
949
+ group_event_count: r.group_event_count ?? 0,
950
+ group_duration_sec: r.group_duration_sec,
951
+ }));
952
+ }
953
+ /**
954
+ * Unbounded count companion for queryAgentBoardPromptRows — the number of
955
+ * prompt rows (not tasks) in the window, for the list paginator's
956
+ * total-pages math. Same WHERE clause minus LIMIT/ORDER (decision b509fd91).
957
+ */
958
+ countAgentBoardPromptRows(opts) {
959
+ const nowMs = opts.now_ms ?? Date.now();
960
+ const sinceMs = nowMs - opts.windowDays * 86400_000;
961
+ const since = new Date(sinceMs).toISOString();
962
+ const projectFilter = opts.projectPath ? 'AND s.project_path = ?' : '';
963
+ const { effectiveKinds, kindFilter } = this.resolveKindFilter(opts);
964
+ const sql = `
965
+ SELECT COUNT(*) AS n
966
+ FROM events e
967
+ JOIN task_events te ON te.event_id = e.event_id
968
+ JOIN tasks t ON t.id = te.task_id
969
+ JOIN sessions s ON s.session_id = t.session_id
970
+ WHERE e.hook_type = 'UserPromptSubmit'
971
+ AND e.user_prompt IS NOT NULL
972
+ AND t.start_time >= ?
973
+ ${projectFilter}
974
+ ${kindFilter}
975
+ `;
976
+ const params = [since];
977
+ if (opts.projectPath)
978
+ params.push(opts.projectPath);
979
+ if (kindFilter)
980
+ params.push(...effectiveKinds);
981
+ const row = this.db.prepare(sql).get(...params);
982
+ return row?.n ?? 0;
983
+ }
984
+ // ── Agent Board v3 — count companion (2026-05-26) ─────────────────────
985
+ //
986
+ // Why a separate method: queryAgentBoardTasks() applies a LIMIT (default 40,
987
+ // perf cap from 2026-05-25 — each card runs ~7 follow-up aggregations). The
988
+ // Web KPI "总任务数 (7d)" needs the UNBOUNDED count for the window, so we
989
+ // run the same WHERE clauses (start_time + project + noise filter) without
990
+ // the aggregates / LIMIT / ORDER BY. Cheap: tasks ⨯ sessions JOIN is index
991
+ // covered and the COUNT(*) returns one row.
992
+ //
993
+ // MUST stay in sync with the WHERE filter in queryAgentBoardTasks above.
994
+ // Tested by web-insights-agent-board.integration.test.ts (4 cases incl.
995
+ // noise filter + project filter + cap-vs-total invariant).
996
+ countAgentBoardTasks(opts) {
997
+ const nowMs = opts.now_ms ?? Date.now();
998
+ const sinceMs = nowMs - opts.windowDays * 86400_000;
999
+ const since = new Date(sinceMs).toISOString();
1000
+ const projectFilter = opts.projectPath ? 'AND s.project_path = ?' : '';
1001
+ // spec b1480935: same resolution order as queryAgentBoardTasks so the
1002
+ // count is always consistent with the row set.
1003
+ const effectiveKinds = opts.includeKinds
1004
+ ?? (opts.includeNoise ? ALL_TASK_KINDS : ['user']);
1005
+ const kindFilter = effectiveKinds.length === ALL_TASK_KINDS.length
1006
+ ? ''
1007
+ : `AND t.task_kind IN (${effectiveKinds.map(() => '?').join(',')})`;
1008
+ const sql = `
1009
+ SELECT COUNT(*) AS n
1010
+ FROM tasks t
1011
+ JOIN sessions s ON s.session_id = t.session_id
1012
+ WHERE t.start_time >= ?
1013
+ ${projectFilter}
1014
+ ${kindFilter}
1015
+ `;
1016
+ const params = [since];
1017
+ if (opts.projectPath)
1018
+ params.push(opts.projectPath);
1019
+ if (kindFilter)
1020
+ params.push(...effectiveKinds);
1021
+ const row = this.db.prepare(sql).get(...params);
1022
+ return row?.n ?? 0;
1023
+ }
1024
+ /**
1025
+ * Count "trivial" tasks since `sinceIso` — driven by DaemonObserverBadge's
1026
+ * `trivial_passed_7d` field (spec 1750 § B1-3).
1027
+ *
1028
+ * Definition: tasks the task-segmenter marked as `is_noise=1` AT INSERT TIME
1029
+ * (single-char prompt / single-word slash command / envelope task title). This
1030
+ * is the closest server-side proxy for "daemon never engaged" — the trivial
1031
+ * filter in `task-segmenter.ts` runs at write time using the same heuristics
1032
+ * that gate decision-hint emission. Frontend's `isTrivialPath()` uses a finer
1033
+ * signal (all daemon-intervention sections empty) but we don't have that
1034
+ * aggregated; this proxy is good enough for the weekly counter chip.
1035
+ *
1036
+ * `datetime()` wrap normalises ISO 'T' vs SQLite default ' ' separator
1037
+ * (see DecisionsOperations.countDecisionsSince for the reasoning).
1038
+ */
1039
+ countTrivialTasksSince(sinceIso) {
1040
+ const row = this.db
1041
+ .prepare(`SELECT COUNT(*) AS n FROM tasks
1042
+ WHERE datetime(start_time) >= datetime(?) AND is_noise = 1`)
1043
+ .get(sinceIso);
1044
+ return row?.n ?? 0;
1045
+ }
1046
+ /**
1047
+ * Daily commit + task activity over a window (default 30 days).
1048
+ *
1049
+ * Used by `/api/insights/commit-activity` to drive the HealthHomePage
1050
+ * 30d "Commit calendar" without the lane LIMIT undercount that the
1051
+ * agent-board path suffered. Aggregates straight off the `tasks` table
1052
+ * by `DATE(start_time)` so the result is unbounded by perf caps.
1053
+ *
1054
+ * The day buckets are filled with zeros for days with no tasks so the
1055
+ * caller doesn't have to fill gaps client-side. Days ordered oldest→newest.
1056
+ *
1057
+ * Added 2026-05-26 — see docs/implementation changelog.
1058
+ */
1059
+ queryCommitActivityDaily(opts) {
1060
+ const days = Math.min(Math.max(opts.days, 1), 365);
1061
+ const nowMs = opts.now_ms ?? Date.now();
1062
+ const sinceMs = nowMs - days * 86400_000;
1063
+ const since = new Date(sinceMs).toISOString();
1064
+ const projectFilter = opts.projectPath ? 'AND s.project_path = ?' : '';
1065
+ const sql = `
1066
+ SELECT
1067
+ DATE(t.start_time) AS date,
1068
+ COUNT(*) AS total,
1069
+ SUM(CASE WHEN t.outcome = 'success' THEN 1 ELSE 0 END) AS succeeded,
1070
+ SUM(CASE WHEN t.outcome = 'failed' THEN 1 ELSE 0 END) AS failed,
1071
+ COALESCE(SUM(t.commit_count), 0) AS commits
1072
+ FROM tasks t
1073
+ JOIN sessions s ON s.session_id = t.session_id
1074
+ WHERE t.start_time >= ?
1075
+ ${projectFilter}
1076
+ GROUP BY DATE(t.start_time)
1077
+ ORDER BY DATE(t.start_time) ASC
1078
+ `;
1079
+ const params = [since];
1080
+ if (opts.projectPath)
1081
+ params.push(opts.projectPath);
1082
+ const rows = this.db.prepare(sql).all(...params);
1083
+ // Fill gaps so days with 0 activity still appear (matches CommitCalendarCard
1084
+ // contract — it expects a contiguous day array indexed by date).
1085
+ const byDate = new Map();
1086
+ for (const r of rows) {
1087
+ byDate.set(r.date, {
1088
+ total: Number(r.total) || 0,
1089
+ succeeded: Number(r.succeeded) || 0,
1090
+ failed: Number(r.failed) || 0,
1091
+ commits: Number(r.commits) || 0,
1092
+ });
1093
+ }
1094
+ const out = [];
1095
+ for (let i = days - 1; i >= 0; i--) {
1096
+ const d = new Date(nowMs - i * 86400_000).toISOString().slice(0, 10);
1097
+ const v = byDate.get(d);
1098
+ out.push({
1099
+ date: d,
1100
+ total: v?.total ?? 0,
1101
+ succeeded: v?.succeeded ?? 0,
1102
+ failed: v?.failed ?? 0,
1103
+ commits: v?.commits ?? 0,
1104
+ });
1105
+ }
1106
+ return out;
1107
+ }
1108
+ /**
1109
+ * Task-tracking deep fix (spec 1551 §6.D): Dry-run query for backfill-orphans CLI.
1110
+ *
1111
+ * Returns stats about how many orphan events can be attached via the smart-window
1112
+ * algorithm. No writes are performed.
1113
+ */
1114
+ queryOrphanStats() {
1115
+ const row = this.db.prepare(`
1116
+ WITH orphans AS (
1117
+ SELECT e.event_id, e.session_id, e.timestamp AS ev_ts
1118
+ FROM events e
1119
+ LEFT JOIN task_events te ON te.event_id = e.event_id
1120
+ WHERE te.event_id IS NULL
1121
+ ),
1122
+ attachable AS (
1123
+ SELECT o.event_id, o.session_id, o.ev_ts,
1124
+ (SELECT t.id FROM tasks t
1125
+ WHERE t.session_id = o.session_id
1126
+ AND t.status = 'completed'
1127
+ AND t.end_time IS NOT NULL
1128
+ AND t.end_time <= o.ev_ts
1129
+ AND NOT EXISTS (
1130
+ SELECT 1 FROM tasks t2
1131
+ WHERE t2.session_id = t.session_id
1132
+ AND t2.start_time > t.end_time
1133
+ AND t2.start_time <= o.ev_ts
1134
+ )
1135
+ ORDER BY t.end_time DESC LIMIT 1) AS task_id
1136
+ FROM orphans o
1137
+ )
1138
+ SELECT
1139
+ COUNT(*) AS total_orphans,
1140
+ SUM(CASE WHEN task_id IS NOT NULL THEN 1 ELSE 0 END) AS attachable,
1141
+ SUM(CASE WHEN task_id IS NULL THEN 1 ELSE 0 END) AS unattachable
1142
+ FROM attachable
1143
+ `).get();
1144
+ const total = row?.total_orphans ?? 0;
1145
+ const attachable = row?.attachable ?? 0;
1146
+ const unattachable = row?.unattachable ?? 0;
1147
+ const pct = total > 0 ? `${((attachable / total) * 100).toFixed(1)}%` : '0.0%';
1148
+ return { total_orphans: total, attachable, unattachable, pct };
1149
+ }
1150
+ /**
1151
+ * Task-tracking deep fix (spec 1551 §6.D): Sample orphan→task pairs for dry-run display.
1152
+ *
1153
+ * Returns up to `limit` (default 5) sample pairs showing which events would be attached.
1154
+ */
1155
+ sampleAttachablePairs(limit = 5) {
1156
+ const rows = this.db.prepare(`
1157
+ WITH orphans AS (
1158
+ SELECT e.event_id, e.session_id, e.timestamp AS ev_ts
1159
+ FROM events e
1160
+ LEFT JOIN task_events te ON te.event_id = e.event_id
1161
+ WHERE te.event_id IS NULL
1162
+ ),
1163
+ attachable AS (
1164
+ SELECT o.event_id, o.session_id, o.ev_ts,
1165
+ (SELECT t.id FROM tasks t
1166
+ WHERE t.session_id = o.session_id
1167
+ AND t.status = 'completed'
1168
+ AND t.end_time IS NOT NULL
1169
+ AND t.end_time <= o.ev_ts
1170
+ AND NOT EXISTS (
1171
+ SELECT 1 FROM tasks t2
1172
+ WHERE t2.session_id = t.session_id
1173
+ AND t2.start_time > t.end_time
1174
+ AND t2.start_time <= o.ev_ts
1175
+ )
1176
+ ORDER BY t.end_time DESC LIMIT 1) AS task_id
1177
+ FROM orphans o
1178
+ )
1179
+ SELECT
1180
+ a.event_id,
1181
+ a.task_id,
1182
+ t.title AS task_title,
1183
+ CAST((julianday(a.ev_ts) - julianday(t.end_time)) * 1440 AS INTEGER) AS gap_min
1184
+ FROM attachable a
1185
+ JOIN tasks t ON t.id = a.task_id
1186
+ WHERE a.task_id IS NOT NULL
1187
+ LIMIT ?
1188
+ `).all(limit);
1189
+ return rows;
1190
+ }
1191
+ /**
1192
+ * Task-tracking deep fix (spec 1551 §6.D.3): Apply backfill in a single atomic transaction.
1193
+ *
1194
+ * Inserts orphan→task links using INSERT OR IGNORE (idempotent).
1195
+ * Then synchronizes event_count on updated tasks.
1196
+ *
1197
+ * Returns the number of rows inserted.
1198
+ */
1199
+ backfillOrphans() {
1200
+ const insertStmt = this.db.prepare(`
1201
+ INSERT OR IGNORE INTO task_events (task_id, event_id)
1202
+ SELECT cand.task_id, cand.event_id
1203
+ FROM (
1204
+ SELECT o.event_id, o.session_id, o.ev_ts,
1205
+ (SELECT t.id FROM tasks t
1206
+ WHERE t.session_id = o.session_id
1207
+ AND t.status = 'completed'
1208
+ AND t.end_time IS NOT NULL
1209
+ AND t.end_time <= o.ev_ts
1210
+ AND NOT EXISTS (
1211
+ SELECT 1 FROM tasks t2
1212
+ WHERE t2.session_id = t.session_id
1213
+ AND t2.start_time > t.end_time
1214
+ AND t2.start_time <= o.ev_ts
1215
+ )
1216
+ ORDER BY t.end_time DESC LIMIT 1) AS task_id
1217
+ FROM (
1218
+ SELECT e.event_id, e.session_id, e.timestamp AS ev_ts
1219
+ FROM events e
1220
+ LEFT JOIN task_events te ON te.event_id = e.event_id
1221
+ WHERE te.event_id IS NULL
1222
+ ) o
1223
+ ) cand
1224
+ WHERE cand.task_id IS NOT NULL
1225
+ `);
1226
+ const updateCountStmt = this.db.prepare(`
1227
+ UPDATE tasks
1228
+ SET event_count = (SELECT COUNT(*) FROM task_events WHERE task_id = tasks.id)
1229
+ `);
1230
+ let inserted = 0;
1231
+ this.db.transaction(() => {
1232
+ const result = insertStmt.run();
1233
+ inserted = result.changes;
1234
+ updateCountStmt.run();
1235
+ })();
1236
+ return inserted;
1237
+ }
1238
+ // ── decision 265f59d5: LLM task-boundary re-attribution helpers ──────────
1239
+ //
1240
+ // Data-safety contract (spec Option A): re-attribution NEVER deletes events
1241
+ // and NEVER mutates event content. The only write is moving a single prompt
1242
+ // event's `task_events.task_id` foreign key onto a freshly-created task, and
1243
+ // stamping `llm_classified_at` for idempotency.
1244
+ /**
1245
+ * The first `limit` user-prompt texts of a task, oldest-first. Feeds the
1246
+ * classifier the "what was the previous task about" context. Reads only the
1247
+ * plaintext `user_prompt` column (never gzip tool_input).
1248
+ */
1249
+ getTaskUserPrompts(taskId, limit = 3) {
1250
+ const rows = this.db.prepare(`
1251
+ SELECT e.user_prompt AS user_prompt
1252
+ FROM task_events te
1253
+ JOIN events e ON e.event_id = te.event_id
1254
+ WHERE te.task_id = ?
1255
+ AND e.hook_type = 'UserPromptSubmit'
1256
+ AND e.user_prompt IS NOT NULL
1257
+ ORDER BY e.timestamp ASC
1258
+ LIMIT ?
1259
+ `).all(taskId, limit);
1260
+ return rows.map(r => r.user_prompt).filter((p) => !!p);
1261
+ }
1262
+ /** True once the async boundary classifier has stamped this task. */
1263
+ isLlmClassified(taskId) {
1264
+ const row = this.db.prepare(`SELECT llm_classified_at FROM tasks WHERE id = ? LIMIT 1`).get(taskId);
1265
+ return !!row?.llm_classified_at;
1266
+ }
1267
+ /** Stamp the idempotency marker without re-attributing (CONTINUATION verdict). */
1268
+ markLlmClassified(taskId, atIso) {
1269
+ this.db.prepare(`UPDATE tasks SET llm_classified_at = ? WHERE id = ?`).run(atIso, taskId);
1270
+ }
1271
+ /**
1272
+ * Re-attribute a single prompt event off `fromTaskId` onto a brand-new task,
1273
+ * because the async classifier judged it NEW_TASK. Atomic + idempotent.
1274
+ *
1275
+ * Safety / idempotency guards (spec OQ #3 "re-check"):
1276
+ * 1. `fromTaskId` must not already be llm_classified (double-write guard).
1277
+ * 2. the event must STILL be linked to `fromTaskId` (no other path moved it
1278
+ * during the LLM delay window).
1279
+ * 3. all writes run in ONE better-sqlite3 transaction.
1280
+ *
1281
+ * On success: creates the new task row, moves ONLY this event's
1282
+ * task_events.task_id FK, decrements the old task's event_count / increments
1283
+ * the new one, and stamps `llm_classified_at` on BOTH rows. Returns the new
1284
+ * task id. On any guard failure returns null and writes nothing.
1285
+ *
1286
+ * Explicitly NOT done: deleting events, editing event content, closing or
1287
+ * reactivating the source task (its lifecycle is owned by the segmenter /
1288
+ * stop handler). OQ1 = "trust the LLM": no conservative event-count/time guard.
1289
+ */
1290
+ reattributePromptToNewTask(opts) {
1291
+ const { fromTaskId, eventId, newTask, atIso } = opts;
1292
+ const txn = this.db.transaction(() => {
1293
+ // Guard 1: idempotency — already classified, do nothing.
1294
+ const fromRow = this.db.prepare(`SELECT llm_classified_at FROM tasks WHERE id = ? LIMIT 1`).get(fromTaskId);
1295
+ if (!fromRow)
1296
+ return null;
1297
+ if (fromRow.llm_classified_at)
1298
+ return null;
1299
+ // Guard 2: re-check the event still belongs to fromTaskId.
1300
+ const link = this.db.prepare(`SELECT task_id FROM task_events WHERE event_id = ? LIMIT 1`).get(eventId);
1301
+ if (!link || link.task_id !== fromTaskId) {
1302
+ // Someone else moved it (or it was never linked). Still stamp the
1303
+ // source so we never retry this prompt, but do not re-attribute.
1304
+ this.db.prepare(`UPDATE tasks SET llm_classified_at = ? WHERE id = ?`).run(atIso, fromTaskId);
1305
+ return null;
1306
+ }
1307
+ // Create the new task row (kind defaults to 'user').
1308
+ this.db.prepare(`
1309
+ INSERT INTO tasks (id, session_id, title, start_time, is_noise, task_kind, llm_classified_at)
1310
+ VALUES (?, ?, ?, ?, 0, ?, ?)
1311
+ `).run(newTask.id, newTask.session_id, newTask.title, newTask.start_time, newTask.task_kind ?? 'user', atIso);
1312
+ // Move ONLY this event's FK. PK is (task_id, event_id), so UPDATE the row.
1313
+ this.db.prepare(`UPDATE task_events SET task_id = ? WHERE event_id = ? AND task_id = ?`).run(newTask.id, eventId, fromTaskId);
1314
+ // Keep event_count honest on both rows (best-effort; floor at 0).
1315
+ this.db.prepare(`UPDATE tasks SET event_count = MAX(event_count - 1, 0) WHERE id = ?`).run(fromTaskId);
1316
+ this.db.prepare(`UPDATE tasks SET event_count = event_count + 1 WHERE id = ?`).run(newTask.id);
1317
+ // Stamp the source task so a replayed event never re-classifies it.
1318
+ this.db.prepare(`UPDATE tasks SET llm_classified_at = ? WHERE id = ?`).run(atIso, fromTaskId);
1319
+ return newTask.id;
1320
+ });
1321
+ try {
1322
+ return txn();
1323
+ }
1324
+ catch (err) {
1325
+ // Defensive: never crash the daemon's async classifier path.
1326
+ // eslint-disable-next-line no-console
1327
+ console.warn('[TaskOperations] reattributePromptToNewTask failed:', err);
1328
+ return null;
1329
+ }
1330
+ }
1331
+ /** Map a raw events row to ForgeEvent (parses JSON columns). */
1332
+ rowToEvent(row) {
1333
+ // tool_output flows through decodeToolOutput so this rowToEvent reads
1334
+ // both legacy plain rows and new codec-encoded rows (gzip / tiny utf8).
1335
+ // Wrapped in try/catch so a malformed payload returns undefined rather
1336
+ // than crashing the whole row map. Matches EventOperations.rowToEvent.
1337
+ let toolOutput = undefined;
1338
+ try {
1339
+ toolOutput = decodeToolOutput(row.tool_output);
1340
+ }
1341
+ catch {
1342
+ toolOutput = undefined;
1343
+ }
221
1344
  return {
222
1345
  event_id: row.event_id,
223
1346
  session_id: row.session_id,
@@ -225,8 +1348,8 @@ export class TaskOperations {
225
1348
  timestamp: row.timestamp,
226
1349
  hook_type: row.hook_type,
227
1350
  tool_name: row.tool_name || undefined,
228
- tool_input: parseJson(row.tool_input),
229
- tool_output: parseJson(row.tool_output),
1351
+ tool_input: decodeToolInput(row.tool_input),
1352
+ tool_output: toolOutput,
230
1353
  user_prompt: row.user_prompt || undefined,
231
1354
  ai_response: row.ai_response || undefined,
232
1355
  };
@@ -254,10 +1377,18 @@ export class TaskOperations {
254
1377
  }
255
1378
  if (filter.search) {
256
1379
  // M3/L2: 当前 LIKE '%search%' 全表扫描;行数 < 10w 时延迟可接受。
257
- // TODO: 加 FTS5 索引(行数 > 10w 时上 FTS 才能避免线性扫描)。
1380
+ // Note: 加 FTS5 索引(触发条件:tasks 表行数 > 10w LIKE 线性扫描 > 100ms)。
258
1381
  conditions.push('t.title LIKE ?');
259
1382
  params.push(`%${filter.search}%`);
260
1383
  }
1384
+ // spec b1480935 (2026-06-01 Option A): task_kind whitelist. Always
1385
+ // applied — when undefined the caller (route) defaults to ['user'] so
1386
+ // /api/tasks stays clean by default. Pass all four kinds to disable.
1387
+ if (filter.include_kinds && filter.include_kinds.length > 0) {
1388
+ const placeholders = filter.include_kinds.map(() => '?').join(', ');
1389
+ conditions.push(`t.task_kind IN (${placeholders})`);
1390
+ params.push(...filter.include_kinds);
1391
+ }
261
1392
  return { conditions, params };
262
1393
  }
263
1394
  mapRow(r) {
@@ -272,7 +1403,71 @@ export class TaskOperations {
272
1403
  event_count: r.event_count || 0,
273
1404
  project_path: r.project_path || undefined,
274
1405
  first_prompt: r.first_prompt !== undefined ? r.first_prompt : undefined,
1406
+ // Outcome columns may be NULL (not yet classified) — pass through verbatim.
1407
+ outcome: r.outcome ?? null,
1408
+ outcome_reason: r.outcome_reason ?? null,
1409
+ commit_count: r.commit_count == null ? null : r.commit_count,
1410
+ reverted_commits: r.reverted_commits == null ? null : r.reverted_commits,
1411
+ user_violation_count: r.user_violation_count == null ? null : r.user_violation_count,
1412
+ classified_at: r.classified_at ?? null,
1413
+ llm_classified_at: r.llm_classified_at ?? null,
1414
+ is_noise: r.is_noise == null ? null : r.is_noise,
1415
+ // spec b1480935: task_kind exposed verbatim. Legacy rows (column missing
1416
+ // for some edge cases mid-migration) read as undefined → callers treat
1417
+ // as 'user' via the TaskRecord default contract.
1418
+ task_kind: r.task_kind ?? 'user',
275
1419
  };
276
1420
  }
1421
+ // ── Outcome classifier (spec 1100 v2 — edit-intent signal) ────────
1422
+ //
1423
+ // The single signal powering the success-vs-answered verdict: did this task
1424
+ // change code? Reads ONLY `tool_name` (never the gzip `tool_input`), so it is
1425
+ // always correct. Sub-agent Edit/Write events are linked to the parent task's
1426
+ // task_events, so "did this task (incl. its sub-agents) edit code?" is
1427
+ // captured. The previous file→task commit-attribution map was deleted — it
1428
+ // decoded gzip tool_input for file paths and silently failed, corrupting the
1429
+ // verdict.
1430
+ /**
1431
+ * Count this task's Edit/Write intent events. Drives the `edit_intent_count`
1432
+ * signal: a task with >0 edit-intent → `success`, one with 0 edit-intent is a
1433
+ * pure Q&A / research task → `answered`.
1434
+ *
1435
+ * `tool_name` is NOT gzip-compressed (only `tool_input` is), so this is a
1436
+ * pure SQL COUNT — no decode needed. We count PreToolUse so a single logical
1437
+ * edit (Pre + Post) is counted once; multi-edit / notebook variants included.
1438
+ */
1439
+ countEditIntentEventsByTask(taskId) {
1440
+ const row = this.db.prepare(`
1441
+ SELECT COUNT(*) AS n
1442
+ FROM task_events te
1443
+ JOIN events e ON e.event_id = te.event_id
1444
+ WHERE te.task_id = ?
1445
+ AND e.hook_type = 'PreToolUse'
1446
+ AND e.tool_name IN ('Edit', 'Write', 'MultiEdit', 'NotebookEdit')
1447
+ `).get(taskId);
1448
+ return row?.n ?? 0;
1449
+ }
1450
+ // ── P1: Idle unclassified tasks (e2e link fix 2026-05-25) ──────────
1451
+ //
1452
+ // TaskSegmenter marks tasks completed when a new prompt switches off the
1453
+ // current task, but does NOT run outcome-classifier. Result: most
1454
+ // `tasks.outcome` rows are NULL even after Stop. This helper lets the
1455
+ // Stop handler sweep all completed/abandoned tasks on the session whose
1456
+ // outcome hasn't been stamped yet.
1457
+ //
1458
+ // Includes both 'completed' and 'abandoned' statuses (never 'active' —
1459
+ // the current task gets classified separately by stop.ts existing path).
1460
+ queryIdleUnclassifiedTasks(sessionId) {
1461
+ const rows = this.db.prepare(`
1462
+ SELECT t.*, s.project_path
1463
+ FROM tasks t
1464
+ LEFT JOIN sessions s ON s.session_id = t.session_id
1465
+ WHERE t.session_id = ?
1466
+ AND t.outcome IS NULL
1467
+ AND t.status IN ('completed', 'abandoned')
1468
+ ORDER BY t.start_time ASC
1469
+ `).all(sessionId);
1470
+ return rows.map(r => this.mapRow(r));
1471
+ }
277
1472
  }
278
1473
  //# sourceMappingURL=tasks.js.map