@winspan/claude-forge 8.54.4 → 9.12.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 (1053) hide show
  1. package/DEVELOPMENT.md +723 -46
  2. package/README.md +166 -21
  3. package/dist/catalogs/agents.json +67 -0
  4. package/dist/catalogs/skills.json +306 -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/agent.d.ts +169 -0
  26. package/dist/cli/commands/agent.d.ts.map +1 -0
  27. package/dist/cli/commands/agent.js +690 -0
  28. package/dist/cli/commands/agent.js.map +1 -0
  29. package/dist/cli/commands/agents.d.ts +18 -0
  30. package/dist/cli/commands/agents.d.ts.map +1 -0
  31. package/dist/cli/commands/agents.js +160 -0
  32. package/dist/cli/commands/agents.js.map +1 -0
  33. package/dist/cli/commands/bypass.d.ts +18 -0
  34. package/dist/cli/commands/bypass.d.ts.map +1 -0
  35. package/dist/cli/commands/bypass.js +87 -0
  36. package/dist/cli/commands/bypass.js.map +1 -0
  37. package/dist/cli/commands/claudemd.d.ts +60 -0
  38. package/dist/cli/commands/claudemd.d.ts.map +1 -1
  39. package/dist/cli/commands/claudemd.js +174 -37
  40. package/dist/cli/commands/claudemd.js.map +1 -1
  41. package/dist/cli/commands/codegraph.d.ts +17 -0
  42. package/dist/cli/commands/codegraph.d.ts.map +1 -0
  43. package/dist/cli/commands/codegraph.js +263 -0
  44. package/dist/cli/commands/codegraph.js.map +1 -0
  45. package/dist/cli/commands/config.d.ts.map +1 -1
  46. package/dist/cli/commands/config.js +94 -1
  47. package/dist/cli/commands/config.js.map +1 -1
  48. package/dist/cli/commands/daemon.d.ts +39 -0
  49. package/dist/cli/commands/daemon.d.ts.map +1 -1
  50. package/dist/cli/commands/daemon.js +167 -20
  51. package/dist/cli/commands/daemon.js.map +1 -1
  52. package/dist/cli/commands/decisions.d.ts +129 -0
  53. package/dist/cli/commands/decisions.d.ts.map +1 -0
  54. package/dist/cli/commands/decisions.js +706 -0
  55. package/dist/cli/commands/decisions.js.map +1 -0
  56. package/dist/cli/commands/doctor.d.ts +29 -0
  57. package/dist/cli/commands/doctor.d.ts.map +1 -0
  58. package/dist/cli/commands/doctor.js +124 -0
  59. package/dist/cli/commands/doctor.js.map +1 -0
  60. package/dist/cli/commands/entropy.d.ts +35 -0
  61. package/dist/cli/commands/entropy.d.ts.map +1 -0
  62. package/dist/cli/commands/entropy.js +121 -0
  63. package/dist/cli/commands/entropy.js.map +1 -0
  64. package/dist/cli/commands/executions.d.ts +1 -0
  65. package/dist/cli/commands/executions.d.ts.map +1 -1
  66. package/dist/cli/commands/executions.js +12 -2
  67. package/dist/cli/commands/executions.js.map +1 -1
  68. package/dist/cli/commands/fix.d.ts +31 -0
  69. package/dist/cli/commands/fix.d.ts.map +1 -0
  70. package/dist/cli/commands/fix.js +108 -0
  71. package/dist/cli/commands/fix.js.map +1 -0
  72. package/dist/cli/commands/governance.d.ts +21 -0
  73. package/dist/cli/commands/governance.d.ts.map +1 -0
  74. package/dist/cli/commands/governance.js +60 -0
  75. package/dist/cli/commands/governance.js.map +1 -0
  76. package/dist/cli/commands/init.d.ts +27 -0
  77. package/dist/cli/commands/init.d.ts.map +1 -1
  78. package/dist/cli/commands/init.js +158 -146
  79. package/dist/cli/commands/init.js.map +1 -1
  80. package/dist/cli/commands/insights-goal-check.d.ts +54 -0
  81. package/dist/cli/commands/insights-goal-check.d.ts.map +1 -0
  82. package/dist/cli/commands/insights-goal-check.js +318 -0
  83. package/dist/cli/commands/insights-goal-check.js.map +1 -0
  84. package/dist/cli/commands/insights.d.ts +15 -0
  85. package/dist/cli/commands/insights.d.ts.map +1 -0
  86. package/dist/cli/commands/insights.js +127 -0
  87. package/dist/cli/commands/insights.js.map +1 -0
  88. package/dist/cli/commands/knowledge.d.ts +117 -0
  89. package/dist/cli/commands/knowledge.d.ts.map +1 -0
  90. package/dist/cli/commands/knowledge.js +1070 -0
  91. package/dist/cli/commands/knowledge.js.map +1 -0
  92. package/dist/cli/commands/loop.d.ts +95 -0
  93. package/dist/cli/commands/loop.d.ts.map +1 -0
  94. package/dist/cli/commands/loop.js +408 -0
  95. package/dist/cli/commands/loop.js.map +1 -0
  96. package/dist/cli/commands/maintenance.d.ts +33 -0
  97. package/dist/cli/commands/maintenance.d.ts.map +1 -0
  98. package/dist/cli/commands/maintenance.js +75 -0
  99. package/dist/cli/commands/maintenance.js.map +1 -0
  100. package/dist/cli/commands/mcp.d.ts +22 -11
  101. package/dist/cli/commands/mcp.d.ts.map +1 -1
  102. package/dist/cli/commands/mcp.js +93 -5
  103. package/dist/cli/commands/mcp.js.map +1 -1
  104. package/dist/cli/commands/menu.d.ts.map +1 -1
  105. package/dist/cli/commands/menu.js +10 -184
  106. package/dist/cli/commands/menu.js.map +1 -1
  107. package/dist/cli/commands/project.d.ts +98 -0
  108. package/dist/cli/commands/project.d.ts.map +1 -0
  109. package/dist/cli/commands/project.js +382 -0
  110. package/dist/cli/commands/project.js.map +1 -0
  111. package/dist/cli/commands/skills.d.ts +131 -0
  112. package/dist/cli/commands/skills.d.ts.map +1 -1
  113. package/dist/cli/commands/skills.js +404 -118
  114. package/dist/cli/commands/skills.js.map +1 -1
  115. package/dist/cli/commands/spec.d.ts +40 -0
  116. package/dist/cli/commands/spec.d.ts.map +1 -0
  117. package/dist/cli/commands/spec.js +49 -0
  118. package/dist/cli/commands/spec.js.map +1 -0
  119. package/dist/cli/commands/stats.d.ts.map +1 -1
  120. package/dist/cli/commands/stats.js +11 -3
  121. package/dist/cli/commands/stats.js.map +1 -1
  122. package/dist/cli/commands/status.d.ts.map +1 -1
  123. package/dist/cli/commands/status.js +17 -2
  124. package/dist/cli/commands/status.js.map +1 -1
  125. package/dist/cli/commands/trace.d.ts.map +1 -1
  126. package/dist/cli/commands/trace.js +4 -9
  127. package/dist/cli/commands/trace.js.map +1 -1
  128. package/dist/cli/commands/violations.d.ts +14 -0
  129. package/dist/cli/commands/violations.d.ts.map +1 -0
  130. package/dist/cli/commands/violations.js +43 -0
  131. package/dist/cli/commands/violations.js.map +1 -0
  132. package/dist/cli/index.js +34 -0
  133. package/dist/cli/index.js.map +1 -1
  134. package/dist/cli/init/hook-manager.d.ts +1 -1
  135. package/dist/cli/init/hook-manager.d.ts.map +1 -1
  136. package/dist/cli/init/hook-manager.js +6 -0
  137. package/dist/cli/init/hook-manager.js.map +1 -1
  138. package/dist/cli/utils/resolve-session.d.ts +32 -0
  139. package/dist/cli/utils/resolve-session.d.ts.map +1 -0
  140. package/dist/cli/utils/resolve-session.js +39 -0
  141. package/dist/cli/utils/resolve-session.js.map +1 -0
  142. package/dist/core/config.d.ts +4 -1
  143. package/dist/core/config.d.ts.map +1 -1
  144. package/dist/core/config.js +11 -23
  145. package/dist/core/config.js.map +1 -1
  146. package/dist/core/constants.d.ts +51 -13
  147. package/dist/core/constants.d.ts.map +1 -1
  148. package/dist/core/constants.js +63 -13
  149. package/dist/core/constants.js.map +1 -1
  150. package/dist/core/diagnostics/checks.d.ts +151 -0
  151. package/dist/core/diagnostics/checks.d.ts.map +1 -0
  152. package/dist/core/diagnostics/checks.js +766 -0
  153. package/dist/core/diagnostics/checks.js.map +1 -0
  154. package/dist/core/diagnostics/daemon-status.d.ts +77 -0
  155. package/dist/core/diagnostics/daemon-status.d.ts.map +1 -0
  156. package/dist/core/diagnostics/daemon-status.js +113 -0
  157. package/dist/core/diagnostics/daemon-status.js.map +1 -0
  158. package/dist/core/diagnostics/entropy-checks.d.ts +83 -0
  159. package/dist/core/diagnostics/entropy-checks.d.ts.map +1 -0
  160. package/dist/core/diagnostics/entropy-checks.js +400 -0
  161. package/dist/core/diagnostics/entropy-checks.js.map +1 -0
  162. package/dist/core/diagnostics/fix-runner.d.ts +54 -0
  163. package/dist/core/diagnostics/fix-runner.d.ts.map +1 -0
  164. package/dist/core/diagnostics/fix-runner.js +90 -0
  165. package/dist/core/diagnostics/fix-runner.js.map +1 -0
  166. package/dist/core/diagnostics/heartbeat-reader.d.ts +28 -0
  167. package/dist/core/diagnostics/heartbeat-reader.d.ts.map +1 -0
  168. package/dist/core/diagnostics/heartbeat-reader.js +50 -0
  169. package/dist/core/diagnostics/heartbeat-reader.js.map +1 -0
  170. package/dist/core/diagnostics/knip-runner.d.ts +49 -0
  171. package/dist/core/diagnostics/knip-runner.d.ts.map +1 -0
  172. package/dist/core/diagnostics/knip-runner.js +100 -0
  173. package/dist/core/diagnostics/knip-runner.js.map +1 -0
  174. package/dist/core/diagnostics/markers.d.ts +96 -0
  175. package/dist/core/diagnostics/markers.d.ts.map +1 -0
  176. package/dist/core/diagnostics/markers.js +153 -0
  177. package/dist/core/diagnostics/markers.js.map +1 -0
  178. package/dist/core/event-fields.d.ts +27 -0
  179. package/dist/core/event-fields.d.ts.map +1 -1
  180. package/dist/core/event-fields.js +43 -0
  181. package/dist/core/event-fields.js.map +1 -1
  182. package/dist/core/governance/global-inject.d.ts +90 -0
  183. package/dist/core/governance/global-inject.d.ts.map +1 -0
  184. package/dist/core/governance/global-inject.js +184 -0
  185. package/dist/core/governance/global-inject.js.map +1 -0
  186. package/dist/core/insights/agent-anchor-guard.d.ts +77 -0
  187. package/dist/core/insights/agent-anchor-guard.d.ts.map +1 -0
  188. package/dist/core/insights/agent-anchor-guard.js +119 -0
  189. package/dist/core/insights/agent-anchor-guard.js.map +1 -0
  190. package/dist/core/insights/agent-distill-context.d.ts +55 -0
  191. package/dist/core/insights/agent-distill-context.d.ts.map +1 -0
  192. package/dist/core/insights/agent-distill-context.js +146 -0
  193. package/dist/core/insights/agent-distill-context.js.map +1 -0
  194. package/dist/core/insights/agent-distill-spawn.d.ts +56 -0
  195. package/dist/core/insights/agent-distill-spawn.d.ts.map +1 -0
  196. package/dist/core/insights/agent-distill-spawn.js +179 -0
  197. package/dist/core/insights/agent-distill-spawn.js.map +1 -0
  198. package/dist/core/insights/agent-drift.d.ts +66 -0
  199. package/dist/core/insights/agent-drift.d.ts.map +1 -0
  200. package/dist/core/insights/agent-drift.js +109 -0
  201. package/dist/core/insights/agent-drift.js.map +1 -0
  202. package/dist/core/insights/agent-evolution-sources.d.ts +21 -0
  203. package/dist/core/insights/agent-evolution-sources.d.ts.map +1 -0
  204. package/dist/core/insights/agent-evolution-sources.js +36 -0
  205. package/dist/core/insights/agent-evolution-sources.js.map +1 -0
  206. package/dist/core/insights/agent-health.d.ts +142 -0
  207. package/dist/core/insights/agent-health.d.ts.map +1 -0
  208. package/dist/core/insights/agent-health.js +296 -0
  209. package/dist/core/insights/agent-health.js.map +1 -0
  210. package/dist/core/insights/agent-patch-apply.d.ts +45 -0
  211. package/dist/core/insights/agent-patch-apply.d.ts.map +1 -0
  212. package/dist/core/insights/agent-patch-apply.js +165 -0
  213. package/dist/core/insights/agent-patch-apply.js.map +1 -0
  214. package/dist/core/insights/agent-suggest.d.ts +128 -0
  215. package/dist/core/insights/agent-suggest.d.ts.map +1 -0
  216. package/dist/core/insights/agent-suggest.js +284 -0
  217. package/dist/core/insights/agent-suggest.js.map +1 -0
  218. package/dist/core/insights/coverage-tiers.d.ts +46 -0
  219. package/dist/core/insights/coverage-tiers.d.ts.map +1 -0
  220. package/dist/core/insights/coverage-tiers.js +95 -0
  221. package/dist/core/insights/coverage-tiers.js.map +1 -0
  222. package/dist/core/insights/experience-extractor.d.ts +60 -0
  223. package/dist/core/insights/experience-extractor.d.ts.map +1 -0
  224. package/dist/core/insights/experience-extractor.js +319 -0
  225. package/dist/core/insights/experience-extractor.js.map +1 -0
  226. package/dist/core/insights/violation-reporter.d.ts +149 -0
  227. package/dist/core/insights/violation-reporter.d.ts.map +1 -0
  228. package/dist/core/insights/violation-reporter.js +391 -0
  229. package/dist/core/insights/violation-reporter.js.map +1 -0
  230. package/dist/core/loop/loop-engine.d.ts +140 -0
  231. package/dist/core/loop/loop-engine.d.ts.map +1 -0
  232. package/dist/core/loop/loop-engine.js +266 -0
  233. package/dist/core/loop/loop-engine.js.map +1 -0
  234. package/dist/core/queue/index.d.ts +16 -3
  235. package/dist/core/queue/index.d.ts.map +1 -1
  236. package/dist/core/queue/index.js +16 -4
  237. package/dist/core/queue/index.js.map +1 -1
  238. package/dist/core/storage/base.d.ts +317 -0
  239. package/dist/core/storage/base.d.ts.map +1 -1
  240. package/dist/core/storage/base.js +1093 -0
  241. package/dist/core/storage/base.js.map +1 -1
  242. package/dist/core/storage/codec/tool-input-codec.d.ts +93 -0
  243. package/dist/core/storage/codec/tool-input-codec.d.ts.map +1 -0
  244. package/dist/core/storage/codec/tool-input-codec.js +159 -0
  245. package/dist/core/storage/codec/tool-input-codec.js.map +1 -0
  246. package/dist/core/storage/codegraph-types.d.ts +79 -0
  247. package/dist/core/storage/codegraph-types.d.ts.map +1 -0
  248. package/dist/core/storage/codegraph-types.js +14 -0
  249. package/dist/core/storage/codegraph-types.js.map +1 -0
  250. package/dist/core/storage/codegraph.d.ts +186 -0
  251. package/dist/core/storage/codegraph.d.ts.map +1 -0
  252. package/dist/core/storage/codegraph.js +452 -0
  253. package/dist/core/storage/codegraph.js.map +1 -0
  254. package/dist/core/storage/decisions.d.ts +387 -0
  255. package/dist/core/storage/decisions.d.ts.map +1 -0
  256. package/dist/core/storage/decisions.js +534 -0
  257. package/dist/core/storage/decisions.js.map +1 -0
  258. package/dist/core/storage/events.d.ts +239 -8
  259. package/dist/core/storage/events.d.ts.map +1 -1
  260. package/dist/core/storage/events.js +705 -39
  261. package/dist/core/storage/events.js.map +1 -1
  262. package/dist/core/storage/feedback.d.ts +111 -0
  263. package/dist/core/storage/feedback.d.ts.map +1 -0
  264. package/dist/core/storage/feedback.js +186 -0
  265. package/dist/core/storage/feedback.js.map +1 -0
  266. package/dist/core/storage/forge-config.d.ts +40 -0
  267. package/dist/core/storage/forge-config.d.ts.map +1 -0
  268. package/dist/core/storage/forge-config.js +65 -0
  269. package/dist/core/storage/forge-config.js.map +1 -0
  270. package/dist/core/storage/injections.d.ts +68 -0
  271. package/dist/core/storage/injections.d.ts.map +1 -1
  272. package/dist/core/storage/injections.js +131 -5
  273. package/dist/core/storage/injections.js.map +1 -1
  274. package/dist/core/storage/knowledge.d.ts +332 -0
  275. package/dist/core/storage/knowledge.d.ts.map +1 -0
  276. package/dist/core/storage/knowledge.js +589 -0
  277. package/dist/core/storage/knowledge.js.map +1 -0
  278. package/dist/core/storage/maintenance.d.ts +36 -9
  279. package/dist/core/storage/maintenance.d.ts.map +1 -1
  280. package/dist/core/storage/maintenance.js +56 -24
  281. package/dist/core/storage/maintenance.js.map +1 -1
  282. package/dist/core/storage/pipeline-rollup.d.ts +111 -0
  283. package/dist/core/storage/pipeline-rollup.d.ts.map +1 -0
  284. package/dist/core/storage/pipeline-rollup.js +432 -0
  285. package/dist/core/storage/pipeline-rollup.js.map +1 -0
  286. package/dist/core/storage/routing.d.ts +50 -3
  287. package/dist/core/storage/routing.d.ts.map +1 -1
  288. package/dist/core/storage/routing.js +131 -10
  289. package/dist/core/storage/routing.js.map +1 -1
  290. package/dist/core/storage/rows.d.ts +31 -8
  291. package/dist/core/storage/rows.d.ts.map +1 -1
  292. package/dist/core/storage/schema.sql +367 -23
  293. package/dist/core/storage/sessions.d.ts +136 -0
  294. package/dist/core/storage/sessions.d.ts.map +1 -1
  295. package/dist/core/storage/sessions.js +352 -15
  296. package/dist/core/storage/sessions.js.map +1 -1
  297. package/dist/core/storage/skills.d.ts +160 -0
  298. package/dist/core/storage/skills.d.ts.map +1 -1
  299. package/dist/core/storage/skills.js +368 -7
  300. package/dist/core/storage/skills.js.map +1 -1
  301. package/dist/core/storage/sqlite.d.ts +309 -20
  302. package/dist/core/storage/sqlite.d.ts.map +1 -1
  303. package/dist/core/storage/sqlite.js +523 -16
  304. package/dist/core/storage/sqlite.js.map +1 -1
  305. package/dist/core/storage/tasks.d.ts +744 -2
  306. package/dist/core/storage/tasks.d.ts.map +1 -1
  307. package/dist/core/storage/tasks.js +1691 -17
  308. package/dist/core/storage/tasks.js.map +1 -1
  309. package/dist/core/storage/tool-intercepts.d.ts +69 -0
  310. package/dist/core/storage/tool-intercepts.d.ts.map +1 -0
  311. package/dist/core/storage/tool-intercepts.js +116 -0
  312. package/dist/core/storage/tool-intercepts.js.map +1 -0
  313. package/dist/core/types.d.ts +136 -18
  314. package/dist/core/types.d.ts.map +1 -1
  315. package/dist/core/types.js +10 -0
  316. package/dist/core/types.js.map +1 -1
  317. package/dist/core/utils/backup.d.ts +81 -0
  318. package/dist/core/utils/backup.d.ts.map +1 -0
  319. package/dist/core/utils/backup.js +98 -0
  320. package/dist/core/utils/backup.js.map +1 -0
  321. package/dist/core/utils/binary-paths.d.ts +124 -0
  322. package/dist/core/utils/binary-paths.d.ts.map +1 -0
  323. package/dist/core/utils/binary-paths.js +218 -0
  324. package/dist/core/utils/binary-paths.js.map +1 -0
  325. package/dist/core/utils/bypass-token.d.ts +75 -0
  326. package/dist/core/utils/bypass-token.d.ts.map +1 -0
  327. package/dist/core/utils/bypass-token.js +133 -0
  328. package/dist/core/utils/bypass-token.js.map +1 -0
  329. package/dist/core/utils/cc-builtin-agents.d.ts +3 -0
  330. package/dist/core/utils/cc-builtin-agents.d.ts.map +1 -0
  331. package/dist/core/utils/cc-builtin-agents.js +29 -0
  332. package/dist/core/utils/cc-builtin-agents.js.map +1 -0
  333. package/dist/core/utils/claude-cli-resolver.d.ts +26 -0
  334. package/dist/core/utils/claude-cli-resolver.d.ts.map +1 -0
  335. package/dist/core/utils/claude-cli-resolver.js +115 -0
  336. package/dist/core/utils/claude-cli-resolver.js.map +1 -0
  337. package/dist/core/utils/claude-cli-spawn.d.ts +106 -0
  338. package/dist/core/utils/claude-cli-spawn.d.ts.map +1 -0
  339. package/dist/core/utils/claude-cli-spawn.js +219 -0
  340. package/dist/core/utils/claude-cli-spawn.js.map +1 -0
  341. package/dist/core/utils/forge-resume-block.d.ts.map +1 -1
  342. package/dist/core/utils/forge-resume-block.js +3 -2
  343. package/dist/core/utils/forge-resume-block.js.map +1 -1
  344. package/dist/core/utils/logger.d.ts +15 -3
  345. package/dist/core/utils/logger.d.ts.map +1 -1
  346. package/dist/core/utils/logger.js +20 -2
  347. package/dist/core/utils/logger.js.map +1 -1
  348. package/dist/core/utils/noise-prompt.d.ts +97 -0
  349. package/dist/core/utils/noise-prompt.d.ts.map +1 -0
  350. package/dist/core/utils/noise-prompt.js +127 -0
  351. package/dist/core/utils/noise-prompt.js.map +1 -0
  352. package/dist/core/utils/path.d.ts +0 -4
  353. package/dist/core/utils/path.d.ts.map +1 -1
  354. package/dist/core/utils/path.js +0 -7
  355. package/dist/core/utils/path.js.map +1 -1
  356. package/dist/core/utils/time.d.ts +67 -0
  357. package/dist/core/utils/time.d.ts.map +1 -1
  358. package/dist/core/utils/time.js +147 -0
  359. package/dist/core/utils/time.js.map +1 -1
  360. package/dist/daemon/agent-sync.d.ts +24 -0
  361. package/dist/daemon/agent-sync.d.ts.map +1 -0
  362. package/dist/daemon/agent-sync.js +114 -0
  363. package/dist/daemon/agent-sync.js.map +1 -0
  364. package/dist/daemon/config-store.d.ts +55 -0
  365. package/dist/daemon/config-store.d.ts.map +1 -0
  366. package/dist/daemon/config-store.js +146 -0
  367. package/dist/daemon/config-store.js.map +1 -0
  368. package/dist/daemon/event-parser.d.ts +22 -0
  369. package/dist/daemon/event-parser.d.ts.map +1 -1
  370. package/dist/daemon/event-parser.js +54 -3
  371. package/dist/daemon/event-parser.js.map +1 -1
  372. package/dist/daemon/handlers/history-exporter.d.ts.map +1 -1
  373. package/dist/daemon/handlers/history-exporter.js +9 -8
  374. package/dist/daemon/handlers/history-exporter.js.map +1 -1
  375. package/dist/daemon/handlers/post-tool-use.d.ts +66 -4
  376. package/dist/daemon/handlers/post-tool-use.d.ts.map +1 -1
  377. package/dist/daemon/handlers/post-tool-use.js +221 -8
  378. package/dist/daemon/handlers/post-tool-use.js.map +1 -1
  379. package/dist/daemon/handlers/pre-tool-use.d.ts +181 -0
  380. package/dist/daemon/handlers/pre-tool-use.d.ts.map +1 -0
  381. package/dist/daemon/handlers/pre-tool-use.js +618 -0
  382. package/dist/daemon/handlers/pre-tool-use.js.map +1 -0
  383. package/dist/daemon/handlers/stop.d.ts +55 -7
  384. package/dist/daemon/handlers/stop.d.ts.map +1 -1
  385. package/dist/daemon/handlers/stop.js +245 -8
  386. package/dist/daemon/handlers/stop.js.map +1 -1
  387. package/dist/daemon/handlers/user-prompt.d.ts +51 -14
  388. package/dist/daemon/handlers/user-prompt.d.ts.map +1 -1
  389. package/dist/daemon/handlers/user-prompt.js +223 -95
  390. package/dist/daemon/handlers/user-prompt.js.map +1 -1
  391. package/dist/daemon/handlers/violation-content-backfill.d.ts +76 -0
  392. package/dist/daemon/handlers/violation-content-backfill.d.ts.map +1 -0
  393. package/dist/daemon/handlers/violation-content-backfill.js +167 -0
  394. package/dist/daemon/handlers/violation-content-backfill.js.map +1 -0
  395. package/dist/daemon/hook-sync.d.ts.map +1 -1
  396. package/dist/daemon/hook-sync.js +2 -1
  397. package/dist/daemon/hook-sync.js.map +1 -1
  398. package/dist/daemon/index.d.ts +19 -0
  399. package/dist/daemon/index.d.ts.map +1 -1
  400. package/dist/daemon/index.js +439 -86
  401. package/dist/daemon/index.js.map +1 -1
  402. package/dist/daemon/lifecycle.d.ts +48 -1
  403. package/dist/daemon/lifecycle.d.ts.map +1 -1
  404. package/dist/daemon/lifecycle.js +98 -2
  405. package/dist/daemon/lifecycle.js.map +1 -1
  406. package/dist/daemon/router.d.ts +4 -1
  407. package/dist/daemon/router.d.ts.map +1 -1
  408. package/dist/daemon/router.js +4 -2
  409. package/dist/daemon/router.js.map +1 -1
  410. package/dist/daemon/rules/defaults.d.ts +20 -0
  411. package/dist/daemon/rules/defaults.d.ts.map +1 -0
  412. package/dist/daemon/rules/defaults.js +779 -0
  413. package/dist/daemon/rules/defaults.js.map +1 -0
  414. package/dist/daemon/rules/registry.d.ts +47 -0
  415. package/dist/daemon/rules/registry.d.ts.map +1 -0
  416. package/dist/daemon/rules/registry.js +84 -0
  417. package/dist/daemon/rules/registry.js.map +1 -0
  418. package/dist/daemon/rules/types.d.ts +176 -0
  419. package/dist/daemon/rules/types.d.ts.map +1 -0
  420. package/dist/daemon/rules/types.js +15 -0
  421. package/dist/daemon/rules/types.js.map +1 -0
  422. package/dist/daemon/rules/whitelist.d.ts +101 -0
  423. package/dist/daemon/rules/whitelist.d.ts.map +1 -0
  424. package/dist/daemon/rules/whitelist.js +210 -0
  425. package/dist/daemon/rules/whitelist.js.map +1 -0
  426. package/dist/daemon/rules/workflow-defaults.d.ts +52 -0
  427. package/dist/daemon/rules/workflow-defaults.d.ts.map +1 -0
  428. package/dist/daemon/rules/workflow-defaults.js +521 -0
  429. package/dist/daemon/rules/workflow-defaults.js.map +1 -0
  430. package/dist/daemon/server.d.ts +11 -1
  431. package/dist/daemon/server.d.ts.map +1 -1
  432. package/dist/daemon/server.js +7 -1
  433. package/dist/daemon/server.js.map +1 -1
  434. package/dist/daemon/services/codegraph-sync.d.ts +94 -0
  435. package/dist/daemon/services/codegraph-sync.d.ts.map +1 -0
  436. package/dist/daemon/services/codegraph-sync.js +159 -0
  437. package/dist/daemon/services/codegraph-sync.js.map +1 -0
  438. package/dist/daemon/services/context-injector.d.ts +34 -0
  439. package/dist/daemon/services/context-injector.d.ts.map +1 -0
  440. package/dist/daemon/services/context-injector.js +61 -0
  441. package/dist/daemon/services/context-injector.js.map +1 -0
  442. package/dist/daemon/services/decision-hint.d.ts +240 -0
  443. package/dist/daemon/services/decision-hint.d.ts.map +1 -0
  444. package/dist/daemon/services/decision-hint.js +562 -0
  445. package/dist/daemon/services/decision-hint.js.map +1 -0
  446. package/dist/daemon/services/event-ttl-sweep.d.ts +86 -0
  447. package/dist/daemon/services/event-ttl-sweep.d.ts.map +1 -0
  448. package/dist/daemon/services/event-ttl-sweep.js +124 -0
  449. package/dist/daemon/services/event-ttl-sweep.js.map +1 -0
  450. package/dist/daemon/services/feedback-aggregator.d.ts +167 -0
  451. package/dist/daemon/services/feedback-aggregator.d.ts.map +1 -0
  452. package/dist/daemon/services/feedback-aggregator.js +415 -0
  453. package/dist/daemon/services/feedback-aggregator.js.map +1 -0
  454. package/dist/daemon/services/heartbeat-writer.d.ts +46 -0
  455. package/dist/daemon/services/heartbeat-writer.d.ts.map +1 -0
  456. package/dist/daemon/services/heartbeat-writer.js +82 -0
  457. package/dist/daemon/services/heartbeat-writer.js.map +1 -0
  458. package/dist/daemon/services/idle-session-sweeper.d.ts +61 -0
  459. package/dist/daemon/services/idle-session-sweeper.d.ts.map +1 -0
  460. package/dist/daemon/services/idle-session-sweeper.js +94 -0
  461. package/dist/daemon/services/idle-session-sweeper.js.map +1 -0
  462. package/dist/daemon/services/idle-task-budget.d.ts +50 -0
  463. package/dist/daemon/services/idle-task-budget.d.ts.map +1 -0
  464. package/dist/daemon/services/idle-task-budget.js +72 -0
  465. package/dist/daemon/services/idle-task-budget.js.map +1 -0
  466. package/dist/daemon/services/intercept-revive.d.ts +60 -0
  467. package/dist/daemon/services/intercept-revive.d.ts.map +1 -0
  468. package/dist/daemon/services/intercept-revive.js +86 -0
  469. package/dist/daemon/services/intercept-revive.js.map +1 -0
  470. package/dist/daemon/services/intercept-rollback-guard.d.ts +105 -0
  471. package/dist/daemon/services/intercept-rollback-guard.d.ts.map +1 -0
  472. package/dist/daemon/services/intercept-rollback-guard.js +152 -0
  473. package/dist/daemon/services/intercept-rollback-guard.js.map +1 -0
  474. package/dist/daemon/services/intercept-timeout-sweeper.d.ts +58 -0
  475. package/dist/daemon/services/intercept-timeout-sweeper.d.ts.map +1 -0
  476. package/dist/daemon/services/intercept-timeout-sweeper.js +83 -0
  477. package/dist/daemon/services/intercept-timeout-sweeper.js.map +1 -0
  478. package/dist/daemon/services/kb-injector.d.ts +57 -0
  479. package/dist/daemon/services/kb-injector.d.ts.map +1 -0
  480. package/dist/daemon/services/kb-injector.js +148 -0
  481. package/dist/daemon/services/kb-injector.js.map +1 -0
  482. package/dist/daemon/services/kb-rebuild-scheduler.d.ts +95 -0
  483. package/dist/daemon/services/kb-rebuild-scheduler.d.ts.map +1 -0
  484. package/dist/daemon/services/kb-rebuild-scheduler.js +149 -0
  485. package/dist/daemon/services/kb-rebuild-scheduler.js.map +1 -0
  486. package/dist/daemon/services/loop-hint.d.ts +139 -0
  487. package/dist/daemon/services/loop-hint.d.ts.map +1 -0
  488. package/dist/daemon/services/loop-hint.js +272 -0
  489. package/dist/daemon/services/loop-hint.js.map +1 -0
  490. package/dist/daemon/services/outcome-classification-service.d.ts +49 -0
  491. package/dist/daemon/services/outcome-classification-service.d.ts.map +1 -0
  492. package/dist/daemon/services/outcome-classification-service.js +214 -0
  493. package/dist/daemon/services/outcome-classification-service.js.map +1 -0
  494. package/dist/daemon/services/outcome-classifier.d.ts +136 -0
  495. package/dist/daemon/services/outcome-classifier.d.ts.map +1 -0
  496. package/dist/daemon/services/outcome-classifier.js +178 -0
  497. package/dist/daemon/services/outcome-classifier.js.map +1 -0
  498. package/dist/daemon/services/outcome-nudge.d.ts +107 -0
  499. package/dist/daemon/services/outcome-nudge.d.ts.map +1 -0
  500. package/dist/daemon/services/outcome-nudge.js +242 -0
  501. package/dist/daemon/services/outcome-nudge.js.map +1 -0
  502. package/dist/daemon/services/spec-approval.d.ts +127 -0
  503. package/dist/daemon/services/spec-approval.d.ts.map +1 -0
  504. package/dist/daemon/services/spec-approval.js +216 -0
  505. package/dist/daemon/services/spec-approval.js.map +1 -0
  506. package/dist/daemon/services/spec-gate.d.ts +54 -0
  507. package/dist/daemon/services/spec-gate.d.ts.map +1 -0
  508. package/dist/daemon/services/spec-gate.js +113 -0
  509. package/dist/daemon/services/spec-gate.js.map +1 -0
  510. package/dist/daemon/services/task-boundary-classifier.d.ts +78 -0
  511. package/dist/daemon/services/task-boundary-classifier.d.ts.map +1 -0
  512. package/dist/daemon/services/task-boundary-classifier.js +202 -0
  513. package/dist/daemon/services/task-boundary-classifier.js.map +1 -0
  514. package/dist/daemon/services/task-segmenter.d.ts +230 -1
  515. package/dist/daemon/services/task-segmenter.d.ts.map +1 -1
  516. package/dist/daemon/services/task-segmenter.js +527 -17
  517. package/dist/daemon/services/task-segmenter.js.map +1 -1
  518. package/dist/daemon/skill-sync.d.ts +7 -2
  519. package/dist/daemon/skill-sync.d.ts.map +1 -1
  520. package/dist/daemon/skill-sync.js +114 -9
  521. package/dist/daemon/skill-sync.js.map +1 -1
  522. package/dist/daemon/startup/maintenance-schedulers.d.ts +68 -0
  523. package/dist/daemon/startup/maintenance-schedulers.d.ts.map +1 -0
  524. package/dist/daemon/startup/maintenance-schedulers.js +294 -0
  525. package/dist/daemon/startup/maintenance-schedulers.js.map +1 -0
  526. package/dist/daemon/templates/agents/agent-retro-distiller.md +106 -0
  527. package/dist/daemon/templates/agents/claudemd-writer.md +102 -0
  528. package/dist/daemon/templates/agents/coder.md +262 -0
  529. package/dist/daemon/templates/agents/decision-maker.md +546 -0
  530. package/dist/daemon/templates/agents/doc-reviewer.md +118 -0
  531. package/dist/daemon/templates/agents/harness-debug-full.md +196 -0
  532. package/dist/daemon/templates/agents/knowledge-builder.md +120 -0
  533. package/dist/daemon/templates/agents/patch-applier.md +145 -0
  534. package/dist/daemon/templates/agents/planner.md +217 -0
  535. package/dist/daemon/templates/agents/safety-net-implementer.md +278 -0
  536. package/dist/daemon/templates/agents/skill-distiller.md +114 -0
  537. package/dist/daemon/templates/agents/task-boundary-classifier.md +65 -0
  538. package/dist/daemon/templates/agents/verify-agent.md +259 -0
  539. package/dist/daemon/utils/inject-block.d.ts +39 -0
  540. package/dist/daemon/utils/inject-block.d.ts.map +1 -0
  541. package/dist/daemon/utils/inject-block.js +25 -0
  542. package/dist/daemon/utils/inject-block.js.map +1 -0
  543. package/dist/hooks/hook-lib.sh +8 -0
  544. package/dist/hooks/notification.sh +19 -8
  545. package/dist/hooks/post-tool-use.sh +41 -23
  546. package/dist/hooks/pre-tool-use.sh +54 -23
  547. package/dist/hooks/session-start.sh +68 -0
  548. package/dist/hooks/stop.sh +31 -11
  549. package/dist/hooks/user-prompt-submit.sh +37 -21
  550. package/dist/knowledge/adapters/go-adapter.d.ts +65 -0
  551. package/dist/knowledge/adapters/go-adapter.d.ts.map +1 -0
  552. package/dist/knowledge/adapters/go-adapter.js +294 -0
  553. package/dist/knowledge/adapters/go-adapter.js.map +1 -0
  554. package/dist/knowledge/adapters/index.d.ts +41 -0
  555. package/dist/knowledge/adapters/index.d.ts.map +1 -0
  556. package/dist/knowledge/adapters/index.js +71 -0
  557. package/dist/knowledge/adapters/index.js.map +1 -0
  558. package/dist/knowledge/adapters/java-adapter.d.ts +66 -0
  559. package/dist/knowledge/adapters/java-adapter.d.ts.map +1 -0
  560. package/dist/knowledge/adapters/java-adapter.js +260 -0
  561. package/dist/knowledge/adapters/java-adapter.js.map +1 -0
  562. package/dist/knowledge/adapters/js-vue-adapter.d.ts +56 -0
  563. package/dist/knowledge/adapters/js-vue-adapter.d.ts.map +1 -0
  564. package/dist/knowledge/adapters/js-vue-adapter.js +203 -0
  565. package/dist/knowledge/adapters/js-vue-adapter.js.map +1 -0
  566. package/dist/knowledge/adapters/kotlin-adapter.d.ts +55 -0
  567. package/dist/knowledge/adapters/kotlin-adapter.d.ts.map +1 -0
  568. package/dist/knowledge/adapters/kotlin-adapter.js +209 -0
  569. package/dist/knowledge/adapters/kotlin-adapter.js.map +1 -0
  570. package/dist/knowledge/adapters/monorepo-adapter.d.ts +77 -0
  571. package/dist/knowledge/adapters/monorepo-adapter.d.ts.map +1 -0
  572. package/dist/knowledge/adapters/monorepo-adapter.js +170 -0
  573. package/dist/knowledge/adapters/monorepo-adapter.js.map +1 -0
  574. package/dist/knowledge/adapters/python-adapter.d.ts +89 -0
  575. package/dist/knowledge/adapters/python-adapter.d.ts.map +1 -0
  576. package/dist/knowledge/adapters/python-adapter.js +358 -0
  577. package/dist/knowledge/adapters/python-adapter.js.map +1 -0
  578. package/dist/knowledge/adapters/rust-adapter.d.ts +73 -0
  579. package/dist/knowledge/adapters/rust-adapter.d.ts.map +1 -0
  580. package/dist/knowledge/adapters/rust-adapter.js +329 -0
  581. package/dist/knowledge/adapters/rust-adapter.js.map +1 -0
  582. package/dist/knowledge/adapters/types.d.ts +99 -0
  583. package/dist/knowledge/adapters/types.d.ts.map +1 -0
  584. package/dist/knowledge/adapters/types.js +17 -0
  585. package/dist/knowledge/adapters/types.js.map +1 -0
  586. package/dist/knowledge/adapters/typescript-adapter.d.ts +57 -0
  587. package/dist/knowledge/adapters/typescript-adapter.d.ts.map +1 -0
  588. package/dist/knowledge/adapters/typescript-adapter.js +171 -0
  589. package/dist/knowledge/adapters/typescript-adapter.js.map +1 -0
  590. package/dist/knowledge/audit-applier.d.ts +70 -0
  591. package/dist/knowledge/audit-applier.d.ts.map +1 -0
  592. package/dist/knowledge/audit-applier.js +251 -0
  593. package/dist/knowledge/audit-applier.js.map +1 -0
  594. package/dist/knowledge/builder.d.ts +261 -0
  595. package/dist/knowledge/builder.d.ts.map +1 -0
  596. package/dist/knowledge/builder.js +966 -0
  597. package/dist/knowledge/builder.js.map +1 -0
  598. package/dist/knowledge/cli-provider.d.ts +151 -0
  599. package/dist/knowledge/cli-provider.d.ts.map +1 -0
  600. package/dist/knowledge/cli-provider.js +313 -0
  601. package/dist/knowledge/cli-provider.js.map +1 -0
  602. package/dist/knowledge/constants.d.ts +78 -0
  603. package/dist/knowledge/constants.d.ts.map +1 -0
  604. package/dist/knowledge/constants.js +98 -0
  605. package/dist/knowledge/constants.js.map +1 -0
  606. package/dist/knowledge/cross-module.d.ts +139 -0
  607. package/dist/knowledge/cross-module.d.ts.map +1 -0
  608. package/dist/knowledge/cross-module.js +370 -0
  609. package/dist/knowledge/cross-module.js.map +1 -0
  610. package/dist/knowledge/git-hooks.d.ts +67 -0
  611. package/dist/knowledge/git-hooks.d.ts.map +1 -0
  612. package/dist/knowledge/git-hooks.js +258 -0
  613. package/dist/knowledge/git-hooks.js.map +1 -0
  614. package/dist/knowledge/graph/edge-extractor.d.ts +45 -0
  615. package/dist/knowledge/graph/edge-extractor.d.ts.map +1 -0
  616. package/dist/knowledge/graph/edge-extractor.js +242 -0
  617. package/dist/knowledge/graph/edge-extractor.js.map +1 -0
  618. package/dist/knowledge/graph/impact.d.ts +73 -0
  619. package/dist/knowledge/graph/impact.d.ts.map +1 -0
  620. package/dist/knowledge/graph/impact.js +94 -0
  621. package/dist/knowledge/graph/impact.js.map +1 -0
  622. package/dist/knowledge/graph/types.d.ts +22 -0
  623. package/dist/knowledge/graph/types.d.ts.map +1 -0
  624. package/dist/knowledge/graph/types.js +13 -0
  625. package/dist/knowledge/graph/types.js.map +1 -0
  626. package/dist/knowledge/module-hash.d.ts +88 -0
  627. package/dist/knowledge/module-hash.d.ts.map +1 -0
  628. package/dist/knowledge/module-hash.js +162 -0
  629. package/dist/knowledge/module-hash.js.map +1 -0
  630. package/dist/knowledge/project-detector.d.ts +101 -0
  631. package/dist/knowledge/project-detector.d.ts.map +1 -0
  632. package/dist/knowledge/project-detector.js +223 -0
  633. package/dist/knowledge/project-detector.js.map +1 -0
  634. package/dist/knowledge/prompt.d.ts +237 -0
  635. package/dist/knowledge/prompt.d.ts.map +1 -0
  636. package/dist/knowledge/prompt.js +416 -0
  637. package/dist/knowledge/prompt.js.map +1 -0
  638. package/dist/knowledge/query.d.ts +118 -0
  639. package/dist/knowledge/query.d.ts.map +1 -0
  640. package/dist/knowledge/query.js +438 -0
  641. package/dist/knowledge/query.js.map +1 -0
  642. package/dist/knowledge/repo-map.d.ts +97 -0
  643. package/dist/knowledge/repo-map.d.ts.map +1 -0
  644. package/dist/knowledge/repo-map.js +447 -0
  645. package/dist/knowledge/repo-map.js.map +1 -0
  646. package/dist/knowledge/tools/index.d.ts +14 -0
  647. package/dist/knowledge/tools/index.d.ts.map +1 -0
  648. package/dist/knowledge/tools/index.js +11 -0
  649. package/dist/knowledge/tools/index.js.map +1 -0
  650. package/dist/knowledge/tools/knowledge-get-page.d.ts +46 -0
  651. package/dist/knowledge/tools/knowledge-get-page.d.ts.map +1 -0
  652. package/dist/knowledge/tools/knowledge-get-page.js +101 -0
  653. package/dist/knowledge/tools/knowledge-get-page.js.map +1 -0
  654. package/dist/knowledge/tools/knowledge-query.d.ts +77 -0
  655. package/dist/knowledge/tools/knowledge-query.d.ts.map +1 -0
  656. package/dist/knowledge/tools/knowledge-query.js +104 -0
  657. package/dist/knowledge/tools/knowledge-query.js.map +1 -0
  658. package/dist/knowledge/tools/repo-map-lookup.d.ts +45 -0
  659. package/dist/knowledge/tools/repo-map-lookup.d.ts.map +1 -0
  660. package/dist/knowledge/tools/repo-map-lookup.js +82 -0
  661. package/dist/knowledge/tools/repo-map-lookup.js.map +1 -0
  662. package/dist/knowledge/types.d.ts +269 -0
  663. package/dist/knowledge/types.d.ts.map +1 -0
  664. package/dist/knowledge/types.js +10 -0
  665. package/dist/knowledge/types.js.map +1 -0
  666. package/dist/knowledge/validator.d.ts +90 -0
  667. package/dist/knowledge/validator.d.ts.map +1 -0
  668. package/dist/knowledge/validator.js +355 -0
  669. package/dist/knowledge/validator.js.map +1 -0
  670. package/dist/mcp/server.d.ts +64 -8
  671. package/dist/mcp/server.d.ts.map +1 -1
  672. package/dist/mcp/server.js +448 -12
  673. package/dist/mcp/server.js.map +1 -1
  674. package/dist/skills/builtin-skills.d.ts +35 -0
  675. package/dist/skills/builtin-skills.d.ts.map +1 -0
  676. package/dist/skills/builtin-skills.js +68 -0
  677. package/dist/skills/builtin-skills.js.map +1 -0
  678. package/dist/skills/distill/attribution.d.ts +59 -0
  679. package/dist/skills/distill/attribution.d.ts.map +1 -0
  680. package/dist/skills/distill/attribution.js +101 -0
  681. package/dist/skills/distill/attribution.js.map +1 -0
  682. package/dist/skills/distill/distiller.d.ts +161 -0
  683. package/dist/skills/distill/distiller.d.ts.map +1 -0
  684. package/dist/skills/distill/distiller.js +461 -0
  685. package/dist/skills/distill/distiller.js.map +1 -0
  686. package/dist/skills/distill/index.d.ts +223 -0
  687. package/dist/skills/distill/index.d.ts.map +1 -0
  688. package/dist/skills/distill/index.js +466 -0
  689. package/dist/skills/distill/index.js.map +1 -0
  690. package/dist/skills/distill/project-anchor-guard.d.ts +116 -0
  691. package/dist/skills/distill/project-anchor-guard.d.ts.map +1 -0
  692. package/dist/skills/distill/project-anchor-guard.js +334 -0
  693. package/dist/skills/distill/project-anchor-guard.js.map +1 -0
  694. package/dist/skills/distill/topic-deduper.d.ts +77 -0
  695. package/dist/skills/distill/topic-deduper.d.ts.map +1 -0
  696. package/dist/skills/distill/topic-deduper.js +119 -0
  697. package/dist/skills/distill/topic-deduper.js.map +1 -0
  698. package/dist/skills/distill/upstream-fetcher.d.ts +71 -0
  699. package/dist/skills/distill/upstream-fetcher.d.ts.map +1 -0
  700. package/dist/skills/distill/upstream-fetcher.js +202 -0
  701. package/dist/skills/distill/upstream-fetcher.js.map +1 -0
  702. package/dist/skills/distilled/distilled-api-design.md +495 -0
  703. package/dist/skills/distilled/distilled-architecture-decision.md +173 -0
  704. package/dist/skills/distilled/distilled-brainstorming.md +79 -0
  705. package/dist/skills/distilled/distilled-brand-guidelines.md +86 -0
  706. package/dist/skills/distilled/distilled-canvas-design.md +128 -0
  707. package/dist/skills/distilled/distilled-claude-api.md +185 -0
  708. package/dist/skills/distilled/distilled-creator.md +181 -0
  709. package/dist/skills/distilled/distilled-db-schema-design.md +245 -0
  710. package/dist/skills/distilled/distilled-dispatching-parallel-agents.md +136 -0
  711. package/dist/skills/distilled/distilled-doc-coauthoring.md +144 -0
  712. package/dist/skills/distilled/distilled-docx.md +231 -0
  713. package/dist/skills/distilled/distilled-executing-plans.md +148 -0
  714. package/dist/skills/distilled/distilled-finishing-a-development-branch.md +213 -0
  715. package/dist/skills/distilled/distilled-frontend-design.md +118 -0
  716. package/dist/skills/distilled/distilled-harness-engineering.md +242 -0
  717. package/dist/skills/distilled/distilled-karpathy-guidelines.md +104 -0
  718. package/dist/skills/distilled/distilled-performance-optimization.md +175 -0
  719. package/dist/skills/distilled/distilled-receiving-code-review.md +185 -0
  720. package/dist/skills/distilled/distilled-spec-driven-design.md +193 -0
  721. package/dist/skills/distilled/distilled-subagent-driven-development.md +124 -0
  722. package/dist/skills/distilled/distilled-systematic-debugging.md +154 -0
  723. package/dist/skills/distilled/distilled-test-driven-development.md +432 -0
  724. package/dist/skills/distilled/distilled-using-superpowers.md +134 -0
  725. package/dist/skills/distilled/distilled-verification-before-completion.md +213 -0
  726. package/dist/skills/distilled/distilled-writing-skills.md +175 -0
  727. package/dist/skills/registry.d.ts +55 -51
  728. package/dist/skills/registry.d.ts.map +1 -1
  729. package/dist/skills/registry.js +82 -196
  730. package/dist/skills/registry.js.map +1 -1
  731. package/dist/skills/tools/pipeline-suggest.js +14 -14
  732. package/dist/skills/tools/pipeline-suggest.js.map +1 -1
  733. package/dist/skills/tools/skill-invoke.d.ts +3 -2
  734. package/dist/skills/tools/skill-invoke.d.ts.map +1 -1
  735. package/dist/skills/tools/skill-invoke.js +4 -2
  736. package/dist/skills/tools/skill-invoke.js.map +1 -1
  737. package/dist/web/analytics/anti-pattern-detector.d.ts.map +1 -1
  738. package/dist/web/analytics/anti-pattern-detector.js +6 -1
  739. package/dist/web/analytics/anti-pattern-detector.js.map +1 -1
  740. package/dist/web/analytics/drift-detector.d.ts +6 -0
  741. package/dist/web/analytics/drift-detector.d.ts.map +1 -1
  742. package/dist/web/analytics/drift-detector.js +15 -8
  743. package/dist/web/analytics/drift-detector.js.map +1 -1
  744. package/dist/web/analytics/weekly-report.d.ts +13 -0
  745. package/dist/web/analytics/weekly-report.d.ts.map +1 -1
  746. package/dist/web/analytics/weekly-report.js +17 -3
  747. package/dist/web/analytics/weekly-report.js.map +1 -1
  748. package/dist/web/routes/_helpers.d.ts +31 -0
  749. package/dist/web/routes/_helpers.d.ts.map +1 -1
  750. package/dist/web/routes/_helpers.js +33 -0
  751. package/dist/web/routes/_helpers.js.map +1 -1
  752. package/dist/web/routes/agent-content.d.ts +30 -0
  753. package/dist/web/routes/agent-content.d.ts.map +1 -0
  754. package/dist/web/routes/agent-content.js +139 -0
  755. package/dist/web/routes/agent-content.js.map +1 -0
  756. package/dist/web/routes/agent-distill.d.ts +49 -0
  757. package/dist/web/routes/agent-distill.d.ts.map +1 -0
  758. package/dist/web/routes/agent-distill.js +526 -0
  759. package/dist/web/routes/agent-distill.js.map +1 -0
  760. package/dist/web/routes/config.d.ts +56 -0
  761. package/dist/web/routes/config.d.ts.map +1 -0
  762. package/dist/web/routes/config.js +243 -0
  763. package/dist/web/routes/config.js.map +1 -0
  764. package/dist/web/routes/decisions.d.ts +15 -0
  765. package/dist/web/routes/decisions.d.ts.map +1 -0
  766. package/dist/web/routes/decisions.js +181 -0
  767. package/dist/web/routes/decisions.js.map +1 -0
  768. package/dist/web/routes/diagnostics.d.ts +61 -0
  769. package/dist/web/routes/diagnostics.d.ts.map +1 -0
  770. package/dist/web/routes/diagnostics.js +203 -0
  771. package/dist/web/routes/diagnostics.js.map +1 -0
  772. package/dist/web/routes/error-handler.d.ts +0 -4
  773. package/dist/web/routes/error-handler.d.ts.map +1 -1
  774. package/dist/web/routes/error-handler.js +0 -8
  775. package/dist/web/routes/error-handler.js.map +1 -1
  776. package/dist/web/routes/events.d.ts.map +1 -1
  777. package/dist/web/routes/events.js +26 -1
  778. package/dist/web/routes/events.js.map +1 -1
  779. package/dist/web/routes/health.d.ts +33 -0
  780. package/dist/web/routes/health.d.ts.map +1 -0
  781. package/dist/web/routes/health.js +37 -0
  782. package/dist/web/routes/health.js.map +1 -0
  783. package/dist/web/routes/insights.d.ts +0 -5
  784. package/dist/web/routes/insights.d.ts.map +1 -1
  785. package/dist/web/routes/insights.js +0 -0
  786. package/dist/web/routes/insights.js.map +1 -1
  787. package/dist/web/routes/knowledge.d.ts +57 -0
  788. package/dist/web/routes/knowledge.d.ts.map +1 -0
  789. package/dist/web/routes/knowledge.js +772 -0
  790. package/dist/web/routes/knowledge.js.map +1 -0
  791. package/dist/web/routes/patch.d.ts +60 -1
  792. package/dist/web/routes/patch.d.ts.map +1 -1
  793. package/dist/web/routes/patch.js +170 -64
  794. package/dist/web/routes/patch.js.map +1 -1
  795. package/dist/web/routes/pipeline.d.ts +28 -0
  796. package/dist/web/routes/pipeline.d.ts.map +1 -0
  797. package/dist/web/routes/pipeline.js +145 -0
  798. package/dist/web/routes/pipeline.js.map +1 -0
  799. package/dist/web/routes/rules.d.ts.map +1 -1
  800. package/dist/web/routes/rules.js +26 -7
  801. package/dist/web/routes/rules.js.map +1 -1
  802. package/dist/web/routes/sessions.d.ts.map +1 -1
  803. package/dist/web/routes/sessions.js +17 -8
  804. package/dist/web/routes/sessions.js.map +1 -1
  805. package/dist/web/routes/skill-content.d.ts +30 -0
  806. package/dist/web/routes/skill-content.d.ts.map +1 -0
  807. package/dist/web/routes/skill-content.js +117 -0
  808. package/dist/web/routes/skill-content.js.map +1 -0
  809. package/dist/web/routes/skill-stats.d.ts.map +1 -1
  810. package/dist/web/routes/skill-stats.js +153 -16
  811. package/dist/web/routes/skill-stats.js.map +1 -1
  812. package/dist/web/routes/skills-distill.d.ts +29 -0
  813. package/dist/web/routes/skills-distill.d.ts.map +1 -0
  814. package/dist/web/routes/skills-distill.js +552 -0
  815. package/dist/web/routes/skills-distill.js.map +1 -0
  816. package/dist/web/routes/skills.js +7 -7
  817. package/dist/web/routes/skills.js.map +1 -1
  818. package/dist/web/routes/stats.d.ts.map +1 -1
  819. package/dist/web/routes/stats.js +2 -1
  820. package/dist/web/routes/stats.js.map +1 -1
  821. package/dist/web/routes/task-timeline.d.ts +178 -0
  822. package/dist/web/routes/task-timeline.d.ts.map +1 -0
  823. package/dist/web/routes/task-timeline.js +530 -0
  824. package/dist/web/routes/task-timeline.js.map +1 -0
  825. package/dist/web/routes/tasks.d.ts.map +1 -1
  826. package/dist/web/routes/tasks.js +377 -8
  827. package/dist/web/routes/tasks.js.map +1 -1
  828. package/dist/web/routes/trace.d.ts.map +1 -1
  829. package/dist/web/routes/trace.js +3 -2
  830. package/dist/web/routes/trace.js.map +1 -1
  831. package/dist/web/routes/types.d.ts +0 -4
  832. package/dist/web/routes/types.d.ts.map +1 -1
  833. package/dist/web/routes/types.js +1 -1
  834. package/dist/web/routes/types.js.map +1 -1
  835. package/dist/web/routes/violations.d.ts +14 -0
  836. package/dist/web/routes/violations.d.ts.map +1 -0
  837. package/dist/web/routes/violations.js +112 -0
  838. package/dist/web/routes/violations.js.map +1 -0
  839. package/dist/web/server.d.ts.map +1 -1
  840. package/dist/web/server.js +99 -19
  841. package/dist/web/server.js.map +1 -1
  842. package/dist/web/services/agent-distill-manager.d.ts +122 -0
  843. package/dist/web/services/agent-distill-manager.d.ts.map +1 -0
  844. package/dist/web/services/agent-distill-manager.js +397 -0
  845. package/dist/web/services/agent-distill-manager.js.map +1 -0
  846. package/dist/web/services/build-manager.d.ts +72 -0
  847. package/dist/web/services/build-manager.d.ts.map +1 -0
  848. package/dist/web/services/build-manager.js +189 -0
  849. package/dist/web/services/build-manager.js.map +1 -0
  850. package/dist/web/services/distill-manager.d.ts +172 -0
  851. package/dist/web/services/distill-manager.d.ts.map +1 -0
  852. package/dist/web/services/distill-manager.js +411 -0
  853. package/dist/web/services/distill-manager.js.map +1 -0
  854. package/dist/web/static/assets/AgentDetailPage-DlUeA1sX.js +2 -0
  855. package/dist/web/static/assets/AgentDetailPage-DlUeA1sX.js.map +1 -0
  856. package/dist/web/static/assets/AgentDistillRunPage-Cybo4bii.js +3 -0
  857. package/dist/web/static/assets/AgentDistillRunPage-Cybo4bii.js.map +1 -0
  858. package/dist/web/static/assets/AgentsPage-Qd9FExLG.js +2 -0
  859. package/dist/web/static/assets/AgentsPage-Qd9FExLG.js.map +1 -0
  860. package/dist/web/static/assets/DaemonHealthPage-DTSVqtrI.js +2 -0
  861. package/dist/web/static/assets/DaemonHealthPage-DTSVqtrI.js.map +1 -0
  862. package/dist/web/static/assets/DecisionDetailPage-b4BA8dhc.js +2 -0
  863. package/dist/web/static/assets/DecisionDetailPage-b4BA8dhc.js.map +1 -0
  864. package/dist/web/static/assets/DecisionsPage-a3NRo_T7.js +2 -0
  865. package/dist/web/static/assets/DecisionsPage-a3NRo_T7.js.map +1 -0
  866. package/dist/web/static/assets/DiagnosticsPage-DIVdiIQG.js +2 -0
  867. package/dist/web/static/assets/DiagnosticsPage-DIVdiIQG.js.map +1 -0
  868. package/dist/web/static/assets/DistillDetailPage-U6a3l2iP.js +4 -0
  869. package/dist/web/static/assets/DistillDetailPage-U6a3l2iP.js.map +1 -0
  870. package/dist/web/static/assets/DistillPage-O7BHtRN8.js +2 -0
  871. package/dist/web/static/assets/DistillPage-O7BHtRN8.js.map +1 -0
  872. package/dist/web/static/assets/DistillRunPage-D1JuRWWr.js +2 -0
  873. package/dist/web/static/assets/DistillRunPage-D1JuRWWr.js.map +1 -0
  874. package/dist/web/static/assets/GlobalScopeHint-Q3wTJx3F.js +2 -0
  875. package/dist/web/static/assets/GlobalScopeHint-Q3wTJx3F.js.map +1 -0
  876. package/dist/web/static/assets/IssueDetailPage-BDfrtk2C.js +2 -0
  877. package/dist/web/static/assets/IssueDetailPage-BDfrtk2C.js.map +1 -0
  878. package/dist/web/static/assets/IssuesPage-SKmhlCrw.js +2 -0
  879. package/dist/web/static/assets/IssuesPage-SKmhlCrw.js.map +1 -0
  880. package/dist/web/static/assets/KbDetailPage-Yna86Na8.js +2 -0
  881. package/dist/web/static/assets/KbDetailPage-Yna86Na8.js.map +1 -0
  882. package/dist/web/static/assets/KbHitsPage-Cljl7H9p.js +2 -0
  883. package/dist/web/static/assets/KbHitsPage-Cljl7H9p.js.map +1 -0
  884. package/dist/web/static/assets/MarkdownRenderer-DlDQNihj.js +3 -0
  885. package/dist/web/static/assets/MarkdownRenderer-DlDQNihj.js.map +1 -0
  886. package/dist/web/static/assets/NotFound-LMzbP51V.js +2 -0
  887. package/dist/web/static/assets/NotFound-LMzbP51V.js.map +1 -0
  888. package/dist/web/static/assets/SettingsPage-DzoK4PKg.js +2 -0
  889. package/dist/web/static/assets/SettingsPage-DzoK4PKg.js.map +1 -0
  890. package/dist/web/static/assets/SkillDetailPage-BuBJJ_NX.js +2 -0
  891. package/dist/web/static/assets/SkillDetailPage-BuBJJ_NX.js.map +1 -0
  892. package/dist/web/static/assets/SkillsPage-aojkJpBc.js +2 -0
  893. package/dist/web/static/assets/SkillsPage-aojkJpBc.js.map +1 -0
  894. package/dist/web/static/assets/TaskDetailPage-1ckxnGhw.js +4 -0
  895. package/dist/web/static/assets/TaskDetailPage-1ckxnGhw.js.map +1 -0
  896. package/dist/web/static/assets/TasksHubPage-C2PLh3eg.js +6 -0
  897. package/dist/web/static/assets/TasksHubPage-C2PLh3eg.js.map +1 -0
  898. package/dist/web/static/assets/WorkplacePage-DHrp5VxS.js +2 -0
  899. package/dist/web/static/assets/WorkplacePage-DHrp5VxS.js.map +1 -0
  900. package/dist/web/static/assets/arco-DFQA6dO_.css +1 -0
  901. package/dist/web/static/assets/arco-DV6xCLhr.js +14 -0
  902. package/dist/web/static/assets/arco-DV6xCLhr.js.map +1 -0
  903. package/dist/web/static/assets/charts-BSV4cyC4.js +37 -0
  904. package/dist/web/static/assets/charts-BSV4cyC4.js.map +1 -0
  905. package/dist/web/static/assets/date-fns-sbWH3_uq.js +2 -0
  906. package/dist/web/static/assets/date-fns-sbWH3_uq.js.map +1 -0
  907. package/dist/web/static/assets/index-B_v_MKlb.css +1 -0
  908. package/dist/web/static/assets/index-DileOOE4.js +4 -0
  909. package/dist/web/static/assets/index-DileOOE4.js.map +1 -0
  910. package/dist/web/static/assets/lucide-CnlPQoG8.js +72 -0
  911. package/dist/web/static/assets/lucide-CnlPQoG8.js.map +1 -0
  912. package/dist/web/static/assets/markdown-CA7ePUts.js +30 -0
  913. package/dist/web/static/assets/markdown-CA7ePUts.js.map +1 -0
  914. package/dist/web/static/assets/outcome-BKGy9azt.js +2 -0
  915. package/dist/web/static/assets/outcome-BKGy9azt.js.map +1 -0
  916. package/dist/web/static/assets/query-CgCOpYWf.js +2 -0
  917. package/dist/web/static/assets/{query-C99w429o.js.map → query-CgCOpYWf.js.map} +1 -1
  918. package/dist/web/static/assets/{react-router-r79dBVy4.js → react-router-Cxmg8RuL.js} +3 -3
  919. package/dist/web/static/assets/{react-router-r79dBVy4.js.map → react-router-Cxmg8RuL.js.map} +1 -1
  920. package/dist/web/static/assets/react-vendor-tkvCrao7.js +57 -0
  921. package/dist/web/static/assets/react-vendor-tkvCrao7.js.map +1 -0
  922. package/dist/web/static/assets/syntax-highlighter-BDYycNja.js +6 -0
  923. package/dist/web/static/assets/syntax-highlighter-BDYycNja.js.map +1 -0
  924. package/dist/web/static/assets/useAgentStats-B-uTgqBd.js +2 -0
  925. package/dist/web/static/assets/useAgentStats-B-uTgqBd.js.map +1 -0
  926. package/dist/web/static/assets/useDecisions-D-G2Ft5T.js +2 -0
  927. package/dist/web/static/assets/useDecisions-D-G2Ft5T.js.map +1 -0
  928. package/dist/web/static/assets/useDistill-21dZkXlT.js +3 -0
  929. package/dist/web/static/assets/useDistill-21dZkXlT.js.map +1 -0
  930. package/dist/web/static/assets/useEffectiveProject-DQiyX54y.js +2 -0
  931. package/dist/web/static/assets/useEffectiveProject-DQiyX54y.js.map +1 -0
  932. package/dist/web/static/assets/useIssuesFeed-CFiyQkAL.js +2 -0
  933. package/dist/web/static/assets/useIssuesFeed-CFiyQkAL.js.map +1 -0
  934. package/dist/web/static/assets/useKbHits-xKXWgqh9.js +2 -0
  935. package/dist/web/static/assets/useKbHits-xKXWgqh9.js.map +1 -0
  936. package/dist/web/static/assets/useSkillStats-B5hbIwdf.js +2 -0
  937. package/dist/web/static/assets/useSkillStats-B5hbIwdf.js.map +1 -0
  938. package/dist/web/static/assets/vendor-DS-q4Eyc.js +36 -0
  939. package/dist/web/static/assets/vendor-DS-q4Eyc.js.map +1 -0
  940. package/dist/web/static/index.html +12 -8
  941. package/package.json +18 -5
  942. package/dist/core/ai/provider.d.ts +0 -63
  943. package/dist/core/ai/provider.d.ts.map +0 -1
  944. package/dist/core/ai/provider.js +0 -241
  945. package/dist/core/ai/provider.js.map +0 -1
  946. package/dist/core/ai/types.d.ts +0 -43
  947. package/dist/core/ai/types.d.ts.map +0 -1
  948. package/dist/core/ai/types.js +0 -5
  949. package/dist/core/ai/types.js.map +0 -1
  950. package/dist/core/storage/token-usage.d.ts +0 -36
  951. package/dist/core/storage/token-usage.d.ts.map +0 -1
  952. package/dist/core/storage/token-usage.js +0 -59
  953. package/dist/core/storage/token-usage.js.map +0 -1
  954. package/dist/core/utils/token-tracker.d.ts +0 -39
  955. package/dist/core/utils/token-tracker.d.ts.map +0 -1
  956. package/dist/core/utils/token-tracker.js +0 -69
  957. package/dist/core/utils/token-tracker.js.map +0 -1
  958. package/dist/skills/index.d.ts +0 -3
  959. package/dist/skills/index.d.ts.map +0 -1
  960. package/dist/skills/index.js +0 -3
  961. package/dist/skills/index.js.map +0 -1
  962. package/dist/skills/matcher.d.ts +0 -26
  963. package/dist/skills/matcher.d.ts.map +0 -1
  964. package/dist/skills/matcher.js +0 -113
  965. package/dist/skills/matcher.js.map +0 -1
  966. package/dist/skills/official/code-simplifier.md +0 -52
  967. package/dist/skills/official/find-skills.md +0 -142
  968. package/dist/skills/official/official-api-design.md +0 -30
  969. package/dist/skills/official/official-architecture-decision.md +0 -41
  970. package/dist/skills/official/official-bmad.md +0 -118
  971. package/dist/skills/official/official-db-schema-design.md +0 -34
  972. package/dist/skills/official/official-debug.md +0 -25
  973. package/dist/skills/official/official-doc-driven.md +0 -31
  974. package/dist/skills/official/official-harness-engineering.md +0 -108
  975. package/dist/skills/official/official-openspec.md +0 -89
  976. package/dist/skills/official/official-performance-optimization.md +0 -30
  977. package/dist/skills/official/official-pr-review.md +0 -35
  978. package/dist/skills/official/official-release-checklist.md +0 -30
  979. package/dist/skills/official/official-security-hardening.md +0 -32
  980. package/dist/skills/official/official-spec-driven-design.md +0 -31
  981. package/dist/skills/official/planning-with-files.md +0 -241
  982. package/dist/skills/official/ui-ux-pro-max.md +0 -105
  983. package/dist/skills/official/webapp-testing.md +0 -96
  984. package/dist/skills/official-skills.d.ts +0 -26
  985. package/dist/skills/official-skills.d.ts.map +0 -1
  986. package/dist/skills/official-skills.js +0 -74
  987. package/dist/skills/official-skills.js.map +0 -1
  988. package/dist/skills/semantic-matcher.d.ts +0 -60
  989. package/dist/skills/semantic-matcher.d.ts.map +0 -1
  990. package/dist/skills/semantic-matcher.js +0 -192
  991. package/dist/skills/semantic-matcher.js.map +0 -1
  992. package/dist/skills/upgrade-engine.d.ts +0 -93
  993. package/dist/skills/upgrade-engine.d.ts.map +0 -1
  994. package/dist/skills/upgrade-engine.js +0 -447
  995. package/dist/skills/upgrade-engine.js.map +0 -1
  996. package/dist/skills/upgrade-prompt.d.ts +0 -20
  997. package/dist/skills/upgrade-prompt.d.ts.map +0 -1
  998. package/dist/skills/upgrade-prompt.js +0 -75
  999. package/dist/skills/upgrade-prompt.js.map +0 -1
  1000. package/dist/web/routes/ai.d.ts +0 -10
  1001. package/dist/web/routes/ai.d.ts.map +0 -1
  1002. package/dist/web/routes/ai.js +0 -186
  1003. package/dist/web/routes/ai.js.map +0 -1
  1004. package/dist/web/routes/token-usage.d.ts +0 -7
  1005. package/dist/web/routes/token-usage.d.ts.map +0 -1
  1006. package/dist/web/routes/token-usage.js +0 -18
  1007. package/dist/web/routes/token-usage.js.map +0 -1
  1008. package/dist/web/ssrf-guard.d.ts +0 -35
  1009. package/dist/web/ssrf-guard.d.ts.map +0 -1
  1010. package/dist/web/ssrf-guard.js +0 -93
  1011. package/dist/web/ssrf-guard.js.map +0 -1
  1012. package/dist/web/static/assets/AIConfig-CdDWzJyO.js +0 -2
  1013. package/dist/web/static/assets/AIConfig-CdDWzJyO.js.map +0 -1
  1014. package/dist/web/static/assets/Dashboard-CoEmmIDt.js +0 -2
  1015. package/dist/web/static/assets/Dashboard-CoEmmIDt.js.map +0 -1
  1016. package/dist/web/static/assets/Drawer-DdRTzlLB.js +0 -2
  1017. package/dist/web/static/assets/Drawer-DdRTzlLB.js.map +0 -1
  1018. package/dist/web/static/assets/Events-DrIq1SUS.js +0 -2
  1019. package/dist/web/static/assets/Events-DrIq1SUS.js.map +0 -1
  1020. package/dist/web/static/assets/Reports-DFBM3MDK.js +0 -2
  1021. package/dist/web/static/assets/Reports-DFBM3MDK.js.map +0 -1
  1022. package/dist/web/static/assets/SearchInput-qCj_jAcf.js +0 -2
  1023. package/dist/web/static/assets/SearchInput-qCj_jAcf.js.map +0 -1
  1024. package/dist/web/static/assets/SessionDetail-CCzwdoT7.js +0 -2
  1025. package/dist/web/static/assets/SessionDetail-CCzwdoT7.js.map +0 -1
  1026. package/dist/web/static/assets/Sessions-FfLYkAw9.js +0 -2
  1027. package/dist/web/static/assets/Sessions-FfLYkAw9.js.map +0 -1
  1028. package/dist/web/static/assets/Skills-C8Gvs3Qa.js +0 -2
  1029. package/dist/web/static/assets/Skills-C8Gvs3Qa.js.map +0 -1
  1030. package/dist/web/static/assets/TaskDetail-BS8pYhaR.js +0 -2
  1031. package/dist/web/static/assets/TaskDetail-BS8pYhaR.js.map +0 -1
  1032. package/dist/web/static/assets/Tasks-CyuhizG8.js +0 -2
  1033. package/dist/web/static/assets/Tasks-CyuhizG8.js.map +0 -1
  1034. package/dist/web/static/assets/charts-CLrM0_uM.js +0 -37
  1035. package/dist/web/static/assets/charts-CLrM0_uM.js.map +0 -1
  1036. package/dist/web/static/assets/date-fns-CZ_bHujz.js +0 -2
  1037. package/dist/web/static/assets/date-fns-CZ_bHujz.js.map +0 -1
  1038. package/dist/web/static/assets/export-L_VBD2p1.js +0 -4
  1039. package/dist/web/static/assets/export-L_VBD2p1.js.map +0 -1
  1040. package/dist/web/static/assets/index-CBX47X8l.js +0 -3
  1041. package/dist/web/static/assets/index-CBX47X8l.js.map +0 -1
  1042. package/dist/web/static/assets/index-DjIoMdoR.css +0 -1
  1043. package/dist/web/static/assets/lucide-Bs_edTLa.js +0 -232
  1044. package/dist/web/static/assets/lucide-Bs_edTLa.js.map +0 -1
  1045. package/dist/web/static/assets/query-C99w429o.js +0 -2
  1046. package/dist/web/static/assets/react-vendor-CSp-GLFF.js +0 -49
  1047. package/dist/web/static/assets/react-vendor-CSp-GLFF.js.map +0 -1
  1048. package/dist/web/static/assets/syntax-highlighter-44FakypI.js +0 -9
  1049. package/dist/web/static/assets/syntax-highlighter-44FakypI.js.map +0 -1
  1050. package/dist/web/static/assets/time-Bxuk0M-C.js +0 -2
  1051. package/dist/web/static/assets/time-Bxuk0M-C.js.map +0 -1
  1052. package/dist/web/static/assets/vendor-CMMjVdZs.js +0 -64
  1053. package/dist/web/static/assets/vendor-CMMjVdZs.js.map +0 -1
