@selvakumaresra/specship 0.1.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/.claude-plugin/plugin.json +6 -0
- package/LICENSE +21 -0
- package/README.md +573 -0
- package/agents/specship-explorer.md +29 -0
- package/commands/cg-drifted.md +23 -0
- package/commands/cg-explore.md +13 -0
- package/commands/cg-fix.md +21 -0
- package/commands/cg-impact.md +13 -0
- package/commands/cg-implement.md +23 -0
- package/commands/cg-relink.md +21 -0
- package/commands/cg-spec.md +19 -0
- package/commands/cg-sync.md +8 -0
- package/commands/cg-trace.md +13 -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 +2018 -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 +101 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +276 -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 +427 -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 +410 -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 +57 -0
- package/dist/directory.d.ts.map +1 -0
- package/dist/directory.js +253 -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 +409 -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 +88 -0
- package/dist/installer/targets/claude.d.ts.map +1 -0
- package/dist/installer/targets/claude.js +582 -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 +406 -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 +480 -0
- package/dist/server/routes/graph.js +149 -0
- package/dist/server/routes/memory.js +272 -0
- package/dist/server/routes/projects.js +197 -0
- package/dist/server/routes/spec.js +105 -0
- package/dist/server/routes/status.js +35 -0
- package/dist/server/routes/workflow.js +184 -0
- package/dist/server/server.js +202 -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-2YZXEHZ2.js +1 -0
- package/dist/web/chunk-3GIC555L.js +18 -0
- package/dist/web/chunk-3IIIGRMT.js +1 -0
- package/dist/web/chunk-47QYKLE5.js +1 -0
- package/dist/web/chunk-4LHBWWP7.js +1 -0
- package/dist/web/chunk-4OAZLD5W.js +1 -0
- package/dist/web/chunk-5OQKAJAE.js +1 -0
- package/dist/web/chunk-7B525GKQ.js +1 -0
- package/dist/web/chunk-BPDXCOOZ.js +1 -0
- package/dist/web/chunk-DT37HTZB.js +1 -0
- package/dist/web/chunk-EIMUHJND.js +1 -0
- package/dist/web/chunk-FTESTUEO.js +1 -0
- package/dist/web/chunk-GLJZV6MU.js +1 -0
- package/dist/web/chunk-I7LS67U5.js +1 -0
- package/dist/web/chunk-L4TVIPSR.js +1 -0
- package/dist/web/chunk-MASCULC2.js +1 -0
- package/dist/web/chunk-MW7ICSRM.js +1 -0
- package/dist/web/chunk-OI5VP2A3.js +1 -0
- package/dist/web/chunk-RA6EBF6I.js +1 -0
- package/dist/web/chunk-RP3WU5Y6.js +1 -0
- package/dist/web/chunk-RQDRMTXN.js +1 -0
- package/dist/web/chunk-TQMT6UDU.js +1 -0
- package/dist/web/chunk-U7IYOV7T.js +1 -0
- package/dist/web/chunk-UE227MWF.js +1 -0
- package/dist/web/chunk-WV573J4K.js +1 -0
- package/dist/web/chunk-WVCKOJZL.js +4 -0
- package/dist/web/chunk-XZKLVPHE.js +1 -0
- package/dist/web/chunk-ZABKKHJ3.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-RI5CO5Z4.js +1 -0
- package/dist/web/styles-CYN7IKT4.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/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-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 +623 -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/hooks/hooks.json +27 -0
- package/package.json +67 -0
- package/scripts/add-lang/bench.sh +60 -0
- package/scripts/add-lang/check-grammar.mjs +75 -0
- package/scripts/add-lang/dump-ast.mjs +103 -0
- package/scripts/add-lang/verify-extraction.mjs +70 -0
- package/scripts/agent-eval/arms-F.sh +21 -0
- package/scripts/agent-eval/arms-matrix.sh +37 -0
- package/scripts/agent-eval/audit.sh +68 -0
- package/scripts/agent-eval/bench-readme.sh +28 -0
- package/scripts/agent-eval/bench-why-repo.sh +22 -0
- package/scripts/agent-eval/block-read-hook.sh +19 -0
- package/scripts/agent-eval/hook-settings.json +15 -0
- package/scripts/agent-eval/itrun.sh +120 -0
- package/scripts/agent-eval/parse-arms.mjs +116 -0
- package/scripts/agent-eval/parse-bench-readme.mjs +84 -0
- package/scripts/agent-eval/parse-run.mjs +45 -0
- package/scripts/agent-eval/parse-session.mjs +93 -0
- package/scripts/agent-eval/probe-context.mjs +21 -0
- package/scripts/agent-eval/probe-explore.mjs +40 -0
- package/scripts/agent-eval/probe-node.mjs +20 -0
- package/scripts/agent-eval/probe-sweep.mjs +119 -0
- package/scripts/agent-eval/probe-trace.mjs +20 -0
- package/scripts/agent-eval/run-agent.sh +34 -0
- package/scripts/agent-eval/run-all.sh +67 -0
- package/scripts/agent-eval/run-arms.sh +56 -0
- package/scripts/agent-eval/seq-matrix.mjs +137 -0
- package/scripts/build-bundle.sh +118 -0
- package/scripts/build-server-bundle.mjs +80 -0
- package/scripts/build-web-bundle.mjs +66 -0
- package/scripts/extract-release-notes.mjs +130 -0
- package/scripts/local-install.sh +41 -0
- package/scripts/npm-sdk.js +75 -0
- package/scripts/npm-shim.js +246 -0
- package/scripts/offline-install.ps1 +148 -0
- package/scripts/offline-install.sh +136 -0
- package/scripts/pack-npm.sh +119 -0
- package/scripts/prepare-release.mjs +270 -0
|
@@ -0,0 +1,406 @@
|
|
|
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, result_length, ts)
|
|
290
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
291
|
+
`);
|
|
292
|
+
let lastSessionId = null;
|
|
293
|
+
let promptsInserted = 0;
|
|
294
|
+
let toolCallsInserted = 0;
|
|
295
|
+
// Track active prompt context (promptId picks up from user entries; the
|
|
296
|
+
// following assistant entries belong to it). When no promptId is known
|
|
297
|
+
// (rare — happens at the start of a session before any user entry), use
|
|
298
|
+
// the assistant's uuid as a stable proxy.
|
|
299
|
+
let activePromptId = null;
|
|
300
|
+
const pendingTools = new Map();
|
|
301
|
+
for (const raw of completeLines) {
|
|
302
|
+
const entry = parseLine(raw);
|
|
303
|
+
if (!entry)
|
|
304
|
+
continue;
|
|
305
|
+
const sessionId = entry.sessionId ?? '';
|
|
306
|
+
if (!sessionId)
|
|
307
|
+
continue;
|
|
308
|
+
lastSessionId = sessionId;
|
|
309
|
+
const ts = toEpochMs(entry.timestamp);
|
|
310
|
+
if (entry.type === 'user') {
|
|
311
|
+
// Bookkeep session row first.
|
|
312
|
+
insSession.run(sessionId, projectPath, filePath, ts, ts, null);
|
|
313
|
+
const text = extractUserPrompt(entry);
|
|
314
|
+
const promptId = entry.promptId ?? entry.uuid ?? null;
|
|
315
|
+
const isSidechain = entry.isSidechain ? 1 : 0;
|
|
316
|
+
if (promptId) {
|
|
317
|
+
activePromptId = promptId;
|
|
318
|
+
// Initial insert with zero usage; usage fills in when assistant turns land.
|
|
319
|
+
insPrompt.run(promptId, sessionId, text || null, ts, entry.leafUuid ?? null, null, 0, 0, 0, 0, 0, isSidechain);
|
|
320
|
+
promptsInserted++;
|
|
321
|
+
// Bump session prompt count immediately (cost accrues later).
|
|
322
|
+
incSessionAggregates.run(1, 0, 0, 0, 0, 0, sessionId);
|
|
323
|
+
}
|
|
324
|
+
// Handle tool_result blocks: scan content for tool_result entries and
|
|
325
|
+
// update the matching pending tool_call row's result_length.
|
|
326
|
+
const content = entry.message?.content;
|
|
327
|
+
if (Array.isArray(content)) {
|
|
328
|
+
for (const block of content) {
|
|
329
|
+
if (block && block.type === 'tool_result' && block.tool_use_id) {
|
|
330
|
+
const len = toolResultLength(block);
|
|
331
|
+
const pending = pendingTools.get(block.tool_use_id);
|
|
332
|
+
if (pending) {
|
|
333
|
+
insToolCall.run(pending.promptId, pending.sessionId, pending.assistantUuid, block.tool_use_id, pending.toolName, pending.summary, len, pending.ts);
|
|
334
|
+
toolCallsInserted++;
|
|
335
|
+
pendingTools.delete(block.tool_use_id);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else if (entry.type === 'assistant') {
|
|
342
|
+
// Make sure session row exists.
|
|
343
|
+
insSession.run(sessionId, projectPath, filePath, ts, ts, entry.message?.model ?? null);
|
|
344
|
+
const promptId = activePromptId ?? entry.uuid ?? null;
|
|
345
|
+
if (!promptId)
|
|
346
|
+
continue;
|
|
347
|
+
const usage = entry.message?.usage;
|
|
348
|
+
const inputTok = usage?.input_tokens ?? 0;
|
|
349
|
+
const outputTok = usage?.output_tokens ?? 0;
|
|
350
|
+
const cacheCreate = usage?.cache_creation_input_tokens ?? 0;
|
|
351
|
+
const cacheRead = usage?.cache_read_input_tokens ?? 0;
|
|
352
|
+
const pricingRow = resolvePricing(entry.message?.model, pricing);
|
|
353
|
+
const cost = computeCost(usage, pricingRow);
|
|
354
|
+
// Update prompt's running usage. Re-uses ON CONFLICT to ADD to existing.
|
|
355
|
+
// Since INSERT...ON CONFLICT DO UPDATE replaces (not increments), do an
|
|
356
|
+
// explicit UPDATE here for the additive case.
|
|
357
|
+
db.prepare(`
|
|
358
|
+
UPDATE claude_prompts SET
|
|
359
|
+
input_tokens = input_tokens + ?,
|
|
360
|
+
output_tokens = output_tokens + ?,
|
|
361
|
+
cache_creation_tokens = cache_creation_tokens + ?,
|
|
362
|
+
cache_read_tokens = cache_read_tokens + ?,
|
|
363
|
+
cost_usd = cost_usd + ?,
|
|
364
|
+
model = COALESCE(?, model)
|
|
365
|
+
WHERE id = ?
|
|
366
|
+
`).run(inputTok, outputTok, cacheCreate, cacheRead, cost, entry.message?.model ?? null, promptId);
|
|
367
|
+
// Session aggregates: only token totals + cost. prompt_count was bumped
|
|
368
|
+
// when the user entry landed.
|
|
369
|
+
incSessionAggregates.run(0, inputTok, outputTok, cacheCreate, cacheRead, cost, sessionId);
|
|
370
|
+
// Scan content for tool_use blocks → queue as pending.
|
|
371
|
+
const content = entry.message?.content;
|
|
372
|
+
const assistantUuid = entry.uuid ?? '';
|
|
373
|
+
if (Array.isArray(content) && assistantUuid) {
|
|
374
|
+
for (const block of content) {
|
|
375
|
+
if (block && block.type === 'tool_use' && block.id && block.name) {
|
|
376
|
+
pendingTools.set(block.id, {
|
|
377
|
+
promptId,
|
|
378
|
+
sessionId,
|
|
379
|
+
assistantUuid,
|
|
380
|
+
toolName: block.name,
|
|
381
|
+
summary: summarizeToolInput(block.name, block.input),
|
|
382
|
+
ts,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// attachment / queue-operation / last-prompt entries are ignored for v1.
|
|
389
|
+
}
|
|
390
|
+
// Any remaining pendingTools didn't have a tool_result yet — they'll be
|
|
391
|
+
// matched on the next pass when the user reply arrives. We flush them with
|
|
392
|
+
// result_length=0 so the tool call still shows up in analytics (better to
|
|
393
|
+
// show "0 tokens returned" than to omit the call).
|
|
394
|
+
for (const [toolUseId, pending] of pendingTools) {
|
|
395
|
+
insToolCall.run(pending.promptId, pending.sessionId, pending.assistantUuid, toolUseId, pending.toolName, pending.summary, 0, pending.ts);
|
|
396
|
+
toolCallsInserted++;
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
modified: true,
|
|
400
|
+
bytes: 0,
|
|
401
|
+
lines: completeLines.length,
|
|
402
|
+
prompts: promptsInserted,
|
|
403
|
+
toolCalls: toolCallsInserted,
|
|
404
|
+
lastSessionId,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Claude Code JSONL transcript ingest.
|
|
3
|
+
*
|
|
4
|
+
* These mirror the fields we actually use — Claude Code's JSONL format has
|
|
5
|
+
* many more fields than these (parentUuid, isSidechain, version, gitBranch,
|
|
6
|
+
* userType, …); we capture only what drives analytics + cost. Unknown fields
|
|
7
|
+
* pass through silently.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem watcher that re-runs the ingestor on JSONL changes.
|
|
3
|
+
*
|
|
4
|
+
* Strategy:
|
|
5
|
+
* - One initial full pass on start (catches everything written while the
|
|
6
|
+
* watcher wasn't running — same offset state, no duplicates).
|
|
7
|
+
* - fs.watch on the ~/.claude/projects/ tree, debounced 300ms — Claude
|
|
8
|
+
* Code writes one line at a time during a session, so we don't want to
|
|
9
|
+
* re-ingest on every keystroke.
|
|
10
|
+
* - On change, re-run ingestAll. The state table makes this cheap: only
|
|
11
|
+
* new bytes are read + parsed.
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as os from 'os';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { ingestAll } from './ingestor.js';
|
|
17
|
+
export function startWatcher(db, options = {}) {
|
|
18
|
+
const claudeRoot = options.claudeRoot ?? path.join(os.homedir(), '.claude', 'projects');
|
|
19
|
+
const debounceMs = options.debounceMs ?? 300;
|
|
20
|
+
const verbose = options.verbose ?? false;
|
|
21
|
+
let pending = null;
|
|
22
|
+
const triggerSoon = () => {
|
|
23
|
+
if (pending)
|
|
24
|
+
clearTimeout(pending);
|
|
25
|
+
pending = setTimeout(() => {
|
|
26
|
+
pending = null;
|
|
27
|
+
try {
|
|
28
|
+
ingestAll(db, { ...options, claudeRoot });
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
if (verbose) {
|
|
32
|
+
// eslint-disable-next-line no-console
|
|
33
|
+
console.error('[ingest watcher] pass failed:', err instanceof Error ? err.message : String(err));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}, debounceMs);
|
|
37
|
+
};
|
|
38
|
+
// Initial pass.
|
|
39
|
+
if (options.initialIngest !== false) {
|
|
40
|
+
triggerSoon();
|
|
41
|
+
}
|
|
42
|
+
// fs.watch on the root, recursive. On macOS + Linux this gives us per-file
|
|
43
|
+
// events; on Windows the recursive flag is supported as of recent Node.
|
|
44
|
+
let watcher = null;
|
|
45
|
+
try {
|
|
46
|
+
watcher = fs.watch(claudeRoot, { recursive: true, persistent: true }, (eventType, filename) => {
|
|
47
|
+
if (!filename)
|
|
48
|
+
return;
|
|
49
|
+
const f = filename.toString();
|
|
50
|
+
if (!f.toLowerCase().endsWith('.jsonl'))
|
|
51
|
+
return;
|
|
52
|
+
triggerSoon();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
// Directory doesn't exist (user hasn't run Claude Code) — just no-op.
|
|
57
|
+
if (verbose) {
|
|
58
|
+
// eslint-disable-next-line no-console
|
|
59
|
+
console.error('[ingest watcher] could not watch', claudeRoot, ':', err instanceof Error ? err.message : String(err));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
stop: () => {
|
|
64
|
+
if (pending) {
|
|
65
|
+
clearTimeout(pending);
|
|
66
|
+
pending = null;
|
|
67
|
+
}
|
|
68
|
+
if (watcher) {
|
|
69
|
+
try {
|
|
70
|
+
watcher.close();
|
|
71
|
+
}
|
|
72
|
+
catch { /* ignore */ }
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
ingestNow: () => ingestAll(db, { ...options, claudeRoot }),
|
|
76
|
+
};
|
|
77
|
+
}
|