@specship/specship 0.11.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 (879) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/LICENSE +21 -0
  3. package/README.md +583 -0
  4. package/agents/specship-explorer.md +29 -0
  5. package/commands/ss-behaviour.md +116 -0
  6. package/commands/ss-check.md +43 -0
  7. package/commands/ss-design-implement.md +84 -0
  8. package/commands/ss-design-loop.md +125 -0
  9. package/commands/ss-explore.md +43 -0
  10. package/commands/ss-spec.md +118 -0
  11. package/dist/activation/starter-prompt.d.ts +57 -0
  12. package/dist/activation/starter-prompt.d.ts.map +1 -0
  13. package/dist/activation/starter-prompt.js +164 -0
  14. package/dist/activation/starter-prompt.js.map +1 -0
  15. package/dist/analytics/specship-impact.d.ts +72 -0
  16. package/dist/analytics/specship-impact.d.ts.map +1 -0
  17. package/dist/analytics/specship-impact.js +216 -0
  18. package/dist/analytics/specship-impact.js.map +1 -0
  19. package/dist/behaviour/behaviour-surface.d.ts +59 -0
  20. package/dist/behaviour/behaviour-surface.d.ts.map +1 -0
  21. package/dist/behaviour/behaviour-surface.js +112 -0
  22. package/dist/behaviour/behaviour-surface.js.map +1 -0
  23. package/dist/bin/node-version-check.d.ts +37 -0
  24. package/dist/bin/node-version-check.d.ts.map +1 -0
  25. package/dist/bin/node-version-check.js +79 -0
  26. package/dist/bin/node-version-check.js.map +1 -0
  27. package/dist/bin/specship.d.ts +25 -0
  28. package/dist/bin/specship.d.ts.map +1 -0
  29. package/dist/bin/specship.js +2823 -0
  30. package/dist/bin/specship.js.map +1 -0
  31. package/dist/bin/uninstall.d.ts +13 -0
  32. package/dist/bin/uninstall.d.ts.map +1 -0
  33. package/dist/bin/uninstall.js +35 -0
  34. package/dist/bin/uninstall.js.map +1 -0
  35. package/dist/context/formatter.d.ts +30 -0
  36. package/dist/context/formatter.d.ts.map +1 -0
  37. package/dist/context/formatter.js +263 -0
  38. package/dist/context/formatter.js.map +1 -0
  39. package/dist/context/index.d.ts +119 -0
  40. package/dist/context/index.d.ts.map +1 -0
  41. package/dist/context/index.js +1289 -0
  42. package/dist/context/index.js.map +1 -0
  43. package/dist/context/markers.d.ts +19 -0
  44. package/dist/context/markers.d.ts.map +1 -0
  45. package/dist/context/markers.js +22 -0
  46. package/dist/context/markers.js.map +1 -0
  47. package/dist/db/index.d.ts +103 -0
  48. package/dist/db/index.d.ts.map +1 -0
  49. package/dist/db/index.js +279 -0
  50. package/dist/db/index.js.map +1 -0
  51. package/dist/db/migrations.d.ts +44 -0
  52. package/dist/db/migrations.d.ts.map +1 -0
  53. package/dist/db/migrations.js +503 -0
  54. package/dist/db/migrations.js.map +1 -0
  55. package/dist/db/queries.d.ts +357 -0
  56. package/dist/db/queries.d.ts.map +1 -0
  57. package/dist/db/queries.js +1504 -0
  58. package/dist/db/queries.js.map +1 -0
  59. package/dist/db/schema.sql +451 -0
  60. package/dist/db/spec-queries.d.ts +130 -0
  61. package/dist/db/spec-queries.d.ts.map +1 -0
  62. package/dist/db/spec-queries.js +738 -0
  63. package/dist/db/spec-queries.js.map +1 -0
  64. package/dist/db/sqlite-adapter.d.ts +65 -0
  65. package/dist/db/sqlite-adapter.d.ts.map +1 -0
  66. package/dist/db/sqlite-adapter.js +214 -0
  67. package/dist/db/sqlite-adapter.js.map +1 -0
  68. package/dist/designer/artifact-store.js +54 -0
  69. package/dist/designer/browser.js +141 -0
  70. package/dist/designer/cdp-ensure.js +60 -0
  71. package/dist/designer/cdp-env.js +18 -0
  72. package/dist/designer/cdp-trace.js +599 -0
  73. package/dist/designer/cross-platform.js +74 -0
  74. package/dist/designer/designer-controller.js +1413 -0
  75. package/dist/designer/file-panel.js +39 -0
  76. package/dist/designer/interstitials.js +97 -0
  77. package/dist/designer/oopif-reader.js +176 -0
  78. package/dist/designer/package-meta.js +18 -0
  79. package/dist/designer/preview-host.js +50 -0
  80. package/dist/designer/repo-root.js +31 -0
  81. package/dist/designer/run-state.js +353 -0
  82. package/dist/designer/session-store.js +59 -0
  83. package/dist/designer/ui-anchors.js +651 -0
  84. package/dist/directory.d.ts +67 -0
  85. package/dist/directory.d.ts.map +1 -0
  86. package/dist/directory.js +267 -0
  87. package/dist/directory.js.map +1 -0
  88. package/dist/enforce/enforce.d.ts +70 -0
  89. package/dist/enforce/enforce.d.ts.map +1 -0
  90. package/dist/enforce/enforce.js +125 -0
  91. package/dist/enforce/enforce.js.map +1 -0
  92. package/dist/errors.d.ts +136 -0
  93. package/dist/errors.d.ts.map +1 -0
  94. package/dist/errors.js +219 -0
  95. package/dist/errors.js.map +1 -0
  96. package/dist/extraction/dfm-extractor.d.ts +31 -0
  97. package/dist/extraction/dfm-extractor.d.ts.map +1 -0
  98. package/dist/extraction/dfm-extractor.js +151 -0
  99. package/dist/extraction/dfm-extractor.js.map +1 -0
  100. package/dist/extraction/generated-detection.d.ts +30 -0
  101. package/dist/extraction/generated-detection.d.ts.map +1 -0
  102. package/dist/extraction/generated-detection.js +80 -0
  103. package/dist/extraction/generated-detection.js.map +1 -0
  104. package/dist/extraction/grammars.d.ts +100 -0
  105. package/dist/extraction/grammars.d.ts.map +1 -0
  106. package/dist/extraction/grammars.js +426 -0
  107. package/dist/extraction/grammars.js.map +1 -0
  108. package/dist/extraction/index.d.ts +138 -0
  109. package/dist/extraction/index.d.ts.map +1 -0
  110. package/dist/extraction/index.js +1394 -0
  111. package/dist/extraction/index.js.map +1 -0
  112. package/dist/extraction/languages/c-cpp.d.ts +4 -0
  113. package/dist/extraction/languages/c-cpp.d.ts.map +1 -0
  114. package/dist/extraction/languages/c-cpp.js +171 -0
  115. package/dist/extraction/languages/c-cpp.js.map +1 -0
  116. package/dist/extraction/languages/csharp.d.ts +3 -0
  117. package/dist/extraction/languages/csharp.d.ts.map +1 -0
  118. package/dist/extraction/languages/csharp.js +73 -0
  119. package/dist/extraction/languages/csharp.js.map +1 -0
  120. package/dist/extraction/languages/dart.d.ts +3 -0
  121. package/dist/extraction/languages/dart.d.ts.map +1 -0
  122. package/dist/extraction/languages/dart.js +192 -0
  123. package/dist/extraction/languages/dart.js.map +1 -0
  124. package/dist/extraction/languages/go.d.ts +3 -0
  125. package/dist/extraction/languages/go.d.ts.map +1 -0
  126. package/dist/extraction/languages/go.js +74 -0
  127. package/dist/extraction/languages/go.js.map +1 -0
  128. package/dist/extraction/languages/index.d.ts +10 -0
  129. package/dist/extraction/languages/index.d.ts.map +1 -0
  130. package/dist/extraction/languages/index.js +51 -0
  131. package/dist/extraction/languages/index.js.map +1 -0
  132. package/dist/extraction/languages/java.d.ts +3 -0
  133. package/dist/extraction/languages/java.d.ts.map +1 -0
  134. package/dist/extraction/languages/java.js +70 -0
  135. package/dist/extraction/languages/java.js.map +1 -0
  136. package/dist/extraction/languages/javascript.d.ts +3 -0
  137. package/dist/extraction/languages/javascript.d.ts.map +1 -0
  138. package/dist/extraction/languages/javascript.js +90 -0
  139. package/dist/extraction/languages/javascript.js.map +1 -0
  140. package/dist/extraction/languages/kotlin.d.ts +3 -0
  141. package/dist/extraction/languages/kotlin.d.ts.map +1 -0
  142. package/dist/extraction/languages/kotlin.js +259 -0
  143. package/dist/extraction/languages/kotlin.js.map +1 -0
  144. package/dist/extraction/languages/lua.d.ts +3 -0
  145. package/dist/extraction/languages/lua.d.ts.map +1 -0
  146. package/dist/extraction/languages/lua.js +150 -0
  147. package/dist/extraction/languages/lua.js.map +1 -0
  148. package/dist/extraction/languages/luau.d.ts +3 -0
  149. package/dist/extraction/languages/luau.d.ts.map +1 -0
  150. package/dist/extraction/languages/luau.js +37 -0
  151. package/dist/extraction/languages/luau.js.map +1 -0
  152. package/dist/extraction/languages/objc.d.ts +3 -0
  153. package/dist/extraction/languages/objc.d.ts.map +1 -0
  154. package/dist/extraction/languages/objc.js +133 -0
  155. package/dist/extraction/languages/objc.js.map +1 -0
  156. package/dist/extraction/languages/pascal.d.ts +3 -0
  157. package/dist/extraction/languages/pascal.d.ts.map +1 -0
  158. package/dist/extraction/languages/pascal.js +66 -0
  159. package/dist/extraction/languages/pascal.js.map +1 -0
  160. package/dist/extraction/languages/php.d.ts +3 -0
  161. package/dist/extraction/languages/php.d.ts.map +1 -0
  162. package/dist/extraction/languages/php.js +107 -0
  163. package/dist/extraction/languages/php.js.map +1 -0
  164. package/dist/extraction/languages/python.d.ts +3 -0
  165. package/dist/extraction/languages/python.d.ts.map +1 -0
  166. package/dist/extraction/languages/python.js +56 -0
  167. package/dist/extraction/languages/python.js.map +1 -0
  168. package/dist/extraction/languages/ruby.d.ts +3 -0
  169. package/dist/extraction/languages/ruby.d.ts.map +1 -0
  170. package/dist/extraction/languages/ruby.js +114 -0
  171. package/dist/extraction/languages/ruby.js.map +1 -0
  172. package/dist/extraction/languages/rust.d.ts +3 -0
  173. package/dist/extraction/languages/rust.d.ts.map +1 -0
  174. package/dist/extraction/languages/rust.js +109 -0
  175. package/dist/extraction/languages/rust.js.map +1 -0
  176. package/dist/extraction/languages/scala.d.ts +3 -0
  177. package/dist/extraction/languages/scala.d.ts.map +1 -0
  178. package/dist/extraction/languages/scala.js +139 -0
  179. package/dist/extraction/languages/scala.js.map +1 -0
  180. package/dist/extraction/languages/swift.d.ts +3 -0
  181. package/dist/extraction/languages/swift.d.ts.map +1 -0
  182. package/dist/extraction/languages/swift.js +91 -0
  183. package/dist/extraction/languages/swift.js.map +1 -0
  184. package/dist/extraction/languages/typescript.d.ts +3 -0
  185. package/dist/extraction/languages/typescript.d.ts.map +1 -0
  186. package/dist/extraction/languages/typescript.js +129 -0
  187. package/dist/extraction/languages/typescript.js.map +1 -0
  188. package/dist/extraction/liquid-extractor.d.ts +52 -0
  189. package/dist/extraction/liquid-extractor.d.ts.map +1 -0
  190. package/dist/extraction/liquid-extractor.js +313 -0
  191. package/dist/extraction/liquid-extractor.js.map +1 -0
  192. package/dist/extraction/mybatis-extractor.d.ts +48 -0
  193. package/dist/extraction/mybatis-extractor.d.ts.map +1 -0
  194. package/dist/extraction/mybatis-extractor.js +198 -0
  195. package/dist/extraction/mybatis-extractor.js.map +1 -0
  196. package/dist/extraction/parse-worker.d.ts +8 -0
  197. package/dist/extraction/parse-worker.d.ts.map +1 -0
  198. package/dist/extraction/parse-worker.js +94 -0
  199. package/dist/extraction/parse-worker.js.map +1 -0
  200. package/dist/extraction/specs/markdown-spec-extractor.d.ts +114 -0
  201. package/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -0
  202. package/dist/extraction/specs/markdown-spec-extractor.js +699 -0
  203. package/dist/extraction/specs/markdown-spec-extractor.js.map +1 -0
  204. package/dist/extraction/specs/types.d.ts +39 -0
  205. package/dist/extraction/specs/types.d.ts.map +1 -0
  206. package/dist/extraction/specs/types.js +8 -0
  207. package/dist/extraction/specs/types.js.map +1 -0
  208. package/dist/extraction/svelte-extractor.d.ts +56 -0
  209. package/dist/extraction/svelte-extractor.d.ts.map +1 -0
  210. package/dist/extraction/svelte-extractor.js +272 -0
  211. package/dist/extraction/svelte-extractor.js.map +1 -0
  212. package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
  213. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -0
  214. package/dist/extraction/tree-sitter-helpers.js +103 -0
  215. package/dist/extraction/tree-sitter-helpers.js.map +1 -0
  216. package/dist/extraction/tree-sitter-types.d.ts +193 -0
  217. package/dist/extraction/tree-sitter-types.d.ts.map +1 -0
  218. package/dist/extraction/tree-sitter-types.js +10 -0
  219. package/dist/extraction/tree-sitter-types.js.map +1 -0
  220. package/dist/extraction/tree-sitter.d.ts +317 -0
  221. package/dist/extraction/tree-sitter.d.ts.map +1 -0
  222. package/dist/extraction/tree-sitter.js +3092 -0
  223. package/dist/extraction/tree-sitter.js.map +1 -0
  224. package/dist/extraction/vue-extractor.d.ts +51 -0
  225. package/dist/extraction/vue-extractor.d.ts.map +1 -0
  226. package/dist/extraction/vue-extractor.js +251 -0
  227. package/dist/extraction/vue-extractor.js.map +1 -0
  228. package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
  229. package/dist/extraction/wasm/tree-sitter-luau.wasm +0 -0
  230. package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  231. package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  232. package/dist/extraction/wasm-runtime-flags.d.ts +38 -0
  233. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -0
  234. package/dist/extraction/wasm-runtime-flags.js +106 -0
  235. package/dist/extraction/wasm-runtime-flags.js.map +1 -0
  236. package/dist/fitness/fitness.d.ts +75 -0
  237. package/dist/fitness/fitness.d.ts.map +1 -0
  238. package/dist/fitness/fitness.js +204 -0
  239. package/dist/fitness/fitness.js.map +1 -0
  240. package/dist/graph/index.d.ts +8 -0
  241. package/dist/graph/index.d.ts.map +1 -0
  242. package/dist/graph/index.js +13 -0
  243. package/dist/graph/index.js.map +1 -0
  244. package/dist/graph/maintainability.d.ts +115 -0
  245. package/dist/graph/maintainability.d.ts.map +1 -0
  246. package/dist/graph/maintainability.js +299 -0
  247. package/dist/graph/maintainability.js.map +1 -0
  248. package/dist/graph/queries.d.ts +106 -0
  249. package/dist/graph/queries.d.ts.map +1 -0
  250. package/dist/graph/queries.js +366 -0
  251. package/dist/graph/queries.js.map +1 -0
  252. package/dist/graph/traversal.d.ts +127 -0
  253. package/dist/graph/traversal.d.ts.map +1 -0
  254. package/dist/graph/traversal.js +531 -0
  255. package/dist/graph/traversal.js.map +1 -0
  256. package/dist/health/smoke-check.d.ts +85 -0
  257. package/dist/health/smoke-check.d.ts.map +1 -0
  258. package/dist/health/smoke-check.js +246 -0
  259. package/dist/health/smoke-check.js.map +1 -0
  260. package/dist/index.d.ts +674 -0
  261. package/dist/index.d.ts.map +1 -0
  262. package/dist/index.js +1473 -0
  263. package/dist/index.js.map +1 -0
  264. package/dist/installer/config-writer.d.ts +28 -0
  265. package/dist/installer/config-writer.d.ts.map +1 -0
  266. package/dist/installer/config-writer.js +91 -0
  267. package/dist/installer/config-writer.js.map +1 -0
  268. package/dist/installer/index.d.ts +100 -0
  269. package/dist/installer/index.d.ts.map +1 -0
  270. package/dist/installer/index.js +442 -0
  271. package/dist/installer/index.js.map +1 -0
  272. package/dist/installer/init-offer.d.ts +31 -0
  273. package/dist/installer/init-offer.d.ts.map +1 -0
  274. package/dist/installer/init-offer.js +30 -0
  275. package/dist/installer/init-offer.js.map +1 -0
  276. package/dist/installer/instructions-template.d.ts +36 -0
  277. package/dist/installer/instructions-template.d.ts.map +1 -0
  278. package/dist/installer/instructions-template.js +57 -0
  279. package/dist/installer/instructions-template.js.map +1 -0
  280. package/dist/installer/targets/claude.d.ts +146 -0
  281. package/dist/installer/targets/claude.d.ts.map +1 -0
  282. package/dist/installer/targets/claude.js +864 -0
  283. package/dist/installer/targets/claude.js.map +1 -0
  284. package/dist/installer/targets/registry.d.ts +19 -0
  285. package/dist/installer/targets/registry.d.ts.map +1 -0
  286. package/dist/installer/targets/registry.js +31 -0
  287. package/dist/installer/targets/registry.js.map +1 -0
  288. package/dist/installer/targets/shared.d.ts +76 -0
  289. package/dist/installer/targets/shared.d.ts.map +1 -0
  290. package/dist/installer/targets/shared.js +260 -0
  291. package/dist/installer/targets/shared.js.map +1 -0
  292. package/dist/installer/targets/types.d.ts +95 -0
  293. package/dist/installer/targets/types.d.ts.map +1 -0
  294. package/dist/installer/targets/types.js +12 -0
  295. package/dist/installer/targets/types.js.map +1 -0
  296. package/dist/isolation/worktree.d.ts +65 -0
  297. package/dist/isolation/worktree.d.ts.map +1 -0
  298. package/dist/isolation/worktree.js +231 -0
  299. package/dist/isolation/worktree.js.map +1 -0
  300. package/dist/mcp/daemon-paths.d.ts +46 -0
  301. package/dist/mcp/daemon-paths.d.ts.map +1 -0
  302. package/dist/mcp/daemon-paths.js +125 -0
  303. package/dist/mcp/daemon-paths.js.map +1 -0
  304. package/dist/mcp/daemon.d.ts +161 -0
  305. package/dist/mcp/daemon.d.ts.map +1 -0
  306. package/dist/mcp/daemon.js +403 -0
  307. package/dist/mcp/daemon.js.map +1 -0
  308. package/dist/mcp/designer-tools.d.ts +33 -0
  309. package/dist/mcp/designer-tools.d.ts.map +1 -0
  310. package/dist/mcp/designer-tools.js +313 -0
  311. package/dist/mcp/designer-tools.js.map +1 -0
  312. package/dist/mcp/engine.d.ts +105 -0
  313. package/dist/mcp/engine.d.ts.map +1 -0
  314. package/dist/mcp/engine.js +270 -0
  315. package/dist/mcp/engine.js.map +1 -0
  316. package/dist/mcp/fitness-tool.d.ts +12 -0
  317. package/dist/mcp/fitness-tool.d.ts.map +1 -0
  318. package/dist/mcp/fitness-tool.js +46 -0
  319. package/dist/mcp/fitness-tool.js.map +1 -0
  320. package/dist/mcp/index.d.ts +112 -0
  321. package/dist/mcp/index.d.ts.map +1 -0
  322. package/dist/mcp/index.js +477 -0
  323. package/dist/mcp/index.js.map +1 -0
  324. package/dist/mcp/maintainability-tool.d.ts +13 -0
  325. package/dist/mcp/maintainability-tool.d.ts.map +1 -0
  326. package/dist/mcp/maintainability-tool.js +64 -0
  327. package/dist/mcp/maintainability-tool.js.map +1 -0
  328. package/dist/mcp/proxy.d.ts +81 -0
  329. package/dist/mcp/proxy.d.ts.map +1 -0
  330. package/dist/mcp/proxy.js +510 -0
  331. package/dist/mcp/proxy.js.map +1 -0
  332. package/dist/mcp/server-instructions.d.ts +18 -0
  333. package/dist/mcp/server-instructions.d.ts.map +1 -0
  334. package/dist/mcp/server-instructions.js +79 -0
  335. package/dist/mcp/server-instructions.js.map +1 -0
  336. package/dist/mcp/session.d.ts +77 -0
  337. package/dist/mcp/session.d.ts.map +1 -0
  338. package/dist/mcp/session.js +294 -0
  339. package/dist/mcp/session.js.map +1 -0
  340. package/dist/mcp/spec-tools.d.ts +39 -0
  341. package/dist/mcp/spec-tools.d.ts.map +1 -0
  342. package/dist/mcp/spec-tools.js +534 -0
  343. package/dist/mcp/spec-tools.js.map +1 -0
  344. package/dist/mcp/tools.d.ts +417 -0
  345. package/dist/mcp/tools.d.ts.map +1 -0
  346. package/dist/mcp/tools.js +3179 -0
  347. package/dist/mcp/tools.js.map +1 -0
  348. package/dist/mcp/transport.d.ts +188 -0
  349. package/dist/mcp/transport.d.ts.map +1 -0
  350. package/dist/mcp/transport.js +343 -0
  351. package/dist/mcp/transport.js.map +1 -0
  352. package/dist/mcp/version.d.ts +19 -0
  353. package/dist/mcp/version.d.ts.map +1 -0
  354. package/dist/mcp/version.js +71 -0
  355. package/dist/mcp/version.js.map +1 -0
  356. package/dist/reflect/apply.d.ts +31 -0
  357. package/dist/reflect/apply.d.ts.map +1 -0
  358. package/dist/reflect/apply.js +286 -0
  359. package/dist/reflect/apply.js.map +1 -0
  360. package/dist/reflect/hash.d.ts +20 -0
  361. package/dist/reflect/hash.d.ts.map +1 -0
  362. package/dist/reflect/hash.js +36 -0
  363. package/dist/reflect/hash.js.map +1 -0
  364. package/dist/reflect/index.d.ts +16 -0
  365. package/dist/reflect/index.d.ts.map +1 -0
  366. package/dist/reflect/index.js +43 -0
  367. package/dist/reflect/index.js.map +1 -0
  368. package/dist/reflect/miner.d.ts +21 -0
  369. package/dist/reflect/miner.d.ts.map +1 -0
  370. package/dist/reflect/miner.js +463 -0
  371. package/dist/reflect/miner.js.map +1 -0
  372. package/dist/reflect/store.d.ts +31 -0
  373. package/dist/reflect/store.d.ts.map +1 -0
  374. package/dist/reflect/store.js +101 -0
  375. package/dist/reflect/store.js.map +1 -0
  376. package/dist/reflect/sweep.d.ts +26 -0
  377. package/dist/reflect/sweep.d.ts.map +1 -0
  378. package/dist/reflect/sweep.js +42 -0
  379. package/dist/reflect/sweep.js.map +1 -0
  380. package/dist/reflect/targets.d.ts +52 -0
  381. package/dist/reflect/targets.d.ts.map +1 -0
  382. package/dist/reflect/targets.js +192 -0
  383. package/dist/reflect/targets.js.map +1 -0
  384. package/dist/reflect/types.d.ts +96 -0
  385. package/dist/reflect/types.d.ts.map +1 -0
  386. package/dist/reflect/types.js +13 -0
  387. package/dist/reflect/types.js.map +1 -0
  388. package/dist/resolution/brief-link-resolver.d.ts +114 -0
  389. package/dist/resolution/brief-link-resolver.d.ts.map +1 -0
  390. package/dist/resolution/brief-link-resolver.js +261 -0
  391. package/dist/resolution/brief-link-resolver.js.map +1 -0
  392. package/dist/resolution/callback-synthesizer.d.ts +10 -0
  393. package/dist/resolution/callback-synthesizer.d.ts.map +1 -0
  394. package/dist/resolution/callback-synthesizer.js +1300 -0
  395. package/dist/resolution/callback-synthesizer.js.map +1 -0
  396. package/dist/resolution/domain-gap-seed.d.ts +60 -0
  397. package/dist/resolution/domain-gap-seed.d.ts.map +1 -0
  398. package/dist/resolution/domain-gap-seed.js +87 -0
  399. package/dist/resolution/domain-gap-seed.js.map +1 -0
  400. package/dist/resolution/frameworks/cargo-workspace.d.ts +18 -0
  401. package/dist/resolution/frameworks/cargo-workspace.d.ts.map +1 -0
  402. package/dist/resolution/frameworks/cargo-workspace.js +225 -0
  403. package/dist/resolution/frameworks/cargo-workspace.js.map +1 -0
  404. package/dist/resolution/frameworks/csharp.d.ts +8 -0
  405. package/dist/resolution/frameworks/csharp.d.ts.map +1 -0
  406. package/dist/resolution/frameworks/csharp.js +241 -0
  407. package/dist/resolution/frameworks/csharp.js.map +1 -0
  408. package/dist/resolution/frameworks/drupal.d.ts +51 -0
  409. package/dist/resolution/frameworks/drupal.d.ts.map +1 -0
  410. package/dist/resolution/frameworks/drupal.js +367 -0
  411. package/dist/resolution/frameworks/drupal.js.map +1 -0
  412. package/dist/resolution/frameworks/expo-modules.d.ts +3 -0
  413. package/dist/resolution/frameworks/expo-modules.d.ts.map +1 -0
  414. package/dist/resolution/frameworks/expo-modules.js +143 -0
  415. package/dist/resolution/frameworks/expo-modules.js.map +1 -0
  416. package/dist/resolution/frameworks/express.d.ts +8 -0
  417. package/dist/resolution/frameworks/express.d.ts.map +1 -0
  418. package/dist/resolution/frameworks/express.js +308 -0
  419. package/dist/resolution/frameworks/express.js.map +1 -0
  420. package/dist/resolution/frameworks/fabric.d.ts +3 -0
  421. package/dist/resolution/frameworks/fabric.d.ts.map +1 -0
  422. package/dist/resolution/frameworks/fabric.js +354 -0
  423. package/dist/resolution/frameworks/fabric.js.map +1 -0
  424. package/dist/resolution/frameworks/go.d.ts +8 -0
  425. package/dist/resolution/frameworks/go.d.ts.map +1 -0
  426. package/dist/resolution/frameworks/go.js +161 -0
  427. package/dist/resolution/frameworks/go.js.map +1 -0
  428. package/dist/resolution/frameworks/index.d.ts +48 -0
  429. package/dist/resolution/frameworks/index.d.ts.map +1 -0
  430. package/dist/resolution/frameworks/index.js +161 -0
  431. package/dist/resolution/frameworks/index.js.map +1 -0
  432. package/dist/resolution/frameworks/java.d.ts +8 -0
  433. package/dist/resolution/frameworks/java.d.ts.map +1 -0
  434. package/dist/resolution/frameworks/java.js +504 -0
  435. package/dist/resolution/frameworks/java.js.map +1 -0
  436. package/dist/resolution/frameworks/laravel.d.ts +13 -0
  437. package/dist/resolution/frameworks/laravel.d.ts.map +1 -0
  438. package/dist/resolution/frameworks/laravel.js +257 -0
  439. package/dist/resolution/frameworks/laravel.js.map +1 -0
  440. package/dist/resolution/frameworks/nestjs.d.ts +26 -0
  441. package/dist/resolution/frameworks/nestjs.d.ts.map +1 -0
  442. package/dist/resolution/frameworks/nestjs.js +698 -0
  443. package/dist/resolution/frameworks/nestjs.js.map +1 -0
  444. package/dist/resolution/frameworks/play.d.ts +19 -0
  445. package/dist/resolution/frameworks/play.d.ts.map +1 -0
  446. package/dist/resolution/frameworks/play.js +111 -0
  447. package/dist/resolution/frameworks/play.js.map +1 -0
  448. package/dist/resolution/frameworks/python.d.ts +10 -0
  449. package/dist/resolution/frameworks/python.d.ts.map +1 -0
  450. package/dist/resolution/frameworks/python.js +396 -0
  451. package/dist/resolution/frameworks/python.js.map +1 -0
  452. package/dist/resolution/frameworks/react-native.d.ts +3 -0
  453. package/dist/resolution/frameworks/react-native.d.ts.map +1 -0
  454. package/dist/resolution/frameworks/react-native.js +360 -0
  455. package/dist/resolution/frameworks/react-native.js.map +1 -0
  456. package/dist/resolution/frameworks/react.d.ts +8 -0
  457. package/dist/resolution/frameworks/react.d.ts.map +1 -0
  458. package/dist/resolution/frameworks/react.js +365 -0
  459. package/dist/resolution/frameworks/react.js.map +1 -0
  460. package/dist/resolution/frameworks/ruby.d.ts +8 -0
  461. package/dist/resolution/frameworks/ruby.d.ts.map +1 -0
  462. package/dist/resolution/frameworks/ruby.js +302 -0
  463. package/dist/resolution/frameworks/ruby.js.map +1 -0
  464. package/dist/resolution/frameworks/rust.d.ts +8 -0
  465. package/dist/resolution/frameworks/rust.d.ts.map +1 -0
  466. package/dist/resolution/frameworks/rust.js +304 -0
  467. package/dist/resolution/frameworks/rust.js.map +1 -0
  468. package/dist/resolution/frameworks/svelte.d.ts +9 -0
  469. package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
  470. package/dist/resolution/frameworks/svelte.js +249 -0
  471. package/dist/resolution/frameworks/svelte.js.map +1 -0
  472. package/dist/resolution/frameworks/swift-objc.d.ts +37 -0
  473. package/dist/resolution/frameworks/swift-objc.d.ts.map +1 -0
  474. package/dist/resolution/frameworks/swift-objc.js +252 -0
  475. package/dist/resolution/frameworks/swift-objc.js.map +1 -0
  476. package/dist/resolution/frameworks/swift.d.ts +10 -0
  477. package/dist/resolution/frameworks/swift.d.ts.map +1 -0
  478. package/dist/resolution/frameworks/swift.js +400 -0
  479. package/dist/resolution/frameworks/swift.js.map +1 -0
  480. package/dist/resolution/frameworks/vue.d.ts +9 -0
  481. package/dist/resolution/frameworks/vue.d.ts.map +1 -0
  482. package/dist/resolution/frameworks/vue.js +306 -0
  483. package/dist/resolution/frameworks/vue.js.map +1 -0
  484. package/dist/resolution/go-module.d.ts +26 -0
  485. package/dist/resolution/go-module.d.ts.map +1 -0
  486. package/dist/resolution/go-module.js +78 -0
  487. package/dist/resolution/go-module.js.map +1 -0
  488. package/dist/resolution/import-resolver.d.ts +68 -0
  489. package/dist/resolution/import-resolver.d.ts.map +1 -0
  490. package/dist/resolution/import-resolver.js +1275 -0
  491. package/dist/resolution/import-resolver.js.map +1 -0
  492. package/dist/resolution/index.d.ts +117 -0
  493. package/dist/resolution/index.d.ts.map +1 -0
  494. package/dist/resolution/index.js +895 -0
  495. package/dist/resolution/index.js.map +1 -0
  496. package/dist/resolution/lru-cache.d.ts +24 -0
  497. package/dist/resolution/lru-cache.d.ts.map +1 -0
  498. package/dist/resolution/lru-cache.js +62 -0
  499. package/dist/resolution/lru-cache.js.map +1 -0
  500. package/dist/resolution/name-matcher.d.ts +32 -0
  501. package/dist/resolution/name-matcher.d.ts.map +1 -0
  502. package/dist/resolution/name-matcher.js +596 -0
  503. package/dist/resolution/name-matcher.js.map +1 -0
  504. package/dist/resolution/path-aliases.d.ts +68 -0
  505. package/dist/resolution/path-aliases.d.ts.map +1 -0
  506. package/dist/resolution/path-aliases.js +238 -0
  507. package/dist/resolution/path-aliases.js.map +1 -0
  508. package/dist/resolution/spec-link-resolver.d.ts +148 -0
  509. package/dist/resolution/spec-link-resolver.d.ts.map +1 -0
  510. package/dist/resolution/spec-link-resolver.js +337 -0
  511. package/dist/resolution/spec-link-resolver.js.map +1 -0
  512. package/dist/resolution/strip-comments.d.ts +27 -0
  513. package/dist/resolution/strip-comments.d.ts.map +1 -0
  514. package/dist/resolution/strip-comments.js +441 -0
  515. package/dist/resolution/strip-comments.js.map +1 -0
  516. package/dist/resolution/swift-objc-bridge.d.ts +134 -0
  517. package/dist/resolution/swift-objc-bridge.d.ts.map +1 -0
  518. package/dist/resolution/swift-objc-bridge.js +256 -0
  519. package/dist/resolution/swift-objc-bridge.js.map +1 -0
  520. package/dist/resolution/types.d.ts +216 -0
  521. package/dist/resolution/types.d.ts.map +1 -0
  522. package/dist/resolution/types.js +8 -0
  523. package/dist/resolution/types.js.map +1 -0
  524. package/dist/resolution/workspace-packages.d.ts +48 -0
  525. package/dist/resolution/workspace-packages.d.ts.map +1 -0
  526. package/dist/resolution/workspace-packages.js +208 -0
  527. package/dist/resolution/workspace-packages.js.map +1 -0
  528. package/dist/search/query-parser.d.ts +57 -0
  529. package/dist/search/query-parser.d.ts.map +1 -0
  530. package/dist/search/query-parser.js +177 -0
  531. package/dist/search/query-parser.js.map +1 -0
  532. package/dist/search/query-utils.d.ts +71 -0
  533. package/dist/search/query-utils.d.ts.map +1 -0
  534. package/dist/search/query-utils.js +380 -0
  535. package/dist/search/query-utils.js.map +1 -0
  536. package/dist/server/cli.js +152 -0
  537. package/dist/server/index.js +12 -0
  538. package/dist/server/ingest/impact-backfill.js +69 -0
  539. package/dist/server/ingest/impact-query.js +343 -0
  540. package/dist/server/ingest/index.js +19 -0
  541. package/dist/server/ingest/ingestor.js +541 -0
  542. package/dist/server/ingest/parser.js +104 -0
  543. package/dist/server/ingest/pricing.js +78 -0
  544. package/dist/server/ingest/specship-classify.js +153 -0
  545. package/dist/server/ingest/types.js +9 -0
  546. package/dist/server/ingest/watcher.js +77 -0
  547. package/dist/server/package.json +3 -0
  548. package/dist/server/project-registry.js +101 -0
  549. package/dist/server/routes/claude.js +907 -0
  550. package/dist/server/routes/domain.js +0 -0
  551. package/dist/server/routes/events.js +134 -0
  552. package/dist/server/routes/graph.js +248 -0
  553. package/dist/server/routes/maintainability.js +18 -0
  554. package/dist/server/routes/memory.js +272 -0
  555. package/dist/server/routes/projects.js +197 -0
  556. package/dist/server/routes/reflect.js +93 -0
  557. package/dist/server/routes/spec.js +373 -0
  558. package/dist/server/routes/status.js +112 -0
  559. package/dist/server/routes/workflow.js +253 -0
  560. package/dist/server/server.js +238 -0
  561. package/dist/server/static-handler.js +87 -0
  562. package/dist/statusline/active-run.d.ts +16 -0
  563. package/dist/statusline/active-run.d.ts.map +1 -0
  564. package/dist/statusline/active-run.js +73 -0
  565. package/dist/statusline/active-run.js.map +1 -0
  566. package/dist/statusline/cache.d.ts +21 -0
  567. package/dist/statusline/cache.d.ts.map +1 -0
  568. package/dist/statusline/cache.js +34 -0
  569. package/dist/statusline/cache.js.map +1 -0
  570. package/dist/statusline/index.d.ts +24 -0
  571. package/dist/statusline/index.d.ts.map +1 -0
  572. package/dist/statusline/index.js +128 -0
  573. package/dist/statusline/index.js.map +1 -0
  574. package/dist/statusline/paths.d.ts +27 -0
  575. package/dist/statusline/paths.d.ts.map +1 -0
  576. package/dist/statusline/paths.js +101 -0
  577. package/dist/statusline/paths.js.map +1 -0
  578. package/dist/statusline/render.d.ts +25 -0
  579. package/dist/statusline/render.d.ts.map +1 -0
  580. package/dist/statusline/render.js +72 -0
  581. package/dist/statusline/render.js.map +1 -0
  582. package/dist/statusline/session-marker.d.ts +30 -0
  583. package/dist/statusline/session-marker.d.ts.map +1 -0
  584. package/dist/statusline/session-marker.js +71 -0
  585. package/dist/statusline/session-marker.js.map +1 -0
  586. package/dist/statusline/types.d.ts +67 -0
  587. package/dist/statusline/types.d.ts.map +1 -0
  588. package/dist/statusline/types.js +16 -0
  589. package/dist/statusline/types.js.map +1 -0
  590. package/dist/sync/git-hooks.d.ts +45 -0
  591. package/dist/sync/git-hooks.d.ts.map +1 -0
  592. package/dist/sync/git-hooks.js +225 -0
  593. package/dist/sync/git-hooks.js.map +1 -0
  594. package/dist/sync/index.d.ts +19 -0
  595. package/dist/sync/index.d.ts.map +1 -0
  596. package/dist/sync/index.js +35 -0
  597. package/dist/sync/index.js.map +1 -0
  598. package/dist/sync/watch-policy.d.ts +48 -0
  599. package/dist/sync/watch-policy.d.ts.map +1 -0
  600. package/dist/sync/watch-policy.js +124 -0
  601. package/dist/sync/watch-policy.js.map +1 -0
  602. package/dist/sync/watcher.d.ts +283 -0
  603. package/dist/sync/watcher.d.ts.map +1 -0
  604. package/dist/sync/watcher.js +606 -0
  605. package/dist/sync/watcher.js.map +1 -0
  606. package/dist/sync/worktree.d.ts +54 -0
  607. package/dist/sync/worktree.d.ts.map +1 -0
  608. package/dist/sync/worktree.js +137 -0
  609. package/dist/sync/worktree.js.map +1 -0
  610. package/dist/types.d.ts +625 -0
  611. package/dist/types.d.ts.map +1 -0
  612. package/dist/types.js +118 -0
  613. package/dist/types.js.map +1 -0
  614. package/dist/ui/glyphs.d.ts +42 -0
  615. package/dist/ui/glyphs.d.ts.map +1 -0
  616. package/dist/ui/glyphs.js +78 -0
  617. package/dist/ui/glyphs.js.map +1 -0
  618. package/dist/ui/shimmer-progress.d.ts +11 -0
  619. package/dist/ui/shimmer-progress.d.ts.map +1 -0
  620. package/dist/ui/shimmer-progress.js +90 -0
  621. package/dist/ui/shimmer-progress.js.map +1 -0
  622. package/dist/ui/shimmer-worker.d.ts +2 -0
  623. package/dist/ui/shimmer-worker.d.ts.map +1 -0
  624. package/dist/ui/shimmer-worker.js +118 -0
  625. package/dist/ui/shimmer-worker.js.map +1 -0
  626. package/dist/ui/types.d.ts +17 -0
  627. package/dist/ui/types.d.ts.map +1 -0
  628. package/dist/ui/types.js +3 -0
  629. package/dist/ui/types.js.map +1 -0
  630. package/dist/utils.d.ts +205 -0
  631. package/dist/utils.d.ts.map +1 -0
  632. package/dist/utils.js +549 -0
  633. package/dist/utils.js.map +1 -0
  634. package/dist/web/chunk-2AJCHB7P.js +1 -0
  635. package/dist/web/chunk-2CPLUFCH.js +2 -0
  636. package/dist/web/chunk-2I7L37NS.js +1 -0
  637. package/dist/web/chunk-2NAWAJB5.js +1 -0
  638. package/dist/web/chunk-2OJBIPE4.js +1 -0
  639. package/dist/web/chunk-372AYXK6.js +17 -0
  640. package/dist/web/chunk-3E2WB6D5.js +1 -0
  641. package/dist/web/chunk-3EBFYSCH.js +2 -0
  642. package/dist/web/chunk-3QCQ4BXS.js +1 -0
  643. package/dist/web/chunk-42XVAQ6I.js +1 -0
  644. package/dist/web/chunk-4IMMPEYM.js +1 -0
  645. package/dist/web/chunk-4JYHAP7B.js +1 -0
  646. package/dist/web/chunk-4TJQJPCZ.js +1 -0
  647. package/dist/web/chunk-4WZIHTPC.js +1 -0
  648. package/dist/web/chunk-4YVSYOSD.js +1 -0
  649. package/dist/web/chunk-52PO6IMB.js +2 -0
  650. package/dist/web/chunk-54D6RFSW.js +1 -0
  651. package/dist/web/chunk-5BQIOYKW.js +1 -0
  652. package/dist/web/chunk-5HGWHUJA.js +1 -0
  653. package/dist/web/chunk-5XRUOPZE.js +1 -0
  654. package/dist/web/chunk-5Y244R4G.js +1 -0
  655. package/dist/web/chunk-6O7Z3P2M.js +1 -0
  656. package/dist/web/chunk-6QXULGLG.js +1 -0
  657. package/dist/web/chunk-6RRDPT5Z.js +1 -0
  658. package/dist/web/chunk-6VKB2ZWM.js +1 -0
  659. package/dist/web/chunk-7DMFVTU4.js +1 -0
  660. package/dist/web/chunk-7P5CVBJZ.js +1 -0
  661. package/dist/web/chunk-7SMPKVEP.js +1 -0
  662. package/dist/web/chunk-AHLX543M.js +1 -0
  663. package/dist/web/chunk-AMGJBO7D.js +3 -0
  664. package/dist/web/chunk-AZJVTPLU.js +1 -0
  665. package/dist/web/chunk-B3CWIVBW.js +1 -0
  666. package/dist/web/chunk-BLBRMCN2.js +1 -0
  667. package/dist/web/chunk-BMIAXD2V.js +2 -0
  668. package/dist/web/chunk-BPCJLNBS.js +47 -0
  669. package/dist/web/chunk-BRHEUDLY.js +6 -0
  670. package/dist/web/chunk-BUXWEHIY.js +1 -0
  671. package/dist/web/chunk-CD5IZM7Y.js +1 -0
  672. package/dist/web/chunk-DLQPZWSI.css +1 -0
  673. package/dist/web/chunk-DSGNOCKQ.js +1 -0
  674. package/dist/web/chunk-DT5LJYFX.js +1 -0
  675. package/dist/web/chunk-DYRFLPJA.js +1 -0
  676. package/dist/web/chunk-E3J3CXR5.js +1 -0
  677. package/dist/web/chunk-E73OX2P7.js +1 -0
  678. package/dist/web/chunk-EAXRKDLV.js +1 -0
  679. package/dist/web/chunk-EBKKDHYI.js +1 -0
  680. package/dist/web/chunk-EE7V7Q5P.js +1 -0
  681. package/dist/web/chunk-EKY2FUHU.js +1 -0
  682. package/dist/web/chunk-EP6XOPXH.js +1 -0
  683. package/dist/web/chunk-ESGDLJOJ.js +1 -0
  684. package/dist/web/chunk-ETJG7NCY.js +1 -0
  685. package/dist/web/chunk-EUUEFEDI.js +1 -0
  686. package/dist/web/chunk-EX4ZHR4F.js +1 -0
  687. package/dist/web/chunk-F5UNCSXP.js +1 -0
  688. package/dist/web/chunk-FFGJXUHI.js +1 -0
  689. package/dist/web/chunk-FGNZDHTL.js +11 -0
  690. package/dist/web/chunk-FIJW2UNJ.js +1 -0
  691. package/dist/web/chunk-FMV5PXRC.js +5 -0
  692. package/dist/web/chunk-G7VZT5KB.js +3 -0
  693. package/dist/web/chunk-GCOM4JPR.js +2 -0
  694. package/dist/web/chunk-GEIIDO6C.js +1 -0
  695. package/dist/web/chunk-GRZYXPSO.js +7 -0
  696. package/dist/web/chunk-GWBABPZ5.js +1 -0
  697. package/dist/web/chunk-GYGPS3AN.js +1 -0
  698. package/dist/web/chunk-H4GLRD3Q.js +1 -0
  699. package/dist/web/chunk-H5TWEFYX.js +1 -0
  700. package/dist/web/chunk-H7AF7YS4.js +1 -0
  701. package/dist/web/chunk-HCB2N2KH.js +1 -0
  702. package/dist/web/chunk-HDZDQILN.js +1 -0
  703. package/dist/web/chunk-HMK6UO6N.js +1 -0
  704. package/dist/web/chunk-HVVXPI4D.js +1 -0
  705. package/dist/web/chunk-IHEE5NYJ.js +1 -0
  706. package/dist/web/chunk-IPB746BT.js +1 -0
  707. package/dist/web/chunk-ISNEBICW.js +1 -0
  708. package/dist/web/chunk-J2GZVLHH.js +1 -0
  709. package/dist/web/chunk-JTFXTIPE.js +903 -0
  710. package/dist/web/chunk-KHU5M2AL.js +1 -0
  711. package/dist/web/chunk-KW3DHCFV.js +1 -0
  712. package/dist/web/chunk-LB6JPLX2.js +1 -0
  713. package/dist/web/chunk-LBXLFPVN.js +1 -0
  714. package/dist/web/chunk-LGNSHRCE.js +1 -0
  715. package/dist/web/chunk-LNSVDHCI.js +1 -0
  716. package/dist/web/chunk-LVGIY3SO.js +1 -0
  717. package/dist/web/chunk-LXLHIHEN.js +1 -0
  718. package/dist/web/chunk-MFHO2F2U.js +4 -0
  719. package/dist/web/chunk-N5OSSQFZ.js +1 -0
  720. package/dist/web/chunk-N6SS4G6S.js +1 -0
  721. package/dist/web/chunk-NAJYJNHS.js +1 -0
  722. package/dist/web/chunk-NHD66NOI.js +1 -0
  723. package/dist/web/chunk-NNLJ55MY.js +1 -0
  724. package/dist/web/chunk-NTBJG6SJ.js +1 -0
  725. package/dist/web/chunk-NUDB3Q2Y.js +3 -0
  726. package/dist/web/chunk-OM7JVWQQ.js +1 -0
  727. package/dist/web/chunk-OXEF5E3E.js +1 -0
  728. package/dist/web/chunk-PGGJPDJG.js +1 -0
  729. package/dist/web/chunk-PUYSJNJR.js +1 -0
  730. package/dist/web/chunk-Q2RVFS45.js +1 -0
  731. package/dist/web/chunk-Q7L6LLAK.js +1 -0
  732. package/dist/web/chunk-QCMKJIWY.js +1 -0
  733. package/dist/web/chunk-QEQRY4QQ.js +1 -0
  734. package/dist/web/chunk-QH6CF3M3.js +1 -0
  735. package/dist/web/chunk-QQ5LD7PI.js +1 -0
  736. package/dist/web/chunk-QR6L3KAC.js +1 -0
  737. package/dist/web/chunk-QXJS6F3U.js +1 -0
  738. package/dist/web/chunk-R2DLK4HO.js +1 -0
  739. package/dist/web/chunk-RD6TVPOT.js +1 -0
  740. package/dist/web/chunk-RKY4EJYJ.js +1 -0
  741. package/dist/web/chunk-RONYWVY7.js +1 -0
  742. package/dist/web/chunk-RSZZWGGC.js +1 -0
  743. package/dist/web/chunk-RXKXYF2C.js +1 -0
  744. package/dist/web/chunk-SCNDZRN2.js +1 -0
  745. package/dist/web/chunk-SH6UVHQC.js +1 -0
  746. package/dist/web/chunk-SWKJRNYY.js +1 -0
  747. package/dist/web/chunk-T7AZ65JP.js +1 -0
  748. package/dist/web/chunk-TCZDVOHD.js +1 -0
  749. package/dist/web/chunk-TF5TF6IP.js +1 -0
  750. package/dist/web/chunk-TPTITA3V.js +1 -0
  751. package/dist/web/chunk-TR335633.js +1 -0
  752. package/dist/web/chunk-UR5KDXPX.js +1 -0
  753. package/dist/web/chunk-UR6O2GEH.js +1 -0
  754. package/dist/web/chunk-UTNMGWTP.js +1 -0
  755. package/dist/web/chunk-VECWMHJP.js +1 -0
  756. package/dist/web/chunk-VUACT35R.js +3 -0
  757. package/dist/web/chunk-VZI7H4SZ.js +1 -0
  758. package/dist/web/chunk-W22AVG3N.js +1 -0
  759. package/dist/web/chunk-W6NGHRHX.js +1 -0
  760. package/dist/web/chunk-WB6YHOD4.js +1 -0
  761. package/dist/web/chunk-WBT64AWV.js +1 -0
  762. package/dist/web/chunk-WFXJIXZE.js +4 -0
  763. package/dist/web/chunk-WTGYRH3Z.js +298 -0
  764. package/dist/web/chunk-WXTCVDTP.js +1 -0
  765. package/dist/web/chunk-XCDHWLVH.js +1 -0
  766. package/dist/web/chunk-Y3H6FFUZ.js +1 -0
  767. package/dist/web/chunk-Y4F5ULGJ.js +1 -0
  768. package/dist/web/chunk-YEGKAAEE.js +1 -0
  769. package/dist/web/chunk-YM2KU57F.js +1 -0
  770. package/dist/web/chunk-YRERBP6T.js +1 -0
  771. package/dist/web/chunk-ZLV4VCDG.js +3 -0
  772. package/dist/web/chunk-ZQUJMA5K.js +4 -0
  773. package/dist/web/chunk-ZTVI5KFF.js +1 -0
  774. package/dist/web/favicon-16.png +0 -0
  775. package/dist/web/favicon-180.png +0 -0
  776. package/dist/web/favicon-32.png +0 -0
  777. package/dist/web/favicon-512.png +0 -0
  778. package/dist/web/favicon-small.svg +15 -0
  779. package/dist/web/favicon.ico +0 -0
  780. package/dist/web/favicon.svg +20 -0
  781. package/dist/web/icon-192.png +0 -0
  782. package/dist/web/icon-512.png +0 -0
  783. package/dist/web/index.html +146 -0
  784. package/dist/web/main-ESADRXN2.css +1 -0
  785. package/dist/web/main-SQFUMVQA.js +1 -0
  786. package/dist/web/manifest.webmanifest +15 -0
  787. package/dist/web/media/codicon-LN6W7LCM.ttf +0 -0
  788. package/dist/web/styles-KSOPUVDA.css +1 -0
  789. package/dist/web/sw.js +69 -0
  790. package/dist/workflows/condition-evaluator.d.ts +75 -0
  791. package/dist/workflows/condition-evaluator.d.ts.map +1 -0
  792. package/dist/workflows/condition-evaluator.js +282 -0
  793. package/dist/workflows/condition-evaluator.js.map +1 -0
  794. package/dist/workflows/defaults/claude-design-implement.yaml +336 -0
  795. package/dist/workflows/defaults/index.d.ts +26 -0
  796. package/dist/workflows/defaults/index.d.ts.map +1 -0
  797. package/dist/workflows/defaults/index.js +94 -0
  798. package/dist/workflows/defaults/index.js.map +1 -0
  799. package/dist/workflows/defaults/spec-author.yaml +214 -0
  800. package/dist/workflows/defaults/spec-fix.yaml +110 -0
  801. package/dist/workflows/defaults/spec-implement.yaml +150 -0
  802. package/dist/workflows/defaults/spec-relink.yaml +81 -0
  803. package/dist/workflows/defaults/spec-verify.yaml +51 -0
  804. package/dist/workflows/discovery.d.ts +46 -0
  805. package/dist/workflows/discovery.d.ts.map +1 -0
  806. package/dist/workflows/discovery.js +193 -0
  807. package/dist/workflows/discovery.js.map +1 -0
  808. package/dist/workflows/executor.d.ts +98 -0
  809. package/dist/workflows/executor.d.ts.map +1 -0
  810. package/dist/workflows/executor.js +664 -0
  811. package/dist/workflows/executor.js.map +1 -0
  812. package/dist/workflows/runners/approval.d.ts +18 -0
  813. package/dist/workflows/runners/approval.d.ts.map +1 -0
  814. package/dist/workflows/runners/approval.js +34 -0
  815. package/dist/workflows/runners/approval.js.map +1 -0
  816. package/dist/workflows/runners/bash.d.ts +13 -0
  817. package/dist/workflows/runners/bash.d.ts.map +1 -0
  818. package/dist/workflows/runners/bash.js +143 -0
  819. package/dist/workflows/runners/bash.js.map +1 -0
  820. package/dist/workflows/runners/cancel.d.ts +10 -0
  821. package/dist/workflows/runners/cancel.d.ts.map +1 -0
  822. package/dist/workflows/runners/cancel.js +19 -0
  823. package/dist/workflows/runners/cancel.js.map +1 -0
  824. package/dist/workflows/runners/prompt.d.ts +51 -0
  825. package/dist/workflows/runners/prompt.d.ts.map +1 -0
  826. package/dist/workflows/runners/prompt.js +306 -0
  827. package/dist/workflows/runners/prompt.js.map +1 -0
  828. package/dist/workflows/runners/script.d.ts +17 -0
  829. package/dist/workflows/runners/script.d.ts.map +1 -0
  830. package/dist/workflows/runners/script.js +155 -0
  831. package/dist/workflows/runners/script.js.map +1 -0
  832. package/dist/workflows/runners/types.d.ts +57 -0
  833. package/dist/workflows/runners/types.d.ts.map +1 -0
  834. package/dist/workflows/runners/types.js +13 -0
  835. package/dist/workflows/runners/types.js.map +1 -0
  836. package/dist/workflows/schemas/workflow.d.ts +166 -0
  837. package/dist/workflows/schemas/workflow.d.ts.map +1 -0
  838. package/dist/workflows/schemas/workflow.js +437 -0
  839. package/dist/workflows/schemas/workflow.js.map +1 -0
  840. package/hooks/hooks.json +38 -0
  841. package/package.json +78 -0
  842. package/scripts/add-lang/bench.sh +60 -0
  843. package/scripts/add-lang/check-grammar.mjs +75 -0
  844. package/scripts/add-lang/dump-ast.mjs +103 -0
  845. package/scripts/add-lang/verify-extraction.mjs +70 -0
  846. package/scripts/agent-eval/arms-F.sh +21 -0
  847. package/scripts/agent-eval/arms-matrix.sh +37 -0
  848. package/scripts/agent-eval/audit.sh +68 -0
  849. package/scripts/agent-eval/bench-readme.sh +28 -0
  850. package/scripts/agent-eval/bench-why-repo.sh +22 -0
  851. package/scripts/agent-eval/block-read-hook.sh +19 -0
  852. package/scripts/agent-eval/hook-settings.json +15 -0
  853. package/scripts/agent-eval/itrun.sh +120 -0
  854. package/scripts/agent-eval/parse-arms.mjs +116 -0
  855. package/scripts/agent-eval/parse-bench-readme.mjs +84 -0
  856. package/scripts/agent-eval/parse-run.mjs +45 -0
  857. package/scripts/agent-eval/parse-session.mjs +93 -0
  858. package/scripts/agent-eval/probe-context.mjs +21 -0
  859. package/scripts/agent-eval/probe-explore.mjs +40 -0
  860. package/scripts/agent-eval/probe-node.mjs +20 -0
  861. package/scripts/agent-eval/probe-sweep.mjs +119 -0
  862. package/scripts/agent-eval/probe-trace.mjs +20 -0
  863. package/scripts/agent-eval/run-agent.sh +34 -0
  864. package/scripts/agent-eval/run-all.sh +67 -0
  865. package/scripts/agent-eval/run-arms.sh +56 -0
  866. package/scripts/agent-eval/seq-matrix.mjs +137 -0
  867. package/scripts/build-bundle.sh +118 -0
  868. package/scripts/build-server-bundle.mjs +80 -0
  869. package/scripts/build-web-bundle.mjs +66 -0
  870. package/scripts/extract-release-notes.mjs +130 -0
  871. package/scripts/local-install.sh +41 -0
  872. package/scripts/npm-sdk.js +75 -0
  873. package/scripts/npm-shim.js +246 -0
  874. package/scripts/offline-install.ps1 +148 -0
  875. package/scripts/offline-install.sh +149 -0
  876. package/scripts/pack-npm.sh +119 -0
  877. package/scripts/prepare-release.mjs +270 -0
  878. package/scripts/sync-shim-version.mjs +64 -0
  879. package/selectors.json +41 -0