@@ -3,15 +3,237 @@
3
3
  *
4
4
  * 拆分自 sqlite.ts。
5
5
  */
6
+ import { decodeToolInput, decodeToolOutput } from './codec/tool-input-codec.js';
7
+ import { DAY_MS } from '../utils/time.js';
8
+ const ALL_TASK_KINDS = ['user', 'agent-callback', 'image', 'system'];
6
9
  export class TaskOperations {
7
10
  db;
8
11
  constructor(db) {
9
12
  this.db = db;
10
13
  }
14
+ /**
15
+ * Insert a task row.
16
+ *
17
+ * `is_noise` (deprecated since 2026-06-01, spec b1480935) is still accepted
18
+ * for back-compat — callers that haven't been migrated to `task_kind` will
19
+ * keep working; new writers should pass `task_kind` directly.
20
+ *
21
+ * `task_kind` (spec b1480935 Option A): when omitted, defaults to `'user'`
22
+ * via the schema DEFAULT. TaskSegmenter passes the classified kind from
23
+ * `classifyPromptKind()` so envelope rows surface in Web with a muted
24
+ * `agent-callback`/`image`/`system` chip instead of being hidden.
25
+ */
11
26
  writeTask(task) {
27
+ // Default kind to 'user' — matches the schema CHECK and the read-side
28
+ // assumption that pre-classification rows ARE user-driven work.
29
+ const kind = task.task_kind ?? 'user';
12
30
  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);
31
+ INSERT INTO tasks (id, session_id, title, start_time, is_noise, task_kind, parent_task_id)
32
+ VALUES (?, ?, ?, ?, ?, ?, ?)
33
+ `).run(task.id, task.session_id, task.title, task.start_time, task.is_noise ? 1 : 0, kind, task.parent_task_id ?? null);
34
+ }
35
+ /**
36
+ * decision f47c2c90 (二期B, spec 1711 § Phase 2): link a task to its parent
37
+ * user task, but ONLY when no parent is set yet (idempotent — never overwrites
38
+ * an existing link). Also refuses to self-link (parentTaskId === taskId). This
39
+ * is the resume-path补链 hook; go-forward创建路径 passes parent_task_id directly
40
+ * to writeTask instead. Defense-in-depth for R4 (no cycles / no self-link).
41
+ */
42
+ setTaskParent(taskId, parentTaskId) {
43
+ if (!taskId || !parentTaskId || taskId === parentTaskId)
44
+ return;
45
+ this.db.prepare(`UPDATE tasks SET parent_task_id = ? WHERE id = ? AND parent_task_id IS NULL`).run(parentTaskId, taskId);
46
+ }
47
+ /**
48
+ * decision f47c2c90 (二期B, spec 1711 § Phase 2): find the most recent prior
49
+ * user task in the same session for the triple-gate parent linkage.
50
+ *
51
+ * Conservative selection (mirrors the backfill heuristic):
52
+ * - same session, task_kind='user'
53
+ * - start_time STRICTLY earlier than `beforeIso` (parent must precede child)
54
+ * - start_time within `windowMs` of `beforeIso` (default 30min)
55
+ * - PREFERS a non-ack-titled task as the substantive originator; falls back
56
+ * to the most recent qualifying user task if none is non-ack.
57
+ *
58
+ * Returns the task id, or null when no qualifying prior task exists.
59
+ * Time math uses ISO lexical comparison (start_time is ISO TEXT — no numeric
60
+ * probe, DB column type landmine). `ackTitles` is the lowercase ack-token set
61
+ * supplied by the caller (single source of truth lives in the segmenter).
62
+ */
63
+ getMostRecentUserTaskBefore(opts) {
64
+ const sinceIso = new Date(Date.parse(opts.beforeIso) - opts.windowMs).toISOString();
65
+ const ackPh = opts.ackTitles.length > 0 ? opts.ackTitles.map(() => '?').join(',') : `''`;
66
+ // Prefer a non-ack-titled originator (avoid ack→ack chains).
67
+ const nonAckSql = `
68
+ SELECT id FROM tasks
69
+ WHERE session_id = ?
70
+ AND task_kind = 'user'
71
+ AND start_time < ?
72
+ AND start_time >= ?
73
+ ${opts.ackTitles.length > 0 ? `AND LOWER(trim(title)) NOT IN (${ackPh})` : ''}
74
+ ORDER BY start_time DESC
75
+ LIMIT 1`;
76
+ const nonAckParams = [opts.session_id, opts.beforeIso, sinceIso];
77
+ if (opts.ackTitles.length > 0)
78
+ nonAckParams.push(...opts.ackTitles);
79
+ const nonAck = this.db.prepare(nonAckSql).get(...nonAckParams);
80
+ if (nonAck?.id)
81
+ return nonAck.id;
82
+ // Fallback: most recent qualifying user task regardless of title.
83
+ const any = this.db.prepare(`
84
+ SELECT id FROM tasks
85
+ WHERE session_id = ?
86
+ AND task_kind = 'user'
87
+ AND start_time < ?
88
+ AND start_time >= ?
89
+ ORDER BY start_time DESC
90
+ LIMIT 1
91
+ `).get(opts.session_id, opts.beforeIso, sinceIso);
92
+ return any?.id ?? null;
93
+ }
94
+ /**
95
+ * decision f47c2c90 (二期B, spec 1711 § Phase 3.1): aggregate ALL Task/Agent
96
+ * spawns across the parent_task_id chain that `anchorTaskId` belongs to —
97
+ * the timeline's fallback for workflows that have NO decision_id.
98
+ *
99
+ * Algorithm (two bounded recursive CTEs, R4 cycle-safe):
100
+ * 1. 向上找根: walk parent_task_id up from the anchor to the root
101
+ * (parent_task_id IS NULL), depth ≤ 8.
102
+ * 2. 向下收子: from that root, collect all descendants (depth ≤ 8).
103
+ * The union of both walks is the chain's task-id set. SQLite recursive CTEs
104
+ * with UNION (set semantics) dedup automatically; the explicit depth column
105
+ * guard caps a pathological cycle (a.parent=b, b.parent=a) at 8 hops so the
106
+ * query always terminates with a finite result.
107
+ *
108
+ * Output shape is IDENTICAL to EventOperations.queryWorkflowSpawnsByDecisionId
109
+ * so task-timeline.ts can feed both through the same buildAgentSpawnEvents
110
+ * helper. subagent_type is read from the PLAINTEXT column (no gzip);
111
+ * tool_input / tool_output are decoded via the codec (same path as the A query)
112
+ * for the prompt / output previews. task_count = distinct chain task ids that
113
+ * actually carry ≥1 spawn (the "跨 N 轮" N for the B path).
114
+ *
115
+ * gzip note: only tool_input/tool_output flow through the codec; the chain walk
116
+ * itself touches plaintext tasks columns only (R7-safe).
117
+ */
118
+ queryParentTaskChainSpawns(anchorTaskId) {
119
+ if (!anchorTaskId)
120
+ return { spawns: [], task_count: 0 };
121
+ // Collect the full chain id-set (up to root + down to descendants), depth≤8,
122
+ // cycle-safe via the depth guard + UNION dedup.
123
+ const chainRows = this.db.prepare(`
124
+ WITH RECURSIVE
125
+ ancestors(id, depth) AS (
126
+ SELECT id, 0 FROM tasks WHERE id = ?
127
+ UNION
128
+ SELECT t.parent_task_id, a.depth + 1
129
+ FROM tasks t JOIN ancestors a ON t.id = a.id
130
+ WHERE t.parent_task_id IS NOT NULL AND a.depth < 8
131
+ ),
132
+ root(id) AS (
133
+ -- the topmost ancestor reached (smallest remaining parent); we take
134
+ -- ALL ancestors as roots-of-subtree to be safe, then collect descendants
135
+ SELECT id FROM ancestors
136
+ ),
137
+ descendants(id, depth) AS (
138
+ SELECT id, 0 FROM root
139
+ UNION
140
+ SELECT t.id, d.depth + 1
141
+ FROM tasks t JOIN descendants d ON t.parent_task_id = d.id
142
+ WHERE d.depth < 8
143
+ )
144
+ SELECT DISTINCT id FROM (
145
+ SELECT id FROM ancestors
146
+ UNION
147
+ SELECT id FROM descendants
148
+ ) WHERE id IS NOT NULL
149
+ `).all(anchorTaskId);
150
+ const chainIds = chainRows.map((r) => r.id);
151
+ if (chainIds.length === 0)
152
+ return { spawns: [], task_count: 0 };
153
+ const ph = chainIds.map(() => '?').join(',');
154
+ const rows = this.db.prepare(`
155
+ SELECT e.event_id, e.session_id, e.timestamp, e.hook_type, e.subagent_type,
156
+ e.tool_input, e.tool_output, te.task_id AS task_id
157
+ FROM task_events te
158
+ JOIN events e ON e.event_id = te.event_id
159
+ WHERE te.task_id IN (${ph})
160
+ AND e.tool_name IN ('Task','Agent')
161
+ AND e.hook_type IN ('PreToolUse','PostToolUse')
162
+ ORDER BY e.timestamp ASC
163
+ `).all(...chainIds);
164
+ const taskIds = new Set();
165
+ const spawns = rows.map((row) => {
166
+ if (row.task_id)
167
+ taskIds.add(row.task_id);
168
+ let toolInput;
169
+ if (row.tool_input) {
170
+ try {
171
+ toolInput = decodeToolInput(row.tool_input);
172
+ }
173
+ catch {
174
+ toolInput = undefined;
175
+ }
176
+ }
177
+ let toolOutput;
178
+ if (row.tool_output) {
179
+ try {
180
+ toolOutput = decodeToolOutput(row.tool_output);
181
+ }
182
+ catch {
183
+ toolOutput = undefined;
184
+ }
185
+ }
186
+ return {
187
+ event_id: row.event_id,
188
+ session_id: row.session_id,
189
+ task_id: row.task_id ?? null,
190
+ timestamp: row.timestamp,
191
+ hook_type: row.hook_type,
192
+ subagent_type: row.subagent_type,
193
+ tool_input: toolInput,
194
+ tool_output: toolOutput,
195
+ };
196
+ });
197
+ return { spawns, task_count: taskIds.size };
198
+ }
199
+ /**
200
+ * Read the plaintext `user_prompt` of every `UserPromptSubmit` event linked to
201
+ * any task in `taskIds` (decision ed5c3e93, spec 0909 per-turn-human-input).
202
+ *
203
+ * Used by the timeline route's two cross-task aggregation arms to collect the
204
+ * per-turn human inputs that live in OTHER per-prompt tasks of the same
205
+ * workflow (the originating task only sees its own first turn). The caller
206
+ * passes the distinct task_id set already computed from the spawn rows.
207
+ *
208
+ * CRITICAL (gzip event-content landmine, MEMORY note): `events.user_prompt` is
209
+ * a PLAINTEXT TEXT column — read it directly. It is NOT a gzip blob; do NOT run
210
+ * it through `decodeToolInput`/`decodeToolOutput` (those are for tool_input /
211
+ * tool_output only).
212
+ *
213
+ * Ordering: `ORDER BY e.timestamp ASC` — timestamp is ISO TEXT, lexically
214
+ * sortable (DB time-column-type landmine: do not treat it as ms INTEGER).
215
+ * Empty `taskIds` → `[]` (avoid an empty `IN ()` clause).
216
+ */
217
+ queryUserPromptsByTaskIds(taskIds) {
218
+ if (!taskIds || taskIds.length === 0)
219
+ return [];
220
+ const ph = taskIds.map(() => '?').join(',');
221
+ const rows = this.db.prepare(`
222
+ SELECT e.event_id, e.timestamp, e.user_prompt, te.task_id AS task_id
223
+ FROM task_events te
224
+ JOIN events e ON e.event_id = te.event_id
225
+ WHERE te.task_id IN (${ph})
226
+ AND e.hook_type = 'UserPromptSubmit'
227
+ AND e.user_prompt IS NOT NULL
228
+ ORDER BY e.timestamp ASC
229
+ `).all(...taskIds);
230
+ return rows.map((row) => ({
231
+ event_id: row.event_id,
232
+ timestamp: row.timestamp,
233
+ // Plaintext column — direct read, never decoded.
234
+ user_prompt: row.user_prompt,
235
+ task_id: row.task_id ?? null,
236
+ }));
15
237
  }
16
238
  updateTask(taskId, updates) {
17
239
  const sets = [];
@@ -32,6 +254,30 @@ export class TaskOperations {
32
254
  sets.push('title = ?');
33
255
  params.push(updates.title);
34
256
  }
257
+ if (updates.outcome !== undefined) {
258
+ sets.push('outcome = ?');
259
+ params.push(updates.outcome);
260
+ }
261
+ if (updates.outcome_reason !== undefined) {
262
+ sets.push('outcome_reason = ?');
263
+ params.push(updates.outcome_reason);
264
+ }
265
+ if (updates.commit_count !== undefined) {
266
+ sets.push('commit_count = ?');
267
+ params.push(updates.commit_count);
268
+ }
269
+ if (updates.reverted_commits !== undefined) {
270
+ sets.push('reverted_commits = ?');
271
+ params.push(updates.reverted_commits);
272
+ }
273
+ if (updates.user_violation_count !== undefined) {
274
+ sets.push('user_violation_count = ?');
275
+ params.push(updates.user_violation_count);
276
+ }
277
+ if (updates.classified_at !== undefined) {
278
+ sets.push('classified_at = ?');
279
+ params.push(updates.classified_at);
280
+ }
35
281
  if (sets.length === 0)
36
282
  return;
37
283
  params.push(taskId);
@@ -41,6 +287,88 @@ export class TaskOperations {
41
287
  this.db.prepare(`INSERT OR IGNORE INTO task_events (task_id, event_id) VALUES (?, ?)`).run(taskId, eventId);
42
288
  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
289
  }
290
+ /**
291
+ * Task-tracking overhaul (2026-05-27 spec §3.3 Q3): pure-attachment variant
292
+ * of linkEventToTask. Inserts the task_events row + increments event_count
293
+ * BUT does NOT advance task.end_time. Used by the orphan-recovery fallback
294
+ * in TaskSegmenter.linkEvent so attaching a late-arriving sub-agent event
295
+ * to a recently-completed task doesn't stretch its duration KPI.
296
+ *
297
+ * Tradeoff: KPIs that count events get the orphan back, KPIs that read
298
+ * end_time stay honest. Outcome / commit_count rollups already ran at Stop
299
+ * time so they're unaffected either way.
300
+ */
301
+ attachEventToTask(taskId, eventId) {
302
+ this.db.prepare(`INSERT OR IGNORE INTO task_events (task_id, event_id) VALUES (?, ?)`).run(taskId, eventId);
303
+ this.db.prepare(`UPDATE tasks SET event_count = event_count + 1 WHERE id = ?`).run(taskId);
304
+ }
305
+ /**
306
+ * Orphan-event robustness fix (2026-06-01): direct lookup of a session's
307
+ * single active *user* task.
308
+ *
309
+ * Replaces the old `queryTasks(limit:5).find(...)` heuristic in
310
+ * TaskSegmenter.recoverActiveTask, which broke on high-task-count sessions:
311
+ * the active user task could be pushed out of the most-recent-5 window by a
312
+ * burst of newer agent-callback / completed rows, so `.find()` returned null
313
+ * and every subsequent event orphaned.
314
+ *
315
+ * SQL pins the predicate at the DB layer (no LIMIT truncation):
316
+ * WHERE session_id = ? AND status = 'active'
317
+ * AND (task_kind = 'user' OR (task_kind IS NULL AND is_noise = 0))
318
+ * ORDER BY start_time DESC LIMIT 1
319
+ *
320
+ * The `task_kind IS NULL AND is_noise = 0` arm preserves backward compat for
321
+ * legacy rows written before the task_kind backfill (spec b1480935).
322
+ */
323
+ /**
324
+ * decision c6613590 P0 缺陷 B: cheap PK existence check for status='active'.
325
+ *
326
+ * Used by TaskSegmenter.getTrustedCurrent to PK-recheck a map hit before
327
+ * trusting it, so a task the stale-GC already flipped to 'completed' can't
328
+ * keep absorbing new events via a dangling in-memory pointer. Returns false
329
+ * for unknown / completed / abandoned ids.
330
+ */
331
+ isTaskActive(taskId) {
332
+ const row = this.db.prepare(`SELECT 1 FROM tasks WHERE id = ? AND status = 'active' LIMIT 1`).get(taskId);
333
+ return row !== undefined;
334
+ }
335
+ getActiveUserTask(sessionId) {
336
+ const row = this.db.prepare(`
337
+ SELECT t.*, s.project_path
338
+ FROM tasks t
339
+ LEFT JOIN sessions s ON s.session_id = t.session_id
340
+ WHERE t.session_id = ?
341
+ AND t.status = 'active'
342
+ AND (t.task_kind = 'user' OR (t.task_kind IS NULL AND t.is_noise = 0))
343
+ ORDER BY t.start_time DESC
344
+ LIMIT 1
345
+ `).get(sessionId);
346
+ if (!row)
347
+ return null;
348
+ return this.mapRow(row);
349
+ }
350
+ /**
351
+ * Orphan-event robustness fix (2026-06-01): every session that currently has
352
+ * at least one active *user* task, with that task's id + start_time anchor.
353
+ *
354
+ * Drives TaskSegmenter.hydrateActiveTasks() so a freshly-restarted daemon can
355
+ * re-populate its in-memory `currentTasks` map up-front, instead of relying
356
+ * on per-event lazy recovery (which misses buffered/replayed events whose
357
+ * recovery heuristic doesn't fire).
358
+ *
359
+ * One row per session (the most recently started active user task wins) —
360
+ * mirrors getActiveUserTask's tie-break so hydrate and lazy-recover agree.
361
+ */
362
+ queryActiveUserTaskSessions() {
363
+ const rows = this.db.prepare(`
364
+ SELECT t.session_id AS session_id, t.id AS task_id, MAX(t.start_time) AS start_time
365
+ FROM tasks t
366
+ WHERE t.status = 'active'
367
+ AND (t.task_kind = 'user' OR (t.task_kind IS NULL AND t.is_noise = 0))
368
+ GROUP BY t.session_id
369
+ `).all();
370
+ return rows;
371
+ }
44
372
  /** Legacy simple query — returns bare array (used by detail route internally) */
45
373
  queryTasks(filter = {}) {
46
374
  const conditions = [];
@@ -109,21 +437,229 @@ export class TaskOperations {
109
437
  const rows = this.db.prepare(`SELECT event_id FROM task_events WHERE task_id = ?`).all(taskId);
110
438
  return rows.map(r => r.event_id);
111
439
  }
440
+ /**
441
+ * 批量把 event_id 反查到 task_id(用 task_events 关联表)。
442
+ *
443
+ * 场景:pipeline 视图想为每个 prompt 行附上 task_id(让用户点 prompt 直接
444
+ * 跳 task drawer 看"这个任务实际干了什么",而不是噪音重的 session 视图)。
445
+ *
446
+ * 实现:一条 IN(...) 查询,event_id 通常 ≤ pipeline limit(默认 50,
447
+ * 上限 200)— 远低于 SQLite 单语句参数限制 (999),无需分批。
448
+ */
449
+ queryTaskIdsByEventIds(eventIds) {
450
+ const out = new Map();
451
+ if (eventIds.length === 0)
452
+ return out;
453
+ const placeholders = eventIds.map(() => '?').join(',');
454
+ const rows = this.db
455
+ .prepare(`SELECT event_id, task_id FROM task_events WHERE event_id IN (${placeholders})`)
456
+ .all(...eventIds);
457
+ for (const r of rows) {
458
+ // 一个 event 理论上只 link 到一个 task(PRIMARY KEY (task_id, event_id) +
459
+ // TaskSegmenter 单分配语义)。万一重复以最先看到的为准。
460
+ if (!out.has(r.event_id))
461
+ out.set(r.event_id, r.task_id);
462
+ }
463
+ return out;
464
+ }
465
+ /**
466
+ * Task-tracking overhaul (2026-05-27 spec §3.3): orphan-event recovery.
467
+ *
468
+ * After StopHandler completes the current task, late-arriving sub-agent
469
+ * PostToolUse events (the spawn's Bash/Read/etc tool calls that fire
470
+ * AFTER Stop closes the parent task) used to orphan because
471
+ * TaskSegmenter.linkEvent could not find an active task. Spec § 3.3
472
+ * Option C: when no active task exists, attach the event to the most
473
+ * recently completed task on the same session if it's within
474
+ * `sinceMs` of the event timestamp.
475
+ *
476
+ * Q3 (spec § 11): we do NOT update `task.end_time` here — the fallback
477
+ * is pure attachment (event_count grows by the linkEventToTask path in
478
+ * the caller). Updating end_time would stretch the duration KPI for
479
+ * every orphan that lands inside the recovery window and corrupt the
480
+ * outcome / task duration analytics.
481
+ *
482
+ * Order: most-recently-ended task first. event_time bound via SQLite
483
+ * julianday() — sinceMs is the look-back window in ms (typically 5 min).
484
+ * Returns null if no candidate within the window.
485
+ */
486
+ findRecentCompletedTaskForOrphan(opts) {
487
+ const row = this.db.prepare(`
488
+ SELECT t.*, s.project_path
489
+ FROM tasks t
490
+ LEFT JOIN sessions s ON s.session_id = t.session_id
491
+ WHERE t.session_id = ?
492
+ AND t.status = 'completed'
493
+ AND t.end_time IS NOT NULL
494
+ AND (julianday(?) - julianday(t.end_time)) * 86400000 <= ?
495
+ ORDER BY t.end_time DESC
496
+ LIMIT 1
497
+ `).get(opts.session_id, opts.event_ts_iso, opts.sinceMs);
498
+ if (!row)
499
+ return null;
500
+ return this.mapRow(row);
501
+ }
502
+ /**
503
+ * Task-tracking deep fix (2026-05-27 spec §3.A): smart orphan-recovery helper.
504
+ *
505
+ * Finds the most recently completed task on the same session whose end_time
506
+ * is before the event timestamp AND there is no other task that started
507
+ * between that completed task's end_time and the event timestamp.
508
+ *
509
+ * Algorithm (plain English):
510
+ * - candidate: status='completed', end_time IS NOT NULL, end_time <= event_ts
511
+ * - guard: NOT EXISTS t2 where t2.session_id = t.session_id
512
+ * AND t2.start_time > t.end_time AND t2.start_time <= event_ts
513
+ * - if multiple candidates, take end_time DESC (most recent)
514
+ * - returns null if no qualifying candidate
515
+ *
516
+ * Unlike findRecentCompletedTaskForOrphan (5-min window), this function has
517
+ * NO time-window cap. If the idle gap is 2h and no new task started during
518
+ * that gap, the event still attaches to the completed task. The "newer task
519
+ * block" guard prevents cross-task mis-attribution.
520
+ */
521
+ findCompletedTaskForOrphanSmart(opts) {
522
+ try {
523
+ const row = this.db.prepare(`
524
+ SELECT t.*, s.project_path
525
+ FROM tasks t
526
+ LEFT JOIN sessions s ON s.session_id = t.session_id
527
+ WHERE t.session_id = ?
528
+ AND t.status = 'completed'
529
+ AND t.end_time IS NOT NULL
530
+ AND t.end_time <= ?
531
+ AND NOT EXISTS (
532
+ SELECT 1 FROM tasks t2
533
+ WHERE t2.session_id = t.session_id
534
+ AND t2.start_time > t.end_time
535
+ AND t2.start_time <= ?
536
+ )
537
+ ORDER BY t.end_time DESC
538
+ LIMIT 1
539
+ `).get(opts.session_id, opts.event_ts_iso, opts.event_ts_iso);
540
+ if (!row)
541
+ return null;
542
+ return this.mapRow(row);
543
+ }
544
+ catch (err) {
545
+ // Defensive: log and return null so callers can fall back to legacy window
546
+ // method rather than crashing the daemon event pipeline.
547
+ // eslint-disable-next-line no-console
548
+ console.warn('[TaskOperations] findCompletedTaskForOrphanSmart SQL error:', err);
549
+ return null;
550
+ }
551
+ }
552
+ /**
553
+ * Task-tracking deep fix (2026-05-27 spec §5.C): UNION fallback query.
554
+ *
555
+ * Returns all events associated with the task via two sources:
556
+ * 1. linked (linked=1): events directly linked via task_events JOIN
557
+ * 2. fallback (linked=0): events in the same session+time window that are
558
+ * NOT in task_events for this task AND NOT linked to any OTHER task
559
+ * on the same session (prevents event theft across tasks)
560
+ *
561
+ * The `linked` field lets callers (web timeline) badge unlinked events.
562
+ */
563
+ queryEventsByTaskIdWithFallback(taskId, opts = {}) {
564
+ const limit = opts.limit ?? 5000;
565
+ // We need the task's session_id + time window first
566
+ const taskRow = this.db.prepare(`
567
+ SELECT session_id, start_time, end_time FROM tasks WHERE id = ?
568
+ `).get(taskId);
569
+ if (!taskRow)
570
+ return [];
571
+ const { session_id, start_time, end_time } = taskRow;
572
+ // Active tasks: end_time IS NULL → use current time as upper bound
573
+ const endTimeBound = end_time ?? new Date().toISOString();
574
+ const rows = this.db.prepare(`
575
+ SELECT e.*, 1 AS linked
576
+ FROM events e
577
+ JOIN task_events te ON te.event_id = e.event_id
578
+ WHERE te.task_id = ?
579
+
580
+ UNION
581
+
582
+ SELECT e.*, 0 AS linked
583
+ FROM events e
584
+ WHERE e.session_id = ?
585
+ AND e.timestamp >= ?
586
+ AND e.timestamp <= ?
587
+ AND e.event_id NOT IN (
588
+ SELECT event_id FROM task_events WHERE task_id = ?
589
+ )
590
+ AND e.event_id NOT IN (
591
+ SELECT event_id FROM task_events
592
+ WHERE task_id IN (
593
+ SELECT id FROM tasks
594
+ WHERE session_id = ?
595
+ AND id != ?
596
+ )
597
+ )
598
+
599
+ ORDER BY timestamp ASC
600
+ LIMIT ?
601
+ `).all(taskId, session_id, start_time, endTimeBound, taskId, session_id, taskId, limit);
602
+ return rows.map(r => ({
603
+ ...this.rowToEvent(r),
604
+ linked: r.linked === 1 ? 1 : 0,
605
+ }));
606
+ }
607
+ /**
608
+ * B+ spec: 30min smart window 续命 helper.
609
+ *
610
+ * Reactivates a previously completed task so that a new UserPromptSubmit
611
+ * that arrives within TASK_RESUME_WINDOW_MS of its end_time can continue
612
+ * the same task instead of creating a new one.
613
+ *
614
+ * Mutations:
615
+ * - status → 'active'
616
+ * - end_time → NULL (cleared; duration KPI will re-anchor at Stop time)
617
+ *
618
+ * start_time is intentionally left untouched (spec §B+: "不改 start_time").
619
+ * Outcome / commit_count / classified_at columns are not touched here — they
620
+ * would have been NULL anyway (no outcome had landed in a gap of < 30min).
621
+ */
622
+ resumeTask(taskId) {
623
+ this.db.prepare(`
624
+ UPDATE tasks SET status = 'active', end_time = NULL WHERE id = ?
625
+ `).run(taskId);
626
+ }
112
627
  /**
113
628
  * 将 idle 超过阈值的 active task 批量转为 completed。
114
629
  * 条件:status='active' AND end_time IS NOT NULL
115
630
  * AND (now - end_time) > idleMinutes 分钟。
116
- * 返回:受影响行数。
631
+ *
632
+ * 返回:被关闭的 task id 列表(decision c6613590 P0 缺陷 B)。
633
+ *
634
+ * 此前返回行数(number),调用方拿不到具体被关的 task id,导致
635
+ * daemon 的 GC 把任务在 DB 标 completed 后,TaskSegmenter.currentTasks
636
+ * 内存 map 仍留着指向这些已关任务的悬空指针——后续事件经
637
+ * linkEventToTask 推进其 end_time,骗过 30min resume 窗,把多条独立
638
+ * prompt 错并进陈旧任务。改为返回 id 列表后,daemon 可据此从内存
639
+ * map 驱逐这些 entry,使 GC 与内存 map 保持一致。
640
+ *
641
+ * 实现:先 SELECT 出符合条件的 id(带 task_kind / session 信息以便
642
+ * 调用方按 session 驱逐),再用同一谓词 UPDATE。两步在 better-sqlite3
643
+ * 同步事务语义下原子。
117
644
  */
118
645
  completeStaleActiveTasks(idleMinutes) {
119
- const result = this.db.prepare(`
646
+ const stale = this.db.prepare(`
647
+ SELECT id, session_id
648
+ FROM tasks
649
+ WHERE status = 'active'
650
+ AND end_time IS NOT NULL
651
+ AND (julianday('now') - julianday(end_time)) * 24 * 60 > ?
652
+ `).all(idleMinutes);
653
+ if (stale.length === 0)
654
+ return [];
655
+ this.db.prepare(`
120
656
  UPDATE tasks
121
657
  SET status = 'completed'
122
658
  WHERE status = 'active'
123
659
  AND end_time IS NOT NULL
124
660
  AND (julianday('now') - julianday(end_time)) * 24 * 60 > ?
125
661
  `).run(idleMinutes);
126
- return result.changes;
662
+ return stale;
127
663
  }
