@selvakumaresra/specship 0.1.3 → 0.3.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.
- package/README.md +2 -2
- package/commands/ss-design-implement.md +79 -0
- package/commands/{cg-drifted.md → ss-drifted.md} +2 -2
- package/commands/{cg-fix.md → ss-fix.md} +1 -1
- package/commands/{cg-implement.md → ss-implement.md} +1 -1
- package/commands/ss-spec-author.md +43 -0
- package/commands/ss-spec-review.md +48 -0
- package/dist/bin/node-version-check.d.ts +37 -0
- package/dist/bin/node-version-check.d.ts.map +1 -0
- package/dist/bin/node-version-check.js +79 -0
- package/dist/bin/node-version-check.js.map +1 -0
- package/dist/bin/specship.d.ts +25 -0
- package/dist/bin/specship.d.ts.map +1 -0
- package/dist/bin/specship.js +2019 -0
- package/dist/bin/specship.js.map +1 -0
- package/dist/bin/uninstall.d.ts +13 -0
- package/dist/bin/uninstall.d.ts.map +1 -0
- package/dist/bin/uninstall.js +35 -0
- package/dist/bin/uninstall.js.map +1 -0
- package/dist/context/formatter.d.ts +30 -0
- package/dist/context/formatter.d.ts.map +1 -0
- package/dist/context/formatter.js +263 -0
- package/dist/context/formatter.js.map +1 -0
- package/dist/context/index.d.ts +119 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +1289 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/markers.d.ts +19 -0
- package/dist/context/markers.d.ts.map +1 -0
- package/dist/context/markers.js +22 -0
- package/dist/context/markers.js.map +1 -0
- package/dist/db/index.d.ts +103 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +279 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrations.d.ts +44 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +462 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/queries.d.ts +357 -0
- package/dist/db/queries.d.ts.map +1 -0
- package/dist/db/queries.js +1504 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/db/schema.sql +419 -0
- package/dist/db/spec-queries.d.ts +101 -0
- package/dist/db/spec-queries.d.ts.map +1 -0
- package/dist/db/spec-queries.js +675 -0
- package/dist/db/spec-queries.js.map +1 -0
- package/dist/db/sqlite-adapter.d.ts +65 -0
- package/dist/db/sqlite-adapter.d.ts.map +1 -0
- package/dist/db/sqlite-adapter.js +214 -0
- package/dist/db/sqlite-adapter.js.map +1 -0
- package/dist/directory.d.ts +67 -0
- package/dist/directory.d.ts.map +1 -0
- package/dist/directory.js +267 -0
- package/dist/directory.js.map +1 -0
- package/dist/errors.d.ts +136 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +219 -0
- package/dist/errors.js.map +1 -0
- package/dist/extraction/dfm-extractor.d.ts +31 -0
- package/dist/extraction/dfm-extractor.d.ts.map +1 -0
- package/dist/extraction/dfm-extractor.js +151 -0
- package/dist/extraction/dfm-extractor.js.map +1 -0
- package/dist/extraction/generated-detection.d.ts +30 -0
- package/dist/extraction/generated-detection.d.ts.map +1 -0
- package/dist/extraction/generated-detection.js +80 -0
- package/dist/extraction/generated-detection.js.map +1 -0
- package/dist/extraction/grammars.d.ts +100 -0
- package/dist/extraction/grammars.d.ts.map +1 -0
- package/dist/extraction/grammars.js +426 -0
- package/dist/extraction/grammars.js.map +1 -0
- package/dist/extraction/index.d.ts +138 -0
- package/dist/extraction/index.d.ts.map +1 -0
- package/dist/extraction/index.js +1394 -0
- package/dist/extraction/index.js.map +1 -0
- package/dist/extraction/languages/c-cpp.d.ts +4 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -0
- package/dist/extraction/languages/c-cpp.js +171 -0
- package/dist/extraction/languages/c-cpp.js.map +1 -0
- package/dist/extraction/languages/csharp.d.ts +3 -0
- package/dist/extraction/languages/csharp.d.ts.map +1 -0
- package/dist/extraction/languages/csharp.js +73 -0
- package/dist/extraction/languages/csharp.js.map +1 -0
- package/dist/extraction/languages/dart.d.ts +3 -0
- package/dist/extraction/languages/dart.d.ts.map +1 -0
- package/dist/extraction/languages/dart.js +192 -0
- package/dist/extraction/languages/dart.js.map +1 -0
- package/dist/extraction/languages/go.d.ts +3 -0
- package/dist/extraction/languages/go.d.ts.map +1 -0
- package/dist/extraction/languages/go.js +74 -0
- package/dist/extraction/languages/go.js.map +1 -0
- package/dist/extraction/languages/index.d.ts +10 -0
- package/dist/extraction/languages/index.d.ts.map +1 -0
- package/dist/extraction/languages/index.js +51 -0
- package/dist/extraction/languages/index.js.map +1 -0
- package/dist/extraction/languages/java.d.ts +3 -0
- package/dist/extraction/languages/java.d.ts.map +1 -0
- package/dist/extraction/languages/java.js +70 -0
- package/dist/extraction/languages/java.js.map +1 -0
- package/dist/extraction/languages/javascript.d.ts +3 -0
- package/dist/extraction/languages/javascript.d.ts.map +1 -0
- package/dist/extraction/languages/javascript.js +90 -0
- package/dist/extraction/languages/javascript.js.map +1 -0
- package/dist/extraction/languages/kotlin.d.ts +3 -0
- package/dist/extraction/languages/kotlin.d.ts.map +1 -0
- package/dist/extraction/languages/kotlin.js +259 -0
- package/dist/extraction/languages/kotlin.js.map +1 -0
- package/dist/extraction/languages/lua.d.ts +3 -0
- package/dist/extraction/languages/lua.d.ts.map +1 -0
- package/dist/extraction/languages/lua.js +150 -0
- package/dist/extraction/languages/lua.js.map +1 -0
- package/dist/extraction/languages/luau.d.ts +3 -0
- package/dist/extraction/languages/luau.d.ts.map +1 -0
- package/dist/extraction/languages/luau.js +37 -0
- package/dist/extraction/languages/luau.js.map +1 -0
- package/dist/extraction/languages/objc.d.ts +3 -0
- package/dist/extraction/languages/objc.d.ts.map +1 -0
- package/dist/extraction/languages/objc.js +133 -0
- package/dist/extraction/languages/objc.js.map +1 -0
- package/dist/extraction/languages/pascal.d.ts +3 -0
- package/dist/extraction/languages/pascal.d.ts.map +1 -0
- package/dist/extraction/languages/pascal.js +66 -0
- package/dist/extraction/languages/pascal.js.map +1 -0
- package/dist/extraction/languages/php.d.ts +3 -0
- package/dist/extraction/languages/php.d.ts.map +1 -0
- package/dist/extraction/languages/php.js +107 -0
- package/dist/extraction/languages/php.js.map +1 -0
- package/dist/extraction/languages/python.d.ts +3 -0
- package/dist/extraction/languages/python.d.ts.map +1 -0
- package/dist/extraction/languages/python.js +56 -0
- package/dist/extraction/languages/python.js.map +1 -0
- package/dist/extraction/languages/ruby.d.ts +3 -0
- package/dist/extraction/languages/ruby.d.ts.map +1 -0
- package/dist/extraction/languages/ruby.js +114 -0
- package/dist/extraction/languages/ruby.js.map +1 -0
- package/dist/extraction/languages/rust.d.ts +3 -0
- package/dist/extraction/languages/rust.d.ts.map +1 -0
- package/dist/extraction/languages/rust.js +109 -0
- package/dist/extraction/languages/rust.js.map +1 -0
- package/dist/extraction/languages/scala.d.ts +3 -0
- package/dist/extraction/languages/scala.d.ts.map +1 -0
- package/dist/extraction/languages/scala.js +139 -0
- package/dist/extraction/languages/scala.js.map +1 -0
- package/dist/extraction/languages/swift.d.ts +3 -0
- package/dist/extraction/languages/swift.d.ts.map +1 -0
- package/dist/extraction/languages/swift.js +91 -0
- package/dist/extraction/languages/swift.js.map +1 -0
- package/dist/extraction/languages/typescript.d.ts +3 -0
- package/dist/extraction/languages/typescript.d.ts.map +1 -0
- package/dist/extraction/languages/typescript.js +129 -0
- package/dist/extraction/languages/typescript.js.map +1 -0
- package/dist/extraction/liquid-extractor.d.ts +52 -0
- package/dist/extraction/liquid-extractor.d.ts.map +1 -0
- package/dist/extraction/liquid-extractor.js +313 -0
- package/dist/extraction/liquid-extractor.js.map +1 -0
- package/dist/extraction/mybatis-extractor.d.ts +48 -0
- package/dist/extraction/mybatis-extractor.d.ts.map +1 -0
- package/dist/extraction/mybatis-extractor.js +198 -0
- package/dist/extraction/mybatis-extractor.js.map +1 -0
- package/dist/extraction/parse-worker.d.ts +8 -0
- package/dist/extraction/parse-worker.d.ts.map +1 -0
- package/dist/extraction/parse-worker.js +94 -0
- package/dist/extraction/parse-worker.js.map +1 -0
- package/dist/extraction/specs/markdown-spec-extractor.d.ts +59 -0
- package/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -0
- package/dist/extraction/specs/markdown-spec-extractor.js +327 -0
- package/dist/extraction/specs/markdown-spec-extractor.js.map +1 -0
- package/dist/extraction/specs/types.d.ts +39 -0
- package/dist/extraction/specs/types.d.ts.map +1 -0
- package/dist/extraction/specs/types.js +8 -0
- package/dist/extraction/specs/types.js.map +1 -0
- package/dist/extraction/svelte-extractor.d.ts +56 -0
- package/dist/extraction/svelte-extractor.d.ts.map +1 -0
- package/dist/extraction/svelte-extractor.js +272 -0
- package/dist/extraction/svelte-extractor.js.map +1 -0
- package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
- package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -0
- package/dist/extraction/tree-sitter-helpers.js +103 -0
- package/dist/extraction/tree-sitter-helpers.js.map +1 -0
- package/dist/extraction/tree-sitter-types.d.ts +193 -0
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -0
- package/dist/extraction/tree-sitter-types.js +10 -0
- package/dist/extraction/tree-sitter-types.js.map +1 -0
- package/dist/extraction/tree-sitter.d.ts +317 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -0
- package/dist/extraction/tree-sitter.js +3092 -0
- package/dist/extraction/tree-sitter.js.map +1 -0
- package/dist/extraction/vue-extractor.d.ts +51 -0
- package/dist/extraction/vue-extractor.d.ts.map +1 -0
- package/dist/extraction/vue-extractor.js +251 -0
- package/dist/extraction/vue-extractor.js.map +1 -0
- package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-luau.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
- package/dist/extraction/wasm-runtime-flags.d.ts +38 -0
- package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -0
- package/dist/extraction/wasm-runtime-flags.js +106 -0
- package/dist/extraction/wasm-runtime-flags.js.map +1 -0
- package/dist/graph/index.d.ts +8 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +13 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/queries.d.ts +106 -0
- package/dist/graph/queries.d.ts.map +1 -0
- package/dist/graph/queries.js +366 -0
- package/dist/graph/queries.js.map +1 -0
- package/dist/graph/traversal.d.ts +127 -0
- package/dist/graph/traversal.d.ts.map +1 -0
- package/dist/graph/traversal.js +531 -0
- package/dist/graph/traversal.js.map +1 -0
- package/dist/index.d.ts +551 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1165 -0
- package/dist/index.js.map +1 -0
- package/dist/installer/config-writer.d.ts +28 -0
- package/dist/installer/config-writer.d.ts.map +1 -0
- package/dist/installer/config-writer.js +91 -0
- package/dist/installer/config-writer.js.map +1 -0
- package/dist/installer/index.d.ts +87 -0
- package/dist/installer/index.d.ts.map +1 -0
- package/dist/installer/index.js +415 -0
- package/dist/installer/index.js.map +1 -0
- package/dist/installer/instructions-template.d.ts +18 -0
- package/dist/installer/instructions-template.d.ts.map +1 -0
- package/dist/installer/instructions-template.js +21 -0
- package/dist/installer/instructions-template.js.map +1 -0
- package/dist/installer/targets/claude.d.ts +98 -0
- package/dist/installer/targets/claude.d.ts.map +1 -0
- package/dist/installer/targets/claude.js +639 -0
- package/dist/installer/targets/claude.js.map +1 -0
- package/dist/installer/targets/registry.d.ts +19 -0
- package/dist/installer/targets/registry.d.ts.map +1 -0
- package/dist/installer/targets/registry.js +31 -0
- package/dist/installer/targets/registry.js.map +1 -0
- package/dist/installer/targets/shared.d.ts +62 -0
- package/dist/installer/targets/shared.d.ts.map +1 -0
- package/dist/installer/targets/shared.js +207 -0
- package/dist/installer/targets/shared.js.map +1 -0
- package/dist/installer/targets/types.d.ts +76 -0
- package/dist/installer/targets/types.d.ts.map +1 -0
- package/dist/installer/targets/types.js +12 -0
- package/dist/installer/targets/types.js.map +1 -0
- package/dist/isolation/worktree.d.ts +65 -0
- package/dist/isolation/worktree.d.ts.map +1 -0
- package/dist/isolation/worktree.js +231 -0
- package/dist/isolation/worktree.js.map +1 -0
- package/dist/mcp/daemon-paths.d.ts +46 -0
- package/dist/mcp/daemon-paths.d.ts.map +1 -0
- package/dist/mcp/daemon-paths.js +125 -0
- package/dist/mcp/daemon-paths.js.map +1 -0
- package/dist/mcp/daemon.d.ts +161 -0
- package/dist/mcp/daemon.d.ts.map +1 -0
- package/dist/mcp/daemon.js +403 -0
- package/dist/mcp/daemon.js.map +1 -0
- package/dist/mcp/engine.d.ts +105 -0
- package/dist/mcp/engine.d.ts.map +1 -0
- package/dist/mcp/engine.js +270 -0
- package/dist/mcp/engine.js.map +1 -0
- package/dist/mcp/index.d.ts +112 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +477 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/proxy.d.ts +81 -0
- package/dist/mcp/proxy.d.ts.map +1 -0
- package/dist/mcp/proxy.js +510 -0
- package/dist/mcp/proxy.js.map +1 -0
- package/dist/mcp/server-instructions.d.ts +18 -0
- package/dist/mcp/server-instructions.d.ts.map +1 -0
- package/dist/mcp/server-instructions.js +77 -0
- package/dist/mcp/server-instructions.js.map +1 -0
- package/dist/mcp/session.d.ts +77 -0
- package/dist/mcp/session.d.ts.map +1 -0
- package/dist/mcp/session.js +294 -0
- package/dist/mcp/session.js.map +1 -0
- package/dist/mcp/spec-tools.d.ts +39 -0
- package/dist/mcp/spec-tools.d.ts.map +1 -0
- package/dist/mcp/spec-tools.js +326 -0
- package/dist/mcp/spec-tools.js.map +1 -0
- package/dist/mcp/tools.d.ts +404 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +3066 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/transport.d.ts +188 -0
- package/dist/mcp/transport.d.ts.map +1 -0
- package/dist/mcp/transport.js +343 -0
- package/dist/mcp/transport.js.map +1 -0
- package/dist/mcp/version.d.ts +19 -0
- package/dist/mcp/version.d.ts.map +1 -0
- package/dist/mcp/version.js +71 -0
- package/dist/mcp/version.js.map +1 -0
- package/dist/resolution/callback-synthesizer.d.ts +10 -0
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -0
- package/dist/resolution/callback-synthesizer.js +1300 -0
- package/dist/resolution/callback-synthesizer.js.map +1 -0
- package/dist/resolution/frameworks/cargo-workspace.d.ts +18 -0
- package/dist/resolution/frameworks/cargo-workspace.d.ts.map +1 -0
- package/dist/resolution/frameworks/cargo-workspace.js +225 -0
- package/dist/resolution/frameworks/cargo-workspace.js.map +1 -0
- package/dist/resolution/frameworks/csharp.d.ts +8 -0
- package/dist/resolution/frameworks/csharp.d.ts.map +1 -0
- package/dist/resolution/frameworks/csharp.js +241 -0
- package/dist/resolution/frameworks/csharp.js.map +1 -0
- package/dist/resolution/frameworks/drupal.d.ts +51 -0
- package/dist/resolution/frameworks/drupal.d.ts.map +1 -0
- package/dist/resolution/frameworks/drupal.js +367 -0
- package/dist/resolution/frameworks/drupal.js.map +1 -0
- package/dist/resolution/frameworks/expo-modules.d.ts +3 -0
- package/dist/resolution/frameworks/expo-modules.d.ts.map +1 -0
- package/dist/resolution/frameworks/expo-modules.js +143 -0
- package/dist/resolution/frameworks/expo-modules.js.map +1 -0
- package/dist/resolution/frameworks/express.d.ts +8 -0
- package/dist/resolution/frameworks/express.d.ts.map +1 -0
- package/dist/resolution/frameworks/express.js +308 -0
- package/dist/resolution/frameworks/express.js.map +1 -0
- package/dist/resolution/frameworks/fabric.d.ts +3 -0
- package/dist/resolution/frameworks/fabric.d.ts.map +1 -0
- package/dist/resolution/frameworks/fabric.js +354 -0
- package/dist/resolution/frameworks/fabric.js.map +1 -0
- package/dist/resolution/frameworks/go.d.ts +8 -0
- package/dist/resolution/frameworks/go.d.ts.map +1 -0
- package/dist/resolution/frameworks/go.js +161 -0
- package/dist/resolution/frameworks/go.js.map +1 -0
- package/dist/resolution/frameworks/index.d.ts +48 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -0
- package/dist/resolution/frameworks/index.js +161 -0
- package/dist/resolution/frameworks/index.js.map +1 -0
- package/dist/resolution/frameworks/java.d.ts +8 -0
- package/dist/resolution/frameworks/java.d.ts.map +1 -0
- package/dist/resolution/frameworks/java.js +504 -0
- package/dist/resolution/frameworks/java.js.map +1 -0
- package/dist/resolution/frameworks/laravel.d.ts +13 -0
- package/dist/resolution/frameworks/laravel.d.ts.map +1 -0
- package/dist/resolution/frameworks/laravel.js +257 -0
- package/dist/resolution/frameworks/laravel.js.map +1 -0
- package/dist/resolution/frameworks/nestjs.d.ts +26 -0
- package/dist/resolution/frameworks/nestjs.d.ts.map +1 -0
- package/dist/resolution/frameworks/nestjs.js +698 -0
- package/dist/resolution/frameworks/nestjs.js.map +1 -0
- package/dist/resolution/frameworks/play.d.ts +19 -0
- package/dist/resolution/frameworks/play.d.ts.map +1 -0
- package/dist/resolution/frameworks/play.js +111 -0
- package/dist/resolution/frameworks/play.js.map +1 -0
- package/dist/resolution/frameworks/python.d.ts +10 -0
- package/dist/resolution/frameworks/python.d.ts.map +1 -0
- package/dist/resolution/frameworks/python.js +396 -0
- package/dist/resolution/frameworks/python.js.map +1 -0
- package/dist/resolution/frameworks/react-native.d.ts +3 -0
- package/dist/resolution/frameworks/react-native.d.ts.map +1 -0
- package/dist/resolution/frameworks/react-native.js +360 -0
- package/dist/resolution/frameworks/react-native.js.map +1 -0
- package/dist/resolution/frameworks/react.d.ts +8 -0
- package/dist/resolution/frameworks/react.d.ts.map +1 -0
- package/dist/resolution/frameworks/react.js +365 -0
- package/dist/resolution/frameworks/react.js.map +1 -0
- package/dist/resolution/frameworks/ruby.d.ts +8 -0
- package/dist/resolution/frameworks/ruby.d.ts.map +1 -0
- package/dist/resolution/frameworks/ruby.js +302 -0
- package/dist/resolution/frameworks/ruby.js.map +1 -0
- package/dist/resolution/frameworks/rust.d.ts +8 -0
- package/dist/resolution/frameworks/rust.d.ts.map +1 -0
- package/dist/resolution/frameworks/rust.js +304 -0
- package/dist/resolution/frameworks/rust.js.map +1 -0
- package/dist/resolution/frameworks/svelte.d.ts +9 -0
- package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
- package/dist/resolution/frameworks/svelte.js +249 -0
- package/dist/resolution/frameworks/svelte.js.map +1 -0
- package/dist/resolution/frameworks/swift-objc.d.ts +37 -0
- package/dist/resolution/frameworks/swift-objc.d.ts.map +1 -0
- package/dist/resolution/frameworks/swift-objc.js +252 -0
- package/dist/resolution/frameworks/swift-objc.js.map +1 -0
- package/dist/resolution/frameworks/swift.d.ts +10 -0
- package/dist/resolution/frameworks/swift.d.ts.map +1 -0
- package/dist/resolution/frameworks/swift.js +400 -0
- package/dist/resolution/frameworks/swift.js.map +1 -0
- package/dist/resolution/frameworks/vue.d.ts +9 -0
- package/dist/resolution/frameworks/vue.d.ts.map +1 -0
- package/dist/resolution/frameworks/vue.js +306 -0
- package/dist/resolution/frameworks/vue.js.map +1 -0
- package/dist/resolution/go-module.d.ts +26 -0
- package/dist/resolution/go-module.d.ts.map +1 -0
- package/dist/resolution/go-module.js +78 -0
- package/dist/resolution/go-module.js.map +1 -0
- package/dist/resolution/import-resolver.d.ts +68 -0
- package/dist/resolution/import-resolver.d.ts.map +1 -0
- package/dist/resolution/import-resolver.js +1275 -0
- package/dist/resolution/import-resolver.js.map +1 -0
- package/dist/resolution/index.d.ts +117 -0
- package/dist/resolution/index.d.ts.map +1 -0
- package/dist/resolution/index.js +895 -0
- package/dist/resolution/index.js.map +1 -0
- package/dist/resolution/lru-cache.d.ts +24 -0
- package/dist/resolution/lru-cache.d.ts.map +1 -0
- package/dist/resolution/lru-cache.js +62 -0
- package/dist/resolution/lru-cache.js.map +1 -0
- package/dist/resolution/name-matcher.d.ts +32 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -0
- package/dist/resolution/name-matcher.js +596 -0
- package/dist/resolution/name-matcher.js.map +1 -0
- package/dist/resolution/path-aliases.d.ts +68 -0
- package/dist/resolution/path-aliases.d.ts.map +1 -0
- package/dist/resolution/path-aliases.js +238 -0
- package/dist/resolution/path-aliases.js.map +1 -0
- package/dist/resolution/spec-link-resolver.d.ts +103 -0
- package/dist/resolution/spec-link-resolver.d.ts.map +1 -0
- package/dist/resolution/spec-link-resolver.js +259 -0
- package/dist/resolution/spec-link-resolver.js.map +1 -0
- package/dist/resolution/strip-comments.d.ts +27 -0
- package/dist/resolution/strip-comments.d.ts.map +1 -0
- package/dist/resolution/strip-comments.js +441 -0
- package/dist/resolution/strip-comments.js.map +1 -0
- package/dist/resolution/swift-objc-bridge.d.ts +134 -0
- package/dist/resolution/swift-objc-bridge.d.ts.map +1 -0
- package/dist/resolution/swift-objc-bridge.js +256 -0
- package/dist/resolution/swift-objc-bridge.js.map +1 -0
- package/dist/resolution/types.d.ts +216 -0
- package/dist/resolution/types.d.ts.map +1 -0
- package/dist/resolution/types.js +8 -0
- package/dist/resolution/types.js.map +1 -0
- package/dist/resolution/workspace-packages.d.ts +48 -0
- package/dist/resolution/workspace-packages.d.ts.map +1 -0
- package/dist/resolution/workspace-packages.js +208 -0
- package/dist/resolution/workspace-packages.js.map +1 -0
- package/dist/search/query-parser.d.ts +57 -0
- package/dist/search/query-parser.d.ts.map +1 -0
- package/dist/search/query-parser.js +177 -0
- package/dist/search/query-parser.js.map +1 -0
- package/dist/search/query-utils.d.ts +71 -0
- package/dist/search/query-utils.d.ts.map +1 -0
- package/dist/search/query-utils.js +380 -0
- package/dist/search/query-utils.js.map +1 -0
- package/dist/server/cli.js +152 -0
- package/dist/server/index.js +12 -0
- package/dist/server/ingest/index.js +18 -0
- package/dist/server/ingest/ingestor.js +506 -0
- package/dist/server/ingest/parser.js +104 -0
- package/dist/server/ingest/pricing.js +78 -0
- package/dist/server/ingest/types.js +9 -0
- package/dist/server/ingest/watcher.js +77 -0
- package/dist/server/package.json +3 -0
- package/dist/server/project-registry.js +101 -0
- package/dist/server/routes/claude.js +868 -0
- package/dist/server/routes/graph.js +211 -0
- package/dist/server/routes/memory.js +272 -0
- package/dist/server/routes/projects.js +197 -0
- package/dist/server/routes/spec.js +265 -0
- package/dist/server/routes/status.js +112 -0
- package/dist/server/routes/workflow.js +212 -0
- package/dist/server/server.js +206 -0
- package/dist/server/static-handler.js +87 -0
- package/dist/sync/git-hooks.d.ts +45 -0
- package/dist/sync/git-hooks.d.ts.map +1 -0
- package/dist/sync/git-hooks.js +225 -0
- package/dist/sync/git-hooks.js.map +1 -0
- package/dist/sync/index.d.ts +19 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +35 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/watch-policy.d.ts +48 -0
- package/dist/sync/watch-policy.d.ts.map +1 -0
- package/dist/sync/watch-policy.js +124 -0
- package/dist/sync/watch-policy.js.map +1 -0
- package/dist/sync/watcher.d.ts +283 -0
- package/dist/sync/watcher.d.ts.map +1 -0
- package/dist/sync/watcher.js +606 -0
- package/dist/sync/watcher.js.map +1 -0
- package/dist/sync/worktree.d.ts +54 -0
- package/dist/sync/worktree.d.ts.map +1 -0
- package/dist/sync/worktree.js +137 -0
- package/dist/sync/worktree.js.map +1 -0
- package/dist/types.d.ts +623 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +108 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/glyphs.d.ts +42 -0
- package/dist/ui/glyphs.d.ts.map +1 -0
- package/dist/ui/glyphs.js +78 -0
- package/dist/ui/glyphs.js.map +1 -0
- package/dist/ui/shimmer-progress.d.ts +11 -0
- package/dist/ui/shimmer-progress.d.ts.map +1 -0
- package/dist/ui/shimmer-progress.js +90 -0
- package/dist/ui/shimmer-progress.js.map +1 -0
- package/dist/ui/shimmer-worker.d.ts +2 -0
- package/dist/ui/shimmer-worker.d.ts.map +1 -0
- package/dist/ui/shimmer-worker.js +118 -0
- package/dist/ui/shimmer-worker.js.map +1 -0
- package/dist/ui/types.d.ts +17 -0
- package/dist/ui/types.d.ts.map +1 -0
- package/dist/ui/types.js +3 -0
- package/dist/ui/types.js.map +1 -0
- package/dist/utils.d.ts +205 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +549 -0
- package/dist/utils.js.map +1 -0
- package/dist/web/chunk-2AJCHB7P.js +1 -0
- package/dist/web/chunk-2CPLUFCH.js +2 -0
- package/dist/web/chunk-2DHIGIOI.js +1 -0
- package/dist/web/chunk-2GBEK2GM.js +1 -0
- package/dist/web/chunk-2I7L37NS.js +1 -0
- package/dist/web/chunk-2NAWAJB5.js +1 -0
- package/dist/web/chunk-2OJBIPE4.js +1 -0
- package/dist/web/chunk-2OKMB4KX.js +2 -0
- package/dist/web/chunk-3E2WB6D5.js +1 -0
- package/dist/web/chunk-3EBFYSCH.js +2 -0
- package/dist/web/chunk-3QCQ4BXS.js +1 -0
- package/dist/web/chunk-3SEJX2BK.js +1 -0
- package/dist/web/chunk-42XVAQ6I.js +1 -0
- package/dist/web/chunk-4IMMPEYM.js +1 -0
- package/dist/web/chunk-4N5DWG46.js +1 -0
- package/dist/web/chunk-4TJQJPCZ.js +1 -0
- package/dist/web/chunk-4WZIHTPC.js +1 -0
- package/dist/web/chunk-4YVSYOSD.js +1 -0
- package/dist/web/chunk-5BQIOYKW.js +1 -0
- package/dist/web/chunk-5HGWHUJA.js +1 -0
- package/dist/web/chunk-5Y244R4G.js +1 -0
- package/dist/web/chunk-6RRDPT5Z.js +1 -0
- package/dist/web/chunk-6VKB2ZWM.js +1 -0
- package/dist/web/chunk-7DMFVTU4.js +1 -0
- package/dist/web/chunk-7RNS77UP.js +1 -0
- package/dist/web/chunk-7SMPKVEP.js +1 -0
- package/dist/web/chunk-AZJVTPLU.js +1 -0
- package/dist/web/chunk-BCZM5AXU.js +1 -0
- package/dist/web/chunk-BLBRMCN2.js +1 -0
- package/dist/web/chunk-BMIAXD2V.js +2 -0
- package/dist/web/chunk-BPECIDVO.js +1 -0
- package/dist/web/chunk-BUXWEHIY.js +1 -0
- package/dist/web/chunk-BYZFQSM6.js +1 -0
- package/dist/web/chunk-DA6SNNAF.js +1 -0
- package/dist/web/chunk-DLQPZWSI.css +1 -0
- package/dist/web/chunk-DTRN7FZR.js +1 -0
- package/dist/web/chunk-DYRFLPJA.js +1 -0
- package/dist/web/chunk-E3J3CXR5.js +1 -0
- package/dist/web/chunk-E44X4RH2.js +1 -0
- package/dist/web/chunk-E73OX2P7.js +1 -0
- package/dist/web/chunk-EAXRKDLV.js +1 -0
- package/dist/web/chunk-EBKKDHYI.js +1 -0
- package/dist/web/chunk-EE7V7Q5P.js +1 -0
- package/dist/web/chunk-EKY2FUHU.js +1 -0
- package/dist/web/chunk-EMGMOEVR.js +1 -0
- package/dist/web/chunk-EP6XOPXH.js +1 -0
- package/dist/web/chunk-ESGDLJOJ.js +1 -0
- package/dist/web/chunk-ETJG7NCY.js +1 -0
- package/dist/web/chunk-EUUEFEDI.js +1 -0
- package/dist/web/chunk-FGNZDHTL.js +11 -0
- package/dist/web/chunk-FIJW2UNJ.js +1 -0
- package/dist/web/chunk-FMV5PXRC.js +5 -0
- package/dist/web/chunk-G7VZT5KB.js +3 -0
- package/dist/web/chunk-GRZYXPSO.js +7 -0
- package/dist/web/chunk-GYGPS3AN.js +1 -0
- package/dist/web/chunk-H7AF7YS4.js +1 -0
- package/dist/web/chunk-HDZDQILN.js +1 -0
- package/dist/web/chunk-HMK6UO6N.js +1 -0
- package/dist/web/chunk-HZA6NEAB.js +1 -0
- package/dist/web/chunk-IHEE5NYJ.js +1 -0
- package/dist/web/chunk-ISNEBICW.js +1 -0
- package/dist/web/chunk-J2GZVLHH.js +1 -0
- package/dist/web/chunk-JFYVCXK3.js +1 -0
- package/dist/web/chunk-JN6W7HCN.js +17 -0
- package/dist/web/chunk-JT7P3DEK.js +6 -0
- package/dist/web/chunk-JTFXTIPE.js +903 -0
- package/dist/web/chunk-L37MTFSG.js +3 -0
- package/dist/web/chunk-LB6JPLX2.js +1 -0
- package/dist/web/chunk-LNSVDHCI.js +1 -0
- package/dist/web/chunk-LV4G6QFG.js +2 -0
- package/dist/web/chunk-LVGIY3SO.js +1 -0
- package/dist/web/chunk-LXLHIHEN.js +1 -0
- package/dist/web/chunk-MC4DFIHG.js +1 -0
- package/dist/web/chunk-MVOMVPYB.js +1 -0
- package/dist/web/chunk-N6SS4G6S.js +1 -0
- package/dist/web/chunk-NTBJG6SJ.js +1 -0
- package/dist/web/chunk-NUDB3Q2Y.js +3 -0
- package/dist/web/chunk-OXEF5E3E.js +1 -0
- package/dist/web/chunk-PDN6QYGJ.js +4 -0
- package/dist/web/chunk-PGGJPDJG.js +1 -0
- package/dist/web/chunk-PUYSJNJR.js +1 -0
- package/dist/web/chunk-Q2RVFS45.js +1 -0
- package/dist/web/chunk-Q7L6LLAK.js +1 -0
- package/dist/web/chunk-QCMKJIWY.js +1 -0
- package/dist/web/chunk-QH6CF3M3.js +1 -0
- package/dist/web/chunk-QQ5LD7PI.js +1 -0
- package/dist/web/chunk-QR6L3KAC.js +1 -0
- package/dist/web/chunk-R2DLK4HO.js +1 -0
- package/dist/web/chunk-R5W2MDZN.js +1 -0
- package/dist/web/chunk-RAAMPHPJ.js +1 -0
- package/dist/web/chunk-RD6TVPOT.js +1 -0
- package/dist/web/chunk-RKY4EJYJ.js +1 -0
- package/dist/web/chunk-RONYWVY7.js +1 -0
- package/dist/web/chunk-RXKXYF2C.js +1 -0
- package/dist/web/chunk-SBWU7JFC.js +1 -0
- package/dist/web/chunk-SEXBRGYK.js +1 -0
- package/dist/web/chunk-SHPTC4RL.js +1 -0
- package/dist/web/chunk-SUZYBYDW.js +1 -0
- package/dist/web/chunk-SWKJRNYY.js +1 -0
- package/dist/web/chunk-T66XVKGB.js +1 -0
- package/dist/web/chunk-T7AZ65JP.js +1 -0
- package/dist/web/chunk-TCZDVOHD.js +1 -0
- package/dist/web/chunk-TPTITA3V.js +1 -0
- package/dist/web/chunk-TR335633.js +1 -0
- package/dist/web/chunk-TWXZK6XM.js +1 -0
- package/dist/web/chunk-UR5KDXPX.js +1 -0
- package/dist/web/chunk-UR6O2GEH.js +1 -0
- package/dist/web/chunk-UTNMGWTP.js +1 -0
- package/dist/web/chunk-UYC52MBC.js +1 -0
- package/dist/web/chunk-VECWMHJP.js +1 -0
- package/dist/web/chunk-VUACT35R.js +3 -0
- package/dist/web/chunk-VZI7H4SZ.js +1 -0
- package/dist/web/chunk-WAI2JMZP.js +1 -0
- package/dist/web/chunk-WB6YHOD4.js +1 -0
- package/dist/web/chunk-WBT64AWV.js +1 -0
- package/dist/web/chunk-WDU3WICG.js +1 -0
- package/dist/web/chunk-WFXJIXZE.js +4 -0
- package/dist/web/chunk-WTGYRH3Z.js +298 -0
- package/dist/web/chunk-WXTCVDTP.js +1 -0
- package/dist/web/chunk-X2HTISHL.js +1 -0
- package/dist/web/chunk-XCDHWLVH.js +1 -0
- package/dist/web/chunk-Y3H6FFUZ.js +1 -0
- package/dist/web/chunk-Y4F5ULGJ.js +1 -0
- package/dist/web/chunk-YAWCRPHV.js +1 -0
- package/dist/web/chunk-YEGKAAEE.js +1 -0
- package/dist/web/chunk-YM2KU57F.js +1 -0
- package/dist/web/chunk-YRERBP6T.js +1 -0
- package/dist/web/chunk-ZLV4VCDG.js +3 -0
- package/dist/web/chunk-ZTVI5KFF.js +1 -0
- package/dist/web/favicon-16.png +0 -0
- package/dist/web/favicon-180.png +0 -0
- package/dist/web/favicon-32.png +0 -0
- package/dist/web/favicon-512.png +0 -0
- package/dist/web/favicon-small.svg +15 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/favicon.svg +20 -0
- package/dist/web/index.html +145 -0
- package/dist/web/main-ESADRXN2.css +1 -0
- package/dist/web/main-WVI3YTDU.js +1 -0
- package/dist/web/media/codicon-LN6W7LCM.ttf +0 -0
- package/dist/web/styles-KSOPUVDA.css +1 -0
- package/dist/workflows/condition-evaluator.d.ts +75 -0
- package/dist/workflows/condition-evaluator.d.ts.map +1 -0
- package/dist/workflows/condition-evaluator.js +282 -0
- package/dist/workflows/condition-evaluator.js.map +1 -0
- package/dist/workflows/defaults/claude-design-implement.yaml +247 -0
- package/dist/workflows/defaults/index.d.ts +26 -0
- package/dist/workflows/defaults/index.d.ts.map +1 -0
- package/dist/workflows/defaults/index.js +94 -0
- package/dist/workflows/defaults/index.js.map +1 -0
- package/dist/workflows/defaults/spec-author.yaml +214 -0
- package/dist/workflows/defaults/spec-fix.yaml +110 -0
- package/dist/workflows/defaults/spec-implement.yaml +150 -0
- package/dist/workflows/defaults/spec-relink.yaml +81 -0
- package/dist/workflows/defaults/spec-verify.yaml +51 -0
- package/dist/workflows/discovery.d.ts +46 -0
- package/dist/workflows/discovery.d.ts.map +1 -0
- package/dist/workflows/discovery.js +193 -0
- package/dist/workflows/discovery.js.map +1 -0
- package/dist/workflows/executor.d.ts +83 -0
- package/dist/workflows/executor.d.ts.map +1 -0
- package/dist/workflows/executor.js +624 -0
- package/dist/workflows/executor.js.map +1 -0
- package/dist/workflows/runners/approval.d.ts +18 -0
- package/dist/workflows/runners/approval.d.ts.map +1 -0
- package/dist/workflows/runners/approval.js +34 -0
- package/dist/workflows/runners/approval.js.map +1 -0
- package/dist/workflows/runners/bash.d.ts +13 -0
- package/dist/workflows/runners/bash.d.ts.map +1 -0
- package/dist/workflows/runners/bash.js +143 -0
- package/dist/workflows/runners/bash.js.map +1 -0
- package/dist/workflows/runners/cancel.d.ts +10 -0
- package/dist/workflows/runners/cancel.d.ts.map +1 -0
- package/dist/workflows/runners/cancel.js +19 -0
- package/dist/workflows/runners/cancel.js.map +1 -0
- package/dist/workflows/runners/prompt.d.ts +28 -0
- package/dist/workflows/runners/prompt.d.ts.map +1 -0
- package/dist/workflows/runners/prompt.js +212 -0
- package/dist/workflows/runners/prompt.js.map +1 -0
- package/dist/workflows/runners/script.d.ts +17 -0
- package/dist/workflows/runners/script.d.ts.map +1 -0
- package/dist/workflows/runners/script.js +155 -0
- package/dist/workflows/runners/script.js.map +1 -0
- package/dist/workflows/runners/types.d.ts +51 -0
- package/dist/workflows/runners/types.d.ts.map +1 -0
- package/dist/workflows/runners/types.js +13 -0
- package/dist/workflows/runners/types.js.map +1 -0
- package/dist/workflows/schemas/workflow.d.ts +166 -0
- package/dist/workflows/schemas/workflow.d.ts.map +1 -0
- package/dist/workflows/schemas/workflow.js +437 -0
- package/dist/workflows/schemas/workflow.js.map +1 -0
- package/package.json +1 -1
- package/scripts/offline-install.sh +19 -6
- /package/commands/{cg-explore.md → ss-explore.md} +0 -0
- /package/commands/{cg-impact.md → ss-impact.md} +0 -0
- /package/commands/{cg-relink.md → ss-relink.md} +0 -0
- /package/commands/{cg-spec.md → ss-spec.md} +0 -0
- /package/commands/{cg-sync.md → ss-sync.md} +0 -0
- /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
|
+
}
|