@@ -0,0 +1,907 @@
1
+ /**
2
+ * Claude Code analytics routes.
3
+ *
4
+ * All queries hit specship's SQLite directly. The ingest worker
5
+ * (`@selvakumaresra/specship-ingest`) writes to claude_* tables; this layer
6
+ * just rolls up.
7
+ *
8
+ * Endpoints:
9
+ * GET /api/claude/projects — every indexed Claude project
10
+ * GET /api/claude/sessions?project=&limit= — sessions list
11
+ * GET /api/claude/session/:id — session detail (prompts + tools)
12
+ * GET /api/claude/heatmap?range= — file/tool/subagent heatmaps
13
+ * GET /api/claude/costs?range= — cost rollup, timeseries, per-model
14
+ * GET /api/claude/compare — per-project cost comparison
15
+ * GET /api/claude/tips — rule-based tips engine output
16
+ * GET /api/claude/specship-impact?range=&project= — SpecShip token-impact aggregation
17
+ * POST /api/claude/ingest — force a one-shot ingest pass
18
+ */
19
+ import { decodeProjectSlug } from '../ingest/index.js';
20
+ import { computeSpecshipImpact } from '../ingest/impact-query.js';
21
+ /**
22
+ * Normalize a `?project=` filter value to the form stored in
23
+ * claude_sessions.project_path. The Sessions page (and any other UI
24
+ * surface that uses ProjectsService.projectQuery()) sends the directory
25
+ * SLUG that names the Claude Code transcript dir (e.g.
26
+ * `-Users-foo-projects-bar`), but the ingestor writes the DECODED path
27
+ * (e.g. `/Users/foo/projects/bar`) into project_path because that's the
28
+ * value the agent actually identifies the project by. Comparing slug
29
+ * against path always fails and the list comes back empty even though
30
+ * the rows are there — fix by decoding the slug form here.
31
+ *
32
+ * A path passed in directly (doesn't start with `-`) round-trips
33
+ * unchanged, so curl-by-path and the old behavior both keep working.
34
+ */
35
+ function normalizeProjectFilter(input) {
36
+ return decodeProjectSlug(input);
37
+ }
38
+ const RANGE_WINDOW_MS = {
39
+ today: 24 * 60 * 60 * 1000,
40
+ week: 7 * 24 * 60 * 60 * 1000,
41
+ month: 30 * 24 * 60 * 60 * 1000,
42
+ all: Number.MAX_SAFE_INTEGER,
43
+ };
44
+ function rangeKey(input) {
45
+ if (input === 'today' || input === 'week' || input === 'month' || input === 'all')
46
+ return input;
47
+ return 'week';
48
+ }
49
+ function rangeStart(key) {
50
+ if (key === 'all')
51
+ return 0;
52
+ return Date.now() - RANGE_WINDOW_MS[key];
53
+ }
54
+ /**
55
+ * Bounds of the period immediately BEFORE the current range window — used for
56
+ * week-over-week (wowDelta) comparisons. For 'all' there's no prior period, so
57
+ * both bounds collapse to 0 and callers should treat the delta as 0.
58
+ */
59
+ function priorWindow(key) {
60
+ if (key === 'all')
61
+ return { start: 0, end: 0 };
62
+ const w = RANGE_WINDOW_MS[key];
63
+ const end = Date.now() - w;
64
+ return { start: end - w, end };
65
+ }
66
+ /**
67
+ * Get the internal SQLite handle off the DatabaseConnection so we can run
68
+ * Claude-specific aggregate queries directly. SpecShip exposes this via
69
+ * `getDb()`-style accessors. Falls back to digging via the queries property.
70
+ */
71
+ function getDb(cg) {
72
+ // SpecShip exposes the underlying DB via its DatabaseConnection. Look it
73
+ // up via the private field as a fallback — works because it's the same
74
+ // shape regardless of which adapter (better-sqlite3 / node:sqlite) is active.
75
+ const anyCg = cg;
76
+ if (anyCg.db?.getDb)
77
+ return anyCg.db.getDb();
78
+ if (anyCg.queries?.db)
79
+ return anyCg.queries.db;
80
+ throw new Error('specship DB handle not accessible from server context');
81
+ }
82
+ export async function registerClaudeRoutes(app) {
83
+ /**
84
+ * Analytics routes share one SQLite — the boot-time "primary" project's
85
+ * specship.db hosts the cross-project claude_* tables. Without a primary
86
+ * the JSONL ingest has nowhere to write and there's nothing to query, so
87
+ * every analytics handler asks here first.
88
+ */
89
+ function requirePrimary(reply) {
90
+ if (!app.primaryCg) {
91
+ reply.code(409).send({ error: 'analytics unavailable: no primary project configured', code: 'no_primary' });
92
+ return null;
93
+ }
94
+ return app.primaryCg;
95
+ }
96
+ app.get('/api/claude/projects', async (_req, reply) => {
97
+ const cg = requirePrimary(reply);
98
+ if (!cg)
99
+ return;
100
+ const db = getDb(cg);
101
+ const rows = db.prepare(`
102
+ SELECT p.path, p.name, p.first_seen, p.last_seen,
103
+ COUNT(s.id) as sessions,
104
+ COALESCE(SUM(s.total_cost_usd), 0) as cost,
105
+ COALESCE(SUM(s.total_cache_read_tokens), 0) as cacheRead,
106
+ COALESCE(SUM(s.total_cache_creation_tokens + s.total_input_tokens), 0) as totalInput,
107
+ COALESCE(SUM(s.prompt_count), 0) as prompts
108
+ FROM claude_projects p
109
+ LEFT JOIN claude_sessions s ON s.project_path = p.path
110
+ GROUP BY p.path
111
+ ORDER BY cost DESC
112
+ `).all();
113
+ return { projects: rows };
114
+ });
115
+ app.get('/api/claude/sessions', async (req, reply) => {
116
+ const cg = requirePrimary(reply);
117
+ if (!cg)
118
+ return;
119
+ const db = getDb(cg);
120
+ const limit = Math.min(parseInt(req.query.limit ?? '100', 10) || 100, 500);
121
+ const since = rangeStart(rangeKey(req.query.range));
122
+ const params = [since];
123
+ let whereProject = '';
124
+ if (req.query.project) {
125
+ whereProject = ' AND project_path = ?';
126
+ params.push(normalizeProjectFilter(req.query.project));
127
+ }
128
+ params.push(limit);
129
+ const sessions = db.prepare(`
130
+ SELECT * FROM claude_sessions
131
+ WHERE started_at >= ?${whereProject}
132
+ ORDER BY started_at DESC
133
+ LIMIT ?
134
+ `).all(...params);
135
+ return { sessions };
136
+ });
137
+ app.get('/api/claude/session/:id', async (req, reply) => {
138
+ const cg = requirePrimary(reply);
139
+ if (!cg)
140
+ return;
141
+ const db = getDb(cg);
142
+ const session = db.prepare('SELECT * FROM claude_sessions WHERE id = ?').get(req.params.id);
143
+ if (!session)
144
+ return reply.code(404).send({ error: 'session not found' });
145
+ const prompts = db.prepare(`
146
+ SELECT * FROM claude_prompts WHERE session_id = ? ORDER BY ts ASC
147
+ `).all(req.params.id);
148
+ const toolCalls = db.prepare(`
149
+ SELECT * FROM claude_tool_calls WHERE session_id = ? ORDER BY ts ASC
150
+ `).all(req.params.id);
151
+ // Per-prompt wall-clock duration: the gap from this prompt to the next one
152
+ // (last prompt runs to the session's end). Lets the UI show "how long did
153
+ // this turn take" without a separate per-event timeline.
154
+ const endedAt = session.ended_at ?? null;
155
+ prompts.forEach((p, i) => {
156
+ const next = prompts[i + 1];
157
+ const end = next ? next.ts : (endedAt ?? p.ts);
158
+ p.durationMs = Math.max(0, end - p.ts);
159
+ });
160
+ return { session, prompts, toolCalls };
161
+ });
162
+ /**
163
+ * SSE event stream for a single session — pushes new prompts and tool
164
+ * calls as the JSONL ingest watcher lands them, so the dashboard's
165
+ * Session Detail page can update without polling. Mirrors the shape used
166
+ * by /api/workflows/runs/:id/events.
167
+ *
168
+ * Polling cadence inside the loop is 500 ms — fast enough that the
169
+ * end-to-end "user hit Enter in Claude Code → prompt visible in
170
+ * dashboard" latency stays well under one second (300 ms watcher
171
+ * debounce + ≤50 ms ingest + ≤500 ms poll). Heartbeat every 15 s
172
+ * keeps idle connections alive past any proxy or browser tab-throttle
173
+ * cutoff.
174
+ *
175
+ * The client (LiveSessionTail in session-detail.ts) doesn't merge events
176
+ * incrementally — it just calls resource.refetch() on every event since
177
+ * the detail endpoint is local + cheap. Server-side, that means we only
178
+ * need to push enough info for the client to know "something changed"
179
+ * (id + ts), not the full row payloads. We send the full row anyway so
180
+ * a future client could merge incrementally without an API change.
181
+ */
182
+ app.get('/api/claude/session/:id/events', async (req, reply) => {
183
+ const cg = requirePrimary(reply);
184
+ if (!cg)
185
+ return;
186
+ const db = getDb(cg);
187
+ const sessionId = req.params.id;
188
+ // Confirm the session exists before opening the stream — saves a
189
+ // doomed connection from polling forever against a typo.
190
+ const sessionRow = db.prepare('SELECT id FROM claude_sessions WHERE id = ?').get(sessionId);
191
+ if (!sessionRow) {
192
+ return reply.code(404).send({ error: 'session not found' });
193
+ }
194
+ // Resume points — clients can pick up after a disconnect without
195
+ // re-receiving every prompt. Defaults to "now" so an opening client
196
+ // only sees future events.
197
+ let lastPromptTs = parseInt(req.query.sincePromptTs ?? '0', 10);
198
+ let lastToolTs = parseInt(req.query.sinceToolTs ?? '0', 10);
199
+ if (!lastPromptTs || Number.isNaN(lastPromptTs)) {
200
+ const row = db.prepare('SELECT MAX(ts) as m FROM claude_prompts WHERE session_id = ?').get(sessionId);
201
+ lastPromptTs = row?.m ?? 0;
202
+ }
203
+ if (!lastToolTs || Number.isNaN(lastToolTs)) {
204
+ const row = db.prepare('SELECT MAX(ts) as m FROM claude_tool_calls WHERE session_id = ?').get(sessionId);
205
+ lastToolTs = row?.m ?? 0;
206
+ }
207
+ reply.raw.setHeader('Content-Type', 'text/event-stream');
208
+ reply.raw.setHeader('Cache-Control', 'no-cache');
209
+ reply.raw.setHeader('Connection', 'keep-alive');
210
+ reply.raw.setHeader('X-Accel-Buffering', 'no'); // nginx-friendly
211
+ reply.raw.flushHeaders();
212
+ // Initial snapshot — gives the client a clean baseline (so it knows
213
+ // SSE is wired up even before any new event fires) and the cursor
214
+ // positions it should resume from on reconnect.
215
+ reply.raw.write(`event: snapshot\ndata: ${JSON.stringify({ sessionId, lastPromptTs, lastToolTs })}\n\n`);
216
+ let closed = false;
217
+ req.raw.on('close', () => { closed = true; });
218
+ const newPromptsStmt = db.prepare('SELECT * FROM claude_prompts WHERE session_id = ? AND ts > ? ORDER BY ts ASC');
219
+ const newToolsStmt = db.prepare('SELECT * FROM claude_tool_calls WHERE session_id = ? AND ts > ? ORDER BY ts ASC');
220
+ let lastHeartbeat = Date.now();
221
+ const tick = () => {
222
+ if (closed)
223
+ return;
224
+ try {
225
+ const freshPrompts = newPromptsStmt.all(sessionId, lastPromptTs);
226
+ for (const p of freshPrompts) {
227
+ reply.raw.write(`event: prompt_added\ndata: ${JSON.stringify(p)}\n\n`);
228
+ if (p.ts > lastPromptTs)
229
+ lastPromptTs = p.ts;
230
+ }
231
+ const freshTools = newToolsStmt.all(sessionId, lastToolTs);
232
+ for (const t of freshTools) {
233
+ reply.raw.write(`event: tool_call_added\ndata: ${JSON.stringify(t)}\n\n`);
234
+ if (t.ts > lastToolTs)
235
+ lastToolTs = t.ts;
236
+ }
237
+ // Heartbeat every 15 s — keeps proxies / browser tab throttles
238
+ // from killing an otherwise-idle connection.
239
+ const now = Date.now();
240
+ if (now - lastHeartbeat >= 15_000) {
241
+ reply.raw.write(`: keepalive ${now}\n\n`);
242
+ lastHeartbeat = now;
243
+ }
244
+ }
245
+ catch (err) {
246
+ // Surface DB errors to the client as a named event and end the
247
+ // stream — the client's onError handler flips to polling.
248
+ reply.raw.write(`event: stream_error\ndata: ${JSON.stringify({ message: err instanceof Error ? err.message : String(err) })}\n\n`);
249
+ reply.raw.end();
250
+ closed = true;
251
+ return;
252
+ }
253
+ setTimeout(tick, 500);
254
+ };
255
+ void tick();
256
+ });
257
+ /**
258
+ * Session-level aggregation for the Session Detail "Session summary" panel.
259
+ * Three things callers can't cheaply derive client-side from the per-prompt
260
+ * list: tool usage rolled up by name, files touched across all turns, and
261
+ * slash-command / skill invocation counts (which require either a regex
262
+ * over every prompt's text or json_extract over every Skill tool input).
263
+ * Computed once per session-detail page load (and after refresh) — cheap
264
+ * enough to recompute on every call, no caching.
265
+ */
266
+ app.get('/api/claude/session/:id/summary', async (req, reply) => {
267
+ const cg = requirePrimary(reply);
268
+ if (!cg)
269
+ return;
270
+ const db = getDb(cg);
271
+ const sessionId = req.params.id;
272
+ const session = db.prepare('SELECT id, started_at, ended_at FROM claude_sessions WHERE id = ?').get(sessionId);
273
+ if (!session)
274
+ return reply.code(404).send({ error: 'session not found' });
275
+ // Tools used in this session, rolled up by name. Mirrors the heatmap
276
+ // tool query but scoped to one session, so the panel can answer "what
277
+ // did Claude DO in this session" at a glance.
278
+ const byTool = db.prepare(`
279
+ SELECT tool_name as name, COUNT(*) as calls, COALESCE(SUM(result_length), 0) as totalBytes
280
+ FROM claude_tool_calls
281
+ WHERE session_id = ?
282
+ GROUP BY tool_name
283
+ ORDER BY calls DESC
284
+ `).all(sessionId);
285
+ // Models used. Most sessions stay on one model but sidechains can fan
286
+ // out to Haiku, so this surfaces multi-model spend that the session-level
287
+ // last_model column hides.
288
+ const byModel = db.prepare(`
289
+ SELECT model, COUNT(*) as prompts, COALESCE(SUM(cost_usd), 0) as cost
290
+ FROM claude_prompts
291
+ WHERE session_id = ? AND model IS NOT NULL
292
+ GROUP BY model
293
+ ORDER BY cost DESC
294
+ `).all(sessionId);
295
+ // Files touched via Read/Edit/Write/NotebookEdit. input_summary IS the
296
+ // file path for those tools (set in the ingestor's parser). Group by
297
+ // path, count ops, also record which tool last touched it so the UI
298
+ // can show a "last op" hint (Read vs Edit).
299
+ const filesTouched = db.prepare(`
300
+ SELECT input_summary as path, COUNT(*) as ops,
301
+ (SELECT tool_name FROM claude_tool_calls AS inner_tc
302
+ WHERE inner_tc.session_id = tc.session_id
303
+ AND inner_tc.input_summary = tc.input_summary
304
+ ORDER BY inner_tc.ts DESC LIMIT 1) as lastOp
305
+ FROM claude_tool_calls AS tc
306
+ WHERE tc.session_id = ?
307
+ AND tc.tool_name IN ('Read','Edit','Write','NotebookEdit','MultiEdit')
308
+ AND tc.input_summary != ''
309
+ GROUP BY tc.input_summary
310
+ ORDER BY ops DESC
311
+ LIMIT 30
312
+ `).all(sessionId);
313
+ // Skills invoked via the Skill tool. The ingestor stashes the
314
+ // JSON-serialized input under input_summary, so json_extract pulls the
315
+ // skill name straight out without needing the v7 input_json column to
316
+ // be backfilled.
317
+ const skills = db.prepare(`
318
+ SELECT
319
+ COALESCE(json_extract(input_summary, '$.skill'), json_extract(input_summary, '$.skill_name'), 'unknown') as name,
320
+ COUNT(*) as count
321
+ FROM claude_tool_calls
322
+ WHERE session_id = ? AND tool_name = 'Skill'
323
+ GROUP BY name
324
+ ORDER BY count DESC
325
+ `).all(sessionId);
326
+ // Slash commands invoked. Claude Code wraps each one in a
327
+ // <command-name>/foo</command-name> tag in the user-prompt text.
328
+ // Pull every prompt that has that tag and regex it client-side here
329
+ // (SQLite has no built-in regex). Cheap — most sessions have <50.
330
+ const taggedPrompts = db.prepare(`
331
+ SELECT text FROM claude_prompts WHERE session_id = ? AND text LIKE '%<command-name>%'
332
+ `).all(sessionId);
333
+ const cmdCounts = new Map();
334
+ const cmdRe = /<command-name>([^<]+)<\/command-name>/g;
335
+ for (const { text } of taggedPrompts) {
336
+ if (!text)
337
+ continue;
338
+ for (const m of text.matchAll(cmdRe)) {
339
+ const raw = m[1]?.trim();
340
+ if (!raw)
341
+ continue;
342
+ cmdCounts.set(raw, (cmdCounts.get(raw) ?? 0) + 1);
343
+ }
344
+ }
345
+ const slashCommands = Array.from(cmdCounts.entries())
346
+ .map(([name, count]) => ({ name, count }))
347
+ .sort((a, b) => b.count - a.count);
348
+ const durationMs = session.started_at && session.ended_at
349
+ ? Math.max(0, session.ended_at - session.started_at)
350
+ : 0;
351
+ // SpecShip token-impact rollup for this session.
352
+ const impact = computeSpecshipImpact(db, { since: 0, sessionId });
353
+ const specship = {
354
+ spendTokens: impact.spendTokens,
355
+ savedTokens: impact.savedTokens,
356
+ netTokens: impact.netTokens,
357
+ };
358
+ return {
359
+ sessionId,
360
+ byTool,
361
+ byModel,
362
+ slashCommands,
363
+ skills,
364
+ filesTouched,
365
+ durationMs,
366
+ specship,
367
+ };
368
+ });
369
+ app.get('/api/claude/heatmap', async (req, reply) => {
370
+ const cg = requirePrimary(reply);
371
+ if (!cg)
372
+ return;
373
+ const db = getDb(cg);
374
+ const since = rangeStart(rangeKey(req.query.range));
375
+ // Files heatmap — input_summary doubles as the file path for Read/Edit/Write.
376
+ const files = db.prepare(`
377
+ SELECT input_summary as path, COUNT(*) as calls, SUM(result_length) as resultBytes
378
+ FROM claude_tool_calls
379
+ WHERE ts >= ? AND tool_name IN ('Read','Edit','Write','NotebookEdit') AND input_summary != ''
380
+ GROUP BY input_summary
381
+ ORDER BY calls DESC
382
+ LIMIT 100
383
+ `).all(since);
384
+ // Per-file 7-day call trend — one sparkline (oldest→newest) per file cell.
385
+ // Always a fixed 7-day window, independent of `range`, so the trend reads
386
+ // consistently regardless of the heatmap's selected window.
387
+ const dayMs = 24 * 60 * 60 * 1000;
388
+ const trend7Start = Math.floor(Date.now() / dayMs) * dayMs - 6 * dayMs;
389
+ const trendRows = db.prepare(`
390
+ SELECT input_summary as path, CAST((ts - ?) / ? AS INTEGER) as bucket, COUNT(*) as calls
391
+ FROM claude_tool_calls
392
+ WHERE ts >= ? AND tool_name IN ('Read','Edit','Write','NotebookEdit') AND input_summary != ''
393
+ GROUP BY input_summary, bucket
394
+ `).all(trend7Start, dayMs, trend7Start);
395
+ const trendByPath = new Map();
396
+ for (const r of trendRows) {
397
+ if (r.bucket < 0 || r.bucket > 6)
398
+ continue;
399
+ const arr = trendByPath.get(r.path) ?? [0, 0, 0, 0, 0, 0, 0];
400
+ arr[r.bucket] = r.calls;
401
+ trendByPath.set(r.path, arr);
402
+ }
403
+ for (const f of files) {
404
+ f.trend = trendByPath.get(f.path) ?? [0, 0, 0, 0, 0, 0, 0];
405
+ }
406
+ // Tools heatmap.
407
+ const tools = db.prepare(`
408
+ SELECT tool_name as name, COUNT(*) as calls, SUM(result_length) as resultBytes
409
+ FROM claude_tool_calls
410
+ WHERE ts >= ?
411
+ GROUP BY tool_name
412
+ ORDER BY calls DESC
413
+ `).all(since);
414
+ // Subagent attribution (is_sidechain at the prompt level — main vs. sidechain rollup).
415
+ const subagents = db.prepare(`
416
+ SELECT
417
+ CASE WHEN p.is_sidechain = 1 THEN 'subagent' ELSE 'main' END as type,
418
+ COUNT(*) as prompts,
419
+ SUM(p.input_tokens + p.output_tokens + p.cache_creation_tokens + p.cache_read_tokens) as tokens,
420
+ SUM(p.cost_usd) as cost
421
+ FROM claude_prompts p
422
+ WHERE p.ts >= ?
423
+ GROUP BY type
424
+ `).all(since);
425
+ // Subagent breakdown by name — Task tool calls grouped by subagent_type.
426
+ // input_summary is the JSON-serialized tool input; json_extract pulls
427
+ // out subagent_type (defaults to 'general-purpose' when unset).
428
+ const subagentByName = db.prepare(`
429
+ SELECT
430
+ COALESCE(NULLIF(json_extract(input_summary, '$.subagent_type'), ''), 'general-purpose') as name,
431
+ COUNT(*) as calls,
432
+ MIN(ts) as firstSeen,
433
+ MAX(ts) as lastSeen
434
+ FROM claude_tool_calls
435
+ WHERE ts >= ? AND tool_name = 'Task'
436
+ GROUP BY name
437
+ ORDER BY calls DESC
438
+ `).all(since);
439
+ return { files, tools, subagents, subagentByName };
440
+ });
441
+ /**
442
+ * Drill-down: which sessions touched a given file (via Read/Edit/Write).
443
+ * Used by the heatmap page when the user clicks a file cell.
444
+ */
445
+ app.get('/api/claude/heatmap/file', async (req, reply) => {
446
+ const path = req.query.path;
447
+ if (!path)
448
+ return reply.code(400).send({ error: 'path required' });
449
+ const cg = requirePrimary(reply);
450
+ if (!cg)
451
+ return;
452
+ const db = getDb(cg);
453
+ const since = rangeStart(rangeKey(req.query.range));
454
+ const sessions = db.prepare(`
455
+ SELECT t.session_id, s.last_model, s.project_path,
456
+ COUNT(*) as calls, COALESCE(SUM(t.result_length), 0) as bytes,
457
+ MAX(t.ts) as lastTs, MIN(t.ts) as firstTs
458
+ FROM claude_tool_calls t
459
+ LEFT JOIN claude_sessions s ON s.id = t.session_id
460
+ WHERE t.input_summary = ? AND t.ts >= ? AND t.tool_name IN ('Read','Edit','Write','NotebookEdit')
461
+ GROUP BY t.session_id
462
+ ORDER BY calls DESC
463
+ LIMIT 50
464
+ `).all(path, since);
465
+ const byTool = db.prepare(`
466
+ SELECT tool_name as name, COUNT(*) as calls, COALESCE(SUM(result_length), 0) as bytes
467
+ FROM claude_tool_calls
468
+ WHERE input_summary = ? AND ts >= ? AND tool_name IN ('Read','Edit','Write','NotebookEdit')
469
+ GROUP BY tool_name
470
+ ORDER BY calls DESC
471
+ `).all(path, since);
472
+ return { path, sessions, byTool };
473
+ });
474
+ /**
475
+ * Drill-down: the top distinct inputs for a given tool (file paths for
476
+ * Read/Edit/Write, patterns for Grep/Glob, commands for Bash).
477
+ */
478
+ app.get('/api/claude/heatmap/tool', async (req, reply) => {
479
+ const name = req.query.name;
480
+ if (!name)
481
+ return reply.code(400).send({ error: 'name required' });
482
+ const cg = requirePrimary(reply);
483
+ if (!cg)
484
+ return;
485
+ const db = getDb(cg);
486
+ const since = rangeStart(rangeKey(req.query.range));
487
+ const totals = db.prepare(`
488
+ SELECT COUNT(*) as calls, COALESCE(SUM(result_length), 0) as bytes,
489
+ COUNT(DISTINCT session_id) as sessions
490
+ FROM claude_tool_calls
491
+ WHERE tool_name = ? AND ts >= ?
492
+ `).get(name, since);
493
+ const inputs = db.prepare(`
494
+ SELECT
495
+ CASE WHEN length(input_summary) > 120 THEN substr(input_summary, 1, 120) || '…'
496
+ ELSE input_summary END as input,
497
+ COUNT(*) as calls,
498
+ COALESCE(SUM(result_length), 0) as bytes,
499
+ MAX(ts) as lastTs
500
+ FROM claude_tool_calls
501
+ WHERE tool_name = ? AND ts >= ? AND input_summary != ''
502
+ GROUP BY input_summary
503
+ ORDER BY calls DESC
504
+ LIMIT 30
505
+ `).all(name, since);
506
+ const recentSessions = db.prepare(`
507
+ SELECT t.session_id, s.last_model, s.project_path, COUNT(*) as calls, MAX(t.ts) as lastTs
508
+ FROM claude_tool_calls t
509
+ LEFT JOIN claude_sessions s ON s.id = t.session_id
510
+ WHERE t.tool_name = ? AND t.ts >= ?
511
+ GROUP BY t.session_id
512
+ ORDER BY lastTs DESC
513
+ LIMIT 20
514
+ `).all(name, since);
515
+ return { tool: name, totals, inputs, recentSessions };
516
+ });
517
+ /**
518
+ * Drill-down: invocations of a specific subagent (by subagent_type name).
519
+ */
520
+ app.get('/api/claude/heatmap/subagent', async (req, reply) => {
521
+ const type = req.query.type;
522
+ if (!type)
523
+ return reply.code(400).send({ error: 'type required' });
524
+ const cg = requirePrimary(reply);
525
+ if (!cg)
526
+ return;
527
+ const db = getDb(cg);
528
+ const since = rangeStart(rangeKey(req.query.range));
529
+ const totals = db.prepare(`
530
+ SELECT COUNT(*) as calls, COUNT(DISTINCT session_id) as sessions
531
+ FROM claude_tool_calls
532
+ WHERE tool_name = 'Task' AND ts >= ?
533
+ AND COALESCE(NULLIF(json_extract(input_summary, '$.subagent_type'), ''), 'general-purpose') = ?
534
+ `).get(since, type);
535
+ const invocations = db.prepare(`
536
+ SELECT
537
+ t.session_id,
538
+ t.ts,
539
+ COALESCE(json_extract(t.input_summary, '$.description'), '') as description,
540
+ COALESCE(json_extract(t.input_summary, '$.prompt'), '') as prompt,
541
+ s.last_model
542
+ FROM claude_tool_calls t
543
+ LEFT JOIN claude_sessions s ON s.id = t.session_id
544
+ WHERE t.tool_name = 'Task' AND t.ts >= ?
545
+ AND COALESCE(NULLIF(json_extract(t.input_summary, '$.subagent_type'), ''), 'general-purpose') = ?
546
+ ORDER BY t.ts DESC
547
+ LIMIT 50
548
+ `).all(since, type);
549
+ return { subagent: type, totals, invocations };
550
+ });
551
+ app.get('/api/claude/cache', async (req, reply) => {
552
+ const cg = requirePrimary(reply);
553
+ if (!cg)
554
+ return;
555
+ const db = getDb(cg);
556
+ const since = rangeStart(rangeKey(req.query.range));
557
+ // Aggregate cache totals from claude_sessions for sessions in window.
558
+ const agg = db.prepare(`
559
+ SELECT
560
+ COALESCE(SUM(total_input_tokens), 0) as inp,
561
+ COALESCE(SUM(total_output_tokens), 0) as out,
562
+ COALESCE(SUM(total_cache_creation_tokens), 0) as cw,
563
+ COALESCE(SUM(total_cache_read_tokens), 0) as cr,
564
+ COALESCE(SUM(total_cost_usd), 0) as cost
565
+ FROM claude_sessions
566
+ WHERE started_at >= ?
567
+ `).get(since);
568
+ const total = (agg.inp ?? 0) + (agg.cw ?? 0) + (agg.cr ?? 0);
569
+ const readRate = total > 0 ? agg.cr / total : 0;
570
+ // Dollars saved estimate: cache_read tokens billed at ~10% of input;
571
+ // savings vs charging them at input rate ≈ 0.9 × (cr / 1M) × inputRate.
572
+ // Use Opus 4-7 input ($15/M) as a generous upper bound — the UI shows
573
+ // this as an approximation.
574
+ const dollarsSaved = ((agg.cr ?? 0) / 1_000_000) * 15 * 0.9;
575
+ // Week-over-week reuse delta: current read-rate minus the prior equal-length
576
+ // window's read-rate. Fractional (e.g. 0.06 → "+6%" in the UI). Zero for
577
+ // 'all' (no prior period) or when there's no prior-window data to compare.
578
+ const prior = priorWindow(rangeKey(req.query.range));
579
+ let wowDelta = 0;
580
+ if (prior.end > prior.start) {
581
+ const pAgg = db.prepare(`
582
+ SELECT
583
+ COALESCE(SUM(total_input_tokens), 0) as inp,
584
+ COALESCE(SUM(total_cache_creation_tokens), 0) as cw,
585
+ COALESCE(SUM(total_cache_read_tokens), 0) as cr
586
+ FROM claude_sessions
587
+ WHERE started_at >= ? AND started_at < ?
588
+ `).get(prior.start, prior.end);
589
+ const pTotal = (pAgg.inp ?? 0) + (pAgg.cw ?? 0) + (pAgg.cr ?? 0);
590
+ if (pTotal > 0)
591
+ wowDelta = readRate - pAgg.cr / pTotal;
592
+ }
593
+ return {
594
+ readRate,
595
+ creationTokens: agg.cw ?? 0,
596
+ readTokens: agg.cr ?? 0,
597
+ inputTokens: agg.inp ?? 0,
598
+ outputTokens: agg.out ?? 0,
599
+ totalCost: agg.cost ?? 0,
600
+ dollarsSaved,
601
+ wowDelta,
602
+ };
603
+ });
604
+ app.get('/api/claude/costs', async (req, reply) => {
605
+ const cg = requirePrimary(reply);
606
+ if (!cg)
607
+ return;
608
+ const db = getDb(cg);
609
+ const since = rangeStart(rangeKey(req.query.range));
610
+ const total = db.prepare(`SELECT SUM(total_cost_usd) as t FROM claude_sessions WHERE started_at >= ?`).get(since);
611
+ // Week-over-week spend delta: fractional change in total cost vs the prior
612
+ // equal-length window (e.g. -0.08 → "-8%"). Zero for 'all' or no prior data.
613
+ const prior = priorWindow(rangeKey(req.query.range));
614
+ let wowDelta = 0;
615
+ if (prior.end > prior.start) {
616
+ const pTotal = db.prepare(`SELECT SUM(total_cost_usd) as t FROM claude_sessions WHERE started_at >= ? AND started_at < ?`).get(prior.start, prior.end);
617
+ const pt = pTotal.t ?? 0;
618
+ if (pt > 0)
619
+ wowDelta = ((total.t ?? 0) - pt) / pt;
620
+ }
621
+ // Top prompts by cost.
622
+ const topPrompts = db.prepare(`
623
+ SELECT id, session_id, substr(text, 1, 200) as text, model, cost_usd,
624
+ input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens, ts
625
+ FROM claude_prompts
626
+ WHERE ts >= ? AND cost_usd > 0
627
+ ORDER BY cost_usd DESC
628
+ LIMIT 50
629
+ `).all(since);
630
+ // Daily timeseries: bucket by 24h windows.
631
+ const days = 30;
632
+ const dayMs = 24 * 60 * 60 * 1000;
633
+ const dayBoundary = Math.floor(Date.now() / dayMs) * dayMs - (days - 1) * dayMs;
634
+ const series = db.prepare(`
635
+ SELECT
636
+ CAST((ts - ?) / ? AS INTEGER) as bucket,
637
+ SUM(cost_usd) as cost,
638
+ COUNT(*) as prompts
639
+ FROM claude_prompts
640
+ WHERE ts >= ?
641
+ GROUP BY bucket
642
+ ORDER BY bucket ASC
643
+ `).all(dayBoundary, dayMs, dayBoundary);
644
+ // Densify with zeros for missing days.
645
+ const dense = [];
646
+ for (let i = 0; i < days; i++) {
647
+ const found = series.find((s) => s.bucket === i);
648
+ dense.push({ day: days - 1 - i, cost: found?.cost ?? 0, prompts: found?.prompts ?? 0 });
649
+ }
650
+ // By model.
651
+ const byModel = db.prepare(`
652
+ SELECT model, COUNT(*) as prompts, SUM(cost_usd) as cost
653
+ FROM claude_prompts
654
+ WHERE ts >= ? AND model IS NOT NULL
655
+ GROUP BY model
656
+ ORDER BY cost DESC
657
+ `).all(since);
658
+ return { total: total.t ?? 0, topPrompts, series: dense, byModel, wowDelta };
659
+ });
660
+ /**
661
+ * GET /api/claude/stats?range= — the four dashboard stat tiles, each with a
662
+ * value, a fractional week-over-week delta (vs the prior equal-length window)
663
+ * and a 7-day sparkline series (oldest→newest). Drift is a live graph metric
664
+ * with no history, so it returns value-only.
665
+ */
666
+ app.get('/api/claude/stats', async (req, reply) => {
667
+ const cg = requirePrimary(reply);
668
+ if (!cg)
669
+ return;
670
+ const db = getDb(cg);
671
+ const rkey = rangeKey(req.query.range);
672
+ const since = rangeStart(rkey);
673
+ const prior = priorWindow(rkey);
674
+ const dayMs = 24 * 60 * 60 * 1000;
675
+ const d7Start = Math.floor(Date.now() / dayMs) * dayMs - 6 * dayMs;
676
+ const dense7 = (rows) => {
677
+ const a = [0, 0, 0, 0, 0, 0, 0];
678
+ for (const r of rows)
679
+ if (r.bucket >= 0 && r.bucket <= 6)
680
+ a[r.bucket] = r.v;
681
+ return a;
682
+ };
683
+ const countSince = (sql, ...p) => db.prepare(sql).get(...p).c ?? 0;
684
+ // --- Tool calls ---
685
+ const tcTotal = countSince(`SELECT COUNT(*) c FROM claude_tool_calls WHERE ts >= ?`, since);
686
+ const tcPrior = prior.end > prior.start
687
+ ? countSince(`SELECT COUNT(*) c FROM claude_tool_calls WHERE ts >= ? AND ts < ?`, prior.start, prior.end)
688
+ : 0;
689
+ const tcSeries = db.prepare(`SELECT CAST((ts - ?) / ? AS INTEGER) as bucket, COUNT(*) as v FROM claude_tool_calls WHERE ts >= ? GROUP BY bucket`).all(d7Start, dayMs, d7Start);
690
+ const toolCalls = { value: tcTotal, delta: tcPrior > 0 ? (tcTotal - tcPrior) / tcPrior : 0, series: dense7(tcSeries) };
691
+ // --- Subagent spend share (by cost) ---
692
+ const subPctOf = (start, end) => {
693
+ const where = end == null ? `ts >= ?` : `ts >= ? AND ts < ?`;
694
+ const args = end == null ? [start] : [start, end];
695
+ const rows = db.prepare(`SELECT is_sidechain as side, COALESCE(SUM(cost_usd), 0) as cost FROM claude_prompts WHERE ${where} GROUP BY is_sidechain`).all(...args);
696
+ const sub = rows.find((r) => r.side === 1)?.cost ?? 0;
697
+ const tot = rows.reduce((a, r) => a + (r.cost ?? 0), 0);
698
+ return tot > 0 ? sub / tot : 0;
699
+ };
700
+ const subPct = subPctOf(since, null);
701
+ const subPctPrior = prior.end > prior.start ? subPctOf(prior.start, prior.end) : 0;
702
+ const saDaily = db.prepare(`
703
+ SELECT CAST((ts - ?) / ? AS INTEGER) as bucket,
704
+ SUM(CASE WHEN is_sidechain = 1 THEN cost_usd ELSE 0 END) as sub,
705
+ SUM(cost_usd) as tot
706
+ FROM claude_prompts WHERE ts >= ? GROUP BY bucket
707
+ `).all(d7Start, dayMs, d7Start);
708
+ const saSeries = [0, 0, 0, 0, 0, 0, 0];
709
+ for (const r of saDaily)
710
+ if (r.bucket >= 0 && r.bucket <= 6)
711
+ saSeries[r.bucket] = r.tot > 0 ? Math.round((r.sub / r.tot) * 100) : 0;
712
+ const subagentPct = { value: Math.round(subPct * 100), delta: subPct - subPctPrior, series: saSeries };
713
+ // --- Last session cost (delta vs previous session, spark of recent sessions) ---
714
+ const recent = db.prepare(`SELECT total_cost_usd as cost FROM claude_sessions ORDER BY started_at DESC LIMIT 10`).all();
715
+ const lastCost = recent[0]?.cost ?? 0;
716
+ const prevCost = recent[1]?.cost ?? 0;
717
+ const lastSessionCost = {
718
+ value: lastCost,
719
+ delta: prevCost > 0 ? (lastCost - prevCost) / prevCost : 0,
720
+ series: recent.map((r) => r.cost ?? 0).reverse(),
721
+ };
722
+ // --- Drift (live graph count, no time series) ---
723
+ const driftCount = cg.getSpecQueries().getLinksByState(['drifted', 'broken', 'orphaned']).length;
724
+ const drift = { value: driftCount, delta: 0, series: [] };
725
+ return { lastSessionCost, toolCalls, subagentPct, drift };
726
+ });
727
+ app.get('/api/claude/compare', async (_req, reply) => {
728
+ const cg = requirePrimary(reply);
729
+ if (!cg)
730
+ return;
731
+ const db = getDb(cg);
732
+ const rows = db.prepare(`
733
+ SELECT
734
+ p.path, p.name,
735
+ COUNT(s.id) as sessions,
736
+ COALESCE(SUM(s.total_cost_usd), 0) as cost,
737
+ COALESCE(AVG(s.total_cost_usd), 0) as avgCost,
738
+ COALESCE(SUM(s.prompt_count), 0) as prompts,
739
+ CASE WHEN SUM(s.total_input_tokens + s.total_cache_creation_tokens + s.total_cache_read_tokens) > 0
740
+ THEN CAST(SUM(s.total_cache_read_tokens) AS REAL) / SUM(s.total_input_tokens + s.total_cache_creation_tokens + s.total_cache_read_tokens)
741
+ ELSE 0 END as cacheHit
742
+ FROM claude_projects p
743
+ LEFT JOIN claude_sessions s ON s.project_path = p.path
744
+ GROUP BY p.path
745
+ ORDER BY cost DESC
746
+ `).all();
747
+ // Per-model cost split per project (drives the stacked bars).
748
+ const modelRows = db.prepare(`
749
+ SELECT s.project_path as path, p.model as model, COALESCE(SUM(p.cost_usd), 0) as cost
750
+ FROM claude_prompts p
751
+ JOIN claude_sessions s ON s.id = p.session_id
752
+ WHERE p.model IS NOT NULL
753
+ GROUP BY s.project_path, p.model
754
+ `).all();
755
+ const byModelByPath = new Map();
756
+ for (const r of modelRows) {
757
+ const arr = byModelByPath.get(r.path) ?? [];
758
+ arr.push({ model: r.model, cost: r.cost });
759
+ byModelByPath.set(r.path, arr);
760
+ }
761
+ // Top tools per project (top 4 by call count).
762
+ const toolRows = db.prepare(`
763
+ SELECT s.project_path as path, t.tool_name as name, COUNT(*) as calls
764
+ FROM claude_tool_calls t
765
+ JOIN claude_sessions s ON s.id = t.session_id
766
+ GROUP BY s.project_path, t.tool_name
767
+ `).all();
768
+ const toolsByPath = new Map();
769
+ for (const r of toolRows) {
770
+ const arr = toolsByPath.get(r.path) ?? [];
771
+ arr.push({ name: r.name, calls: r.calls });
772
+ toolsByPath.set(r.path, arr);
773
+ }
774
+ // Drifted-link count is only knowable for the PRIMARY project's indexed
775
+ // graph (compare rows are Claude-cost projects keyed by cwd; only one has a
776
+ // spec graph loaded here). Attach the real count to the matching project
777
+ // row and 0 to the rest, rather than fabricating per-project drift.
778
+ const driftCount = cg.getSpecQueries().getLinksByState(['drifted', 'broken', 'orphaned']).length;
779
+ const primaryRoot = (cg.getProjectRoot ? cg.getProjectRoot() : '').replace(/\/+$/, '');
780
+ const projects = rows.map((p) => ({
781
+ ...p,
782
+ drift: p.path.replace(/\/+$/, '') === primaryRoot ? driftCount : 0,
783
+ byModel: (byModelByPath.get(p.path) ?? []).sort((a, b) => b.cost - a.cost),
784
+ topTools: (toolsByPath.get(p.path) ?? [])
785
+ .sort((a, b) => b.calls - a.calls)
786
+ .slice(0, 4)
787
+ .map((t) => t.name),
788
+ }));
789
+ return { projects };
790
+ });
791
+ /**
792
+ * Rule-based tips engine. Each rule is a SQL query that finds a wasteful
793
+ * pattern in the user's recent transcripts; the result is shaped into a
794
+ * tip card matching the design system's voice.
795
+ */
796
+ app.get('/api/claude/tips', async (_req, reply) => {
797
+ const cg = requirePrimary(reply);
798
+ if (!cg)
799
+ return;
800
+ const db = getDb(cg);
801
+ const tips = [];
802
+ // Rule 1: "you read X N times" — same file path Read more than 10 times in
803
+ // a single session.
804
+ const wastefulReads = db.prepare(`
805
+ SELECT session_id, input_summary as file, COUNT(*) as n
806
+ FROM claude_tool_calls
807
+ WHERE tool_name = 'Read' AND input_summary != ''
808
+ GROUP BY session_id, input_summary
809
+ HAVING n >= 10
810
+ ORDER BY n DESC
811
+ LIMIT 5
812
+ `).all();
813
+ for (const r of wastefulReads) {
814
+ tips.push({
815
+ id: 'wasteful_reads:' + r.session_id + ':' + r.file,
816
+ severity: 'error',
817
+ icon: 'wrench',
818
+ title: `You read ${r.file.split('/').pop()} ${r.n}× in one session — specship_explore covers it`,
819
+ why: 'Re-reading the same file burns input tokens every turn. A single structural query returns callers, callees, and linked specs at once.',
820
+ evidence: { session: r.session_id, detail: `Read(${r.file}) × ${r.n}` },
821
+ fix: `specship_explore --symbol ${r.file.replace(/\.\w+$/, '').split('/').pop()}`,
822
+ saving: '≈$0.10/read avoided',
823
+ });
824
+ }
825
+ // Rule 2: "tool returned X tokens" — any single tool call with result_length > 50000.
826
+ const heavyResults = db.prepare(`
827
+ SELECT id, session_id, tool_name, input_summary, result_length
828
+ FROM claude_tool_calls
829
+ WHERE result_length > 50000
830
+ ORDER BY result_length DESC
831
+ LIMIT 5
832
+ `).all();
833
+ for (const r of heavyResults) {
834
+ tips.push({
835
+ id: 'heavy_result:' + r.id,
836
+ severity: 'error',
837
+ icon: 'flame',
838
+ title: `${r.tool_name} returned ${Math.round(r.result_length / 1000)}k tokens — try a structural query`,
839
+ why: 'Tools that dump raw content into context are the dominant cost driver. A structural query returns just what the agent needs.',
840
+ evidence: { session: r.session_id, detail: `${r.tool_name}(${r.input_summary.slice(0, 100)}) → ${r.result_length} tokens` },
841
+ fix: r.tool_name === 'Bash' ? 'specship_search instead of Bash(grep)' : 'specship_explore on the symbol',
842
+ saving: `~$${((r.result_length / 1_000_000) * 15).toFixed(2)} on this call`,
843
+ });
844
+ }
845
+ // Rule 3: "cache miss rate" — sessions with > 10 prompts and cache_read_rate < 0.3.
846
+ const lowCache = db.prepare(`
847
+ SELECT s.id, s.total_cache_read_tokens as cr, s.total_input_tokens as ti, s.total_cache_creation_tokens as cw, s.prompt_count, s.last_model
848
+ FROM claude_sessions s
849
+ WHERE s.prompt_count >= 10
850
+ AND (s.total_input_tokens + s.total_cache_creation_tokens + s.total_cache_read_tokens) > 0
851
+ ORDER BY (CAST(s.total_cache_read_tokens AS REAL) / (s.total_input_tokens + s.total_cache_creation_tokens + s.total_cache_read_tokens)) ASC
852
+ LIMIT 3
853
+ `).all();
854
+ for (const r of lowCache) {
855
+ const total = r.ti + r.cw + r.cr;
856
+ const rate = total > 0 ? r.cr / total : 0;
857
+ if (rate >= 0.3)
858
+ continue;
859
+ tips.push({
860
+ id: 'low_cache:' + r.id,
861
+ severity: 'warn',
862
+ icon: 'database',
863
+ title: `Cache read rate is ${Math.round(rate * 100)}% on a ${r.prompt_count}-prompt session`,
864
+ why: 'When the prompt prefix changes every turn, the 1h cache gets invalidated. Pinning a stable system-prompt prefix lets the cache absorb most of your input.',
865
+ evidence: { session: r.id, detail: `cache_read=${(r.cr / 1_000_000).toFixed(2)}M / total=${(total / 1_000_000).toFixed(2)}M` },
866
+ fix: 'Pin a stable system-prompt prefix in .claude/settings.json',
867
+ saving: '~$X.XX / session (model-dependent)',
868
+ });
869
+ }
870
+ // Sort: errors before warns before info, then within bucket by saving heuristic.
871
+ const order = { error: 0, warn: 1, info: 2 };
872
+ tips.sort((a, b) => (order[a.severity] ?? 9) - (order[b.severity] ?? 9));
873
+ return { tips };
874
+ });
875
+ /**
876
+ * GET /api/claude/specship-impact?range=&project=
877
+ *
878
+ * Aggregate SpecShip token-impact metrics: how many tokens specship calls
879
+ * spent vs. how many tokens' worth of Read calls they displaced (saved),
880
+ * with per-prompt dedup so a file referenced by two specship calls in the
881
+ * same prompt counts only once. See packages/server/src/ingest/impact-query.ts
882
+ * for the full algorithm.
883
+ *
884
+ * Query params:
885
+ * range — 'today' | 'week' (default) | 'month' | 'all'
886
+ * project — project slug (decoded to path) or raw path; omit for all projects
887
+ */
888
+ app.get('/api/claude/specship-impact', async (req, reply) => {
889
+ const cg = requirePrimary(reply);
890
+ if (!cg)
891
+ return;
892
+ const db = getDb(cg);
893
+ const since = rangeStart(rangeKey(req.query.range));
894
+ const project = req.query.project ? normalizeProjectFilter(req.query.project) : undefined;
895
+ return computeSpecshipImpact(db, { since, project });
896
+ });
897
+ /**
898
+ * Force a one-shot ingest pass. Useful for "Refresh" button in the UI.
899
+ */
900
+ app.post('/api/claude/ingest', async () => {
901
+ const watcher = app.watcher;
902
+ if (!watcher)
903
+ return { ok: false, error: 'watcher not running' };
904
+ const stats = watcher.ingestNow();
905
+ return { ok: true, stats };
906
+ });
907
+ }