128
664
  // ── H1: detail route 用的 PK 直查 / JOIN 接口 ──────────────────────
129
665
  /**
@@ -177,6 +713,7 @@ export class TaskOperations {
177
713
  source_handler: r.source_handler,
178
714
  injection_type: r.injection_type,
179
715
  content: r.content,
716
+ metadata_json: r.metadata_json || undefined,
180
717
  }));
181
718
  }
182
719
  /**
@@ -204,20 +741,1073 @@ export class TaskOperations {
204
741
  `).all(taskId, nowMs);
205
742
  return rows;
206
743
  }
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;
744
+ /**
745
+ * 该任务执行窗内 agent 主动 pull 的 KB(decision 93b3075b)—— 任务详情页右栏
746
+ * 「用了什么 KB 主动 pull」从「无主动 pull」降级占位接成真实数据。
747
+ *
748
+ * scope 口径(与 querySkillInvocationsByTaskWindow 同思路,但 kb_query_log
749
+ * **没有 session_id 列**,故纯按时间窗 + project 圈到具体任务):
750
+ * - 时间窗:kb_query_log.ts(INTEGER ms) BETWEEN task.start_time(→ms) 和
751
+ * COALESCE(task.end_time→ms+999, now_ms)。task.start/end 是 ISO TEXT,
752
+ * 用 strftime('%s', ...)*1000 转 ms 比较(kb_query_log.ts 地雷:是 ms 整数,
753
+ * 别打成 1970)。end_time IS NULL(active task)→ 取 now_ms 兜底。
754
+ * - **project 圈定**:JOIN tasks t LEFT JOIN sessions s 取 project_path,
755
+ * 仅取 kb_query_log.project_path = 该任务 project 的 pull。本 session 多任务
756
+ * 共享 session_id,且同一时间窗可能并发别项目的 pull(实测 64d37b1d 窗内有
757
+ * /prm 项目的 coder pull),故 project 过滤 + 时间窗一起把 pull 圈到本任务
758
+ * 的本项目,不串别项目/别任务。project_path 为 NULL(孤儿 task)→ 不串任何
759
+ * pull(IS NOT NULL guard)。
760
+ * - **只取真实 agent pull**:reason LIKE '%kb-precedent%'(coder/verify/
761
+ * safetynet/decision 的 pull-reason 约定),与 countAgentKbPulls 同口径,
762
+ * 排掉 `cf knowledge query foo` 探针(同时 NULL-safe 排 sentinel
763
+ * agent_id='cli-no-session',decision 2d6d59b0)。
764
+ *
765
+ * `now_ms` 通过参数注入(end_time IS NULL 时取此值),便于测试 mock。
766
+ */
767
+ queryTaskKbPulls(taskId, opts = {}) {
768
+ const nowMs = opts.now_ms ?? Date.now();
769
+ const rows = this.db.prepare(`
770
+ SELECT k.ts AS ts, k.query AS query, k.reason AS reason,
771
+ k.workflow AS workflow, k.phase AS phase,
772
+ k.usefulness AS usefulness, k.usefulness_note AS usefulness_note,
773
+ k.result_count AS result_count
774
+ FROM kb_query_log k
775
+ JOIN tasks t ON t.id = ?
776
+ LEFT JOIN sessions s ON s.session_id = t.session_id
777
+ WHERE s.project_path IS NOT NULL
778
+ AND k.project_path = s.project_path
779
+ AND k.reason LIKE '%kb-precedent%'
780
+ AND (k.agent_id IS NULL OR k.agent_id != 'cli-no-session')
781
+ AND k.ts >= CAST(strftime('%s', t.start_time) AS INTEGER) * 1000
782
+ AND k.ts <= COALESCE(
783
+ CAST(strftime('%s', t.end_time) AS INTEGER) * 1000 + 999,
784
+ ?
785
+ )
786
+ ORDER BY k.ts ASC
787
+ `).all(taskId, nowMs);
788
+ return rows;
789
+ }
790
+ // ── Agent Board v3 (task-centric, 2026-05-22) ─────────────────────────
791
+ //
792
+ // 每张卡片 = 一个 task 行(TaskSegmenter 自动按 user prompt 切分)。
793
+ // 替换 v2 的 session-centric 视图(session 是执行痕迹,不是工作项)。
794
+ //
795
+ // v3.1 (2026-05-25, perf P0.3) — N+1 → batched fetch:
796
+ // - 旧版:1 SELECT × 40 rows × 7 内嵌相关子查询 = ~280 隐式子查询,
797
+ // 线性 scaling(10 行=308ms / 40 行=1.2s / 120 行=3.6s)。
798
+ // - 新版:1 SQL 拿基础行(含 ids),7 个批量聚合 SQL 用 `WHERE ... IN (?,?,..)`
799
+ // 一次性取所有 aggregate,JS 内存 merge 回每行。
800
+ //
801
+ // SQL 设计:
802
+ // - 主表 tasks t JOIN sessions s(取 project_path)
803
+ // - 噪音过滤:title 长度 / task-notification 前缀 / 纯应答词 / 单 word slash command
804
+ // - 批量聚合(按维度):
805
+ // top_tools / spawned / files_changed / last_file → events JOIN task_events on task_id
806
+ // GROUP BY task_id
807
+ // intercepts (deny/warn) → tool_intercepts WHERE session_id IN (...)
808
+ // AND timestamp >= min(start_time)
809
+ // JS 内按 task 时间窗口 bucket
810
+ // kb_meta_concat → injections WHERE session_id IN (...)
811
+ // AND source_handler='UserPromptSubmitHandler:kb'
812
+ // JS 内按 task 时间窗口 bucket
813
+ // - 时间窗口 + project filter 作用在 tasks.start_time
814
+ //
815
+ // Notes:
816
+ // - tool_intercepts / injections 按 session+timestamp 窗口过滤而非 task_events 关联,
817
+ // 因为这两张表不一定 link 到 task_events(intercept 在 PreToolUse 阶段,
818
+ // injection 在 UserPromptSubmit 阶段)。用时间窗口可覆盖 task 范围。
819
+ // - 噪音过滤 case-insensitive:title 经 LOWER() 比较 ('approve','停' 等)。
820
+ // - now_ms 可注入便于测试(mock end_time IS NULL 的情况)。
821
+ queryAgentBoardTasks(opts) {
822
+ const limit = Math.min(Math.max(opts.limit ?? 120, 1), 300);
823
+ const offset = Math.max(opts.offset ?? 0, 0);
824
+ const nowMs = opts.now_ms ?? Date.now();
825
+ const sinceMs = nowMs - opts.windowDays * DAY_MS;
826
+ const since = new Date(sinceMs).toISOString();
827
+ const nowIso = new Date(nowMs).toISOString();
828
+ let projectFilter = '';
829
+ if (opts.projectPath) {
830
+ projectFilter = 'AND s.project_path = ?';
831
+ }
832
+ // spec b1480935 (2026-06-01 Option A): SQL filter switched from
833
+ // `t.is_noise = 0` to `t.task_kind IN (...)`. is_noise is deprecated;
834
+ // the kind enum is more expressive (callback / image / system distinct).
835
+ //
836
+ // Filter resolution order:
837
+ // 1) opts.includeKinds explicit → use as-is
838
+ // 2) opts.includeNoise=true (legacy) → all 4 kinds
839
+ // 3) default → ['user'] only
840
+ const effectiveKinds = opts.includeKinds
841
+ ?? (opts.includeNoise ? ALL_TASK_KINDS : ['user']);
842
+ const kindFilter = effectiveKinds.length === ALL_TASK_KINDS.length
843
+ ? '' // no filter needed when caller wants everything
844
+ : `AND t.task_kind IN (${effectiveKinds.map(() => '?').join(',')})`;
845
+ // ── Step 1: 基础查询(无内嵌子查询,毫秒级) ──────────────────────
846
+ const baseSql = `
847
+ SELECT
848
+ t.id AS id,
849
+ t.session_id AS session_id,
850
+ s.project_path AS project_path,
851
+ t.title AS title,
852
+ t.description AS description,
853
+ t.start_time AS start_time,
854
+ t.end_time AS end_time,
855
+ t.status AS status,
856
+ t.event_count AS event_count,
857
+ t.outcome AS outcome,
858
+ t.outcome_reason AS outcome_reason,
859
+ t.commit_count AS commit_count,
860
+ t.reverted_commits AS reverted_commits,
861
+ t.user_violation_count AS user_violation_count,
862
+ t.task_kind AS task_kind,
863
+ (CASE
864
+ WHEN t.end_time IS NOT NULL THEN
865
+ CAST(strftime('%s', t.end_time) AS INTEGER) - CAST(strftime('%s', t.start_time) AS INTEGER)
866
+ ELSE
867
+ CAST(strftime('%s', 'now') AS INTEGER) - CAST(strftime('%s', t.start_time) AS INTEGER)
868
+ END) AS duration_sec
869
+ FROM tasks t
870
+ JOIN sessions s ON s.session_id = t.session_id
871
+ WHERE t.start_time >= ?
872
+ ${projectFilter}
873
+ ${kindFilter}
874
+ ORDER BY t.start_time DESC
875
+ LIMIT ? OFFSET ?
876
+ `;
877
+ const baseParams = [since];
878
+ if (opts.projectPath)
879
+ baseParams.push(opts.projectPath);
880
+ if (kindFilter)
881
+ baseParams.push(...effectiveKinds);
882
+ baseParams.push(limit, offset);
883
+ const baseRows = this.db.prepare(baseSql).all(...baseParams);
884
+ if (baseRows.length === 0)
885
+ return [];
886
+ // (count companion lives in countAgentBoardTasks below — same WHERE clauses
887
+ // minus LIMIT/ORDER, so the KPI "总任务数 (7d)" doesn't get clipped by
888
+ // the lane render cap. Added 2026-05-26 to fix "stuck at 40" bug.)
889
+ const taskIds = baseRows.map(r => r.id);
890
+ const sessionIdSet = new Set(baseRows.map(r => r.session_id));
891
+ const sessionIds = Array.from(sessionIdSet);
892
+ const taskPh = taskIds.map(() => '?').join(',');
893
+ const sessionPh = sessionIds.map(() => '?').join(',');
894
+ const toolRows = this.db.prepare(`
895
+ SELECT te.task_id AS task_id, e.tool_name AS tool, COUNT(*) AS cnt
896
+ FROM task_events te INDEXED BY idx_task_events_task
897
+ JOIN events e ON e.event_id = te.event_id
898
+ WHERE te.task_id IN (${taskPh})
899
+ AND e.hook_type = 'PostToolUse'
900
+ AND e.tool_name IS NOT NULL
901
+ GROUP BY te.task_id, e.tool_name
902
+ `).all(...taskIds);
903
+ const rawRows = this.db.prepare(`
904
+ SELECT te.task_id AS task_id,
905
+ e.tool_name AS tool_name,
906
+ e.tool_input AS tool_input,
907
+ e.timestamp AS ts
908
+ FROM task_events te JOIN events e ON e.event_id = te.event_id
909
+ WHERE te.task_id IN (${taskPh})
910
+ AND e.hook_type = 'PostToolUse'
911
+ AND e.tool_input IS NOT NULL
912
+ AND e.tool_name IN ('Task', 'Agent', 'Edit', 'Write', 'NotebookEdit')
913
+ `).all(...taskIds);
914
+ const spawnAggMap = new Map(); // task_id → (sub_type → cnt)
915
+ const filesByTaskSet = new Map(); // task_id → distinct file_paths
916
+ const lastFileRowsRaw = [];
917
+ for (const raw of rawRows) {
918
+ let decoded = null;
214
919
  try {
215
- return JSON.parse(val);
920
+ const d = decodeToolInput(raw.tool_input);
921
+ if (d && typeof d === 'object' && !Array.isArray(d)) {
922
+ decoded = d;
923
+ }
216
924
  }
217
925
  catch {
218
- return val;
926
+ continue; // skip malformed rows
927
+ }
928
+ if (!decoded)
929
+ continue;
930
+ if (raw.tool_name === 'Task' || raw.tool_name === 'Agent') {
931
+ const subType = decoded.subagent_type;
932
+ if (typeof subType === 'string' && subType.length > 0) {
933
+ let inner = spawnAggMap.get(raw.task_id);
934
+ if (!inner) {
935
+ inner = new Map();
936
+ spawnAggMap.set(raw.task_id, inner);
937
+ }
938
+ inner.set(subType, (inner.get(subType) ?? 0) + 1);
939
+ }
940
+ }
941
+ else if (raw.tool_name === 'Edit' || raw.tool_name === 'Write' || raw.tool_name === 'NotebookEdit') {
942
+ const filePath = decoded.file_path;
943
+ if (typeof filePath === 'string' && filePath.length > 0) {
944
+ let set = filesByTaskSet.get(raw.task_id);
945
+ if (!set) {
946
+ set = new Set();
947
+ filesByTaskSet.set(raw.task_id, set);
948
+ }
949
+ set.add(filePath);
950
+ lastFileRowsRaw.push({ task_id: raw.task_id, file_path: filePath, ts: raw.ts });
951
+ }
952
+ }
953
+ }
954
+ const spawnRows = [];
955
+ for (const [task_id, subMap] of spawnAggMap) {
956
+ for (const [sub_type, cnt] of subMap) {
957
+ spawnRows.push({ task_id, sub_type, cnt });
958
+ }
959
+ }
960
+ const filesRows = [];
961
+ for (const [task_id, set] of filesByTaskSet) {
962
+ filesRows.push({ task_id, n: set.size });
963
+ }
964
+ // 2e + 2f. intercepts (deny/warn) per session — fetch all rows in window,
965
+ // JS buckets by task time range. We bound the timestamp scan with the
966
+ // earliest task start_time to keep the scan tight.
967
+ const earliestStart = baseRows.reduce((min, r) => (r.start_time < min ? r.start_time : min), baseRows[0].start_time);
968
+ const interceptRows = sessionIds.length === 0 ? [] : this.db.prepare(`
969
+ SELECT ti.session_id AS session_id, ti.timestamp AS timestamp, ti.decision AS decision
970
+ FROM tool_intercepts ti
971
+ WHERE ti.session_id IN (${sessionPh})
972
+ AND ti.timestamp >= ?
973
+ AND ti.decision IN ('deny','warn')
974
+ `).all(...sessionIds, earliestStart);
975
+ const injectionRows = sessionIds.length === 0 ? [] : this.db.prepare(`
976
+ SELECT i.session_id AS session_id, i.timestamp AS timestamp, i.metadata_json AS metadata_json
977
+ FROM injections i
978
+ WHERE i.session_id IN (${sessionPh})
979
+ AND i.source_handler = 'UserPromptSubmitHandler:kb'
980
+ AND i.timestamp >= ?
981
+ `).all(...sessionIds, earliestStart);
982
+ // ── Step 3: JS merge ────────────────────────────────────────────────
983
+ // 3a/b/c. Bucket by task_id
984
+ const toolsByTask = new Map();
985
+ for (const r of toolRows) {
986
+ const list = toolsByTask.get(r.task_id);
987
+ if (list)
988
+ list.push(r);
989
+ else
990
+ toolsByTask.set(r.task_id, [r]);
991
+ }
992
+ const spawnByTask = new Map();
993
+ for (const r of spawnRows) {
994
+ const list = spawnByTask.get(r.task_id);
995
+ if (list)
996
+ list.push(r);
997
+ else
998
+ spawnByTask.set(r.task_id, [r]);
999
+ }
1000
+ const filesByTask = new Map();
1001
+ for (const r of filesRows)
1002
+ filesByTask.set(r.task_id, r.n);
1003
+ // 3d. last_file: pick most recent ts per task
1004
+ const lastFileByTask = new Map();
1005
+ for (const r of lastFileRowsRaw) {
1006
+ const cur = lastFileByTask.get(r.task_id);
1007
+ if (!cur || r.ts > cur.ts)
1008
+ lastFileByTask.set(r.task_id, r);
1009
+ }
1010
+ // 3e/f/g. For per-task time-window aggregates we group source rows by
1011
+ // session first, then scan only that session's rows for each task — keeps
1012
+ // it O(per-task scan over session subset) rather than O(tasks * all rows).
1013
+ const interceptsBySession = new Map();
1014
+ for (const r of interceptRows) {
1015
+ const list = interceptsBySession.get(r.session_id);
1016
+ if (list)
1017
+ list.push(r);
1018
+ else
1019
+ interceptsBySession.set(r.session_id, [r]);
1020
+ }
1021
+ const injectionsBySession = new Map();
1022
+ for (const r of injectionRows) {
1023
+ const list = injectionsBySession.get(r.session_id);
1024
+ if (list)
1025
+ list.push(r);
1026
+ else
1027
+ injectionsBySession.set(r.session_id, [r]);
1028
+ }
1029
+ // ── Step 4: build final rows in AgentBoardTaskRow shape ─────────────
1030
+ return baseRows.map(b => {
1031
+ // top_tools_json (top 3 desc by cnt, matches old SQL semantic)
1032
+ const toolsRaw = (toolsByTask.get(b.id) ?? [])
1033
+ .slice()
1034
+ .sort((a, c) => c.cnt - a.cnt)
1035
+ .slice(0, 3)
1036
+ .map(r => ({ tool: r.tool, n: r.cnt }));
1037
+ // spawned_json (top 5 desc by cnt)
1038
+ const spawnedRaw = (spawnByTask.get(b.id) ?? [])
1039
+ .slice()
1040
+ .sort((a, c) => c.cnt - a.cnt)
1041
+ .slice(0, 5)
1042
+ .map(r => ({ type: r.sub_type, n: r.cnt }));
1043
+ // intercepts: scan session rows within [start_time, end_time||now]
1044
+ const endIso = b.end_time ?? nowIso;
1045
+ let deny = 0;
1046
+ let warn = 0;
1047
+ const sessIntercepts = interceptsBySession.get(b.session_id);
1048
+ if (sessIntercepts) {
1049
+ for (const r of sessIntercepts) {
1050
+ if (r.timestamp < b.start_time || r.timestamp > endIso)
1051
+ continue;
1052
+ if (r.decision === 'deny')
1053
+ deny += 1;
1054
+ else if (r.decision === 'warn')
1055
+ warn += 1;
1056
+ }
219
1057
  }
1058
+ // kb_meta_concat: '||' join the metadata_json fields in window
1059
+ const sessInjections = injectionsBySession.get(b.session_id);
1060
+ let kbConcat = null;
1061
+ if (sessInjections) {
1062
+ const parts = [];
1063
+ for (const r of sessInjections) {
1064
+ if (r.timestamp < b.start_time || r.timestamp > endIso)
1065
+ continue;
1066
+ if (r.metadata_json)
1067
+ parts.push(r.metadata_json);
1068
+ }
1069
+ if (parts.length > 0)
1070
+ kbConcat = parts.join('||');
1071
+ }
1072
+ return {
1073
+ id: b.id,
1074
+ session_id: b.session_id,
1075
+ project_path: b.project_path,
1076
+ title: b.title,
1077
+ description: b.description,
1078
+ start_time: b.start_time,
1079
+ end_time: b.end_time,
1080
+ status: b.status,
1081
+ event_count: b.event_count,
1082
+ outcome: b.outcome,
1083
+ outcome_reason: b.outcome_reason,
1084
+ commit_count: b.commit_count,
1085
+ reverted_commits: b.reverted_commits,
1086
+ user_violation_count: b.user_violation_count,
1087
+ task_kind: b.task_kind ?? 'user',
1088
+ duration_sec: b.duration_sec,
1089
+ top_tools_json: toolsRaw.length > 0 ? JSON.stringify(toolsRaw) : '[]',
1090
+ spawned_json: spawnedRaw.length > 0 ? JSON.stringify(spawnedRaw) : '[]',
1091
+ files_changed: filesByTask.get(b.id) ?? 0,
1092
+ last_file: lastFileByTask.get(b.id)?.file_path ?? null,
1093
+ deny_count: deny,
1094
+ warn_count: warn,
1095
+ kb_meta_concat: kbConcat,
1096
+ };
1097
+ });
1098
+ }
1099
+ /**
1100
+ * Shared task_kind filter resolution (decision b509fd91, 2026-06-08).
1101
+ *
1102
+ * Extracted so queryAgentBoardTasks / countAgentBoardTasks /
1103
+ * queryAgentBoardPromptRows / countAgentBoardPromptRows all resolve the
1104
+ * effective kind whitelist identically — no copy-paste drift. Resolution
1105
+ * order matches the pre-existing inline logic:
1106
+ * 1) explicit includeKinds → use as-is
1107
+ * 2) includeNoise=true (legacy) → all 4 kinds
1108
+ * 3) default → ['user'] only
1109
+ *
1110
+ * Returns { effectiveKinds, kindFilter } where kindFilter is the SQL
1111
+ * fragment (empty string when the caller wants all kinds — no filter needed).
1112
+ */
1113
+ resolveKindFilter(opts) {
1114
+ const effectiveKinds = opts.includeKinds
1115
+ ?? (opts.includeNoise ? ALL_TASK_KINDS : ['user']);
1116
+ const kindFilter = effectiveKinds.length === ALL_TASK_KINDS.length
1117
+ ? ''
1118
+ : `AND t.task_kind IN (${effectiveKinds.map(() => '?').join(',')})`;
1119
+ return { effectiveKinds, kindFilter };
1120
+ }
1121
+ /**
1122
+ * Shared outcome-bucket filter resolution (decision 0c1549ec, 2026-06-11).
1123
+ *
1124
+ * The TasksHub「我的任务」list view's outcome chips push their selection to
1125
+ * the backend so filtering happens over the WHOLE window (not the page-local
1126
+ * ~120 rows). The bucket classification here MUST mirror the CASE expression
1127
+ * in queryAgentBoardPromptWindowAggregates so the chip COUNTS and the chip
1128
+ * FILTER agree exactly — otherwise we re-introduce the count/filter 口径
1129
+ * split that commit 40e03283 caused.
1130
+ *
1131
+ * Bucket → predicate over the `t` (tasks) alias:
1132
+ * - 'all' / undefined → '' (no filter, backward compatible)
1133
+ * - 'success'|'partial'|'failed' → t.outcome = '<bucket>'
1134
+ * - 'abandoned' → t.outcome='abandoned' OR (t.outcome IS NULL AND status='abandoned')
1135
+ * - 'answered' → t.outcome='answered'
1136
+ * - 'none' → residual catch-all (the in-progress / no-terminal-outcome
1137
+ * bucket + any unknown/legacy outcome string), exactly the
1138
+ * complement of the five buckets above.
1139
+ * Returns the bare predicate (no leading AND); caller wraps in `AND (...)`.
1140
+ * The `t` alias is parameterless (no bind values), so callers don't push args.
1141
+ */
1142
+ resolveOutcomeFilter(outcome) {
1143
+ switch (outcome) {
1144
+ case 'success':
1145
+ case 'partial':
1146
+ case 'failed':
1147
+ return `t.outcome = '${outcome}'`;
1148
+ case 'abandoned':
1149
+ return `(t.outcome = 'abandoned' OR (t.outcome IS NULL AND t.status = 'abandoned'))`;
1150
+ case 'answered':
1151
+ return `t.outcome = 'answered'`;
1152
+ case 'none':
1153
+ // Complement of the five named buckets: NULL-and-not-abandoned (active /
1154
+ // pending / in-progress) plus any unknown/legacy outcome string.
1155
+ return `(
1156
+ t.outcome NOT IN ('success','partial','failed','abandoned','answered')
1157
+ OR (t.outcome IS NULL AND t.status <> 'abandoned')
1158
+ OR (t.outcome IS NULL AND t.status IS NULL)
1159
+ )`;
1160
+ case 'all':
1161
+ case undefined:
1162
+ default:
1163
+ return '';
1164
+ }
1165
+ }
1166
+ /**
1167
+ * Shared free-text search filter for the TasksHub「我的任务」list view
1168
+ * (decision 540ed063, 2026-06-11). Same downsink shape as resolveOutcomeFilter
1169
+ * above: the search box pushes its term to the backend so matching happens
1170
+ * over the WHOLE window (every page), not just the page-local ~120 rows a
1171
+ * client `.filter` could see.
1172
+ *
1173
+ * Matching 口径 (must stay in lockstep between query and count):
1174
+ * - case-insensitive substring (`LIKE '%term%'`, LOWER on both sides);
1175
+ * - OR'd across two PLAINTEXT columns — `e.user_prompt` (the prompt text)
1176
+ * and `t.title` (the task title).
1177
+ *
1178
+ * ⚠️ gzip One-way Door (KB landmine): `events.tool_input` / `tool_output` hold
1179
+ * GZIP-compressed blobs (declared TEXT but binary content) — a bare SQL LIKE
1180
+ * over them silently matches nothing. This predicate therefore touches ONLY
1181
+ * the two plaintext columns above and NEVER tool_input/tool_output.
1182
+ *
1183
+ * Empty / whitespace-only term → '' (no predicate), so absent or blank search
1184
+ * is backward-compatible (= no filter). Returns { predicate, params }: the
1185
+ * predicate has two `?` placeholders, so unlike resolveOutcomeFilter the caller
1186
+ * MUST push the two bind values in clause order.
1187
+ */
1188
+ resolveSearchFilter(search) {
1189
+ const term = (search ?? '').trim();
1190
+ if (!term)
1191
+ return { predicate: '', params: [] };
1192
+ // LIKE-metacharacter hardening (decision 540ed063 follow-up): a user typing a
1193
+ // literal `%` or `_` must NOT get wildcard behavior (`%` matches everything,
1194
+ // `_` matches any single char). Escape the escape char FIRST (so `\` → `\\`),
1195
+ // then `%` and `_`, with backslash as the ESCAPE char declared on each LIKE.
1196
+ // The escaped term is still a bound `?` param (never string-concatenated into
1197
+ // SQL), so this is injection-safe and behavior-preserving for ordinary words
1198
+ // (terms with no `%`/`_`/`\` are unaffected by the replaces).
1199
+ const escaped = term
1200
+ .toLowerCase()
1201
+ .replace(/\\/g, '\\\\')
1202
+ .replace(/%/g, '\\%')
1203
+ .replace(/_/g, '\\_');
1204
+ const like = `%${escaped}%`;
1205
+ return {
1206
+ predicate: "(LOWER(e.user_prompt) LIKE ? ESCAPE '\\' OR LOWER(t.title) LIKE ? ESCAPE '\\')",
1207
+ params: [like, like],
1208
+ };
1209
+ }
1210
+ // ── Agent Board prompt-rows projection (decision b509fd91, 2026-06-08) ───
1211
+ //
1212
+ // "一行 = 一条 user prompt" view (P1 方案 B, zero schema). Each row is one
1213
+ // UserPromptSubmit event; its `group_id` reuses the existing `task_id` so a
1214
+ // task is the group. No new columns, no migration — events.user_prompt is a
1215
+ // plaintext column (only tool_input/tool_output are gzip), so prompt text is
1216
+ // read with a bare SQL substr, never decoding gzip.
1217
+ //
1218
+ // Contrast with queryAgentBoardTasks (one row = one task): this query does NOT
1219
+ // run the 7 per-task aggregations (top_tools / spawned / kb / intercepts). Each
1220
+ // prompt row carries only lightweight fields + its group(task)'s lane metadata,
1221
+ // which is cheap and keeps the (potentially 100+) prompt rows fast to render.
1222
+ //
1223
+ // SQL: events e JOIN task_events te JOIN tasks t JOIN sessions s, filtered to
1224
+ // UserPromptSubmit events with non-null user_prompt, within the window and the
1225
+ // same task_kind whitelist as the task-row query. ORDER BY e.timestamp DESC.
1226
+ //
1227
+ // MUST keep the WHERE filter in sync with countAgentBoardPromptRows below.
1228
+ queryAgentBoardPromptRows(opts) {
1229
+ const limit = Math.min(Math.max(opts.limit ?? 120, 1), 300);
1230
+ const offset = Math.max(opts.offset ?? 0, 0);
1231
+ const nowMs = opts.now_ms ?? Date.now();
1232
+ const sinceMs = nowMs - opts.windowDays * DAY_MS;
1233
+ const since = new Date(sinceMs).toISOString();
1234
+ const projectFilter = opts.projectPath ? 'AND s.project_path = ?' : '';
1235
+ const { effectiveKinds, kindFilter } = this.resolveKindFilter(opts);
1236
+ const outcomePred = this.resolveOutcomeFilter(opts.outcome);
1237
+ const outcomeFilter = outcomePred ? `AND ${outcomePred}` : '';
1238
+ const searchClause = this.resolveSearchFilter(opts.search);
1239
+ const searchFilter = searchClause.predicate ? `AND ${searchClause.predicate}` : '';
1240
+ const sql = `
1241
+ SELECT
1242
+ e.event_id AS event_id,
1243
+ substr(e.user_prompt, 1, 300) AS prompt_text,
1244
+ e.timestamp AS prompt_ts,
1245
+ e.session_id AS session_id,
1246
+ s.project_path AS project_path,
1247
+ t.id AS group_id,
1248
+ t.title AS group_title,
1249
+ t.status AS group_status,
1250
+ t.outcome AS group_outcome,
1251
+ t.task_kind AS group_task_kind,
1252
+ t.start_time AS group_start_time,
1253
+ t.end_time AS group_end_time,
1254
+ t.event_count AS group_event_count,
1255
+ (CASE
1256
+ WHEN t.end_time IS NOT NULL THEN
1257
+ CAST(strftime('%s', t.end_time) AS INTEGER) - CAST(strftime('%s', t.start_time) AS INTEGER)
1258
+ ELSE
1259
+ CAST(strftime('%s', 'now') AS INTEGER) - CAST(strftime('%s', t.start_time) AS INTEGER)
1260
+ END) AS group_duration_sec
1261
+ FROM events e
1262
+ -- perf (decision 2f05b0bf, 2026-06-13): CROSS JOIN pins the join ORDER so
1263
+ -- the small, selective UserPromptSubmit set on events
1264
+ -- (idx_events_hook_type, ~hundreds of rows) drives the join. Without it the
1265
+ -- optimizer picks tasks USING idx_tasks_kind as the outer loop and fans
1266
+ -- out across EVERY task_events row (tens of thousands), ~90ms PER query,
1267
+ -- linear in window. CROSS JOIN is a pure join-order hint (SQLite keeps the
1268
+ -- written table order); the ON/WHERE predicates are unchanged so the result
1269
+ -- set is byte-identical. Profiled 30d window: q1 91ms to under 2ms, +project
1270
+ -- 48ms to 0.7ms, +search 320ms to 0.8ms (all rowsets verified identical).
1271
+ CROSS JOIN task_events te ON te.event_id = e.event_id
1272
+ CROSS JOIN tasks t ON t.id = te.task_id
1273
+ CROSS JOIN sessions s ON s.session_id = t.session_id
1274
+ WHERE e.hook_type = 'UserPromptSubmit'
1275
+ AND e.user_prompt IS NOT NULL
1276
+ AND t.start_time >= ?
1277
+ ${projectFilter}
1278
+ ${kindFilter}
1279
+ ${outcomeFilter}
1280
+ ${searchFilter}
1281
+ ORDER BY e.timestamp DESC
1282
+ LIMIT ? OFFSET ?
1283
+ `;
1284
+ const params = [since];
1285
+ if (opts.projectPath)
1286
+ params.push(opts.projectPath);
1287
+ if (kindFilter)
1288
+ params.push(...effectiveKinds);
1289
+ // search placeholders come after kind, before LIMIT/OFFSET — clause order.
1290
+ if (searchClause.params.length)
1291
+ params.push(...searchClause.params);
1292
+ params.push(limit, offset);
1293
+ const rows = this.db.prepare(sql).all(...params);
1294
+ return rows.map(r => ({
1295
+ event_id: r.event_id,
1296
+ prompt_text: r.prompt_text ?? '',
1297
+ prompt_ts: r.prompt_ts,
1298
+ session_id: r.session_id,
1299
+ project_path: r.project_path,
1300
+ group_id: r.group_id,
1301
+ group_title: r.group_title,
1302
+ group_status: r.group_status,
1303
+ group_outcome: r.group_outcome,
1304
+ group_task_kind: r.group_task_kind ?? 'user',
1305
+ group_start_time: r.group_start_time,
1306
+ group_end_time: r.group_end_time,
1307
+ group_event_count: r.group_event_count ?? 0,
1308
+ group_duration_sec: r.group_duration_sec,
1309
+ }));
1310
+ }
1311
+ /**
1312
+ * Unbounded count companion for queryAgentBoardPromptRows — the number of
1313
+ * prompt rows (not tasks) in the window, for the list paginator's
1314
+ * total-pages math. Same WHERE clause minus LIMIT/ORDER (decision b509fd91).
1315
+ */
1316
+ countAgentBoardPromptRows(opts) {
1317
+ const nowMs = opts.now_ms ?? Date.now();
1318
+ const sinceMs = nowMs - opts.windowDays * DAY_MS;
1319
+ const since = new Date(sinceMs).toISOString();
1320
+ const projectFilter = opts.projectPath ? 'AND s.project_path = ?' : '';
1321
+ const { effectiveKinds, kindFilter } = this.resolveKindFilter(opts);
1322
+ const outcomePred = this.resolveOutcomeFilter(opts.outcome);
1323
+ const outcomeFilter = outcomePred ? `AND ${outcomePred}` : '';
1324
+ // Same predicate + same bind values as queryAgentBoardPromptRows — count and
1325
+ // query MUST share one 口径 or the paginator denominator diverges from the rows.
1326
+ const searchClause = this.resolveSearchFilter(opts.search);
1327
+ const searchFilter = searchClause.predicate ? `AND ${searchClause.predicate}` : '';
1328
+ const sql = `
1329
+ SELECT COUNT(*) AS n
1330
+ FROM events e
1331
+ -- perf (decision 2f05b0bf): CROSS JOIN pins events-first join order — see
1332
+ -- queryAgentBoardPromptRows for the full rationale (90ms → <2ms; pure
1333
+ -- join-order hint, result count unchanged).
1334
+ CROSS JOIN task_events te ON te.event_id = e.event_id
1335
+ CROSS JOIN tasks t ON t.id = te.task_id
1336
+ CROSS JOIN sessions s ON s.session_id = t.session_id
1337
+ WHERE e.hook_type = 'UserPromptSubmit'
1338
+ AND e.user_prompt IS NOT NULL
1339
+ AND t.start_time >= ?
1340
+ ${projectFilter}
1341
+ ${kindFilter}
1342
+ ${outcomeFilter}
1343
+ ${searchFilter}
1344
+ `;
1345
+ const params = [since];
1346
+ if (opts.projectPath)
1347
+ params.push(opts.projectPath);
1348
+ if (kindFilter)
1349
+ params.push(...effectiveKinds);
1350
+ if (searchClause.params.length)
1351
+ params.push(...searchClause.params);
1352
+ const row = this.db.prepare(sql).get(...params);
1353
+ return row?.n ?? 0;
1354
+ }
1355
+ /**
1356
+ * Window-level task aggregates for the prompt-rows list view (2026-06-10).
1357
+ *
1358
+ * Why: the list view's KPI bar ("总任务数 / 失败 / 平均时长") and outcome tab
1359
+ * counts were derived client-side from `queryAgentBoardPromptRows` groups —
1360
+ * i.e. from ONE PAGE (LIMIT default 120) — so they showed page-local numbers
1361
+ * (e.g. 34) instead of true window totals (e.g. 709). This method runs the
1362
+ * SAME WHERE 口径 as countAgentBoardPromptRows (window / project / kind
1363
+ * whitelist, prompt-bearing tasks only) but dedupes to distinct tasks and
1364
+ * aggregates outcome distribution + average duration in SQL.
1365
+ *
1366
+ * Semantics intentionally mirror the page's previous client-side math:
1367
+ * - task_total = COUNT(DISTINCT task) — a task with N prompts counts once.
1368
+ * - outcome_abandoned = outcome='abandoned' OR (outcome IS NULL AND
1369
+ * status='abandoned') — same fold the outcome chips did.
1370
+ * - outcome_answered = outcome='answered' (spec 1100 Option C, 2026-06-02 —
1371
+ * a first-class fifth value; no UI tab, but live data
1372
+ * has hundreds of these so it must not vanish).
1373
+ * - outcome_none = residual catch-all: outcome IS NULL with a
1374
+ * non-abandoned status, OR any unknown/legacy outcome
1375
+ * string (the column CHECK only enforces non-empty).
1376
+ * - avg_duration_sec = AVG over tasks with duration > 0, where duration is
1377
+ * the same CASE expression queryAgentBoardPromptRows
1378
+ * computes for group_duration_sec (end-start, or
1379
+ * now-start while active). Rounded to whole seconds.
1380
+ *
1381
+ * Invariant (tested, holds for ARBITRARY outcome strings thanks to the
1382
+ * catch-all): success+partial+failed+abandoned+answered+none == task_total.
1383
+ * MUST keep the inner WHERE in sync with queryAgentBoardPromptRows /
1384
+ * countAgentBoardPromptRows above.
1385
+ */
1386
+ queryAgentBoardPromptWindowAggregates(opts) {
1387
+ const nowMs = opts.now_ms ?? Date.now();
1388
+ const sinceMs = nowMs - opts.windowDays * DAY_MS;
1389
+ const since = new Date(sinceMs).toISOString();
1390
+ const projectFilter = opts.projectPath ? 'AND s.project_path = ?' : '';
1391
+ const { effectiveKinds, kindFilter } = this.resolveKindFilter(opts);
1392
+ const sql = `
1393
+ SELECT
1394
+ COUNT(*) AS task_total,
1395
+ SUM(CASE WHEN g.outcome = 'success' THEN 1 ELSE 0 END) AS outcome_success,
1396
+ SUM(CASE WHEN g.outcome = 'partial' THEN 1 ELSE 0 END) AS outcome_partial,
1397
+ SUM(CASE WHEN g.outcome = 'failed' THEN 1 ELSE 0 END) AS outcome_failed,
1398
+ SUM(CASE WHEN g.outcome = 'abandoned'
1399
+ OR (g.outcome IS NULL AND g.status = 'abandoned')
1400
+ THEN 1 ELSE 0 END) AS outcome_abandoned,
1401
+ SUM(CASE WHEN g.outcome = 'answered' THEN 1 ELSE 0 END) AS outcome_answered,
1402
+ SUM(CASE
1403
+ WHEN g.outcome IN ('success','partial','failed','abandoned','answered') THEN 0
1404
+ WHEN g.outcome IS NULL AND g.status = 'abandoned' THEN 0
1405
+ ELSE 1
1406
+ END) AS outcome_none,
1407
+ AVG(CASE WHEN g.duration_sec > 0 THEN g.duration_sec END) AS avg_duration_sec
1408
+ FROM (
1409
+ SELECT
1410
+ t.id AS id,
1411
+ t.outcome AS outcome,
1412
+ t.status AS status,
1413
+ (CASE
1414
+ WHEN t.end_time IS NOT NULL THEN
1415
+ CAST(strftime('%s', t.end_time) AS INTEGER) - CAST(strftime('%s', t.start_time) AS INTEGER)
1416
+ ELSE
1417
+ CAST(strftime('%s', 'now') AS INTEGER) - CAST(strftime('%s', t.start_time) AS INTEGER)
1418
+ END) AS duration_sec
1419
+ FROM events e
1420
+ -- perf (decision 2f05b0bf): CROSS JOIN pins events-first join order — see
1421
+ -- queryAgentBoardPromptRows for the full rationale (90ms → <2ms; pure
1422
+ -- join-order hint, aggregate result unchanged).
1423
+ CROSS JOIN task_events te ON te.event_id = e.event_id
1424
+ CROSS JOIN tasks t ON t.id = te.task_id
1425
+ CROSS JOIN sessions s ON s.session_id = t.session_id
1426
+ WHERE e.hook_type = 'UserPromptSubmit'
1427
+ AND e.user_prompt IS NOT NULL
1428
+ AND t.start_time >= ?
1429
+ ${projectFilter}
1430
+ ${kindFilter}
1431
+ GROUP BY t.id
1432
+ ) g
1433
+ `;
1434
+ const params = [since];
1435
+ if (opts.projectPath)
1436
+ params.push(opts.projectPath);
1437
+ if (kindFilter)
1438
+ params.push(...effectiveKinds);
1439
+ const row = this.db.prepare(sql).get(...params);
1440
+ return {
1441
+ task_total: row?.task_total ?? 0,
1442
+ outcome_success: row?.outcome_success ?? 0,
1443
+ outcome_partial: row?.outcome_partial ?? 0,
1444
+ outcome_failed: row?.outcome_failed ?? 0,
1445
+ outcome_abandoned: row?.outcome_abandoned ?? 0,
1446
+ outcome_answered: row?.outcome_answered ?? 0,
1447
+ outcome_none: row?.outcome_none ?? 0,
1448
+ avg_duration_sec: row?.avg_duration_sec != null ? Math.round(row.avg_duration_sec) : null,
220
1449
  };
1450
+ }
1451
+ // ── Agent Board v3 — count companion (2026-05-26) ─────────────────────
1452
+ //
1453
+ // Why a separate method: queryAgentBoardTasks() applies a LIMIT (default 40,
1454
+ // perf cap from 2026-05-25 — each card runs ~7 follow-up aggregations). The
1455
+ // Web KPI "总任务数 (7d)" needs the UNBOUNDED count for the window, so we
1456
+ // run the same WHERE clauses (start_time + project + noise filter) without
1457
+ // the aggregates / LIMIT / ORDER BY. Cheap: tasks ⨯ sessions JOIN is index
1458
+ // covered and the COUNT(*) returns one row.
1459
+ //
1460
+ // MUST stay in sync with the WHERE filter in queryAgentBoardTasks above.
1461
+ // Tested by web-insights-agent-board.integration.test.ts (4 cases incl.
1462
+ // noise filter + project filter + cap-vs-total invariant).
1463
+ countAgentBoardTasks(opts) {
1464
+ const nowMs = opts.now_ms ?? Date.now();
1465
+ const sinceMs = nowMs - opts.windowDays * DAY_MS;
1466
+ const since = new Date(sinceMs).toISOString();
1467
+ const projectFilter = opts.projectPath ? 'AND s.project_path = ?' : '';
1468
+ // spec b1480935: same resolution order as queryAgentBoardTasks so the
1469
+ // count is always consistent with the row set.
1470
+ const effectiveKinds = opts.includeKinds
1471
+ ?? (opts.includeNoise ? ALL_TASK_KINDS : ['user']);
1472
+ const kindFilter = effectiveKinds.length === ALL_TASK_KINDS.length
1473
+ ? ''
1474
+ : `AND t.task_kind IN (${effectiveKinds.map(() => '?').join(',')})`;
1475
+ const sql = `
1476
+ SELECT COUNT(*) AS n
1477
+ FROM tasks t
1478
+ JOIN sessions s ON s.session_id = t.session_id
1479
+ WHERE t.start_time >= ?
1480
+ ${projectFilter}
1481
+ ${kindFilter}
1482
+ `;
1483
+ const params = [since];
1484
+ if (opts.projectPath)
1485
+ params.push(opts.projectPath);
1486
+ if (kindFilter)
1487
+ params.push(...effectiveKinds);
1488
+ const row = this.db.prepare(sql).get(...params);
1489
+ return row?.n ?? 0;
1490
+ }
1491
+ /**
1492
+ * Count "trivial" tasks since `sinceIso` — driven by DaemonObserverBadge's
1493
+ * `trivial_passed_7d` field (spec 1750 § B1-3).
1494
+ *
1495
+ * Definition: tasks the task-segmenter marked as `is_noise=1` AT INSERT TIME
1496
+ * (single-char prompt / single-word slash command / envelope task title). This
1497
+ * is the closest server-side proxy for "daemon never engaged" — the trivial
1498
+ * filter in `task-segmenter.ts` runs at write time using the same heuristics
1499
+ * that gate decision-hint emission. Frontend's `isTrivialPath()` uses a finer
1500
+ * signal (all daemon-intervention sections empty) but we don't have that
1501
+ * aggregated; this proxy is good enough for the weekly counter chip.
1502
+ *
1503
+ * `datetime()` wrap normalises ISO 'T' vs SQLite default ' ' separator
1504
+ * (see DecisionsOperations.countDecisionsSince for the reasoning).
1505
+ */
1506
+ countTrivialTasksSince(sinceIso) {
1507
+ const row = this.db
1508
+ .prepare(`SELECT COUNT(*) AS n FROM tasks
1509
+ WHERE datetime(start_time) >= datetime(?) AND is_noise = 1`)
1510
+ .get(sinceIso);
1511
+ return row?.n ?? 0;
1512
+ }
1513
+ /**
1514
+ * Daily commit + task activity over a window (default 30 days).
1515
+ *
1516
+ * Used by `/api/insights/commit-activity` to drive the HealthHomePage
1517
+ * 30d "Commit calendar" without the lane LIMIT undercount that the
1518
+ * agent-board path suffered. Aggregates straight off the `tasks` table
1519
+ * by `DATE(start_time)` so the result is unbounded by perf caps.
1520
+ *
1521
+ * The day buckets are filled with zeros for days with no tasks so the
1522
+ * caller doesn't have to fill gaps client-side. Days ordered oldest→newest.
1523
+ *
1524
+ * Added 2026-05-26 — see docs/implementation changelog.
1525
+ */
1526
+ queryCommitActivityDaily(opts) {
1527
+ const days = Math.min(Math.max(opts.days, 1), 365);
1528
+ const nowMs = opts.now_ms ?? Date.now();
1529
+ const sinceMs = nowMs - days * DAY_MS;
1530
+ const since = new Date(sinceMs).toISOString();
1531
+ const projectFilter = opts.projectPath ? 'AND s.project_path = ?' : '';
1532
+ const sql = `
1533
+ SELECT
1534
+ DATE(t.start_time) AS date,
1535
+ COUNT(*) AS total,
1536
+ SUM(CASE WHEN t.outcome = 'success' THEN 1 ELSE 0 END) AS succeeded,
1537
+ SUM(CASE WHEN t.outcome = 'failed' THEN 1 ELSE 0 END) AS failed,
1538
+ COALESCE(SUM(t.commit_count), 0) AS commits
1539
+ FROM tasks t
1540
+ JOIN sessions s ON s.session_id = t.session_id
1541
+ WHERE t.start_time >= ?
1542
+ ${projectFilter}
1543
+ GROUP BY DATE(t.start_time)
1544
+ ORDER BY DATE(t.start_time) ASC
1545
+ `;
1546
+ const params = [since];
1547
+ if (opts.projectPath)
1548
+ params.push(opts.projectPath);
1549
+ const rows = this.db.prepare(sql).all(...params);
1550
+ // Fill gaps so days with 0 activity still appear (matches CommitCalendarCard
1551
+ // contract — it expects a contiguous day array indexed by date).
1552
+ const byDate = new Map();
1553
+ for (const r of rows) {
1554
+ byDate.set(r.date, {
1555
+ total: Number(r.total) || 0,
1556
+ succeeded: Number(r.succeeded) || 0,
1557
+ failed: Number(r.failed) || 0,
1558
+ commits: Number(r.commits) || 0,
1559
+ });
1560
+ }
1561
+ const out = [];
1562
+ for (let i = days - 1; i >= 0; i--) {
1563
+ const d = new Date(nowMs - i * DAY_MS).toISOString().slice(0, 10);
1564
+ const v = byDate.get(d);
1565
+ out.push({
1566
+ date: d,
1567
+ total: v?.total ?? 0,
1568
+ succeeded: v?.succeeded ?? 0,
1569
+ failed: v?.failed ?? 0,
1570
+ commits: v?.commits ?? 0,
1571
+ });
1572
+ }
1573
+ return out;
1574
+ }
1575
+ /**
1576
+ * Task-tracking deep fix (spec 1551 §6.D): Dry-run query for backfill-orphans CLI.
1577
+ *
1578
+ * Returns stats about how many orphan events can be attached via the smart-window
1579
+ * algorithm. No writes are performed.
1580
+ */
1581
+ queryOrphanStats() {
1582
+ const row = this.db.prepare(`
1583
+ WITH orphans AS (
1584
+ SELECT e.event_id, e.session_id, e.timestamp AS ev_ts
1585
+ FROM events e
1586
+ LEFT JOIN task_events te ON te.event_id = e.event_id
1587
+ WHERE te.event_id IS NULL
1588
+ ),
1589
+ attachable AS (
1590
+ SELECT o.event_id, o.session_id, o.ev_ts,
1591
+ (SELECT t.id FROM tasks t
1592
+ WHERE t.session_id = o.session_id
1593
+ AND t.status = 'completed'
1594
+ AND t.end_time IS NOT NULL
1595
+ AND t.end_time <= o.ev_ts
1596
+ AND NOT EXISTS (
1597
+ SELECT 1 FROM tasks t2
1598
+ WHERE t2.session_id = t.session_id
1599
+ AND t2.start_time > t.end_time
1600
+ AND t2.start_time <= o.ev_ts
1601
+ )
1602
+ ORDER BY t.end_time DESC LIMIT 1) AS task_id
1603
+ FROM orphans o
1604
+ )
1605
+ SELECT
1606
+ COUNT(*) AS total_orphans,
1607
+ SUM(CASE WHEN task_id IS NOT NULL THEN 1 ELSE 0 END) AS attachable,
1608
+ SUM(CASE WHEN task_id IS NULL THEN 1 ELSE 0 END) AS unattachable
1609
+ FROM attachable
1610
+ `).get();
1611
+ const total = row?.total_orphans ?? 0;
1612
+ const attachable = row?.attachable ?? 0;
1613
+ const unattachable = row?.unattachable ?? 0;
1614
+ const pct = total > 0 ? `${((attachable / total) * 100).toFixed(1)}%` : '0.0%';
1615
+ return { total_orphans: total, attachable, unattachable, pct };
1616
+ }
1617
+ /**
1618
+ * Task-tracking deep fix (spec 1551 §6.D): Sample orphan→task pairs for dry-run display.
1619
+ *
1620
+ * Returns up to `limit` (default 5) sample pairs showing which events would be attached.
1621
+ */
1622
+ sampleAttachablePairs(limit = 5) {
1623
+ const rows = this.db.prepare(`
1624
+ WITH orphans AS (
1625
+ SELECT e.event_id, e.session_id, e.timestamp AS ev_ts
1626
+ FROM events e
1627
+ LEFT JOIN task_events te ON te.event_id = e.event_id
1628
+ WHERE te.event_id IS NULL
1629
+ ),
1630
+ attachable AS (
1631
+ SELECT o.event_id, o.session_id, o.ev_ts,
1632
+ (SELECT t.id FROM tasks t
1633
+ WHERE t.session_id = o.session_id
1634
+ AND t.status = 'completed'
1635
+ AND t.end_time IS NOT NULL
1636
+ AND t.end_time <= o.ev_ts
1637
+ AND NOT EXISTS (
1638
+ SELECT 1 FROM tasks t2
1639
+ WHERE t2.session_id = t.session_id
1640
+ AND t2.start_time > t.end_time
1641
+ AND t2.start_time <= o.ev_ts
1642
+ )
1643
+ ORDER BY t.end_time DESC LIMIT 1) AS task_id
1644
+ FROM orphans o
1645
+ )
1646
+ SELECT
1647
+ a.event_id,
1648
+ a.task_id,
1649
+ t.title AS task_title,
1650
+ CAST((julianday(a.ev_ts) - julianday(t.end_time)) * 1440 AS INTEGER) AS gap_min
1651
+ FROM attachable a
1652
+ JOIN tasks t ON t.id = a.task_id
1653
+ WHERE a.task_id IS NOT NULL
1654
+ LIMIT ?
1655
+ `).all(limit);
1656
+ return rows;
1657
+ }
1658
+ /**
1659
+ * Task-tracking deep fix (spec 1551 §6.D.3): Apply backfill in a single atomic transaction.
1660
+ *
1661
+ * Inserts orphan→task links using INSERT OR IGNORE (idempotent).
1662
+ * Then synchronizes event_count on updated tasks.
1663
+ *
1664
+ * Returns the number of rows inserted.
1665
+ */
1666
+ backfillOrphans() {
1667
+ const insertStmt = this.db.prepare(`
1668
+ INSERT OR IGNORE INTO task_events (task_id, event_id)
1669
+ SELECT cand.task_id, cand.event_id
1670
+ FROM (
1671
+ SELECT o.event_id, o.session_id, o.ev_ts,
1672
+ (SELECT t.id FROM tasks t
1673
+ WHERE t.session_id = o.session_id
1674
+ AND t.status = 'completed'
1675
+ AND t.end_time IS NOT NULL
1676
+ AND t.end_time <= o.ev_ts
1677
+ AND NOT EXISTS (
1678
+ SELECT 1 FROM tasks t2
1679
+ WHERE t2.session_id = t.session_id
1680
+ AND t2.start_time > t.end_time
1681
+ AND t2.start_time <= o.ev_ts
1682
+ )
1683
+ ORDER BY t.end_time DESC LIMIT 1) AS task_id
1684
+ FROM (
1685
+ SELECT e.event_id, e.session_id, e.timestamp AS ev_ts
1686
+ FROM events e
1687
+ LEFT JOIN task_events te ON te.event_id = e.event_id
1688
+ WHERE te.event_id IS NULL
1689
+ ) o
1690
+ ) cand
1691
+ WHERE cand.task_id IS NOT NULL
1692
+ `);
1693
+ const updateCountStmt = this.db.prepare(`
1694
+ UPDATE tasks
1695
+ SET event_count = (SELECT COUNT(*) FROM task_events WHERE task_id = tasks.id)
1696
+ `);
1697
+ let inserted = 0;
1698
+ this.db.transaction(() => {
1699
+ const result = insertStmt.run();
1700
+ inserted = result.changes;
1701
+ updateCountStmt.run();
1702
+ })();
1703
+ return inserted;
1704
+ }
1705
+ // ── decision 265f59d5: LLM task-boundary re-attribution helpers ──────────
1706
+ //
1707
+ // Data-safety contract (spec Option A): re-attribution NEVER deletes events
1708
+ // and NEVER mutates event content. The only write is moving a single prompt
1709
+ // event's `task_events.task_id` foreign key onto a freshly-created task, and
1710
+ // stamping `llm_classified_at` for idempotency.
1711
+ /**
1712
+ * The first `limit` user-prompt texts of a task, oldest-first. Feeds the
1713
+ * classifier the "what was the previous task about" context. Reads only the
1714
+ * plaintext `user_prompt` column (never gzip tool_input).
1715
+ */
1716
+ getTaskUserPrompts(taskId, limit = 3) {
1717
+ const rows = this.db.prepare(`
1718
+ SELECT e.user_prompt AS user_prompt
1719
+ FROM task_events te
1720
+ JOIN events e ON e.event_id = te.event_id
1721
+ WHERE te.task_id = ?
1722
+ AND e.hook_type = 'UserPromptSubmit'
1723
+ AND e.user_prompt IS NOT NULL
1724
+ ORDER BY e.timestamp ASC
1725
+ LIMIT ?
1726
+ `).all(taskId, limit);
1727
+ return rows.map(r => r.user_prompt).filter((p) => !!p);
1728
+ }
1729
+ /** True once the async boundary classifier has stamped this task. */
1730
+ isLlmClassified(taskId) {
1731
+ const row = this.db.prepare(`SELECT llm_classified_at FROM tasks WHERE id = ? LIMIT 1`).get(taskId);
1732
+ return !!row?.llm_classified_at;
1733
+ }
1734
+ /** Stamp the idempotency marker without re-attributing (CONTINUATION verdict). */
1735
+ markLlmClassified(taskId, atIso) {
1736
+ this.db.prepare(`UPDATE tasks SET llm_classified_at = ? WHERE id = ?`).run(atIso, taskId);
1737
+ }
1738
+ /**
1739
+ * Re-attribute a single prompt event off `fromTaskId` onto a brand-new task,
1740
+ * because the async classifier judged it NEW_TASK. Atomic + idempotent.
1741
+ *
1742
+ * Safety / idempotency guards (spec OQ #3 "re-check"):
1743
+ * 1. `fromTaskId` must not already be llm_classified (double-write guard).
1744
+ * 2. the event must STILL be linked to `fromTaskId` (no other path moved it
1745
+ * during the LLM delay window).
1746
+ * 3. all writes run in ONE better-sqlite3 transaction.
1747
+ *
1748
+ * On success: creates the new task row, moves ONLY this event's
1749
+ * task_events.task_id FK, decrements the old task's event_count / increments
1750
+ * the new one, and stamps `llm_classified_at` on BOTH rows. Returns the new
1751
+ * task id. On any guard failure returns null and writes nothing.
1752
+ *
1753
+ * Explicitly NOT done: deleting events, editing event content, closing or
1754
+ * reactivating the source task (its lifecycle is owned by the segmenter /
1755
+ * stop handler). OQ1 = "trust the LLM": no conservative event-count/time guard.
1756
+ */
1757
+ reattributePromptToNewTask(opts) {
1758
+ const { fromTaskId, eventId, newTask, atIso } = opts;
1759
+ const txn = this.db.transaction(() => {
1760
+ // Guard 1: idempotency — already classified, do nothing.
1761
+ const fromRow = this.db.prepare(`SELECT llm_classified_at FROM tasks WHERE id = ? LIMIT 1`).get(fromTaskId);
1762
+ if (!fromRow)
1763
+ return null;
1764
+ if (fromRow.llm_classified_at)
1765
+ return null;
1766
+ // Guard 2: re-check the event still belongs to fromTaskId.
1767
+ const link = this.db.prepare(`SELECT task_id FROM task_events WHERE event_id = ? LIMIT 1`).get(eventId);
1768
+ if (!link || link.task_id !== fromTaskId) {
1769
+ // Someone else moved it (or it was never linked). Still stamp the
1770
+ // source so we never retry this prompt, but do not re-attribute.
1771
+ this.db.prepare(`UPDATE tasks SET llm_classified_at = ? WHERE id = ?`).run(atIso, fromTaskId);
1772
+ return null;
1773
+ }
1774
+ // Create the new task row (kind defaults to 'user').
1775
+ this.db.prepare(`
1776
+ INSERT INTO tasks (id, session_id, title, start_time, is_noise, task_kind, llm_classified_at)
1777
+ VALUES (?, ?, ?, ?, 0, ?, ?)
1778
+ `).run(newTask.id, newTask.session_id, newTask.title, newTask.start_time, newTask.task_kind ?? 'user', atIso);
1779
+ // Move ONLY this event's FK. PK is (task_id, event_id), so UPDATE the row.
1780
+ this.db.prepare(`UPDATE task_events SET task_id = ? WHERE event_id = ? AND task_id = ?`).run(newTask.id, eventId, fromTaskId);
1781
+ // Keep event_count honest on both rows (best-effort; floor at 0).
1782
+ this.db.prepare(`UPDATE tasks SET event_count = MAX(event_count - 1, 0) WHERE id = ?`).run(fromTaskId);
1783
+ this.db.prepare(`UPDATE tasks SET event_count = event_count + 1 WHERE id = ?`).run(newTask.id);
1784
+ // Stamp the source task so a replayed event never re-classifies it.
1785
+ this.db.prepare(`UPDATE tasks SET llm_classified_at = ? WHERE id = ?`).run(atIso, fromTaskId);
1786
+ return newTask.id;
1787
+ });
1788
+ try {
1789
+ return txn();
1790
+ }
1791
+ catch (err) {
1792
+ // Defensive: never crash the daemon's async classifier path.
1793
+ // eslint-disable-next-line no-console
1794
+ console.warn('[TaskOperations] reattributePromptToNewTask failed:', err);
1795
+ return null;
1796
+ }
1797
+ }
1798
+ /** Map a raw events row to ForgeEvent (parses JSON columns). */
1799
+ rowToEvent(row) {
1800
+ // tool_output flows through decodeToolOutput so this rowToEvent reads
1801
+ // both legacy plain rows and new codec-encoded rows (gzip / tiny utf8).
1802
+ // Wrapped in try/catch so a malformed payload returns undefined rather
1803
+ // than crashing the whole row map. Matches EventOperations.rowToEvent.
1804
+ let toolOutput = undefined;
1805
+ try {
1806
+ toolOutput = decodeToolOutput(row.tool_output);
1807
+ }
1808
+ catch {
1809
+ toolOutput = undefined;
1810
+ }
221
1811
  return {
222
1812
  event_id: row.event_id,
223
1813
  session_id: row.session_id,
@@ -225,8 +1815,8 @@ export class TaskOperations {
225
1815
  timestamp: row.timestamp,
226
1816
  hook_type: row.hook_type,
227
1817
  tool_name: row.tool_name || undefined,
228
- tool_input: parseJson(row.tool_input),
229
- tool_output: parseJson(row.tool_output),
1818
+ tool_input: decodeToolInput(row.tool_input),
1819
+ tool_output: toolOutput,
230
1820
  user_prompt: row.user_prompt || undefined,
231
1821
  ai_response: row.ai_response || undefined,
232
1822
  };
@@ -254,10 +1844,18 @@ export class TaskOperations {
254
1844
  }
255
1845
  if (filter.search) {
256
1846
  // M3/L2: 当前 LIKE '%search%' 全表扫描;行数 < 10w 时延迟可接受。
257
- // TODO: 加 FTS5 索引(行数 > 10w 时上 FTS 才能避免线性扫描)。
1847
+ // Note: 加 FTS5 索引(触发条件:tasks 表行数 > 10w LIKE 线性扫描 > 100ms)。
258
1848
  conditions.push('t.title LIKE ?');
259
1849
  params.push(`%${filter.search}%`);
260
1850
  }
1851
+ // spec b1480935 (2026-06-01 Option A): task_kind whitelist. Always
1852
+ // applied — when undefined the caller (route) defaults to ['user'] so
1853
+ // /api/tasks stays clean by default. Pass all four kinds to disable.
1854
+ if (filter.include_kinds && filter.include_kinds.length > 0) {
1855
+ const placeholders = filter.include_kinds.map(() => '?').join(', ');
1856
+ conditions.push(`t.task_kind IN (${placeholders})`);
1857
+ params.push(...filter.include_kinds);
1858
+ }
261
1859
  return { conditions, params };
262
1860
  }
263
1861
  mapRow(r) {
@@ -272,7 +1870,83 @@ export class TaskOperations {
272
1870
  event_count: r.event_count || 0,
273
1871
  project_path: r.project_path || undefined,
274
1872
  first_prompt: r.first_prompt !== undefined ? r.first_prompt : undefined,
1873
+ // Outcome columns may be NULL (not yet classified) — pass through verbatim.
1874
+ outcome: r.outcome ?? null,
1875
+ outcome_reason: r.outcome_reason ?? null,
1876
+ commit_count: r.commit_count == null ? null : r.commit_count,
1877
+ reverted_commits: r.reverted_commits == null ? null : r.reverted_commits,
1878
+ user_violation_count: r.user_violation_count == null ? null : r.user_violation_count,
1879
+ classified_at: r.classified_at ?? null,
1880
+ llm_classified_at: r.llm_classified_at ?? null,
1881
+ is_noise: r.is_noise == null ? null : r.is_noise,
1882
+ // spec b1480935: task_kind exposed verbatim. Legacy rows (column missing
1883
+ // for some edge cases mid-migration) read as undefined → callers treat
1884
+ // as 'user' via the TaskRecord default contract.
1885
+ task_kind: r.task_kind ?? 'user',
1886
+ // decision f47c2c90: self-FK passthrough (NULL on legacy / non-linked rows).
1887
+ parent_task_id: r.parent_task_id ?? null,
275
1888
  };
276
1889
  }
1890
+ // ── Outcome classifier (spec 1100 v2 — edit-intent signal) ────────
1891
+ //
1892
+ // The single signal powering the success-vs-answered verdict: did this task
1893
+ // change code? Reads ONLY `tool_name` (never the gzip `tool_input`), so it is
1894
+ // always correct. Sub-agent Edit/Write events are linked to the parent task's
1895
+ // task_events, so "did this task (incl. its sub-agents) edit code?" is
1896
+ // captured. The previous file→task commit-attribution map was deleted — it
1897
+ // decoded gzip tool_input for file paths and silently failed, corrupting the
1898
+ // verdict.
1899
+ /**
1900
+ * Count this task's Edit/Write intent events. Drives the `edit_intent_count`
1901
+ * signal: a task with >0 edit-intent → `success`, one with 0 edit-intent is a
1902
+ * pure Q&A / research task → `answered`.
1903
+ *
1904
+ * `tool_name` is NOT gzip-compressed (only `tool_input` is), so this is a
1905
+ * pure SQL COUNT — no decode needed. We count PreToolUse so a single logical
1906
+ * edit (Pre + Post) is counted once; multi-edit / notebook variants included.
1907
+ */
1908
+ countEditIntentEventsByTask(taskId) {
1909
+ const row = this.db.prepare(`
1910
+ SELECT COUNT(*) AS n
1911
+ FROM task_events te
1912
+ JOIN events e ON e.event_id = te.event_id
1913
+ WHERE te.task_id = ?
1914
+ AND e.hook_type = 'PreToolUse'
1915
+ AND e.tool_name IN ('Edit', 'Write', 'MultiEdit', 'NotebookEdit')
1916
+ `).get(taskId);
1917
+ return row?.n ?? 0;
1918
+ }
1919
+ // ── P1: Idle unclassified tasks (e2e link fix 2026-05-25) ──────────
1920
+ //
1921
+ // TaskSegmenter marks tasks completed when a new prompt switches off the
1922
+ // current task, but does NOT run outcome-classifier. Result: most
1923
+ // `tasks.outcome` rows are NULL even after Stop. This helper lets the
1924
+ // Stop handler sweep all completed/abandoned tasks on the session whose
1925
+ // outcome hasn't been stamped yet.
1926
+ //
1927
+ // Includes both 'completed' and 'abandoned' statuses (never 'active' —
1928
+ // the current task gets classified separately by stop.ts existing path).
1929
+ //
1930
+ // task_kind gate (fix 2026-06-17): only real user tasks get an outcome.
1931
+ // Without this filter the idle sweep stamped outcome on agent-callback
1932
+ // (`<task-notification>`), image, and system-envelope rows, e.g. a callback
1933
+ // notification was wrongly labelled `success`/`answered` (128/133 rows
1934
+ // polluted). Mirrors getActiveUserTask's predicate exactly so the idle sweep
1935
+ // and the active-task path agree on what "a user task" is. The
1936
+ // `task_kind IS NULL AND is_noise = 0` arm preserves legacy rows written
1937
+ // before the task_kind backfill (spec b1480935).
1938
+ queryIdleUnclassifiedTasks(sessionId) {
1939
+ const rows = this.db.prepare(`
1940
+ SELECT t.*, s.project_path
1941
+ FROM tasks t
1942
+ LEFT JOIN sessions s ON s.session_id = t.session_id
1943
+ WHERE t.session_id = ?
1944
+ AND t.outcome IS NULL
1945
+ AND t.status IN ('completed', 'abandoned')
1946
+ AND (t.task_kind = 'user' OR (t.task_kind IS NULL AND t.is_noise = 0))
1947
+ ORDER BY t.start_time ASC
1948
+ `).all(sessionId);
1949
+ return rows.map(r => this.mapRow(r));
1950
+ }
277
1951
  }
278
1952
  //# sourceMappingURL=tasks.js.map