@selvakumaresra/specship 0.1.3 → 0.4.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 (718) hide show
  1. package/README.md +2 -2
  2. package/commands/ss-design-implement.md +84 -0
  3. package/commands/ss-design-loop.md +125 -0
  4. package/commands/{cg-drifted.md → ss-drifted.md} +2 -2
  5. package/commands/{cg-fix.md → ss-fix.md} +1 -1
  6. package/commands/{cg-implement.md → ss-implement.md} +1 -1
  7. package/commands/ss-spec-author.md +43 -0
  8. package/commands/ss-spec-review.md +48 -0
  9. package/dist/bin/node-version-check.d.ts +37 -0
  10. package/dist/bin/node-version-check.d.ts.map +1 -0
  11. package/dist/bin/node-version-check.js +79 -0
  12. package/dist/bin/node-version-check.js.map +1 -0
  13. package/dist/bin/specship.d.ts +25 -0
  14. package/dist/bin/specship.d.ts.map +1 -0
  15. package/dist/bin/specship.js +2085 -0
  16. package/dist/bin/specship.js.map +1 -0
  17. package/dist/bin/uninstall.d.ts +13 -0
  18. package/dist/bin/uninstall.d.ts.map +1 -0
  19. package/dist/bin/uninstall.js +35 -0
  20. package/dist/bin/uninstall.js.map +1 -0
  21. package/dist/context/formatter.d.ts +30 -0
  22. package/dist/context/formatter.d.ts.map +1 -0
  23. package/dist/context/formatter.js +263 -0
  24. package/dist/context/formatter.js.map +1 -0
  25. package/dist/context/index.d.ts +119 -0
  26. package/dist/context/index.d.ts.map +1 -0
  27. package/dist/context/index.js +1289 -0
  28. package/dist/context/index.js.map +1 -0
  29. package/dist/context/markers.d.ts +19 -0
  30. package/dist/context/markers.d.ts.map +1 -0
  31. package/dist/context/markers.js +22 -0
  32. package/dist/context/markers.js.map +1 -0
  33. package/dist/db/index.d.ts +103 -0
  34. package/dist/db/index.d.ts.map +1 -0
  35. package/dist/db/index.js +279 -0
  36. package/dist/db/index.js.map +1 -0
  37. package/dist/db/migrations.d.ts +44 -0
  38. package/dist/db/migrations.d.ts.map +1 -0
  39. package/dist/db/migrations.js +462 -0
  40. package/dist/db/migrations.js.map +1 -0
  41. package/dist/db/queries.d.ts +357 -0
  42. package/dist/db/queries.d.ts.map +1 -0
  43. package/dist/db/queries.js +1504 -0
  44. package/dist/db/queries.js.map +1 -0
  45. package/dist/db/schema.sql +419 -0
  46. package/dist/db/spec-queries.d.ts +101 -0
  47. package/dist/db/spec-queries.d.ts.map +1 -0
  48. package/dist/db/spec-queries.js +675 -0
  49. package/dist/db/spec-queries.js.map +1 -0
  50. package/dist/db/sqlite-adapter.d.ts +65 -0
  51. package/dist/db/sqlite-adapter.d.ts.map +1 -0
  52. package/dist/db/sqlite-adapter.js +214 -0
  53. package/dist/db/sqlite-adapter.js.map +1 -0
  54. package/dist/designer/artifact-store.js +54 -0
  55. package/dist/designer/browser.js +141 -0
  56. package/dist/designer/cdp-ensure.js +60 -0
  57. package/dist/designer/cdp-env.js +18 -0
  58. package/dist/designer/cdp-trace.js +599 -0
  59. package/dist/designer/cross-platform.js +74 -0
  60. package/dist/designer/designer-controller.js +1413 -0
  61. package/dist/designer/file-panel.js +39 -0
  62. package/dist/designer/interstitials.js +97 -0
  63. package/dist/designer/oopif-reader.js +176 -0
  64. package/dist/designer/package-meta.js +18 -0
  65. package/dist/designer/preview-host.js +50 -0
  66. package/dist/designer/repo-root.js +31 -0
  67. package/dist/designer/run-state.js +353 -0
  68. package/dist/designer/session-store.js +59 -0
  69. package/dist/designer/ui-anchors.js +651 -0
  70. package/dist/directory.d.ts +67 -0
  71. package/dist/directory.d.ts.map +1 -0
  72. package/dist/directory.js +267 -0
  73. package/dist/directory.js.map +1 -0
  74. package/dist/errors.d.ts +136 -0
  75. package/dist/errors.d.ts.map +1 -0
  76. package/dist/errors.js +219 -0
  77. package/dist/errors.js.map +1 -0
  78. package/dist/extraction/dfm-extractor.d.ts +31 -0
  79. package/dist/extraction/dfm-extractor.d.ts.map +1 -0
  80. package/dist/extraction/dfm-extractor.js +151 -0
  81. package/dist/extraction/dfm-extractor.js.map +1 -0
  82. package/dist/extraction/generated-detection.d.ts +30 -0
  83. package/dist/extraction/generated-detection.d.ts.map +1 -0
  84. package/dist/extraction/generated-detection.js +80 -0
  85. package/dist/extraction/generated-detection.js.map +1 -0
  86. package/dist/extraction/grammars.d.ts +100 -0
  87. package/dist/extraction/grammars.d.ts.map +1 -0
  88. package/dist/extraction/grammars.js +426 -0
  89. package/dist/extraction/grammars.js.map +1 -0
  90. package/dist/extraction/index.d.ts +138 -0
  91. package/dist/extraction/index.d.ts.map +1 -0
  92. package/dist/extraction/index.js +1394 -0
  93. package/dist/extraction/index.js.map +1 -0
  94. package/dist/extraction/languages/c-cpp.d.ts +4 -0
  95. package/dist/extraction/languages/c-cpp.d.ts.map +1 -0
  96. package/dist/extraction/languages/c-cpp.js +171 -0
  97. package/dist/extraction/languages/c-cpp.js.map +1 -0
  98. package/dist/extraction/languages/csharp.d.ts +3 -0
  99. package/dist/extraction/languages/csharp.d.ts.map +1 -0
  100. package/dist/extraction/languages/csharp.js +73 -0
  101. package/dist/extraction/languages/csharp.js.map +1 -0
  102. package/dist/extraction/languages/dart.d.ts +3 -0
  103. package/dist/extraction/languages/dart.d.ts.map +1 -0
  104. package/dist/extraction/languages/dart.js +192 -0
  105. package/dist/extraction/languages/dart.js.map +1 -0
  106. package/dist/extraction/languages/go.d.ts +3 -0
  107. package/dist/extraction/languages/go.d.ts.map +1 -0
  108. package/dist/extraction/languages/go.js +74 -0
  109. package/dist/extraction/languages/go.js.map +1 -0
  110. package/dist/extraction/languages/index.d.ts +10 -0
  111. package/dist/extraction/languages/index.d.ts.map +1 -0
  112. package/dist/extraction/languages/index.js +51 -0
  113. package/dist/extraction/languages/index.js.map +1 -0
  114. package/dist/extraction/languages/java.d.ts +3 -0
  115. package/dist/extraction/languages/java.d.ts.map +1 -0
  116. package/dist/extraction/languages/java.js +70 -0
  117. package/dist/extraction/languages/java.js.map +1 -0
  118. package/dist/extraction/languages/javascript.d.ts +3 -0
  119. package/dist/extraction/languages/javascript.d.ts.map +1 -0
  120. package/dist/extraction/languages/javascript.js +90 -0
  121. package/dist/extraction/languages/javascript.js.map +1 -0
  122. package/dist/extraction/languages/kotlin.d.ts +3 -0
  123. package/dist/extraction/languages/kotlin.d.ts.map +1 -0
  124. package/dist/extraction/languages/kotlin.js +259 -0
  125. package/dist/extraction/languages/kotlin.js.map +1 -0
  126. package/dist/extraction/languages/lua.d.ts +3 -0
  127. package/dist/extraction/languages/lua.d.ts.map +1 -0
  128. package/dist/extraction/languages/lua.js +150 -0
  129. package/dist/extraction/languages/lua.js.map +1 -0
  130. package/dist/extraction/languages/luau.d.ts +3 -0
  131. package/dist/extraction/languages/luau.d.ts.map +1 -0
  132. package/dist/extraction/languages/luau.js +37 -0
  133. package/dist/extraction/languages/luau.js.map +1 -0
  134. package/dist/extraction/languages/objc.d.ts +3 -0
  135. package/dist/extraction/languages/objc.d.ts.map +1 -0
  136. package/dist/extraction/languages/objc.js +133 -0
  137. package/dist/extraction/languages/objc.js.map +1 -0
  138. package/dist/extraction/languages/pascal.d.ts +3 -0
  139. package/dist/extraction/languages/pascal.d.ts.map +1 -0
  140. package/dist/extraction/languages/pascal.js +66 -0
  141. package/dist/extraction/languages/pascal.js.map +1 -0
  142. package/dist/extraction/languages/php.d.ts +3 -0
  143. package/dist/extraction/languages/php.d.ts.map +1 -0
  144. package/dist/extraction/languages/php.js +107 -0
  145. package/dist/extraction/languages/php.js.map +1 -0
  146. package/dist/extraction/languages/python.d.ts +3 -0
  147. package/dist/extraction/languages/python.d.ts.map +1 -0
  148. package/dist/extraction/languages/python.js +56 -0
  149. package/dist/extraction/languages/python.js.map +1 -0
  150. package/dist/extraction/languages/ruby.d.ts +3 -0
  151. package/dist/extraction/languages/ruby.d.ts.map +1 -0
  152. package/dist/extraction/languages/ruby.js +114 -0
  153. package/dist/extraction/languages/ruby.js.map +1 -0
  154. package/dist/extraction/languages/rust.d.ts +3 -0
  155. package/dist/extraction/languages/rust.d.ts.map +1 -0
  156. package/dist/extraction/languages/rust.js +109 -0
  157. package/dist/extraction/languages/rust.js.map +1 -0
  158. package/dist/extraction/languages/scala.d.ts +3 -0
  159. package/dist/extraction/languages/scala.d.ts.map +1 -0
  160. package/dist/extraction/languages/scala.js +139 -0
  161. package/dist/extraction/languages/scala.js.map +1 -0
  162. package/dist/extraction/languages/swift.d.ts +3 -0
  163. package/dist/extraction/languages/swift.d.ts.map +1 -0
  164. package/dist/extraction/languages/swift.js +91 -0
  165. package/dist/extraction/languages/swift.js.map +1 -0
  166. package/dist/extraction/languages/typescript.d.ts +3 -0
  167. package/dist/extraction/languages/typescript.d.ts.map +1 -0
  168. package/dist/extraction/languages/typescript.js +129 -0
  169. package/dist/extraction/languages/typescript.js.map +1 -0
  170. package/dist/extraction/liquid-extractor.d.ts +52 -0
  171. package/dist/extraction/liquid-extractor.d.ts.map +1 -0
  172. package/dist/extraction/liquid-extractor.js +313 -0
  173. package/dist/extraction/liquid-extractor.js.map +1 -0
  174. package/dist/extraction/mybatis-extractor.d.ts +48 -0
  175. package/dist/extraction/mybatis-extractor.d.ts.map +1 -0
  176. package/dist/extraction/mybatis-extractor.js +198 -0
  177. package/dist/extraction/mybatis-extractor.js.map +1 -0
  178. package/dist/extraction/parse-worker.d.ts +8 -0
  179. package/dist/extraction/parse-worker.d.ts.map +1 -0
  180. package/dist/extraction/parse-worker.js +94 -0
  181. package/dist/extraction/parse-worker.js.map +1 -0
  182. package/dist/extraction/specs/markdown-spec-extractor.d.ts +59 -0
  183. package/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -0
  184. package/dist/extraction/specs/markdown-spec-extractor.js +327 -0
  185. package/dist/extraction/specs/markdown-spec-extractor.js.map +1 -0
  186. package/dist/extraction/specs/types.d.ts +39 -0
  187. package/dist/extraction/specs/types.d.ts.map +1 -0
  188. package/dist/extraction/specs/types.js +8 -0
  189. package/dist/extraction/specs/types.js.map +1 -0
  190. package/dist/extraction/svelte-extractor.d.ts +56 -0
  191. package/dist/extraction/svelte-extractor.d.ts.map +1 -0
  192. package/dist/extraction/svelte-extractor.js +272 -0
  193. package/dist/extraction/svelte-extractor.js.map +1 -0
  194. package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
  195. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -0
  196. package/dist/extraction/tree-sitter-helpers.js +103 -0
  197. package/dist/extraction/tree-sitter-helpers.js.map +1 -0
  198. package/dist/extraction/tree-sitter-types.d.ts +193 -0
  199. package/dist/extraction/tree-sitter-types.d.ts.map +1 -0
  200. package/dist/extraction/tree-sitter-types.js +10 -0
  201. package/dist/extraction/tree-sitter-types.js.map +1 -0
  202. package/dist/extraction/tree-sitter.d.ts +317 -0
  203. package/dist/extraction/tree-sitter.d.ts.map +1 -0
  204. package/dist/extraction/tree-sitter.js +3092 -0
  205. package/dist/extraction/tree-sitter.js.map +1 -0
  206. package/dist/extraction/vue-extractor.d.ts +51 -0
  207. package/dist/extraction/vue-extractor.d.ts.map +1 -0
  208. package/dist/extraction/vue-extractor.js +251 -0
  209. package/dist/extraction/vue-extractor.js.map +1 -0
  210. package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
  211. package/dist/extraction/wasm/tree-sitter-luau.wasm +0 -0
  212. package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  213. package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  214. package/dist/extraction/wasm-runtime-flags.d.ts +38 -0
  215. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -0
  216. package/dist/extraction/wasm-runtime-flags.js +106 -0
  217. package/dist/extraction/wasm-runtime-flags.js.map +1 -0
  218. package/dist/graph/index.d.ts +8 -0
  219. package/dist/graph/index.d.ts.map +1 -0
  220. package/dist/graph/index.js +13 -0
  221. package/dist/graph/index.js.map +1 -0
  222. package/dist/graph/queries.d.ts +106 -0
  223. package/dist/graph/queries.d.ts.map +1 -0
  224. package/dist/graph/queries.js +366 -0
  225. package/dist/graph/queries.js.map +1 -0
  226. package/dist/graph/traversal.d.ts +127 -0
  227. package/dist/graph/traversal.d.ts.map +1 -0
  228. package/dist/graph/traversal.js +531 -0
  229. package/dist/graph/traversal.js.map +1 -0
  230. package/dist/index.d.ts +551 -0
  231. package/dist/index.d.ts.map +1 -0
  232. package/dist/index.js +1165 -0
  233. package/dist/index.js.map +1 -0
  234. package/dist/installer/config-writer.d.ts +28 -0
  235. package/dist/installer/config-writer.d.ts.map +1 -0
  236. package/dist/installer/config-writer.js +91 -0
  237. package/dist/installer/config-writer.js.map +1 -0
  238. package/dist/installer/index.d.ts +92 -0
  239. package/dist/installer/index.d.ts.map +1 -0
  240. package/dist/installer/index.js +416 -0
  241. package/dist/installer/index.js.map +1 -0
  242. package/dist/installer/instructions-template.d.ts +35 -0
  243. package/dist/installer/instructions-template.d.ts.map +1 -0
  244. package/dist/installer/instructions-template.js +51 -0
  245. package/dist/installer/instructions-template.js.map +1 -0
  246. package/dist/installer/targets/claude.d.ts +117 -0
  247. package/dist/installer/targets/claude.d.ts.map +1 -0
  248. package/dist/installer/targets/claude.js +736 -0
  249. package/dist/installer/targets/claude.js.map +1 -0
  250. package/dist/installer/targets/registry.d.ts +19 -0
  251. package/dist/installer/targets/registry.d.ts.map +1 -0
  252. package/dist/installer/targets/registry.js +31 -0
  253. package/dist/installer/targets/registry.js.map +1 -0
  254. package/dist/installer/targets/shared.d.ts +76 -0
  255. package/dist/installer/targets/shared.d.ts.map +1 -0
  256. package/dist/installer/targets/shared.js +256 -0
  257. package/dist/installer/targets/shared.js.map +1 -0
  258. package/dist/installer/targets/types.d.ts +84 -0
  259. package/dist/installer/targets/types.d.ts.map +1 -0
  260. package/dist/installer/targets/types.js +12 -0
  261. package/dist/installer/targets/types.js.map +1 -0
  262. package/dist/isolation/worktree.d.ts +65 -0
  263. package/dist/isolation/worktree.d.ts.map +1 -0
  264. package/dist/isolation/worktree.js +231 -0
  265. package/dist/isolation/worktree.js.map +1 -0
  266. package/dist/mcp/daemon-paths.d.ts +46 -0
  267. package/dist/mcp/daemon-paths.d.ts.map +1 -0
  268. package/dist/mcp/daemon-paths.js +125 -0
  269. package/dist/mcp/daemon-paths.js.map +1 -0
  270. package/dist/mcp/daemon.d.ts +161 -0
  271. package/dist/mcp/daemon.d.ts.map +1 -0
  272. package/dist/mcp/daemon.js +403 -0
  273. package/dist/mcp/daemon.js.map +1 -0
  274. package/dist/mcp/designer-tools.d.ts +33 -0
  275. package/dist/mcp/designer-tools.d.ts.map +1 -0
  276. package/dist/mcp/designer-tools.js +313 -0
  277. package/dist/mcp/designer-tools.js.map +1 -0
  278. package/dist/mcp/engine.d.ts +105 -0
  279. package/dist/mcp/engine.d.ts.map +1 -0
  280. package/dist/mcp/engine.js +270 -0
  281. package/dist/mcp/engine.js.map +1 -0
  282. package/dist/mcp/index.d.ts +112 -0
  283. package/dist/mcp/index.d.ts.map +1 -0
  284. package/dist/mcp/index.js +477 -0
  285. package/dist/mcp/index.js.map +1 -0
  286. package/dist/mcp/proxy.d.ts +81 -0
  287. package/dist/mcp/proxy.d.ts.map +1 -0
  288. package/dist/mcp/proxy.js +510 -0
  289. package/dist/mcp/proxy.js.map +1 -0
  290. package/dist/mcp/server-instructions.d.ts +18 -0
  291. package/dist/mcp/server-instructions.d.ts.map +1 -0
  292. package/dist/mcp/server-instructions.js +77 -0
  293. package/dist/mcp/server-instructions.js.map +1 -0
  294. package/dist/mcp/session.d.ts +77 -0
  295. package/dist/mcp/session.d.ts.map +1 -0
  296. package/dist/mcp/session.js +294 -0
  297. package/dist/mcp/session.js.map +1 -0
  298. package/dist/mcp/spec-tools.d.ts +39 -0
  299. package/dist/mcp/spec-tools.d.ts.map +1 -0
  300. package/dist/mcp/spec-tools.js +326 -0
  301. package/dist/mcp/spec-tools.js.map +1 -0
  302. package/dist/mcp/tools.d.ts +404 -0
  303. package/dist/mcp/tools.d.ts.map +1 -0
  304. package/dist/mcp/tools.js +3087 -0
  305. package/dist/mcp/tools.js.map +1 -0
  306. package/dist/mcp/transport.d.ts +188 -0
  307. package/dist/mcp/transport.d.ts.map +1 -0
  308. package/dist/mcp/transport.js +343 -0
  309. package/dist/mcp/transport.js.map +1 -0
  310. package/dist/mcp/version.d.ts +19 -0
  311. package/dist/mcp/version.d.ts.map +1 -0
  312. package/dist/mcp/version.js +71 -0
  313. package/dist/mcp/version.js.map +1 -0
  314. package/dist/resolution/callback-synthesizer.d.ts +10 -0
  315. package/dist/resolution/callback-synthesizer.d.ts.map +1 -0
  316. package/dist/resolution/callback-synthesizer.js +1300 -0
  317. package/dist/resolution/callback-synthesizer.js.map +1 -0
  318. package/dist/resolution/frameworks/cargo-workspace.d.ts +18 -0
  319. package/dist/resolution/frameworks/cargo-workspace.d.ts.map +1 -0
  320. package/dist/resolution/frameworks/cargo-workspace.js +225 -0
  321. package/dist/resolution/frameworks/cargo-workspace.js.map +1 -0
  322. package/dist/resolution/frameworks/csharp.d.ts +8 -0
  323. package/dist/resolution/frameworks/csharp.d.ts.map +1 -0
  324. package/dist/resolution/frameworks/csharp.js +241 -0
  325. package/dist/resolution/frameworks/csharp.js.map +1 -0
  326. package/dist/resolution/frameworks/drupal.d.ts +51 -0
  327. package/dist/resolution/frameworks/drupal.d.ts.map +1 -0
  328. package/dist/resolution/frameworks/drupal.js +367 -0
  329. package/dist/resolution/frameworks/drupal.js.map +1 -0
  330. package/dist/resolution/frameworks/expo-modules.d.ts +3 -0
  331. package/dist/resolution/frameworks/expo-modules.d.ts.map +1 -0
  332. package/dist/resolution/frameworks/expo-modules.js +143 -0
  333. package/dist/resolution/frameworks/expo-modules.js.map +1 -0
  334. package/dist/resolution/frameworks/express.d.ts +8 -0
  335. package/dist/resolution/frameworks/express.d.ts.map +1 -0
  336. package/dist/resolution/frameworks/express.js +308 -0
  337. package/dist/resolution/frameworks/express.js.map +1 -0
  338. package/dist/resolution/frameworks/fabric.d.ts +3 -0
  339. package/dist/resolution/frameworks/fabric.d.ts.map +1 -0
  340. package/dist/resolution/frameworks/fabric.js +354 -0
  341. package/dist/resolution/frameworks/fabric.js.map +1 -0
  342. package/dist/resolution/frameworks/go.d.ts +8 -0
  343. package/dist/resolution/frameworks/go.d.ts.map +1 -0
  344. package/dist/resolution/frameworks/go.js +161 -0
  345. package/dist/resolution/frameworks/go.js.map +1 -0
  346. package/dist/resolution/frameworks/index.d.ts +48 -0
  347. package/dist/resolution/frameworks/index.d.ts.map +1 -0
  348. package/dist/resolution/frameworks/index.js +161 -0
  349. package/dist/resolution/frameworks/index.js.map +1 -0
  350. package/dist/resolution/frameworks/java.d.ts +8 -0
  351. package/dist/resolution/frameworks/java.d.ts.map +1 -0
  352. package/dist/resolution/frameworks/java.js +504 -0
  353. package/dist/resolution/frameworks/java.js.map +1 -0
  354. package/dist/resolution/frameworks/laravel.d.ts +13 -0
  355. package/dist/resolution/frameworks/laravel.d.ts.map +1 -0
  356. package/dist/resolution/frameworks/laravel.js +257 -0
  357. package/dist/resolution/frameworks/laravel.js.map +1 -0
  358. package/dist/resolution/frameworks/nestjs.d.ts +26 -0
  359. package/dist/resolution/frameworks/nestjs.d.ts.map +1 -0
  360. package/dist/resolution/frameworks/nestjs.js +698 -0
  361. package/dist/resolution/frameworks/nestjs.js.map +1 -0
  362. package/dist/resolution/frameworks/play.d.ts +19 -0
  363. package/dist/resolution/frameworks/play.d.ts.map +1 -0
  364. package/dist/resolution/frameworks/play.js +111 -0
  365. package/dist/resolution/frameworks/play.js.map +1 -0
  366. package/dist/resolution/frameworks/python.d.ts +10 -0
  367. package/dist/resolution/frameworks/python.d.ts.map +1 -0
  368. package/dist/resolution/frameworks/python.js +396 -0
  369. package/dist/resolution/frameworks/python.js.map +1 -0
  370. package/dist/resolution/frameworks/react-native.d.ts +3 -0
  371. package/dist/resolution/frameworks/react-native.d.ts.map +1 -0
  372. package/dist/resolution/frameworks/react-native.js +360 -0
  373. package/dist/resolution/frameworks/react-native.js.map +1 -0
  374. package/dist/resolution/frameworks/react.d.ts +8 -0
  375. package/dist/resolution/frameworks/react.d.ts.map +1 -0
  376. package/dist/resolution/frameworks/react.js +365 -0
  377. package/dist/resolution/frameworks/react.js.map +1 -0
  378. package/dist/resolution/frameworks/ruby.d.ts +8 -0
  379. package/dist/resolution/frameworks/ruby.d.ts.map +1 -0
  380. package/dist/resolution/frameworks/ruby.js +302 -0
  381. package/dist/resolution/frameworks/ruby.js.map +1 -0
  382. package/dist/resolution/frameworks/rust.d.ts +8 -0
  383. package/dist/resolution/frameworks/rust.d.ts.map +1 -0
  384. package/dist/resolution/frameworks/rust.js +304 -0
  385. package/dist/resolution/frameworks/rust.js.map +1 -0
  386. package/dist/resolution/frameworks/svelte.d.ts +9 -0
  387. package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
  388. package/dist/resolution/frameworks/svelte.js +249 -0
  389. package/dist/resolution/frameworks/svelte.js.map +1 -0
  390. package/dist/resolution/frameworks/swift-objc.d.ts +37 -0
  391. package/dist/resolution/frameworks/swift-objc.d.ts.map +1 -0
  392. package/dist/resolution/frameworks/swift-objc.js +252 -0
  393. package/dist/resolution/frameworks/swift-objc.js.map +1 -0
  394. package/dist/resolution/frameworks/swift.d.ts +10 -0
  395. package/dist/resolution/frameworks/swift.d.ts.map +1 -0
  396. package/dist/resolution/frameworks/swift.js +400 -0
  397. package/dist/resolution/frameworks/swift.js.map +1 -0
  398. package/dist/resolution/frameworks/vue.d.ts +9 -0
  399. package/dist/resolution/frameworks/vue.d.ts.map +1 -0
  400. package/dist/resolution/frameworks/vue.js +306 -0
  401. package/dist/resolution/frameworks/vue.js.map +1 -0
  402. package/dist/resolution/go-module.d.ts +26 -0
  403. package/dist/resolution/go-module.d.ts.map +1 -0
  404. package/dist/resolution/go-module.js +78 -0
  405. package/dist/resolution/go-module.js.map +1 -0
  406. package/dist/resolution/import-resolver.d.ts +68 -0
  407. package/dist/resolution/import-resolver.d.ts.map +1 -0
  408. package/dist/resolution/import-resolver.js +1275 -0
  409. package/dist/resolution/import-resolver.js.map +1 -0
  410. package/dist/resolution/index.d.ts +117 -0
  411. package/dist/resolution/index.d.ts.map +1 -0
  412. package/dist/resolution/index.js +895 -0
  413. package/dist/resolution/index.js.map +1 -0
  414. package/dist/resolution/lru-cache.d.ts +24 -0
  415. package/dist/resolution/lru-cache.d.ts.map +1 -0
  416. package/dist/resolution/lru-cache.js +62 -0
  417. package/dist/resolution/lru-cache.js.map +1 -0
  418. package/dist/resolution/name-matcher.d.ts +32 -0
  419. package/dist/resolution/name-matcher.d.ts.map +1 -0
  420. package/dist/resolution/name-matcher.js +596 -0
  421. package/dist/resolution/name-matcher.js.map +1 -0
  422. package/dist/resolution/path-aliases.d.ts +68 -0
  423. package/dist/resolution/path-aliases.d.ts.map +1 -0
  424. package/dist/resolution/path-aliases.js +238 -0
  425. package/dist/resolution/path-aliases.js.map +1 -0
  426. package/dist/resolution/spec-link-resolver.d.ts +103 -0
  427. package/dist/resolution/spec-link-resolver.d.ts.map +1 -0
  428. package/dist/resolution/spec-link-resolver.js +259 -0
  429. package/dist/resolution/spec-link-resolver.js.map +1 -0
  430. package/dist/resolution/strip-comments.d.ts +27 -0
  431. package/dist/resolution/strip-comments.d.ts.map +1 -0
  432. package/dist/resolution/strip-comments.js +441 -0
  433. package/dist/resolution/strip-comments.js.map +1 -0
  434. package/dist/resolution/swift-objc-bridge.d.ts +134 -0
  435. package/dist/resolution/swift-objc-bridge.d.ts.map +1 -0
  436. package/dist/resolution/swift-objc-bridge.js +256 -0
  437. package/dist/resolution/swift-objc-bridge.js.map +1 -0
  438. package/dist/resolution/types.d.ts +216 -0
  439. package/dist/resolution/types.d.ts.map +1 -0
  440. package/dist/resolution/types.js +8 -0
  441. package/dist/resolution/types.js.map +1 -0
  442. package/dist/resolution/workspace-packages.d.ts +48 -0
  443. package/dist/resolution/workspace-packages.d.ts.map +1 -0
  444. package/dist/resolution/workspace-packages.js +208 -0
  445. package/dist/resolution/workspace-packages.js.map +1 -0
  446. package/dist/search/query-parser.d.ts +57 -0
  447. package/dist/search/query-parser.d.ts.map +1 -0
  448. package/dist/search/query-parser.js +177 -0
  449. package/dist/search/query-parser.js.map +1 -0
  450. package/dist/search/query-utils.d.ts +71 -0
  451. package/dist/search/query-utils.d.ts.map +1 -0
  452. package/dist/search/query-utils.js +380 -0
  453. package/dist/search/query-utils.js.map +1 -0
  454. package/dist/server/cli.js +152 -0
  455. package/dist/server/index.js +12 -0
  456. package/dist/server/ingest/index.js +18 -0
  457. package/dist/server/ingest/ingestor.js +506 -0
  458. package/dist/server/ingest/parser.js +104 -0
  459. package/dist/server/ingest/pricing.js +78 -0
  460. package/dist/server/ingest/types.js +9 -0
  461. package/dist/server/ingest/watcher.js +77 -0
  462. package/dist/server/package.json +3 -0
  463. package/dist/server/project-registry.js +101 -0
  464. package/dist/server/routes/claude.js +868 -0
  465. package/dist/server/routes/graph.js +211 -0
  466. package/dist/server/routes/memory.js +272 -0
  467. package/dist/server/routes/projects.js +197 -0
  468. package/dist/server/routes/spec.js +265 -0
  469. package/dist/server/routes/status.js +112 -0
  470. package/dist/server/routes/workflow.js +212 -0
  471. package/dist/server/server.js +206 -0
  472. package/dist/server/static-handler.js +87 -0
  473. package/dist/sync/git-hooks.d.ts +45 -0
  474. package/dist/sync/git-hooks.d.ts.map +1 -0
  475. package/dist/sync/git-hooks.js +225 -0
  476. package/dist/sync/git-hooks.js.map +1 -0
  477. package/dist/sync/index.d.ts +19 -0
  478. package/dist/sync/index.d.ts.map +1 -0
  479. package/dist/sync/index.js +35 -0
  480. package/dist/sync/index.js.map +1 -0
  481. package/dist/sync/watch-policy.d.ts +48 -0
  482. package/dist/sync/watch-policy.d.ts.map +1 -0
  483. package/dist/sync/watch-policy.js +124 -0
  484. package/dist/sync/watch-policy.js.map +1 -0
  485. package/dist/sync/watcher.d.ts +283 -0
  486. package/dist/sync/watcher.d.ts.map +1 -0
  487. package/dist/sync/watcher.js +606 -0
  488. package/dist/sync/watcher.js.map +1 -0
  489. package/dist/sync/worktree.d.ts +54 -0
  490. package/dist/sync/worktree.d.ts.map +1 -0
  491. package/dist/sync/worktree.js +137 -0
  492. package/dist/sync/worktree.js.map +1 -0
  493. package/dist/types.d.ts +623 -0
  494. package/dist/types.d.ts.map +1 -0
  495. package/dist/types.js +108 -0
  496. package/dist/types.js.map +1 -0
  497. package/dist/ui/glyphs.d.ts +42 -0
  498. package/dist/ui/glyphs.d.ts.map +1 -0
  499. package/dist/ui/glyphs.js +78 -0
  500. package/dist/ui/glyphs.js.map +1 -0
  501. package/dist/ui/shimmer-progress.d.ts +11 -0
  502. package/dist/ui/shimmer-progress.d.ts.map +1 -0
  503. package/dist/ui/shimmer-progress.js +90 -0
  504. package/dist/ui/shimmer-progress.js.map +1 -0
  505. package/dist/ui/shimmer-worker.d.ts +2 -0
  506. package/dist/ui/shimmer-worker.d.ts.map +1 -0
  507. package/dist/ui/shimmer-worker.js +118 -0
  508. package/dist/ui/shimmer-worker.js.map +1 -0
  509. package/dist/ui/types.d.ts +17 -0
  510. package/dist/ui/types.d.ts.map +1 -0
  511. package/dist/ui/types.js +3 -0
  512. package/dist/ui/types.js.map +1 -0
  513. package/dist/utils.d.ts +205 -0
  514. package/dist/utils.d.ts.map +1 -0
  515. package/dist/utils.js +549 -0
  516. package/dist/utils.js.map +1 -0
  517. package/dist/web/chunk-2AJCHB7P.js +1 -0
  518. package/dist/web/chunk-2CPLUFCH.js +2 -0
  519. package/dist/web/chunk-2GBEK2GM.js +1 -0
  520. package/dist/web/chunk-2I7L37NS.js +1 -0
  521. package/dist/web/chunk-2NAWAJB5.js +1 -0
  522. package/dist/web/chunk-2OJBIPE4.js +1 -0
  523. package/dist/web/chunk-2YUJNZ2Y.js +6 -0
  524. package/dist/web/chunk-3E2WB6D5.js +1 -0
  525. package/dist/web/chunk-3EBFYSCH.js +2 -0
  526. package/dist/web/chunk-3QCQ4BXS.js +1 -0
  527. package/dist/web/chunk-42XVAQ6I.js +1 -0
  528. package/dist/web/chunk-45QHGCB4.js +17 -0
  529. package/dist/web/chunk-4IMMPEYM.js +1 -0
  530. package/dist/web/chunk-4TJQJPCZ.js +1 -0
  531. package/dist/web/chunk-4WZIHTPC.js +1 -0
  532. package/dist/web/chunk-4YVSYOSD.js +1 -0
  533. package/dist/web/chunk-5BQIOYKW.js +1 -0
  534. package/dist/web/chunk-5HGWHUJA.js +1 -0
  535. package/dist/web/chunk-5Y244R4G.js +1 -0
  536. package/dist/web/chunk-6RRDPT5Z.js +1 -0
  537. package/dist/web/chunk-6VKB2ZWM.js +1 -0
  538. package/dist/web/chunk-7DMFVTU4.js +1 -0
  539. package/dist/web/chunk-7RNS77UP.js +1 -0
  540. package/dist/web/chunk-7SMPKVEP.js +1 -0
  541. package/dist/web/chunk-A5R3MJMO.js +1 -0
  542. package/dist/web/chunk-ASZ77FMZ.js +1 -0
  543. package/dist/web/chunk-AZJVTPLU.js +1 -0
  544. package/dist/web/chunk-B3YPFY6A.js +1 -0
  545. package/dist/web/chunk-BLBRMCN2.js +1 -0
  546. package/dist/web/chunk-BMIAXD2V.js +2 -0
  547. package/dist/web/chunk-BUXWEHIY.js +1 -0
  548. package/dist/web/chunk-BYZFQSM6.js +1 -0
  549. package/dist/web/chunk-D5OCNEJA.js +2 -0
  550. package/dist/web/chunk-DLQPZWSI.css +1 -0
  551. package/dist/web/chunk-DTRN7FZR.js +1 -0
  552. package/dist/web/chunk-DYRFLPJA.js +1 -0
  553. package/dist/web/chunk-E3J3CXR5.js +1 -0
  554. package/dist/web/chunk-E44X4RH2.js +1 -0
  555. package/dist/web/chunk-E73OX2P7.js +1 -0
  556. package/dist/web/chunk-EAXRKDLV.js +1 -0
  557. package/dist/web/chunk-EBKKDHYI.js +1 -0
  558. package/dist/web/chunk-EE7V7Q5P.js +1 -0
  559. package/dist/web/chunk-EKY2FUHU.js +1 -0
  560. package/dist/web/chunk-EMGMOEVR.js +1 -0
  561. package/dist/web/chunk-EP6XOPXH.js +1 -0
  562. package/dist/web/chunk-ESGDLJOJ.js +1 -0
  563. package/dist/web/chunk-ETJG7NCY.js +1 -0
  564. package/dist/web/chunk-EUUEFEDI.js +1 -0
  565. package/dist/web/chunk-FGNZDHTL.js +11 -0
  566. package/dist/web/chunk-FHZHD2ZG.js +1 -0
  567. package/dist/web/chunk-FIJW2UNJ.js +1 -0
  568. package/dist/web/chunk-FMV5PXRC.js +5 -0
  569. package/dist/web/chunk-G7VZT5KB.js +3 -0
  570. package/dist/web/chunk-GR72OOCN.js +1 -0
  571. package/dist/web/chunk-GRZYXPSO.js +7 -0
  572. package/dist/web/chunk-GWPVKJIY.js +1 -0
  573. package/dist/web/chunk-GYGPS3AN.js +1 -0
  574. package/dist/web/chunk-H7AF7YS4.js +1 -0
  575. package/dist/web/chunk-HDZDQILN.js +1 -0
  576. package/dist/web/chunk-HMK6UO6N.js +1 -0
  577. package/dist/web/chunk-HZA6NEAB.js +1 -0
  578. package/dist/web/chunk-IHEE5NYJ.js +1 -0
  579. package/dist/web/chunk-ISNEBICW.js +1 -0
  580. package/dist/web/chunk-J2GZVLHH.js +1 -0
  581. package/dist/web/chunk-JTFXTIPE.js +903 -0
  582. package/dist/web/chunk-L37MTFSG.js +3 -0
  583. package/dist/web/chunk-LB6JPLX2.js +1 -0
  584. package/dist/web/chunk-LNSVDHCI.js +1 -0
  585. package/dist/web/chunk-LVGIY3SO.js +1 -0
  586. package/dist/web/chunk-LXLHIHEN.js +1 -0
  587. package/dist/web/chunk-MC4DFIHG.js +1 -0
  588. package/dist/web/chunk-MVOMVPYB.js +1 -0
  589. package/dist/web/chunk-N6SS4G6S.js +1 -0
  590. package/dist/web/chunk-NTBJG6SJ.js +1 -0
  591. package/dist/web/chunk-NUDB3Q2Y.js +3 -0
  592. package/dist/web/chunk-NZEZCT65.js +1 -0
  593. package/dist/web/chunk-OXEF5E3E.js +1 -0
  594. package/dist/web/chunk-PDN6QYGJ.js +4 -0
  595. package/dist/web/chunk-PGGJPDJG.js +1 -0
  596. package/dist/web/chunk-PUYSJNJR.js +1 -0
  597. package/dist/web/chunk-Q2RVFS45.js +1 -0
  598. package/dist/web/chunk-Q7L6LLAK.js +1 -0
  599. package/dist/web/chunk-QCMKJIWY.js +1 -0
  600. package/dist/web/chunk-QH6CF3M3.js +1 -0
  601. package/dist/web/chunk-QQ5LD7PI.js +1 -0
  602. package/dist/web/chunk-QR6L3KAC.js +1 -0
  603. package/dist/web/chunk-R2DLK4HO.js +1 -0
  604. package/dist/web/chunk-R5W2MDZN.js +1 -0
  605. package/dist/web/chunk-RD6TVPOT.js +1 -0
  606. package/dist/web/chunk-RKY4EJYJ.js +1 -0
  607. package/dist/web/chunk-RONYWVY7.js +1 -0
  608. package/dist/web/chunk-RXKXYF2C.js +1 -0
  609. package/dist/web/chunk-SBWU7JFC.js +1 -0
  610. package/dist/web/chunk-SEXBRGYK.js +1 -0
  611. package/dist/web/chunk-SHPTC4RL.js +1 -0
  612. package/dist/web/chunk-SUZYBYDW.js +1 -0
  613. package/dist/web/chunk-SWKJRNYY.js +1 -0
  614. package/dist/web/chunk-T66XVKGB.js +1 -0
  615. package/dist/web/chunk-T7AZ65JP.js +1 -0
  616. package/dist/web/chunk-TCZDVOHD.js +1 -0
  617. package/dist/web/chunk-TPTITA3V.js +1 -0
  618. package/dist/web/chunk-TR335633.js +1 -0
  619. package/dist/web/chunk-UBOZGQNK.js +1 -0
  620. package/dist/web/chunk-UR5KDXPX.js +1 -0
  621. package/dist/web/chunk-UR6O2GEH.js +1 -0
  622. package/dist/web/chunk-UTNMGWTP.js +1 -0
  623. package/dist/web/chunk-UYC52MBC.js +1 -0
  624. package/dist/web/chunk-VECWMHJP.js +1 -0
  625. package/dist/web/chunk-VUACT35R.js +3 -0
  626. package/dist/web/chunk-VZI7H4SZ.js +1 -0
  627. package/dist/web/chunk-WAI2JMZP.js +1 -0
  628. package/dist/web/chunk-WB6YHOD4.js +1 -0
  629. package/dist/web/chunk-WBT64AWV.js +1 -0
  630. package/dist/web/chunk-WCKHQIYN.js +1 -0
  631. package/dist/web/chunk-WDU3WICG.js +1 -0
  632. package/dist/web/chunk-WFXJIXZE.js +4 -0
  633. package/dist/web/chunk-WLIMNDS3.js +1 -0
  634. package/dist/web/chunk-WTGYRH3Z.js +298 -0
  635. package/dist/web/chunk-WXTCVDTP.js +1 -0
  636. package/dist/web/chunk-X2HTISHL.js +1 -0
  637. package/dist/web/chunk-XCDHWLVH.js +1 -0
  638. package/dist/web/chunk-Y3H6FFUZ.js +1 -0
  639. package/dist/web/chunk-Y4F5ULGJ.js +1 -0
  640. package/dist/web/chunk-YAMRN47K.js +2 -0
  641. package/dist/web/chunk-YEGKAAEE.js +1 -0
  642. package/dist/web/chunk-YM2KU57F.js +1 -0
  643. package/dist/web/chunk-YRERBP6T.js +1 -0
  644. package/dist/web/chunk-ZLV4VCDG.js +3 -0
  645. package/dist/web/chunk-ZTVI5KFF.js +1 -0
  646. package/dist/web/favicon-16.png +0 -0
  647. package/dist/web/favicon-180.png +0 -0
  648. package/dist/web/favicon-32.png +0 -0
  649. package/dist/web/favicon-512.png +0 -0
  650. package/dist/web/favicon-small.svg +15 -0
  651. package/dist/web/favicon.ico +0 -0
  652. package/dist/web/favicon.svg +20 -0
  653. package/dist/web/index.html +145 -0
  654. package/dist/web/main-ESADRXN2.css +1 -0
  655. package/dist/web/main-R53HA54V.js +1 -0
  656. package/dist/web/media/codicon-LN6W7LCM.ttf +0 -0
  657. package/dist/web/styles-KSOPUVDA.css +1 -0
  658. package/dist/web/sw.js +69 -0
  659. package/dist/workflows/condition-evaluator.d.ts +75 -0
  660. package/dist/workflows/condition-evaluator.d.ts.map +1 -0
  661. package/dist/workflows/condition-evaluator.js +282 -0
  662. package/dist/workflows/condition-evaluator.js.map +1 -0
  663. package/dist/workflows/defaults/claude-design-implement.yaml +336 -0
  664. package/dist/workflows/defaults/index.d.ts +26 -0
  665. package/dist/workflows/defaults/index.d.ts.map +1 -0
  666. package/dist/workflows/defaults/index.js +94 -0
  667. package/dist/workflows/defaults/index.js.map +1 -0
  668. package/dist/workflows/defaults/spec-author.yaml +214 -0
  669. package/dist/workflows/defaults/spec-fix.yaml +110 -0
  670. package/dist/workflows/defaults/spec-implement.yaml +150 -0
  671. package/dist/workflows/defaults/spec-relink.yaml +81 -0
  672. package/dist/workflows/defaults/spec-verify.yaml +51 -0
  673. package/dist/workflows/discovery.d.ts +46 -0
  674. package/dist/workflows/discovery.d.ts.map +1 -0
  675. package/dist/workflows/discovery.js +193 -0
  676. package/dist/workflows/discovery.js.map +1 -0
  677. package/dist/workflows/executor.d.ts +83 -0
  678. package/dist/workflows/executor.d.ts.map +1 -0
  679. package/dist/workflows/executor.js +624 -0
  680. package/dist/workflows/executor.js.map +1 -0
  681. package/dist/workflows/runners/approval.d.ts +18 -0
  682. package/dist/workflows/runners/approval.d.ts.map +1 -0
  683. package/dist/workflows/runners/approval.js +34 -0
  684. package/dist/workflows/runners/approval.js.map +1 -0
  685. package/dist/workflows/runners/bash.d.ts +13 -0
  686. package/dist/workflows/runners/bash.d.ts.map +1 -0
  687. package/dist/workflows/runners/bash.js +143 -0
  688. package/dist/workflows/runners/bash.js.map +1 -0
  689. package/dist/workflows/runners/cancel.d.ts +10 -0
  690. package/dist/workflows/runners/cancel.d.ts.map +1 -0
  691. package/dist/workflows/runners/cancel.js +19 -0
  692. package/dist/workflows/runners/cancel.js.map +1 -0
  693. package/dist/workflows/runners/prompt.d.ts +28 -0
  694. package/dist/workflows/runners/prompt.d.ts.map +1 -0
  695. package/dist/workflows/runners/prompt.js +212 -0
  696. package/dist/workflows/runners/prompt.js.map +1 -0
  697. package/dist/workflows/runners/script.d.ts +17 -0
  698. package/dist/workflows/runners/script.d.ts.map +1 -0
  699. package/dist/workflows/runners/script.js +155 -0
  700. package/dist/workflows/runners/script.js.map +1 -0
  701. package/dist/workflows/runners/types.d.ts +51 -0
  702. package/dist/workflows/runners/types.d.ts.map +1 -0
  703. package/dist/workflows/runners/types.js +13 -0
  704. package/dist/workflows/runners/types.js.map +1 -0
  705. package/dist/workflows/schemas/workflow.d.ts +166 -0
  706. package/dist/workflows/schemas/workflow.d.ts.map +1 -0
  707. package/dist/workflows/schemas/workflow.js +437 -0
  708. package/dist/workflows/schemas/workflow.js.map +1 -0
  709. package/hooks/hooks.json +11 -0
  710. package/package.json +7 -3
  711. package/scripts/offline-install.sh +19 -6
  712. package/selectors.json +41 -0
  713. /package/commands/{cg-explore.md → ss-explore.md} +0 -0
  714. /package/commands/{cg-impact.md → ss-impact.md} +0 -0
  715. /package/commands/{cg-relink.md → ss-relink.md} +0 -0
  716. /package/commands/{cg-spec.md → ss-spec.md} +0 -0
  717. /package/commands/{cg-sync.md → ss-sync.md} +0 -0
  718. /package/commands/{cg-trace.md → ss-trace.md} +0 -0
@@ -0,0 +1,506 @@
1
+ /**
2
+ * JSONL → SQLite ingestor for Claude Code transcripts.
3
+ *
4
+ * Each Claude Code session lives in `~/.claude/projects/<slug>/<sessionId>.jsonl`,
5
+ * where `<slug>` is the slash-escaped absolute path of the user's project.
6
+ * Files are append-only (Claude Code writes one JSON line per assistant/user
7
+ * turn), so we resume from the last byte offset on every pass — no re-parse
8
+ * of the whole file.
9
+ *
10
+ * Pipeline per file:
11
+ * 1. Read state from `claude_ingest_state` (offset, last_size). If file
12
+ * grew, open it and seek to offset; otherwise skip.
13
+ * 2. Parse each line with parseLine(). Group entries by promptId.
14
+ * 3. For each user entry: upsert claude_prompts row. For each assistant
15
+ * entry: aggregate usage, compute cost, accumulate tool_use blocks.
16
+ * 4. For each tool_use block: insert into claude_tool_calls with
17
+ * input_summary + result_length (matched from the next user entry's
18
+ * tool_result content).
19
+ * 5. Update claude_sessions aggregates (prompt_count, totals).
20
+ * 6. Persist new offset + file_size to claude_ingest_state.
21
+ *
22
+ * The ingestor is idempotent — re-running on the same offset is a no-op.
23
+ * Crash-safe — partial writes use a transaction per file.
24
+ */
25
+ import * as fs from 'fs';
26
+ import * as os from 'os';
27
+ import * as path from 'path';
28
+ import { computeCost, resolvePricing } from './pricing.js';
29
+ import { parseLine, toEpochMs, extractUserPrompt, summarizeToolInput, toolResultLength, } from './parser.js';
30
+ /**
31
+ * Convert Claude's slash-escaped project dir name back into a real path.
32
+ * `~/.claude/projects/-Users-alice-projects-foo` → `/Users/alice/projects/foo`
33
+ */
34
+ export function decodeProjectSlug(slug) {
35
+ return slug.startsWith('-') ? '/' + slug.slice(1).replace(/-/g, '/') : slug;
36
+ }
37
+ /**
38
+ * List every JSONL file inside `<claudeRoot>/<slug>/<sessionId>.jsonl`.
39
+ * Returns the absolute file path + the decoded project path.
40
+ */
41
+ export function listTranscriptFiles(claudeRoot) {
42
+ const out = [];
43
+ let projects;
44
+ try {
45
+ projects = fs.readdirSync(claudeRoot, { withFileTypes: true });
46
+ }
47
+ catch {
48
+ return out;
49
+ }
50
+ for (const p of projects) {
51
+ if (!p.isDirectory())
52
+ continue;
53
+ const dir = path.join(claudeRoot, p.name);
54
+ let files;
55
+ try {
56
+ files = fs.readdirSync(dir, { withFileTypes: true });
57
+ }
58
+ catch {
59
+ continue;
60
+ }
61
+ for (const f of files) {
62
+ if (f.isFile() && f.name.toLowerCase().endsWith('.jsonl')) {
63
+ out.push({
64
+ filePath: path.join(dir, f.name),
65
+ projectPath: decodeProjectSlug(p.name),
66
+ projectSlug: p.name,
67
+ });
68
+ }
69
+ }
70
+ }
71
+ return out;
72
+ }
73
+ /**
74
+ * Default Anthropic pricing tiers (USD per 1M tokens) — used to seed the
75
+ * pricing table on first run if it's empty. Keeping these here (not just
76
+ * in the v6 migration) makes ingest self-sufficient: if a user upgrades
77
+ * specship but their migration ran in an older binary that didn't seed,
78
+ * the ingestor seeds on first pass instead of failing silently.
79
+ */
80
+ const DEFAULT_PRICING = [
81
+ ['claude-opus-4-7', 15.0, 75.0, 18.75, 1.5],
82
+ ['claude-opus-4', 15.0, 75.0, 18.75, 1.5],
83
+ ['claude-sonnet-4-6', 3.0, 15.0, 3.75, 0.3],
84
+ ['claude-sonnet-4-7', 3.0, 15.0, 3.75, 0.3],
85
+ ['claude-sonnet-4', 3.0, 15.0, 3.75, 0.3],
86
+ ['claude-haiku-4-5', 0.80, 4.0, 1.0, 0.08],
87
+ ['claude-haiku-4', 0.80, 4.0, 1.0, 0.08],
88
+ ];
89
+ /** Load the pricing table once per ingest pass. Seeds defaults if empty. */
90
+ function loadPricing(db) {
91
+ let rows = db
92
+ .prepare('SELECT model, input_per_mtok, output_per_mtok, cache_creation_per_mtok, cache_read_per_mtok FROM claude_pricing')
93
+ .all();
94
+ if (rows.length === 0) {
95
+ const now = Date.now();
96
+ const ins = db.prepare(`
97
+ INSERT OR IGNORE INTO claude_pricing
98
+ (model, input_per_mtok, output_per_mtok, cache_creation_per_mtok, cache_read_per_mtok, updated_at)
99
+ VALUES (?, ?, ?, ?, ?, ?)
100
+ `);
101
+ for (const r of DEFAULT_PRICING)
102
+ ins.run(r[0], r[1], r[2], r[3], r[4], now);
103
+ rows = db
104
+ .prepare('SELECT model, input_per_mtok, output_per_mtok, cache_creation_per_mtok, cache_read_per_mtok FROM claude_pricing')
105
+ .all();
106
+ }
107
+ return rows;
108
+ }
109
+ /**
110
+ * Read a file's tail from `fromOffset` to end. Returns the full text plus
111
+ * the new file size. We read the whole tail at once — JSONL transcripts are
112
+ * usually < 5MB even for long sessions, well within Node's sync read budget.
113
+ */
114
+ function readTail(filePath, fromOffset) {
115
+ const stat = fs.statSync(filePath);
116
+ if (stat.size <= fromOffset)
117
+ return { text: '', size: stat.size };
118
+ const fd = fs.openSync(filePath, 'r');
119
+ try {
120
+ const length = stat.size - fromOffset;
121
+ const buf = Buffer.allocUnsafe(length);
122
+ fs.readSync(fd, buf, 0, length, fromOffset);
123
+ return { text: buf.toString('utf-8'), size: stat.size };
124
+ }
125
+ finally {
126
+ fs.closeSync(fd);
127
+ }
128
+ }
129
+ /**
130
+ * Walk every transcript file under claudeRoot, ingest any new bytes, and
131
+ * return aggregate stats. Synchronous: the caller controls cadence.
132
+ */
133
+ export function ingestAll(db, options = {}) {
134
+ const start = Date.now();
135
+ const claudeRoot = options.claudeRoot ?? path.join(os.homedir(), '.claude', 'projects');
136
+ const verbose = options.verbose ?? false;
137
+ const stats = {
138
+ filesScanned: 0,
139
+ filesSkipped: 0,
140
+ bytesIngested: 0,
141
+ linesParsed: 0,
142
+ promptsInserted: 0,
143
+ toolCallsInserted: 0,
144
+ errors: 0,
145
+ durationMs: 0,
146
+ };
147
+ const pricing = loadPricing(db);
148
+ const files = listTranscriptFiles(claudeRoot);
149
+ for (const f of files) {
150
+ stats.filesScanned++;
151
+ try {
152
+ const wasIngested = ingestFile(db, f.filePath, f.projectPath, pricing, options);
153
+ if (!wasIngested.modified) {
154
+ stats.filesSkipped++;
155
+ }
156
+ else {
157
+ stats.bytesIngested += wasIngested.bytes;
158
+ stats.linesParsed += wasIngested.lines;
159
+ stats.promptsInserted += wasIngested.prompts;
160
+ stats.toolCallsInserted += wasIngested.toolCalls;
161
+ }
162
+ }
163
+ catch (err) {
164
+ stats.errors++;
165
+ if (verbose) {
166
+ // eslint-disable-next-line no-console
167
+ console.error('[ingest] failed:', f.filePath, err instanceof Error ? err.message : String(err));
168
+ }
169
+ }
170
+ }
171
+ stats.durationMs = Date.now() - start;
172
+ return stats;
173
+ }
174
+ function ingestFile(db, filePath, projectPath, pricing, options) {
175
+ // Load state.
176
+ const stateRow = db
177
+ .prepare('SELECT last_offset, file_size, session_id FROM claude_ingest_state WHERE file_path = ?')
178
+ .get(filePath);
179
+ const lastOffset = stateRow?.last_offset ?? 0;
180
+ const { text, size } = readTail(filePath, lastOffset);
181
+ if (text.length === 0) {
182
+ return { modified: false, bytes: 0, lines: 0, prompts: 0, toolCalls: 0 };
183
+ }
184
+ const lines = text.split('\n');
185
+ // The last fragment may be a partial line (the JSONL is append-only — Claude
186
+ // might be mid-write). Only consume complete lines; remember where the last
187
+ // complete newline ends so we resume from there.
188
+ let consumedLen = 0;
189
+ const completeLines = [];
190
+ for (let i = 0; i < lines.length; i++) {
191
+ const line = lines[i];
192
+ if (line === undefined)
193
+ continue;
194
+ // Last entry: if there's no trailing newline, treat as incomplete.
195
+ if (i === lines.length - 1) {
196
+ // If text ends with "\n", the last split element is "" — already complete.
197
+ if (line === '') {
198
+ consumedLen += 0; // already accounted for
199
+ }
200
+ else if (text.endsWith('\n')) {
201
+ completeLines.push(line);
202
+ consumedLen += line.length + 1;
203
+ }
204
+ else {
205
+ // partial — leave for next pass
206
+ }
207
+ continue;
208
+ }
209
+ completeLines.push(line);
210
+ consumedLen += line.length + 1; // +1 for the newline
211
+ }
212
+ const newOffset = lastOffset + consumedLen;
213
+ if (completeLines.length === 0) {
214
+ return { modified: false, bytes: 0, lines: 0, prompts: 0, toolCalls: 0 };
215
+ }
216
+ // Project lazy upsert — keep first_seen on insert, bump last_seen on update.
217
+ const projectSlug = path.basename(path.dirname(filePath));
218
+ const projectName = decodeProjectSlug(projectSlug);
219
+ const now = Date.now();
220
+ db.prepare(`
221
+ INSERT INTO claude_projects (path, name, first_seen, last_seen)
222
+ VALUES (?, ?, ?, ?)
223
+ ON CONFLICT(path) DO UPDATE SET last_seen = excluded.last_seen
224
+ `).run(projectPath, projectName, now, now);
225
+ // Per-file ingest in a transaction.
226
+ const txn = db.transaction(() => {
227
+ return processLines(db, filePath, projectPath, completeLines, pricing);
228
+ });
229
+ const result = txn();
230
+ // Persist new offset.
231
+ db.prepare(`
232
+ INSERT INTO claude_ingest_state (file_path, last_offset, last_ingested_at, file_size, session_id)
233
+ VALUES (?, ?, ?, ?, ?)
234
+ ON CONFLICT(file_path) DO UPDATE SET
235
+ last_offset = excluded.last_offset,
236
+ last_ingested_at = excluded.last_ingested_at,
237
+ file_size = excluded.file_size,
238
+ session_id = excluded.session_id
239
+ `).run(filePath, newOffset, now, size, result.lastSessionId);
240
+ if (options.verbose) {
241
+ // eslint-disable-next-line no-console
242
+ console.error(`[ingest] ${path.basename(filePath)}: +${result.prompts} prompts, +${result.toolCalls} tools, +${consumedLen}b`);
243
+ }
244
+ return {
245
+ modified: true,
246
+ bytes: consumedLen,
247
+ lines: completeLines.length,
248
+ prompts: result.prompts,
249
+ toolCalls: result.toolCalls,
250
+ };
251
+ }
252
+ /**
253
+ * Single-pass over the entries in this file batch. We need to relate
254
+ * tool_use blocks (in assistant entries) to their tool_result blocks (in
255
+ * the next user entry) so result_length is captured. Maintain a map of
256
+ * pending tool_use_id → { promptId, name, summary, ts } as we walk.
257
+ */
258
+ function processLines(db, filePath, projectPath, completeLines, pricing) {
259
+ const insSession = db.prepare(`
260
+ INSERT INTO claude_sessions (id, project_path, source_file, started_at, ended_at, prompt_count, last_model)
261
+ VALUES (?, ?, ?, ?, ?, 0, ?)
262
+ ON CONFLICT(id) DO UPDATE SET
263
+ ended_at = excluded.ended_at,
264
+ last_model = COALESCE(excluded.last_model, claude_sessions.last_model)
265
+ `);
266
+ const incSessionAggregates = db.prepare(`
267
+ UPDATE claude_sessions SET
268
+ prompt_count = prompt_count + ?,
269
+ total_input_tokens = total_input_tokens + ?,
270
+ total_output_tokens = total_output_tokens + ?,
271
+ total_cache_creation_tokens = total_cache_creation_tokens + ?,
272
+ total_cache_read_tokens = total_cache_read_tokens + ?,
273
+ total_cost_usd = total_cost_usd + ?
274
+ WHERE id = ?
275
+ `);
276
+ const insPrompt = db.prepare(`
277
+ INSERT INTO claude_prompts (id, session_id, text, ts, leaf_uuid, model, input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens, cost_usd, is_sidechain)
278
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
279
+ ON CONFLICT(id) DO UPDATE SET
280
+ text = COALESCE(excluded.text, claude_prompts.text),
281
+ model = COALESCE(excluded.model, claude_prompts.model),
282
+ input_tokens = excluded.input_tokens,
283
+ output_tokens = excluded.output_tokens,
284
+ cache_creation_tokens = excluded.cache_creation_tokens,
285
+ cache_read_tokens = excluded.cache_read_tokens,
286
+ cost_usd = excluded.cost_usd
287
+ `);
288
+ const insToolCall = db.prepare(`
289
+ INSERT INTO claude_tool_calls (prompt_id, session_id, assistant_uuid, tool_use_id, tool_name, input_summary, input_json, result_length, ts)
290
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
291
+ `);
292
+ /**
293
+ * Append the assistant's text + thinking blocks from one assistant turn
294
+ * onto the prompt row. A single user prompt can span multiple assistant
295
+ * turns (model re-renders / continues after a tool round-trip), so we
296
+ * concatenate with `||` instead of overwriting. Empty contributions
297
+ * (e.g. an assistant turn that's all tool_use) leave the column NULL
298
+ * if nothing was previously accumulated — kept as NULL so the UI can
299
+ * cleanly hide the section.
300
+ */
301
+ const appendPromptText = db.prepare(`
302
+ UPDATE claude_prompts SET
303
+ assistant_text = CASE
304
+ WHEN ? = '' THEN assistant_text
305
+ WHEN assistant_text IS NULL THEN ?
306
+ ELSE assistant_text || ?
307
+ END,
308
+ thinking_text = CASE
309
+ WHEN ? = '' THEN thinking_text
310
+ WHEN thinking_text IS NULL THEN ?
311
+ ELSE thinking_text || ?
312
+ END
313
+ WHERE id = ?
314
+ `);
315
+ let lastSessionId = null;
316
+ let promptsInserted = 0;
317
+ let toolCallsInserted = 0;
318
+ // Dedupe state for "user entries that share a promptId with one we
319
+ // already inserted." `insertedPromptIds` covers the in-batch case
320
+ // (sequential tool_results inside the same JSONL chunk); the prepared
321
+ // `existsPromptStmt` covers the cross-batch case (tool_results that
322
+ // arrive after the original prompt landed in a previous batch).
323
+ const insertedPromptIds = new Set();
324
+ const existsPromptStmt = db.prepare(`SELECT 1 FROM claude_prompts WHERE id = ?`);
325
+ // Track active prompt context. Only user entries carry a `promptId` in the
326
+ // JSONL — assistant entries belong to whichever prompt the most-recent user
327
+ // entry started. When we resume from a saved offset, the first lines in the
328
+ // batch are typically assistant turns answering a prompt whose user entry
329
+ // landed in an earlier batch, so `activePromptId` starts null and we must
330
+ // recover it from the DB; otherwise tool_use blocks queue with a fabricated
331
+ // promptId and the eventual tool_result insert violates the prompt_id FK,
332
+ // rolling back the whole batch transaction and stalling the file forever.
333
+ let activePromptId = null;
334
+ const lookupLatestPrompt = db.prepare(`SELECT id FROM claude_prompts WHERE session_id = ? ORDER BY ts DESC LIMIT 1`);
335
+ const resolveActivePromptId = (sessionId) => {
336
+ if (activePromptId)
337
+ return activePromptId;
338
+ const row = lookupLatestPrompt.get(sessionId);
339
+ if (row?.id)
340
+ activePromptId = row.id;
341
+ return activePromptId;
342
+ };
343
+ const pendingTools = new Map();
344
+ for (const raw of completeLines) {
345
+ const entry = parseLine(raw);
346
+ if (!entry)
347
+ continue;
348
+ const sessionId = entry.sessionId ?? '';
349
+ if (!sessionId)
350
+ continue;
351
+ lastSessionId = sessionId;
352
+ const ts = toEpochMs(entry.timestamp);
353
+ if (entry.type === 'user') {
354
+ // Bookkeep session row first.
355
+ insSession.run(sessionId, projectPath, filePath, ts, ts, null);
356
+ const text = extractUserPrompt(entry);
357
+ const promptId = entry.promptId ?? entry.uuid ?? null;
358
+ const isSidechain = entry.isSidechain ? 1 : 0;
359
+ if (promptId) {
360
+ activePromptId = promptId;
361
+ // Claude Code emits MULTIPLE user-type entries per logical prompt:
362
+ // the initial user message + one entry per tool_result the assistant
363
+ // requested. All share the same `promptId`. The original ingestor
364
+ // ran insPrompt + bumped prompt_count for every one of them — so a
365
+ // 50-prompt session with ~15 tool calls each landed in the DB as
366
+ // 800+ "prompts" and the per-prompt token columns were repeatedly
367
+ // reset to 0 via the ON CONFLICT DO UPDATE clause. Detect follow-up
368
+ // tool_result entries by checking whether the prompt row already
369
+ // exists (in this batch or persisted from an earlier batch) and
370
+ // skip both the upsert and the aggregate bump for them.
371
+ const isFollowUp = insertedPromptIds.has(promptId) ||
372
+ !!existsPromptStmt.get(promptId);
373
+ if (!isFollowUp) {
374
+ // First time we've seen this promptId — INSERT, count it, mark seen.
375
+ insPrompt.run(promptId, sessionId, text || null, ts, entry.leafUuid ?? null, null, 0, 0, 0, 0, 0, isSidechain);
376
+ promptsInserted++;
377
+ insertedPromptIds.add(promptId);
378
+ incSessionAggregates.run(1, 0, 0, 0, 0, 0, sessionId);
379
+ }
380
+ }
381
+ // Handle tool_result blocks: scan content for tool_result entries and
382
+ // update the matching pending tool_call row's result_length.
383
+ const content = entry.message?.content;
384
+ if (Array.isArray(content)) {
385
+ for (const block of content) {
386
+ if (block && block.type === 'tool_result' && block.tool_use_id) {
387
+ const len = toolResultLength(block);
388
+ const pending = pendingTools.get(block.tool_use_id);
389
+ if (pending) {
390
+ insToolCall.run(pending.promptId, pending.sessionId, pending.assistantUuid, block.tool_use_id, pending.toolName, pending.summary, pending.inputJson, len, pending.ts);
391
+ toolCallsInserted++;
392
+ pendingTools.delete(block.tool_use_id);
393
+ }
394
+ }
395
+ }
396
+ }
397
+ }
398
+ else if (entry.type === 'assistant') {
399
+ // Make sure session row exists.
400
+ insSession.run(sessionId, projectPath, filePath, ts, ts, entry.message?.model ?? null);
401
+ // Resolve the prompt this assistant turn belongs to. Never fall back to
402
+ // `entry.uuid` — that's the assistant's per-message id, not a row in
403
+ // claude_prompts, and using it would re-introduce the FK violation that
404
+ // caused this entire path to stall.
405
+ const promptId = resolveActivePromptId(sessionId);
406
+ if (!promptId)
407
+ continue;
408
+ const usage = entry.message?.usage;
409
+ const inputTok = usage?.input_tokens ?? 0;
410
+ const outputTok = usage?.output_tokens ?? 0;
411
+ const cacheCreate = usage?.cache_creation_input_tokens ?? 0;
412
+ const cacheRead = usage?.cache_read_input_tokens ?? 0;
413
+ const pricingRow = resolvePricing(entry.message?.model, pricing);
414
+ const cost = computeCost(usage, pricingRow);
415
+ // Update prompt's running usage. Re-uses ON CONFLICT to ADD to existing.
416
+ // Since INSERT...ON CONFLICT DO UPDATE replaces (not increments), do an
417
+ // explicit UPDATE here for the additive case.
418
+ db.prepare(`
419
+ UPDATE claude_prompts SET
420
+ input_tokens = input_tokens + ?,
421
+ output_tokens = output_tokens + ?,
422
+ cache_creation_tokens = cache_creation_tokens + ?,
423
+ cache_read_tokens = cache_read_tokens + ?,
424
+ cost_usd = cost_usd + ?,
425
+ model = COALESCE(?, model)
426
+ WHERE id = ?
427
+ `).run(inputTok, outputTok, cacheCreate, cacheRead, cost, entry.message?.model ?? null, promptId);
428
+ // Session aggregates: only token totals + cost. prompt_count was bumped
429
+ // when the user entry landed.
430
+ incSessionAggregates.run(0, inputTok, outputTok, cacheCreate, cacheRead, cost, sessionId);
431
+ // Scan content for tool_use, text, and thinking blocks. tool_use →
432
+ // queued for a later tool_result match; text + thinking → appended
433
+ // onto the prompt row so the dashboard can show the assistant's
434
+ // actual response, not just token counts. One pass over the array
435
+ // so we don't iterate twice on what can be a large list.
436
+ const content = entry.message?.content;
437
+ const assistantUuid = entry.uuid ?? '';
438
+ let assistantTextChunk = '';
439
+ let thinkingTextChunk = '';
440
+ if (Array.isArray(content)) {
441
+ for (const block of content) {
442
+ if (!block)
443
+ continue;
444
+ if (block.type === 'tool_use' && block.id && block.name && assistantUuid) {
445
+ // JSON-stringify the full input. Schema column is TEXT;
446
+ // large inputs (e.g. Bash with long heredocs) are fine as-is.
447
+ // Falsy / undefined / circular inputs degrade to a quiet null
448
+ // rather than throwing — the summary column still captures
449
+ // a display value.
450
+ let inputJson = null;
451
+ try {
452
+ inputJson = JSON.stringify(block.input ?? null);
453
+ }
454
+ catch {
455
+ inputJson = null;
456
+ }
457
+ pendingTools.set(block.id, {
458
+ promptId,
459
+ sessionId,
460
+ assistantUuid,
461
+ toolName: block.name,
462
+ summary: summarizeToolInput(block.name, block.input),
463
+ inputJson,
464
+ ts,
465
+ });
466
+ }
467
+ else if (block.type === 'text' && typeof block.text === 'string') {
468
+ // The assistant's prose response. Concatenated with the
469
+ // existing assistant_text via the appendPromptText UPDATE
470
+ // below — one prompt can span multiple assistant turns.
471
+ assistantTextChunk += block.text;
472
+ }
473
+ else if (block.type === 'thinking' && typeof block.text === 'string') {
474
+ // Extended thinking. Same accumulation pattern as text.
475
+ thinkingTextChunk += block.text;
476
+ }
477
+ }
478
+ }
479
+ if (assistantTextChunk || thinkingTextChunk) {
480
+ // Separate consecutive turn contributions with a blank line so
481
+ // multi-turn responses render with paragraph breaks instead of
482
+ // collapsing into a single run-on block.
483
+ const at = assistantTextChunk ? assistantTextChunk + '\n\n' : '';
484
+ const tt = thinkingTextChunk ? thinkingTextChunk + '\n\n' : '';
485
+ appendPromptText.run(at, at, at, tt, tt, tt, promptId);
486
+ }
487
+ }
488
+ // attachment / queue-operation / last-prompt entries are ignored for v1.
489
+ }
490
+ // Any remaining pendingTools didn't have a tool_result yet — they'll be
491
+ // matched on the next pass when the user reply arrives. We flush them with
492
+ // result_length=0 so the tool call still shows up in analytics (better to
493
+ // show "0 tokens returned" than to omit the call).
494
+ for (const [toolUseId, pending] of pendingTools) {
495
+ insToolCall.run(pending.promptId, pending.sessionId, pending.assistantUuid, toolUseId, pending.toolName, pending.summary, pending.inputJson, 0, pending.ts);
496
+ toolCallsInserted++;
497
+ }
498
+ return {
499
+ modified: true,
500
+ bytes: 0,
501
+ lines: completeLines.length,
502
+ prompts: promptsInserted,
503
+ toolCalls: toolCallsInserted,
504
+ lastSessionId,
505
+ };
506
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * JSONL line parser — converts one line of a Claude Code transcript into a
3
+ * structured ClaudeRawEntry, or null for unparseable / unrecognised lines.
4
+ *
5
+ * Tolerant: a malformed line never throws; the worker logs and skips it.
6
+ */
7
+ export function parseLine(line) {
8
+ const trimmed = line.trim();
9
+ if (!trimmed)
10
+ return null;
11
+ try {
12
+ const obj = JSON.parse(trimmed);
13
+ if (!obj || typeof obj !== 'object' || typeof obj.type !== 'string') {
14
+ return null;
15
+ }
16
+ return obj;
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ /** Convert a timestamp field (ISO string or epoch number) to epoch ms. */
23
+ export function toEpochMs(ts) {
24
+ if (ts == null)
25
+ return Date.now();
26
+ if (typeof ts === 'number') {
27
+ // Heuristic: 10-digit values are seconds, 13-digit are ms.
28
+ return ts > 1e12 ? ts : ts * 1000;
29
+ }
30
+ const parsed = Date.parse(ts);
31
+ return Number.isFinite(parsed) ? parsed : Date.now();
32
+ }
33
+ /** Extract the user-visible prompt text from a user-entry message. */
34
+ export function extractUserPrompt(entry) {
35
+ // Some user entries store text at the top level; most use message.content.
36
+ if (typeof entry.text === 'string' && entry.text.length > 0)
37
+ return entry.text;
38
+ const content = entry.message?.content;
39
+ if (typeof content === 'string')
40
+ return content;
41
+ if (Array.isArray(content)) {
42
+ return content
43
+ .filter((b) => b && typeof b === 'object')
44
+ .map((b) => (b.type === 'text' && typeof b.text === 'string' ? b.text : ''))
45
+ .filter((t) => t.length > 0)
46
+ .join('\n')
47
+ .trim();
48
+ }
49
+ return '';
50
+ }
51
+ /**
52
+ * For a tool_use block, build a short, format-aware input summary:
53
+ * - Read/Edit/Write → file path
54
+ * - Bash → first 200 chars of command
55
+ * - others → JSON of input, truncated to 200 chars
56
+ *
57
+ * This is what shows up in the heatmap drill-down and the tips engine.
58
+ */
59
+ export function summarizeToolInput(name, input) {
60
+ if (!input || typeof input !== 'object')
61
+ return '';
62
+ const n = name.toLowerCase();
63
+ // File-targeted tools
64
+ if (n === 'read' || n === 'edit' || n === 'write' || n === 'notebookedit') {
65
+ const p = (input.file_path ?? input.path ?? input.notebook_path);
66
+ if (typeof p === 'string')
67
+ return p.slice(0, 400);
68
+ }
69
+ if (n === 'glob') {
70
+ const p = (input.pattern ?? input.glob);
71
+ if (typeof p === 'string')
72
+ return p.slice(0, 400);
73
+ }
74
+ if (n === 'grep') {
75
+ const p = (input.pattern ?? input.query);
76
+ if (typeof p === 'string')
77
+ return p.slice(0, 400);
78
+ }
79
+ if (n === 'bash' || n === 'shell') {
80
+ const c = (input.command ?? input.cmd);
81
+ if (typeof c === 'string')
82
+ return c.slice(0, 400);
83
+ }
84
+ // MCP tool — input.tool_name + JSON keys
85
+ try {
86
+ const j = JSON.stringify(input);
87
+ return j.length > 400 ? j.slice(0, 400) + '…' : j;
88
+ }
89
+ catch {
90
+ return '';
91
+ }
92
+ }
93
+ /** Sum the character length of a tool_result content block. */
94
+ export function toolResultLength(block) {
95
+ if (!block)
96
+ return 0;
97
+ const c = block.content;
98
+ if (typeof c === 'string')
99
+ return c.length;
100
+ if (Array.isArray(c)) {
101
+ return c.reduce((sum, item) => sum + (typeof item?.text === 'string' ? item.text.length : 0), 0);
102
+ }
103
+ return 0;
104
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Cost calculation from Claude Code usage records.
3
+ *
4
+ * Pricing is per-model and per-token-bucket (input / output / cache write /
5
+ * cache read). The DB seeds defaults for the current Anthropic public tiers;
6
+ * users can override via the UI / CLI. The seeded rates live in the
7
+ * `claude_pricing` table — see the v6 migration in specship core.
8
+ *
9
+ * Model name matching is loose: Claude sometimes reports versioned model
10
+ * names like `claude-opus-4-7-20260601`. We strip the date suffix and try
11
+ * exact match first, then fall back to family-level rates (e.g. any
12
+ * `claude-opus-*` falls back to a generic Opus rate).
13
+ */
14
+ const FAMILY_FALLBACKS = {
15
+ opus: 'claude-opus-4',
16
+ sonnet: 'claude-sonnet-4',
17
+ haiku: 'claude-haiku-4',
18
+ };
19
+ /**
20
+ * Normalize a Claude model ID:
21
+ * - Lower-case
22
+ * - Strip trailing -YYYYMMDD or -YYYY-MM-DD date markers
23
+ * - Strip [1m] context-window suffixes (e.g. claude-opus-4-7[1m])
24
+ */
25
+ export function normalizeModelId(model) {
26
+ if (!model)
27
+ return '';
28
+ let m = model.toLowerCase();
29
+ m = m.replace(/\[\d+[a-z]+\]$/, '');
30
+ m = m.replace(/-(\d{8}|\d{4}-\d{2}-\d{2})$/, '');
31
+ return m;
32
+ }
33
+ /**
34
+ * Look up a PricingRow for a model. Tries:
35
+ * 1. exact match on normalized model id
36
+ * 2. exact match on the original id
37
+ * 3. family fallback (opus / sonnet / haiku → generic v4 rate)
38
+ * 4. null (caller should treat cost as 0)
39
+ */
40
+ export function resolvePricing(model, rows) {
41
+ if (!model)
42
+ return null;
43
+ const norm = normalizeModelId(model);
44
+ for (const r of rows) {
45
+ if (r.model.toLowerCase() === norm)
46
+ return r;
47
+ }
48
+ for (const r of rows) {
49
+ if (r.model.toLowerCase() === model.toLowerCase())
50
+ return r;
51
+ }
52
+ // Family fallback.
53
+ for (const [family, fallbackModel] of Object.entries(FAMILY_FALLBACKS)) {
54
+ if (norm.includes(family)) {
55
+ for (const r of rows) {
56
+ if (r.model.toLowerCase() === fallbackModel)
57
+ return r;
58
+ }
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+ /**
64
+ * Compute USD cost for one assistant turn's usage record. Returns 0 if no
65
+ * pricing row resolves — the caller will report that separately.
66
+ */
67
+ export function computeCost(usage, pricing) {
68
+ if (!usage || !pricing)
69
+ return 0;
70
+ const input = usage.input_tokens ?? 0;
71
+ const output = usage.output_tokens ?? 0;
72
+ const cacheCreate = usage.cache_creation_input_tokens ?? 0;
73
+ const cacheRead = usage.cache_read_input_tokens ?? 0;
74
+ return ((input * pricing.input_per_mtok) / 1_000_000 +
75
+ (output * pricing.output_per_mtok) / 1_000_000 +
76
+ (cacheCreate * pricing.cache_creation_per_mtok) / 1_000_000 +
77
+ (cacheRead * pricing.cache_read_per_mtok) / 1_000_000);
78
+ }