bluera-knowledge 0.9.21
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/commands/commit.md +37 -0
- package/.claude/hooks/post-edit-check.sh +41 -0
- package/.claude/settings.local.json.example +40 -0
- package/.claude/skills/atomic-commits/SKILL.md +53 -0
- package/.claude-plugin/plugin.json +13 -0
- package/.editorconfig +15 -0
- package/.github/workflows/auto-release.yml +59 -0
- package/.github/workflows/ci.yml +142 -0
- package/.github/workflows/release.yml +66 -0
- package/.github/workflows/update-marketplace.yml +96 -0
- package/.husky/pre-commit +47 -0
- package/.husky/pre-push +29 -0
- package/.versionrc.json +28 -0
- package/CHANGELOG.md +410 -0
- package/CLAUDE.md +109 -0
- package/LICENSE +21 -0
- package/NOTICE +47 -0
- package/README.md +1546 -0
- package/SECURITY.md +65 -0
- package/bun.lock +1758 -0
- package/commands/add-folder.md +48 -0
- package/commands/add-repo.md +50 -0
- package/commands/cancel.md +63 -0
- package/commands/check-status.md +78 -0
- package/commands/crawl.md +51 -0
- package/commands/index.md +48 -0
- package/commands/remove-store.md +52 -0
- package/commands/search.md +79 -0
- package/commands/search.sh +63 -0
- package/commands/stores.md +54 -0
- package/commands/suggest.md +82 -0
- package/dist/chunk-5QMHZUC4.js +3617 -0
- package/dist/chunk-5QMHZUC4.js.map +1 -0
- package/dist/chunk-BICFAWMN.js +656 -0
- package/dist/chunk-BICFAWMN.js.map +1 -0
- package/dist/chunk-J7J6LXOJ.js +958 -0
- package/dist/chunk-J7J6LXOJ.js.map +1 -0
- package/dist/chunk-L2YVNC63.js +59 -0
- package/dist/chunk-L2YVNC63.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1429 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +15 -0
- package/dist/mcp/server.js +11 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/watch.service-YAIKKDCF.js +7 -0
- package/dist/watch.service-YAIKKDCF.js.map +1 -0
- package/dist/workers/background-worker-cli.d.ts +1 -0
- package/dist/workers/background-worker-cli.js +310 -0
- package/dist/workers/background-worker-cli.js.map +1 -0
- package/docs/plans/2024-12-17-ai-search-quality-implementation.md +752 -0
- package/docs/plans/2024-12-17-ai-search-quality-testing-design.md +201 -0
- package/docs/plans/2025-12-16-bluera-knowledge-cli.md +2951 -0
- package/docs/plans/2025-12-16-phase2-features.md +1518 -0
- package/docs/plans/2025-12-17-hil-implementation.md +926 -0
- package/docs/plans/2025-12-17-hil-quality-testing.md +224 -0
- package/docs/plans/2025-12-17-search-quality-phase1-implementation.md +1416 -0
- package/docs/plans/2025-12-17-search-quality-testing-v2-design.md +212 -0
- package/docs/plans/2025-12-28-ai-agent-optimization.md +1630 -0
- package/eslint-rules/require-skip-comment.js +81 -0
- package/eslint.config.js +61 -0
- package/hooks/check-dependencies.sh +110 -0
- package/hooks/format-search-results.py +132 -0
- package/hooks/hooks.json +27 -0
- package/hooks/job-status-hook.sh +51 -0
- package/knip.json +43 -0
- package/mcp.plugin.json +12 -0
- package/package.json +103 -0
- package/python/crawl_worker.py +275 -0
- package/python/requirements.txt +2 -0
- package/scripts/readme-version-updater.cjs +18 -0
- package/skills/advanced-workflows/SKILL.md +273 -0
- package/skills/atomic-commits/SKILL.md +77 -0
- package/skills/knowledge-search/SKILL.md +54 -0
- package/skills/search-optimization/SKILL.md +396 -0
- package/skills/store-lifecycle/SKILL.md +470 -0
- package/skills/when-to-query/SKILL.md +66 -0
- package/src/analysis/ast-parser.test.ts +423 -0
- package/src/analysis/ast-parser.ts +192 -0
- package/src/analysis/code-graph.test.ts +698 -0
- package/src/analysis/code-graph.ts +245 -0
- package/src/analysis/dependency-usage-analyzer.test.ts +799 -0
- package/src/analysis/dependency-usage-analyzer.ts +405 -0
- package/src/analysis/go-ast-parser.test.ts +531 -0
- package/src/analysis/go-ast-parser.ts +478 -0
- package/src/analysis/parser-factory.test.ts +132 -0
- package/src/analysis/parser-factory.ts +44 -0
- package/src/analysis/python-ast-parser.test.ts +210 -0
- package/src/analysis/python-ast-parser.ts +34 -0
- package/src/analysis/repo-url-resolver.test.ts +533 -0
- package/src/analysis/repo-url-resolver.ts +233 -0
- package/src/analysis/rust-ast-parser.test.ts +568 -0
- package/src/analysis/rust-ast-parser.ts +477 -0
- package/src/analysis/tree-sitter-parser.test.ts +297 -0
- package/src/analysis/tree-sitter-parser.ts +223 -0
- package/src/cli/commands/crawl.test.ts +942 -0
- package/src/cli/commands/crawl.ts +141 -0
- package/src/cli/commands/index-cmd.test.ts +722 -0
- package/src/cli/commands/index-cmd.ts +105 -0
- package/src/cli/commands/mcp.test.ts +218 -0
- package/src/cli/commands/mcp.ts +18 -0
- package/src/cli/commands/plugin-api.test.ts +313 -0
- package/src/cli/commands/plugin-api.ts +45 -0
- package/src/cli/commands/search.test.ts +911 -0
- package/src/cli/commands/search.ts +113 -0
- package/src/cli/commands/serve.test.ts +329 -0
- package/src/cli/commands/serve.ts +28 -0
- package/src/cli/commands/setup.test.ts +820 -0
- package/src/cli/commands/setup.ts +153 -0
- package/src/cli/commands/store.test.ts +1003 -0
- package/src/cli/commands/store.ts +167 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/program.ts +59 -0
- package/src/crawl/article-converter.test.ts +604 -0
- package/src/crawl/article-converter.ts +98 -0
- package/src/crawl/bridge.test.ts +674 -0
- package/src/crawl/bridge.ts +236 -0
- package/src/crawl/claude-client.test.ts +663 -0
- package/src/crawl/claude-client.ts +234 -0
- package/src/crawl/intelligent-crawler.test.ts +931 -0
- package/src/crawl/intelligent-crawler.ts +428 -0
- package/src/crawl/markdown-utils.test.ts +703 -0
- package/src/crawl/markdown-utils.ts +228 -0
- package/src/crawl/schemas.ts +114 -0
- package/src/db/embeddings.test.ts +63 -0
- package/src/db/embeddings.ts +69 -0
- package/src/db/index.ts +2 -0
- package/src/db/lance.test.ts +390 -0
- package/src/db/lance.ts +164 -0
- package/src/defaults/repos.ts +67 -0
- package/src/index.ts +107 -0
- package/src/mcp/cache.test.ts +202 -0
- package/src/mcp/cache.ts +103 -0
- package/src/mcp/commands/index.ts +20 -0
- package/src/mcp/commands/job.commands.ts +54 -0
- package/src/mcp/commands/meta.commands.ts +54 -0
- package/src/mcp/commands/registry.ts +183 -0
- package/src/mcp/commands/store.commands.ts +75 -0
- package/src/mcp/handlers/execute.handler.test.ts +179 -0
- package/src/mcp/handlers/execute.handler.ts +24 -0
- package/src/mcp/handlers/index.ts +43 -0
- package/src/mcp/handlers/job.handler.test.ts +189 -0
- package/src/mcp/handlers/job.handler.ts +116 -0
- package/src/mcp/handlers/search.handler.test.ts +334 -0
- package/src/mcp/handlers/search.handler.ts +209 -0
- package/src/mcp/handlers/store.handler.test.ts +415 -0
- package/src/mcp/handlers/store.handler.ts +295 -0
- package/src/mcp/schemas/index.test.ts +315 -0
- package/src/mcp/schemas/index.ts +138 -0
- package/src/mcp/server.test.ts +36 -0
- package/src/mcp/server.ts +162 -0
- package/src/mcp/types.ts +41 -0
- package/src/plugin/commands.test.ts +789 -0
- package/src/plugin/commands.ts +257 -0
- package/src/plugin/dependency-analyzer.test.ts +380 -0
- package/src/plugin/dependency-analyzer.ts +147 -0
- package/src/plugin/git-clone.test.ts +332 -0
- package/src/plugin/git-clone.ts +57 -0
- package/src/server/app.test.ts +752 -0
- package/src/server/app.ts +119 -0
- package/src/server/index.test.ts +477 -0
- package/src/server/index.ts +1 -0
- package/src/services/chunking.service.test.ts +363 -0
- package/src/services/chunking.service.ts +350 -0
- package/src/services/code-graph.service.test.ts +304 -0
- package/src/services/code-graph.service.ts +302 -0
- package/src/services/code-unit.service.test.ts +596 -0
- package/src/services/code-unit.service.ts +115 -0
- package/src/services/config.service.test.ts +127 -0
- package/src/services/config.service.ts +69 -0
- package/src/services/index.service.test.ts +1002 -0
- package/src/services/index.service.ts +266 -0
- package/src/services/index.ts +75 -0
- package/src/services/job.service.test.ts +418 -0
- package/src/services/job.service.ts +246 -0
- package/src/services/project-root.service.test.ts +506 -0
- package/src/services/project-root.service.ts +112 -0
- package/src/services/search.service.test.ts +1105 -0
- package/src/services/search.service.ts +892 -0
- package/src/services/services.test.ts +38 -0
- package/src/services/snippet.service.test.ts +205 -0
- package/src/services/snippet.service.ts +166 -0
- package/src/services/store.service.test.ts +474 -0
- package/src/services/store.service.ts +225 -0
- package/src/services/watch.service.test.ts +553 -0
- package/src/services/watch.service.ts +71 -0
- package/src/types/brands.test.ts +45 -0
- package/src/types/brands.ts +32 -0
- package/src/types/config.ts +79 -0
- package/src/types/document.ts +30 -0
- package/src/types/index.ts +66 -0
- package/src/types/job.ts +46 -0
- package/src/types/progress.ts +9 -0
- package/src/types/result.test.ts +44 -0
- package/src/types/result.ts +41 -0
- package/src/types/search.ts +95 -0
- package/src/types/store.test.ts +69 -0
- package/src/types/store.ts +47 -0
- package/src/utils/type-guards.test.ts +346 -0
- package/src/utils/type-guards.ts +61 -0
- package/src/workers/background-worker-cli.ts +105 -0
- package/src/workers/background-worker.test.ts +178 -0
- package/src/workers/background-worker.ts +294 -0
- package/src/workers/spawn-worker.test.ts +128 -0
- package/src/workers/spawn-worker.ts +49 -0
- package/tests/analysis/ast-parser.test.ts +98 -0
- package/tests/analysis/code-graph.test.ts +60 -0
- package/tests/fixtures/README.md +114 -0
- package/tests/fixtures/code-snippets/api/error-handling.ts +267 -0
- package/tests/fixtures/code-snippets/api/rest-controller.ts +303 -0
- package/tests/fixtures/code-snippets/auth/jwt-auth.ts +213 -0
- package/tests/fixtures/code-snippets/auth/oauth-flow.ts +245 -0
- package/tests/fixtures/code-snippets/database/repository-pattern.ts +272 -0
- package/tests/fixtures/corpus/VERSION.md +25 -0
- package/tests/fixtures/corpus/articles/jwt-authentication.md +97 -0
- package/tests/fixtures/corpus/articles/react-hooks-patterns.md +127 -0
- package/tests/fixtures/corpus/articles/typescript-generics.md +111 -0
- package/tests/fixtures/corpus/documentation/express-middleware.md +71 -0
- package/tests/fixtures/corpus/documentation/express-routing.md +83 -0
- package/tests/fixtures/corpus/documentation/node-streams.md +78 -0
- package/tests/fixtures/corpus/oss-repos/express/History.md +3871 -0
- package/tests/fixtures/corpus/oss-repos/express/LICENSE +24 -0
- package/tests/fixtures/corpus/oss-repos/express/Readme.md +276 -0
- package/tests/fixtures/corpus/oss-repos/express/SECURITY.md +56 -0
- package/tests/fixtures/corpus/oss-repos/express/benchmarks/Makefile +17 -0
- package/tests/fixtures/corpus/oss-repos/express/benchmarks/README.md +34 -0
- package/tests/fixtures/corpus/oss-repos/express/benchmarks/middleware.js +20 -0
- package/tests/fixtures/corpus/oss-repos/express/benchmarks/run +18 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/README.md +29 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/auth/index.js +134 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/auth/views/foot.ejs +2 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/auth/views/head.ejs +20 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/auth/views/login.ejs +21 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/content-negotiation/db.js +9 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/content-negotiation/index.js +46 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/content-negotiation/users.js +19 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/cookie-sessions/index.js +25 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/cookies/index.js +53 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/downloads/files/CCTV/345/244/247/350/265/233/344/270/212/346/265/267/345/210/206/350/265/233/345/214/272.txt +2 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/downloads/files/amazing.txt +1 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/downloads/files/notes/groceries.txt +3 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/downloads/index.js +40 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/ejs/index.js +57 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/ejs/public/stylesheets/style.css +4 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/ejs/views/footer.html +2 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/ejs/views/header.html +9 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/ejs/views/users.html +10 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/error/index.js +53 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/error-pages/index.js +103 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/error-pages/views/404.ejs +3 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/error-pages/views/500.ejs +8 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/error-pages/views/error_header.ejs +10 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/error-pages/views/footer.ejs +2 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/error-pages/views/index.ejs +20 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/hello-world/index.js +15 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/markdown/index.js +44 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/markdown/views/index.md +4 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/multi-router/controllers/api_v1.js +15 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/multi-router/controllers/api_v2.js +15 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/multi-router/index.js +18 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/main/index.js +5 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/pet/index.js +31 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/pet/views/edit.ejs +17 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/pet/views/show.ejs +15 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/user/index.js +41 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/user/views/edit.hbs +27 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/user/views/list.hbs +18 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/user/views/show.hbs +31 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/user-pet/index.js +22 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/db.js +16 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/index.js +95 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/lib/boot.js +83 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/public/style.css +14 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/views/404.ejs +13 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/mvc/views/5xx.ejs +13 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/online/index.js +61 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/params/index.js +74 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/resource/index.js +95 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-map/index.js +75 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-middleware/index.js +90 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/index.js +55 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/post.js +13 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/public/style.css +24 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/site.js +5 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/user.js +47 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/footer.ejs +2 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/header.ejs +9 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/index.ejs +10 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/posts/index.ejs +12 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/users/edit.ejs +23 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/users/index.ejs +14 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/users/view.ejs +9 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/search/index.js +61 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/search/public/client.js +15 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/search/public/index.html +21 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/session/index.js +37 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/session/redis.js +39 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/static-files/index.js +43 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/static-files/public/css/style.css +3 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/static-files/public/hello.txt +1 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/static-files/public/js/app.js +1 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/vhost/index.js +53 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/view-constructor/github-view.js +53 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/view-constructor/index.js +48 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/view-locals/index.js +155 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/view-locals/user.js +36 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/view-locals/views/index.ejs +20 -0
- package/tests/fixtures/corpus/oss-repos/express/examples/web-service/index.js +117 -0
- package/tests/fixtures/corpus/oss-repos/express/index.js +11 -0
- package/tests/fixtures/corpus/oss-repos/express/lib/application.js +631 -0
- package/tests/fixtures/corpus/oss-repos/express/lib/express.js +81 -0
- package/tests/fixtures/corpus/oss-repos/express/lib/request.js +514 -0
- package/tests/fixtures/corpus/oss-repos/express/lib/response.js +1053 -0
- package/tests/fixtures/corpus/oss-repos/express/lib/utils.js +271 -0
- package/tests/fixtures/corpus/oss-repos/express/lib/view.js +205 -0
- package/tests/fixtures/corpus/oss-repos/express/package.json +99 -0
- package/tests/fixtures/corpus/oss-repos/express/test/Route.js +274 -0
- package/tests/fixtures/corpus/oss-repos/express/test/Router.js +636 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/auth.js +117 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/content-negotiation.js +49 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/cookie-sessions.js +38 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/cookies.js +71 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/downloads.js +47 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/ejs.js +17 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/error-pages.js +99 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/error.js +29 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/hello-world.js +21 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/markdown.js +21 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/multi-router.js +44 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/mvc.js +132 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/params.js +44 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/resource.js +68 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/route-map.js +45 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/route-separation.js +97 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/vhost.js +46 -0
- package/tests/fixtures/corpus/oss-repos/express/test/acceptance/web-service.js +105 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.all.js +38 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.engine.js +83 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.head.js +66 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.js +120 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.listen.js +55 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.locals.js +26 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.options.js +116 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.param.js +323 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.render.js +374 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.request.js +143 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.response.js +143 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.route.js +197 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.router.js +1217 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.routes.error.js +62 -0
- package/tests/fixtures/corpus/oss-repos/express/test/app.use.js +542 -0
- package/tests/fixtures/corpus/oss-repos/express/test/config.js +207 -0
- package/tests/fixtures/corpus/oss-repos/express/test/exports.js +82 -0
- package/tests/fixtures/corpus/oss-repos/express/test/express.json.js +755 -0
- package/tests/fixtures/corpus/oss-repos/express/test/express.raw.js +513 -0
- package/tests/fixtures/corpus/oss-repos/express/test/express.static.js +815 -0
- package/tests/fixtures/corpus/oss-repos/express/test/express.text.js +566 -0
- package/tests/fixtures/corpus/oss-repos/express/test/express.urlencoded.js +828 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/% of dogs.txt +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/.name +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/blog/index.html +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/blog/post/index.tmpl +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/broken.send +0 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/default_layout/name.tmpl +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/default_layout/user.tmpl +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/email.tmpl +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/empty.txt +0 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/local_layout/user.tmpl +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/name.tmpl +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/name.txt +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/nums.txt +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/pets/names.txt +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/snow /342/230/203/.gitkeep +0 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/todo.html +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/todo.txt +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/user.html +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/user.tmpl +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/users/index.html +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/fixtures/users/tobi.txt +1 -0
- package/tests/fixtures/corpus/oss-repos/express/test/middleware.basic.js +42 -0
- package/tests/fixtures/corpus/oss-repos/express/test/regression.js +20 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.accepts.js +125 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.acceptsCharsets.js +50 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.acceptsEncodings.js +39 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.acceptsLanguages.js +57 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.baseUrl.js +88 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.fresh.js +70 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.get.js +60 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.host.js +156 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.hostname.js +188 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.ip.js +113 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.ips.js +71 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.is.js +169 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.path.js +20 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.protocol.js +113 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.query.js +106 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.range.js +104 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.route.js +28 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.secure.js +101 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.signedCookies.js +37 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.stale.js +50 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.subdomains.js +173 -0
- package/tests/fixtures/corpus/oss-repos/express/test/req.xhr.js +42 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.append.js +116 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.attachment.js +79 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.clearCookie.js +62 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.cookie.js +295 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.download.js +487 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.format.js +248 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.get.js +21 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.json.js +186 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.jsonp.js +344 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.links.js +65 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.locals.js +40 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.location.js +316 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.redirect.js +214 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.render.js +367 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.send.js +569 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.sendFile.js +913 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.sendStatus.js +44 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.set.js +124 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.status.js +206 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.type.js +46 -0
- package/tests/fixtures/corpus/oss-repos/express/test/res.vary.js +90 -0
- package/tests/fixtures/corpus/oss-repos/express/test/support/env.js +3 -0
- package/tests/fixtures/corpus/oss-repos/express/test/support/tmpl.js +36 -0
- package/tests/fixtures/corpus/oss-repos/express/test/support/utils.js +86 -0
- package/tests/fixtures/corpus/oss-repos/express/test/utils.js +83 -0
- package/tests/fixtures/corpus/oss-repos/hono/.devcontainer/Dockerfile +11 -0
- package/tests/fixtures/corpus/oss-repos/hono/.devcontainer/devcontainer.json +21 -0
- package/tests/fixtures/corpus/oss-repos/hono/.devcontainer/docker-compose.yml +18 -0
- package/tests/fixtures/corpus/oss-repos/hono/.eslintignore +1 -0
- package/tests/fixtures/corpus/oss-repos/hono/.eslintrc.cjs +9 -0
- package/tests/fixtures/corpus/oss-repos/hono/.gitpod.yml +9 -0
- package/tests/fixtures/corpus/oss-repos/hono/.prettierrc +9 -0
- package/tests/fixtures/corpus/oss-repos/hono/.vitest.config/jsx-runtime-default.ts +15 -0
- package/tests/fixtures/corpus/oss-repos/hono/.vitest.config/jsx-runtime-dom.ts +15 -0
- package/tests/fixtures/corpus/oss-repos/hono/.vitest.config/setup-vitest.ts +47 -0
- package/tests/fixtures/corpus/oss-repos/hono/LICENSE +21 -0
- package/tests/fixtures/corpus/oss-repos/hono/README.md +91 -0
- package/tests/fixtures/corpus/oss-repos/hono/build.ts +80 -0
- package/tests/fixtures/corpus/oss-repos/hono/bun.lockb +0 -0
- package/tests/fixtures/corpus/oss-repos/hono/bunfig.toml +7 -0
- package/tests/fixtures/corpus/oss-repos/hono/codecov.yml +13 -0
- package/tests/fixtures/corpus/oss-repos/hono/docs/CODE_OF_CONDUCT.md +128 -0
- package/tests/fixtures/corpus/oss-repos/hono/docs/CONTRIBUTING.md +62 -0
- package/tests/fixtures/corpus/oss-repos/hono/docs/MIGRATION.md +295 -0
- package/tests/fixtures/corpus/oss-repos/hono/docs/images/hono-logo.png +0 -0
- package/tests/fixtures/corpus/oss-repos/hono/docs/images/hono-logo.pxm +0 -0
- package/tests/fixtures/corpus/oss-repos/hono/docs/images/hono-logo.svg +6 -0
- package/tests/fixtures/corpus/oss-repos/hono/docs/images/hono-title.png +0 -0
- package/tests/fixtures/corpus/oss-repos/hono/docs/images/hono-title.pxm +0 -0
- package/tests/fixtures/corpus/oss-repos/hono/jsr.json +119 -0
- package/tests/fixtures/corpus/oss-repos/hono/package.cjs.json +3 -0
- package/tests/fixtures/corpus/oss-repos/hono/package.json +650 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/aws-lambda/handler.ts +492 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/aws-lambda/index.ts +13 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/aws-lambda/types.ts +144 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/bun/conninfo.ts +28 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/bun/index.ts +9 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/bun/serve-static.ts +35 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/bun/server.ts +30 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/bun/ssg.ts +27 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/bun/websocket.ts +110 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-pages/handler.ts +120 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-pages/index.ts +7 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/conninfo.ts +7 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/index.ts +8 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/serve-static-module.ts +12 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/serve-static.ts +39 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/utils.ts +50 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/websocket.ts +50 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/deno/conninfo.ts +17 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/deno/deno.d.ts +28 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/deno/index.ts +9 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/deno/serve-static.ts +40 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/deno/ssg.ts +27 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/deno/websocket.ts +51 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/lambda-edge/conninfo.ts +15 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/lambda-edge/handler.ts +189 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/lambda-edge/index.ts +14 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/netlify/handler.ts +10 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/netlify/index.ts +6 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/netlify/mod.ts +1 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/service-worker/handler.ts +34 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/service-worker/index.ts +5 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/service-worker/types.ts +14 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/vercel/conninfo.ts +8 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/vercel/handler.ts +9 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/vercel/index.ts +7 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/client/client.ts +214 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/client/index.ts +14 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/client/types.ts +180 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/client/utils.ts +54 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/compose.ts +94 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/context.ts +914 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/accepts/accepts.ts +81 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/accepts/index.ts +6 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/adapter/index.ts +85 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/conninfo/index.ts +6 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/conninfo/types.ts +45 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/cookie/index.ts +130 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/css/common.ts +243 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/css/index.ts +220 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/dev/index.ts +79 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/factory/index.ts +246 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/html/index.ts +56 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/ssg/index.ts +13 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/ssg/middleware.ts +79 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/ssg/ssg.ts +388 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/ssg/utils.ts +71 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/streaming/index.ts +9 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/streaming/sse.ts +89 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/streaming/stream.ts +36 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/streaming/text.ts +15 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/testing/index.ts +26 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/websocket/index.ts +57 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/hono-base.ts +523 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/hono.ts +34 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/http-exception.ts +78 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/index.ts +51 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/base.ts +419 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/children.ts +20 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/components.ts +195 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/constants.ts +5 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/context.ts +50 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/client.ts +89 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/components.ts +39 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/context.ts +52 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/css.ts +246 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/hooks/index.ts +91 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/index.ts +159 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/intrinsic-element/components.ts +398 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/jsx-dev-runtime.ts +22 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/jsx-runtime.ts +7 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/render.ts +772 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/server.ts +70 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/utils.ts +7 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/hooks/index.ts +426 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/index.ts +114 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/intrinsic-element/common.ts +11 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/intrinsic-element/components.ts +196 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/intrinsic-elements.ts +924 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/jsx-dev-runtime.ts +26 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/jsx-runtime.ts +18 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/streaming.ts +184 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/types.ts +41 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/utils.ts +36 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/basic-auth/index.ts +128 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/bearer-auth/index.ts +159 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/body-limit/index.ts +115 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/cache/index.ts +127 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/combine/index.ts +153 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/compress/index.ts +79 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/context-storage/index.ts +55 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/cors/index.ts +141 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/csrf/index.ts +90 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/etag/index.ts +88 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/ip-restriction/index.ts +178 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/jsx-renderer/index.ts +158 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/jwt/index.ts +8 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/jwt/jwt.ts +159 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/logger/index.ts +93 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/method-override/index.ts +146 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/powered-by/index.ts +13 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/pretty-json/index.ts +50 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/request-id/index.ts +8 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/request-id/request-id.ts +59 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/secure-headers/index.ts +8 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/secure-headers/permissions-policy.ts +86 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/secure-headers/secure-headers.ts +319 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/serve-static/index.ts +140 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/timeout/index.ts +58 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/timing/index.ts +7 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/timing/timing.ts +225 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/trailing-slash/index.ts +71 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/preset/quick.ts +24 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/preset/tiny.ts +20 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/request.ts +403 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/linear-router/index.ts +6 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/linear-router/router.ts +132 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/pattern-router/index.ts +6 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/pattern-router/router.ts +54 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/index.ts +6 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/node.ts +159 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/router.ts +274 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/trie.ts +74 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/smart-router/index.ts +6 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/smart-router/router.ts +69 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/trie-router/index.ts +6 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/trie-router/node.ts +205 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router/trie-router/router.ts +28 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/router.ts +103 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/types.ts +2009 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/basic-auth.ts +26 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/body.ts +225 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/buffer.ts +65 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/color.ts +26 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/concurrent.ts +55 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/cookie.ts +230 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/crypto.ts +65 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/encode.ts +34 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/filepath.ts +56 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/handler.ts +15 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/html.ts +182 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/http-status.ts +69 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/ipaddr.ts +113 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/jwt/index.ts +7 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/jwt/jwa.ts +23 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/jwt/jws.ts +226 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/jwt/jwt.ts +114 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/jwt/types.ts +83 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/jwt/utf8.ts +7 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/mime.ts +142 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/stream.ts +96 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/types.ts +105 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/url.ts +310 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/validator/index.ts +7 -0
- package/tests/fixtures/corpus/oss-repos/hono/src/validator/validator.ts +151 -0
- package/tests/fixtures/corpus/oss-repos/hono/tsconfig.build.json +23 -0
- package/tests/fixtures/corpus/oss-repos/hono/tsconfig.json +28 -0
- package/tests/fixtures/corpus/oss-repos/hono/vitest.config.ts +34 -0
- package/tests/fixtures/corpus/oss-repos/hono/yarn.lock +6232 -0
- package/tests/fixtures/documentation/api-reference.md +412 -0
- package/tests/fixtures/documentation/architecture.md +214 -0
- package/tests/fixtures/documentation/deployment-guide.md +420 -0
- package/tests/fixtures/github-readmes/express.md +133 -0
- package/tests/fixtures/github-readmes/nextjs.md +106 -0
- package/tests/fixtures/github-readmes/react.md +74 -0
- package/tests/fixtures/github-readmes/typescript.md +93 -0
- package/tests/fixtures/github-readmes/vite.md +79 -0
- package/tests/fixtures/queries/core.json +125 -0
- package/tests/fixtures/queries/extended.json +427 -0
- package/tests/fixtures/queries/generated/.gitkeep +0 -0
- package/tests/fixtures/test-server.ts +267 -0
- package/tests/helpers/performance-metrics.ts +387 -0
- package/tests/helpers/search-relevance.ts +381 -0
- package/tests/integration/cli-consistency.test.ts +299 -0
- package/tests/integration/cli.test.ts +69 -0
- package/tests/integration/e2e-workflow.test.ts +612 -0
- package/tests/integration/python-bridge.test.ts +183 -0
- package/tests/integration/search-quality.test.ts +718 -0
- package/tests/integration/stress.test.ts +326 -0
- package/tests/mcp/server.test.ts +15 -0
- package/tests/scripts/schemas/evaluation.json +44 -0
- package/tests/scripts/schemas/query-generation.json +21 -0
- package/tests/services/code-unit.service.test.ts +47 -0
- package/tests/services/search.progressive-context.test.ts +35 -0
- package/tsconfig.json +34 -0
- package/tsup.config.ts +15 -0
- package/turndown-plugin-gfm.d.ts +29 -0
- package/vitest.config.ts +79 -0
|
@@ -0,0 +1,3617 @@
|
|
|
1
|
+
// src/services/job.service.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
|
|
6
|
+
// src/types/result.ts
|
|
7
|
+
function ok(data) {
|
|
8
|
+
return { success: true, data };
|
|
9
|
+
}
|
|
10
|
+
function err(error) {
|
|
11
|
+
return { success: false, error };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/services/job.service.ts
|
|
15
|
+
var JobService = class {
|
|
16
|
+
jobsDir;
|
|
17
|
+
constructor(dataDir) {
|
|
18
|
+
const baseDir = dataDir ?? path.join(
|
|
19
|
+
process.env["HOME"] ?? process.env["USERPROFILE"] ?? ".",
|
|
20
|
+
".local/share/bluera-knowledge"
|
|
21
|
+
);
|
|
22
|
+
this.jobsDir = path.join(baseDir, "jobs");
|
|
23
|
+
if (!fs.existsSync(this.jobsDir)) {
|
|
24
|
+
fs.mkdirSync(this.jobsDir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a new job
|
|
29
|
+
*/
|
|
30
|
+
createJob(params) {
|
|
31
|
+
const job = {
|
|
32
|
+
id: `job_${randomUUID().replace(/-/g, "").substring(0, 12)}`,
|
|
33
|
+
type: params.type,
|
|
34
|
+
status: "pending",
|
|
35
|
+
progress: 0,
|
|
36
|
+
message: params.message ?? `${params.type} job created`,
|
|
37
|
+
details: params.details,
|
|
38
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
40
|
+
};
|
|
41
|
+
this.writeJob(job);
|
|
42
|
+
return job;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Update an existing job
|
|
46
|
+
*/
|
|
47
|
+
updateJob(jobId, updates) {
|
|
48
|
+
const job = this.getJob(jobId);
|
|
49
|
+
if (!job) {
|
|
50
|
+
throw new Error(`Job ${jobId} not found`);
|
|
51
|
+
}
|
|
52
|
+
if (updates.status !== void 0) {
|
|
53
|
+
job.status = updates.status;
|
|
54
|
+
}
|
|
55
|
+
if (updates.progress !== void 0) {
|
|
56
|
+
job.progress = updates.progress;
|
|
57
|
+
}
|
|
58
|
+
if (updates.message !== void 0) {
|
|
59
|
+
job.message = updates.message;
|
|
60
|
+
}
|
|
61
|
+
if (updates.details !== void 0) {
|
|
62
|
+
job.details = { ...job.details, ...updates.details };
|
|
63
|
+
}
|
|
64
|
+
job.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
65
|
+
this.writeJob(job);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get a job by ID
|
|
69
|
+
*/
|
|
70
|
+
getJob(jobId) {
|
|
71
|
+
const jobFile = path.join(this.jobsDir, `${jobId}.json`);
|
|
72
|
+
if (!fs.existsSync(jobFile)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const content = fs.readFileSync(jobFile, "utf-8");
|
|
77
|
+
return JSON.parse(content);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(`Error reading job ${jobId}:`, error);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* List all jobs with optional status filter
|
|
85
|
+
*/
|
|
86
|
+
listJobs(statusFilter) {
|
|
87
|
+
if (!fs.existsSync(this.jobsDir)) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
const files = fs.readdirSync(this.jobsDir);
|
|
91
|
+
const jobs = [];
|
|
92
|
+
for (const file of files) {
|
|
93
|
+
if (!file.endsWith(".json") || file.endsWith(".pid")) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const content = fs.readFileSync(path.join(this.jobsDir, file), "utf-8");
|
|
98
|
+
const job = JSON.parse(content);
|
|
99
|
+
if (statusFilter !== void 0) {
|
|
100
|
+
const filters = Array.isArray(statusFilter) ? statusFilter : [statusFilter];
|
|
101
|
+
if (filters.includes(job.status)) {
|
|
102
|
+
jobs.push(job);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
jobs.push(job);
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error(`Error reading job file ${file}:`, error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
jobs.sort(
|
|
112
|
+
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
113
|
+
);
|
|
114
|
+
return jobs;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* List active jobs (pending or running)
|
|
118
|
+
*/
|
|
119
|
+
listActiveJobs() {
|
|
120
|
+
return this.listJobs(["pending", "running"]);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Cancel a job
|
|
124
|
+
*/
|
|
125
|
+
cancelJob(jobId) {
|
|
126
|
+
const job = this.getJob(jobId);
|
|
127
|
+
if (!job) {
|
|
128
|
+
return err(new Error(`Job ${jobId} not found`));
|
|
129
|
+
}
|
|
130
|
+
if (job.status === "completed" || job.status === "failed") {
|
|
131
|
+
return err(new Error(`Cannot cancel ${job.status} job`));
|
|
132
|
+
}
|
|
133
|
+
if (job.status === "cancelled") {
|
|
134
|
+
return ok(void 0);
|
|
135
|
+
}
|
|
136
|
+
this.updateJob(jobId, {
|
|
137
|
+
status: "cancelled",
|
|
138
|
+
message: "Job cancelled by user",
|
|
139
|
+
details: { cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
140
|
+
});
|
|
141
|
+
const pidFile = path.join(this.jobsDir, `${jobId}.pid`);
|
|
142
|
+
if (fs.existsSync(pidFile)) {
|
|
143
|
+
try {
|
|
144
|
+
const pid = parseInt(fs.readFileSync(pidFile, "utf-8"), 10);
|
|
145
|
+
process.kill(pid, "SIGTERM");
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
fs.unlinkSync(pidFile);
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return ok(void 0);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Clean up old completed/failed/cancelled jobs
|
|
157
|
+
*/
|
|
158
|
+
cleanupOldJobs(olderThanHours = 24) {
|
|
159
|
+
const jobs = this.listJobs();
|
|
160
|
+
const cutoffTime = Date.now() - olderThanHours * 60 * 60 * 1e3;
|
|
161
|
+
let cleaned = 0;
|
|
162
|
+
for (const job of jobs) {
|
|
163
|
+
if ((job.status === "completed" || job.status === "failed" || job.status === "cancelled") && new Date(job.updatedAt).getTime() < cutoffTime) {
|
|
164
|
+
const jobFile = path.join(this.jobsDir, `${job.id}.json`);
|
|
165
|
+
try {
|
|
166
|
+
fs.unlinkSync(jobFile);
|
|
167
|
+
cleaned++;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error(`Error deleting job file ${job.id}:`, error);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return cleaned;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Delete a specific job
|
|
177
|
+
*/
|
|
178
|
+
deleteJob(jobId) {
|
|
179
|
+
const jobFile = path.join(this.jobsDir, `${jobId}.json`);
|
|
180
|
+
if (!fs.existsSync(jobFile)) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
fs.unlinkSync(jobFile);
|
|
185
|
+
return true;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error(`Error deleting job ${jobId}:`, error);
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Write job to file
|
|
193
|
+
*/
|
|
194
|
+
writeJob(job) {
|
|
195
|
+
const jobFile = path.join(this.jobsDir, `${job.id}.json`);
|
|
196
|
+
fs.writeFileSync(jobFile, JSON.stringify(job, null, 2), "utf-8");
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// src/services/config.service.ts
|
|
201
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
202
|
+
import { dirname as dirname2, resolve } from "path";
|
|
203
|
+
import { homedir } from "os";
|
|
204
|
+
|
|
205
|
+
// src/types/config.ts
|
|
206
|
+
var DEFAULT_CONFIG = {
|
|
207
|
+
version: 1,
|
|
208
|
+
dataDir: ".bluera/bluera-knowledge/data",
|
|
209
|
+
embedding: {
|
|
210
|
+
model: "Xenova/all-MiniLM-L6-v2",
|
|
211
|
+
batchSize: 32,
|
|
212
|
+
dimensions: 384
|
|
213
|
+
},
|
|
214
|
+
indexing: {
|
|
215
|
+
concurrency: 4,
|
|
216
|
+
chunkSize: 1e3,
|
|
217
|
+
chunkOverlap: 150,
|
|
218
|
+
ignorePatterns: ["node_modules/**", ".git/**", "*.min.js", "*.map"]
|
|
219
|
+
},
|
|
220
|
+
search: {
|
|
221
|
+
defaultMode: "hybrid",
|
|
222
|
+
defaultLimit: 10,
|
|
223
|
+
minScore: 0.5,
|
|
224
|
+
rrf: {
|
|
225
|
+
k: 40,
|
|
226
|
+
vectorWeight: 0.7,
|
|
227
|
+
ftsWeight: 0.3
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
crawl: {
|
|
231
|
+
userAgent: "BlueraKnowledge/1.0",
|
|
232
|
+
timeout: 3e4,
|
|
233
|
+
maxConcurrency: 3
|
|
234
|
+
},
|
|
235
|
+
server: {
|
|
236
|
+
port: 3847,
|
|
237
|
+
host: "127.0.0.1"
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// src/services/project-root.service.ts
|
|
242
|
+
import { existsSync, statSync, realpathSync } from "fs";
|
|
243
|
+
import { dirname, join, normalize, sep } from "path";
|
|
244
|
+
var ProjectRootService = class {
|
|
245
|
+
/**
|
|
246
|
+
* Resolve project root directory using hierarchical detection.
|
|
247
|
+
*/
|
|
248
|
+
static resolve(options) {
|
|
249
|
+
if (options?.projectRoot !== void 0 && options.projectRoot !== "") {
|
|
250
|
+
return this.normalize(options.projectRoot);
|
|
251
|
+
}
|
|
252
|
+
const projectRootEnv = process.env["PROJECT_ROOT"];
|
|
253
|
+
if (projectRootEnv !== void 0 && projectRootEnv !== "") {
|
|
254
|
+
return this.normalize(projectRootEnv);
|
|
255
|
+
}
|
|
256
|
+
const pwdEnv = process.env["PWD"];
|
|
257
|
+
if (pwdEnv !== void 0 && pwdEnv !== "") {
|
|
258
|
+
return this.normalize(pwdEnv);
|
|
259
|
+
}
|
|
260
|
+
const gitRoot = this.findGitRoot(process.cwd());
|
|
261
|
+
if (gitRoot !== null) {
|
|
262
|
+
return gitRoot;
|
|
263
|
+
}
|
|
264
|
+
return process.cwd();
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Find git repository root by walking up the directory tree looking for .git
|
|
268
|
+
*/
|
|
269
|
+
static findGitRoot(startPath) {
|
|
270
|
+
let currentPath = normalize(startPath);
|
|
271
|
+
const root = normalize(sep);
|
|
272
|
+
while (currentPath !== root) {
|
|
273
|
+
const gitPath = join(currentPath, ".git");
|
|
274
|
+
if (existsSync(gitPath)) {
|
|
275
|
+
try {
|
|
276
|
+
const stats = statSync(gitPath);
|
|
277
|
+
if (stats.isDirectory() || stats.isFile()) {
|
|
278
|
+
return currentPath;
|
|
279
|
+
}
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const parentPath = dirname(currentPath);
|
|
284
|
+
if (parentPath === currentPath) {
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
currentPath = parentPath;
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Normalize path by resolving symlinks and normalizing separators
|
|
293
|
+
*/
|
|
294
|
+
static normalize(path3) {
|
|
295
|
+
try {
|
|
296
|
+
const realPath = realpathSync(path3);
|
|
297
|
+
return normalize(realPath);
|
|
298
|
+
} catch {
|
|
299
|
+
return normalize(path3);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Validate that a path exists and is a directory
|
|
304
|
+
*/
|
|
305
|
+
static validate(path3) {
|
|
306
|
+
try {
|
|
307
|
+
const stats = statSync(path3);
|
|
308
|
+
return stats.isDirectory();
|
|
309
|
+
} catch {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/services/config.service.ts
|
|
316
|
+
var ConfigService = class {
|
|
317
|
+
configPath;
|
|
318
|
+
dataDir;
|
|
319
|
+
config = null;
|
|
320
|
+
constructor(configPath = `${homedir()}/.bluera/bluera-knowledge/config.json`, dataDir, projectRoot) {
|
|
321
|
+
this.configPath = configPath;
|
|
322
|
+
if (dataDir !== void 0 && dataDir !== "") {
|
|
323
|
+
this.dataDir = dataDir;
|
|
324
|
+
} else {
|
|
325
|
+
const root = projectRoot ?? ProjectRootService.resolve();
|
|
326
|
+
this.dataDir = this.expandPath(DEFAULT_CONFIG.dataDir, root);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async load() {
|
|
330
|
+
if (this.config !== null) {
|
|
331
|
+
return this.config;
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const content = await readFile(this.configPath, "utf-8");
|
|
335
|
+
this.config = { ...DEFAULT_CONFIG, ...JSON.parse(content) };
|
|
336
|
+
} catch {
|
|
337
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
338
|
+
}
|
|
339
|
+
return this.config;
|
|
340
|
+
}
|
|
341
|
+
async save(config) {
|
|
342
|
+
await mkdir(dirname2(this.configPath), { recursive: true });
|
|
343
|
+
await writeFile(this.configPath, JSON.stringify(config, null, 2));
|
|
344
|
+
this.config = config;
|
|
345
|
+
}
|
|
346
|
+
resolveDataDir() {
|
|
347
|
+
return this.dataDir;
|
|
348
|
+
}
|
|
349
|
+
expandPath(path3, baseDir) {
|
|
350
|
+
if (path3.startsWith("~")) {
|
|
351
|
+
return path3.replace("~", homedir());
|
|
352
|
+
}
|
|
353
|
+
if (!path3.startsWith("/")) {
|
|
354
|
+
return resolve(baseDir, path3);
|
|
355
|
+
}
|
|
356
|
+
return path3;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// src/services/store.service.ts
|
|
361
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir3, stat } from "fs/promises";
|
|
362
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
363
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
364
|
+
|
|
365
|
+
// src/types/brands.ts
|
|
366
|
+
var ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
367
|
+
function isStoreId(value) {
|
|
368
|
+
return value.length > 0 && ID_PATTERN.test(value);
|
|
369
|
+
}
|
|
370
|
+
function isDocumentId(value) {
|
|
371
|
+
return value.length > 0 && ID_PATTERN.test(value);
|
|
372
|
+
}
|
|
373
|
+
function createStoreId(value) {
|
|
374
|
+
if (!isStoreId(value)) {
|
|
375
|
+
throw new Error(`Invalid store ID: ${value}`);
|
|
376
|
+
}
|
|
377
|
+
return value;
|
|
378
|
+
}
|
|
379
|
+
function createDocumentId(value) {
|
|
380
|
+
if (!isDocumentId(value)) {
|
|
381
|
+
throw new Error(`Invalid document ID: ${value}`);
|
|
382
|
+
}
|
|
383
|
+
return value;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/plugin/git-clone.ts
|
|
387
|
+
import { spawn } from "child_process";
|
|
388
|
+
import { mkdir as mkdir2 } from "fs/promises";
|
|
389
|
+
async function cloneRepository(options) {
|
|
390
|
+
const { url, targetDir, branch, depth = 1 } = options;
|
|
391
|
+
await mkdir2(targetDir, { recursive: true });
|
|
392
|
+
const args = ["clone", "--depth", String(depth)];
|
|
393
|
+
if (branch !== void 0) {
|
|
394
|
+
args.push("--branch", branch);
|
|
395
|
+
}
|
|
396
|
+
args.push(url, targetDir);
|
|
397
|
+
return new Promise((resolve3) => {
|
|
398
|
+
const git = spawn("git", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
399
|
+
let stderr = "";
|
|
400
|
+
git.stderr.on("data", (data) => {
|
|
401
|
+
stderr += data.toString();
|
|
402
|
+
});
|
|
403
|
+
git.on("close", (code) => {
|
|
404
|
+
if (code === 0) {
|
|
405
|
+
resolve3(ok(targetDir));
|
|
406
|
+
} else {
|
|
407
|
+
resolve3(err(new Error(`Git clone failed: ${stderr}`)));
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
function extractRepoName(url) {
|
|
413
|
+
const match = /\/([^/]+?)(\.git)?$/.exec(url);
|
|
414
|
+
const name = match?.[1];
|
|
415
|
+
if (name === void 0) {
|
|
416
|
+
return "repository";
|
|
417
|
+
}
|
|
418
|
+
return name;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// src/services/store.service.ts
|
|
422
|
+
var StoreService = class {
|
|
423
|
+
dataDir;
|
|
424
|
+
registry = { stores: [] };
|
|
425
|
+
constructor(dataDir) {
|
|
426
|
+
this.dataDir = dataDir;
|
|
427
|
+
}
|
|
428
|
+
async initialize() {
|
|
429
|
+
await mkdir3(this.dataDir, { recursive: true });
|
|
430
|
+
await this.loadRegistry();
|
|
431
|
+
}
|
|
432
|
+
async create(input) {
|
|
433
|
+
const existing = await this.getByName(input.name);
|
|
434
|
+
if (existing !== void 0) {
|
|
435
|
+
return err(new Error(`Store with name "${input.name}" already exists`));
|
|
436
|
+
}
|
|
437
|
+
const id = createStoreId(randomUUID2());
|
|
438
|
+
const now = /* @__PURE__ */ new Date();
|
|
439
|
+
let store;
|
|
440
|
+
switch (input.type) {
|
|
441
|
+
case "file": {
|
|
442
|
+
if (input.path === void 0) {
|
|
443
|
+
return err(new Error("Path is required for file stores"));
|
|
444
|
+
}
|
|
445
|
+
const normalizedPath = resolve2(input.path);
|
|
446
|
+
try {
|
|
447
|
+
const stats = await stat(normalizedPath);
|
|
448
|
+
if (!stats.isDirectory()) {
|
|
449
|
+
return err(new Error(`Path is not a directory: ${normalizedPath}`));
|
|
450
|
+
}
|
|
451
|
+
} catch {
|
|
452
|
+
return err(new Error(`Directory does not exist: ${normalizedPath}`));
|
|
453
|
+
}
|
|
454
|
+
store = {
|
|
455
|
+
type: "file",
|
|
456
|
+
id,
|
|
457
|
+
name: input.name,
|
|
458
|
+
path: normalizedPath,
|
|
459
|
+
description: input.description,
|
|
460
|
+
tags: input.tags,
|
|
461
|
+
status: "ready",
|
|
462
|
+
createdAt: now,
|
|
463
|
+
updatedAt: now
|
|
464
|
+
};
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
case "repo": {
|
|
468
|
+
let repoPath = input.path;
|
|
469
|
+
if (input.url !== void 0) {
|
|
470
|
+
const cloneDir = join2(this.dataDir, "repos", id);
|
|
471
|
+
const result = await cloneRepository({
|
|
472
|
+
url: input.url,
|
|
473
|
+
targetDir: cloneDir,
|
|
474
|
+
...input.branch !== void 0 ? { branch: input.branch } : {},
|
|
475
|
+
depth: input.depth ?? 1
|
|
476
|
+
});
|
|
477
|
+
if (!result.success) {
|
|
478
|
+
return err(result.error);
|
|
479
|
+
}
|
|
480
|
+
repoPath = result.data;
|
|
481
|
+
}
|
|
482
|
+
if (repoPath === void 0) {
|
|
483
|
+
return err(new Error("Path or URL required for repo stores"));
|
|
484
|
+
}
|
|
485
|
+
const normalizedRepoPath = resolve2(repoPath);
|
|
486
|
+
store = {
|
|
487
|
+
type: "repo",
|
|
488
|
+
id,
|
|
489
|
+
name: input.name,
|
|
490
|
+
path: normalizedRepoPath,
|
|
491
|
+
url: input.url,
|
|
492
|
+
branch: input.branch,
|
|
493
|
+
description: input.description,
|
|
494
|
+
tags: input.tags,
|
|
495
|
+
status: "ready",
|
|
496
|
+
createdAt: now,
|
|
497
|
+
updatedAt: now
|
|
498
|
+
};
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
case "web":
|
|
502
|
+
if (input.url === void 0) {
|
|
503
|
+
return err(new Error("URL is required for web stores"));
|
|
504
|
+
}
|
|
505
|
+
store = {
|
|
506
|
+
type: "web",
|
|
507
|
+
id,
|
|
508
|
+
name: input.name,
|
|
509
|
+
url: input.url,
|
|
510
|
+
depth: input.depth ?? 1,
|
|
511
|
+
description: input.description,
|
|
512
|
+
tags: input.tags,
|
|
513
|
+
status: "ready",
|
|
514
|
+
createdAt: now,
|
|
515
|
+
updatedAt: now
|
|
516
|
+
};
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
this.registry.stores.push(store);
|
|
520
|
+
await this.saveRegistry();
|
|
521
|
+
return ok(store);
|
|
522
|
+
}
|
|
523
|
+
async list(type) {
|
|
524
|
+
if (type !== void 0) {
|
|
525
|
+
return Promise.resolve(this.registry.stores.filter((s) => s.type === type));
|
|
526
|
+
}
|
|
527
|
+
return Promise.resolve([...this.registry.stores]);
|
|
528
|
+
}
|
|
529
|
+
async get(id) {
|
|
530
|
+
return Promise.resolve(this.registry.stores.find((s) => s.id === id));
|
|
531
|
+
}
|
|
532
|
+
async getByName(name) {
|
|
533
|
+
return Promise.resolve(this.registry.stores.find((s) => s.name === name));
|
|
534
|
+
}
|
|
535
|
+
async getByIdOrName(idOrName) {
|
|
536
|
+
return Promise.resolve(this.registry.stores.find((s) => s.id === idOrName || s.name === idOrName));
|
|
537
|
+
}
|
|
538
|
+
async update(id, updates) {
|
|
539
|
+
const index = this.registry.stores.findIndex((s) => s.id === id);
|
|
540
|
+
if (index === -1) {
|
|
541
|
+
return err(new Error(`Store not found: ${id}`));
|
|
542
|
+
}
|
|
543
|
+
const store = this.registry.stores[index];
|
|
544
|
+
if (store === void 0) {
|
|
545
|
+
return err(new Error(`Store not found: ${id}`));
|
|
546
|
+
}
|
|
547
|
+
const updated = {
|
|
548
|
+
...store,
|
|
549
|
+
...updates,
|
|
550
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
551
|
+
};
|
|
552
|
+
this.registry.stores[index] = updated;
|
|
553
|
+
await this.saveRegistry();
|
|
554
|
+
return ok(updated);
|
|
555
|
+
}
|
|
556
|
+
async delete(id) {
|
|
557
|
+
const index = this.registry.stores.findIndex((s) => s.id === id);
|
|
558
|
+
if (index === -1) {
|
|
559
|
+
return err(new Error(`Store not found: ${id}`));
|
|
560
|
+
}
|
|
561
|
+
this.registry.stores.splice(index, 1);
|
|
562
|
+
await this.saveRegistry();
|
|
563
|
+
return ok(void 0);
|
|
564
|
+
}
|
|
565
|
+
async loadRegistry() {
|
|
566
|
+
const registryPath = join2(this.dataDir, "stores.json");
|
|
567
|
+
try {
|
|
568
|
+
const content = await readFile2(registryPath, "utf-8");
|
|
569
|
+
const data = JSON.parse(content);
|
|
570
|
+
this.registry = {
|
|
571
|
+
stores: data.stores.map((s) => ({
|
|
572
|
+
...s,
|
|
573
|
+
id: createStoreId(s.id),
|
|
574
|
+
createdAt: new Date(s.createdAt),
|
|
575
|
+
updatedAt: new Date(s.updatedAt)
|
|
576
|
+
}))
|
|
577
|
+
};
|
|
578
|
+
} catch {
|
|
579
|
+
this.registry = { stores: [] };
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
async saveRegistry() {
|
|
583
|
+
const registryPath = join2(this.dataDir, "stores.json");
|
|
584
|
+
await writeFile2(registryPath, JSON.stringify(this.registry, null, 2));
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
// src/services/code-unit.service.ts
|
|
589
|
+
var CodeUnitService = class {
|
|
590
|
+
extractCodeUnit(code, symbolName, language) {
|
|
591
|
+
const lines = code.split("\n");
|
|
592
|
+
let startLine = -1;
|
|
593
|
+
let type = "function";
|
|
594
|
+
for (let i = 0; i < lines.length; i++) {
|
|
595
|
+
const line = lines[i] ?? "";
|
|
596
|
+
if (line.includes(`function ${symbolName}`)) {
|
|
597
|
+
startLine = i + 1;
|
|
598
|
+
type = "function";
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
if (line.includes(`class ${symbolName}`)) {
|
|
602
|
+
startLine = i + 1;
|
|
603
|
+
type = "class";
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
if (line.match(new RegExp(`(?:const|let|var)\\s+${symbolName}\\s*=`))) {
|
|
607
|
+
startLine = i + 1;
|
|
608
|
+
type = "const";
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (startLine === -1) return void 0;
|
|
613
|
+
let endLine = startLine;
|
|
614
|
+
let braceCount = 0;
|
|
615
|
+
let foundFirstBrace = false;
|
|
616
|
+
for (let i = startLine - 1; i < lines.length; i++) {
|
|
617
|
+
const line = lines[i] ?? "";
|
|
618
|
+
for (const char of line) {
|
|
619
|
+
if (char === "{") {
|
|
620
|
+
braceCount++;
|
|
621
|
+
foundFirstBrace = true;
|
|
622
|
+
}
|
|
623
|
+
if (char === "}") braceCount--;
|
|
624
|
+
}
|
|
625
|
+
if (foundFirstBrace && braceCount === 0) {
|
|
626
|
+
endLine = i + 1;
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
const fullContent = lines.slice(startLine - 1, endLine).join("\n");
|
|
631
|
+
const firstLine = lines[startLine - 1] ?? "";
|
|
632
|
+
const signature = this.extractSignature(firstLine, symbolName, type);
|
|
633
|
+
return {
|
|
634
|
+
type,
|
|
635
|
+
name: symbolName,
|
|
636
|
+
signature,
|
|
637
|
+
fullContent,
|
|
638
|
+
startLine,
|
|
639
|
+
endLine,
|
|
640
|
+
language
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
extractSignature(line, name, type) {
|
|
644
|
+
const sig = line.replace(/^\s*export\s+/, "").replace(/^\s*async\s+/, "").trim();
|
|
645
|
+
if (type === "function") {
|
|
646
|
+
const match = sig.match(/function\s+(\w+\([^)]*\):\s*\w+)/);
|
|
647
|
+
if (match?.[1] !== void 0 && match[1].length > 0) return match[1];
|
|
648
|
+
}
|
|
649
|
+
if (type === "class") {
|
|
650
|
+
return `class ${name}`;
|
|
651
|
+
}
|
|
652
|
+
if (type === "const") {
|
|
653
|
+
const arrowMatch = sig.match(new RegExp(`((?:const|let|var)\\s+${name}\\s*=\\s*(?:async\\s+)?\\([^)]*\\)(?::\\s*[^=]+)?)`));
|
|
654
|
+
if (arrowMatch?.[1] != null && arrowMatch[1] !== "") return arrowMatch[1].trim();
|
|
655
|
+
return `const ${name}`;
|
|
656
|
+
}
|
|
657
|
+
return sig;
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
// src/services/search.service.ts
|
|
662
|
+
var INTENT_FILE_BOOSTS = {
|
|
663
|
+
"how-to": {
|
|
664
|
+
"documentation-primary": 1.3,
|
|
665
|
+
// Strong boost for docs
|
|
666
|
+
"documentation": 1.2,
|
|
667
|
+
"example": 1.5,
|
|
668
|
+
// Examples are ideal for "how to"
|
|
669
|
+
"source": 0.85,
|
|
670
|
+
// Moderate penalty - source might still have good content
|
|
671
|
+
"source-internal": 0.7,
|
|
672
|
+
// Stronger penalty - internal code less useful
|
|
673
|
+
"test": 0.8,
|
|
674
|
+
"config": 0.7,
|
|
675
|
+
"other": 0.9
|
|
676
|
+
},
|
|
677
|
+
"implementation": {
|
|
678
|
+
"documentation-primary": 0.95,
|
|
679
|
+
"documentation": 1,
|
|
680
|
+
"example": 1,
|
|
681
|
+
"source": 1.1,
|
|
682
|
+
// Slight boost for source code
|
|
683
|
+
"source-internal": 1.05,
|
|
684
|
+
// Internal code can be relevant
|
|
685
|
+
"test": 1,
|
|
686
|
+
"config": 0.95,
|
|
687
|
+
"other": 1
|
|
688
|
+
},
|
|
689
|
+
"conceptual": {
|
|
690
|
+
"documentation-primary": 1.1,
|
|
691
|
+
"documentation": 1.05,
|
|
692
|
+
"example": 1,
|
|
693
|
+
"source": 0.95,
|
|
694
|
+
"source-internal": 0.9,
|
|
695
|
+
"test": 0.9,
|
|
696
|
+
"config": 0.85,
|
|
697
|
+
"other": 0.95
|
|
698
|
+
},
|
|
699
|
+
"comparison": {
|
|
700
|
+
"documentation-primary": 1.15,
|
|
701
|
+
"documentation": 1.1,
|
|
702
|
+
"example": 1.05,
|
|
703
|
+
"source": 0.9,
|
|
704
|
+
"source-internal": 0.85,
|
|
705
|
+
"test": 0.9,
|
|
706
|
+
"config": 0.85,
|
|
707
|
+
"other": 0.95
|
|
708
|
+
},
|
|
709
|
+
"debugging": {
|
|
710
|
+
"documentation-primary": 1,
|
|
711
|
+
"documentation": 1,
|
|
712
|
+
"example": 1.05,
|
|
713
|
+
"source": 1,
|
|
714
|
+
// Source code helps with debugging
|
|
715
|
+
"source-internal": 0.95,
|
|
716
|
+
"test": 1.05,
|
|
717
|
+
// Tests can show expected behavior
|
|
718
|
+
"config": 0.9,
|
|
719
|
+
"other": 1
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
var FRAMEWORK_PATTERNS = [
|
|
723
|
+
{ pattern: /\bexpress\b/i, terms: ["express", "expressjs", "express.js"] },
|
|
724
|
+
{ pattern: /\bhono\b/i, terms: ["hono"] },
|
|
725
|
+
{ pattern: /\bzod\b/i, terms: ["zod"] },
|
|
726
|
+
{ pattern: /\breact\b/i, terms: ["react", "reactjs", "react.js"] },
|
|
727
|
+
{ pattern: /\bvue\b/i, terms: ["vue", "vuejs", "vue.js", "vue3"] },
|
|
728
|
+
{ pattern: /\bnode\b/i, terms: ["node", "nodejs", "node.js"] },
|
|
729
|
+
{ pattern: /\btypescript\b/i, terms: ["typescript", "ts"] },
|
|
730
|
+
{ pattern: /\bjwt\b/i, terms: ["jwt", "jsonwebtoken", "json-web-token"] }
|
|
731
|
+
];
|
|
732
|
+
function classifyQueryIntent(query) {
|
|
733
|
+
const q = query.toLowerCase();
|
|
734
|
+
const howToPatterns = [
|
|
735
|
+
/how (do|can|should|would) (i|you|we)/i,
|
|
736
|
+
/how to\b/i,
|
|
737
|
+
/what('s| is) the (best |right |correct )?(way|approach) to/i,
|
|
738
|
+
/i (need|want|have) to/i,
|
|
739
|
+
/show me how/i,
|
|
740
|
+
/\bwhat's the syntax\b/i,
|
|
741
|
+
/\bhow do i (use|create|make|set up|configure|implement|add|get)\b/i,
|
|
742
|
+
/\bi'm (trying|building|creating|making)\b/i
|
|
743
|
+
];
|
|
744
|
+
const implementationPatterns = [
|
|
745
|
+
/how (does|is) .* (implemented|work internally)/i,
|
|
746
|
+
/\binternal(ly)?\b/i,
|
|
747
|
+
/\bsource code\b/i,
|
|
748
|
+
/\bunder the hood\b/i,
|
|
749
|
+
/\bimplementation (of|details?)\b/i
|
|
750
|
+
];
|
|
751
|
+
const comparisonPatterns = [
|
|
752
|
+
/\b(vs\.?|versus)\b/i,
|
|
753
|
+
/\bdifference(s)? between\b/i,
|
|
754
|
+
/\bcompare\b/i,
|
|
755
|
+
/\bshould (i|we) use .* or\b/i,
|
|
756
|
+
/\bwhat's the difference\b/i,
|
|
757
|
+
/\bwhich (one|is better)\b/i,
|
|
758
|
+
/\bwhen (should|to) use\b/i
|
|
759
|
+
];
|
|
760
|
+
const debuggingPatterns = [
|
|
761
|
+
/\b(error|bug|issue|problem|crash|fail|broken|wrong)\b/i,
|
|
762
|
+
/\bdoesn't (work|compile|run)\b/i,
|
|
763
|
+
/\bisn't (working|updating|rendering)\b/i,
|
|
764
|
+
/\bwhy (is|does|doesn't|isn't)\b/i,
|
|
765
|
+
/\bwhat('s| is) (wrong|happening|going on)\b/i,
|
|
766
|
+
/\bwhat am i doing wrong\b/i,
|
|
767
|
+
/\bnot (working|updating|showing)\b/i,
|
|
768
|
+
/\bhow do i (fix|debug|solve|resolve)\b/i
|
|
769
|
+
];
|
|
770
|
+
const conceptualPatterns = [
|
|
771
|
+
/\bwhat (is|are)\b/i,
|
|
772
|
+
/\bexplain\b/i,
|
|
773
|
+
/\bwhat does .* (mean|do)\b/i,
|
|
774
|
+
/\bhow does .* work\b/i,
|
|
775
|
+
/\bwhat('s| is) the (purpose|point|idea)\b/i
|
|
776
|
+
];
|
|
777
|
+
if (implementationPatterns.some((p) => p.test(q))) {
|
|
778
|
+
return "implementation";
|
|
779
|
+
}
|
|
780
|
+
if (debuggingPatterns.some((p) => p.test(q))) {
|
|
781
|
+
return "debugging";
|
|
782
|
+
}
|
|
783
|
+
if (comparisonPatterns.some((p) => p.test(q))) {
|
|
784
|
+
return "comparison";
|
|
785
|
+
}
|
|
786
|
+
if (howToPatterns.some((p) => p.test(q))) {
|
|
787
|
+
return "how-to";
|
|
788
|
+
}
|
|
789
|
+
if (conceptualPatterns.some((p) => p.test(q))) {
|
|
790
|
+
return "conceptual";
|
|
791
|
+
}
|
|
792
|
+
return "how-to";
|
|
793
|
+
}
|
|
794
|
+
var SearchService = class {
|
|
795
|
+
lanceStore;
|
|
796
|
+
embeddingEngine;
|
|
797
|
+
rrfConfig;
|
|
798
|
+
codeUnitService;
|
|
799
|
+
codeGraphService;
|
|
800
|
+
graphCache;
|
|
801
|
+
constructor(lanceStore, embeddingEngine, rrfConfig = { k: 20, vectorWeight: 0.6, ftsWeight: 0.4 }, codeGraphService) {
|
|
802
|
+
this.lanceStore = lanceStore;
|
|
803
|
+
this.embeddingEngine = embeddingEngine;
|
|
804
|
+
this.rrfConfig = rrfConfig;
|
|
805
|
+
this.codeUnitService = new CodeUnitService();
|
|
806
|
+
this.codeGraphService = codeGraphService;
|
|
807
|
+
this.graphCache = /* @__PURE__ */ new Map();
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Load code graph for a store, with caching.
|
|
811
|
+
* Returns null if no graph is available.
|
|
812
|
+
*/
|
|
813
|
+
async loadGraphForStore(storeId) {
|
|
814
|
+
if (!this.codeGraphService) return null;
|
|
815
|
+
const cached = this.graphCache.get(storeId);
|
|
816
|
+
if (cached !== void 0) return cached;
|
|
817
|
+
const graph = await this.codeGraphService.loadGraph(storeId);
|
|
818
|
+
const result = graph ?? null;
|
|
819
|
+
this.graphCache.set(storeId, result);
|
|
820
|
+
return result;
|
|
821
|
+
}
|
|
822
|
+
async search(query) {
|
|
823
|
+
const startTime = Date.now();
|
|
824
|
+
const mode = query.mode ?? "hybrid";
|
|
825
|
+
const limit = query.limit ?? 10;
|
|
826
|
+
const stores = query.stores ?? [];
|
|
827
|
+
const detail = query.detail ?? "minimal";
|
|
828
|
+
let allResults = [];
|
|
829
|
+
const fetchLimit = limit * 3;
|
|
830
|
+
if (mode === "vector") {
|
|
831
|
+
allResults = await this.vectorSearch(query.query, stores, fetchLimit, query.threshold);
|
|
832
|
+
} else if (mode === "fts") {
|
|
833
|
+
allResults = await this.ftsSearch(query.query, stores, fetchLimit);
|
|
834
|
+
} else {
|
|
835
|
+
allResults = await this.hybridSearch(query.query, stores, fetchLimit, query.threshold);
|
|
836
|
+
}
|
|
837
|
+
const dedupedResults = this.deduplicateBySource(allResults, query.query);
|
|
838
|
+
const resultsToEnhance = dedupedResults.slice(0, limit);
|
|
839
|
+
const graphs = /* @__PURE__ */ new Map();
|
|
840
|
+
if (detail === "contextual" || detail === "full") {
|
|
841
|
+
const storeIds = new Set(resultsToEnhance.map((r) => r.metadata.storeId));
|
|
842
|
+
for (const storeId of storeIds) {
|
|
843
|
+
graphs.set(storeId, await this.loadGraphForStore(storeId));
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
const enhancedResults = resultsToEnhance.map((r) => {
|
|
847
|
+
const graph = graphs.get(r.metadata.storeId) ?? null;
|
|
848
|
+
return this.addProgressiveContext(r, query.query, detail, graph);
|
|
849
|
+
});
|
|
850
|
+
return {
|
|
851
|
+
query: query.query,
|
|
852
|
+
mode,
|
|
853
|
+
stores,
|
|
854
|
+
results: enhancedResults,
|
|
855
|
+
totalResults: enhancedResults.length,
|
|
856
|
+
timeMs: Date.now() - startTime
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Deduplicate results by source file path.
|
|
861
|
+
* Keeps the best chunk for each unique source, considering both score and query relevance.
|
|
862
|
+
*/
|
|
863
|
+
deduplicateBySource(results, query) {
|
|
864
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
865
|
+
const queryTerms = query.toLowerCase().split(/\s+/).filter((t2) => t2.length > 2);
|
|
866
|
+
for (const result of results) {
|
|
867
|
+
const sourceKey = result.metadata.path ?? result.metadata.url ?? result.id;
|
|
868
|
+
const existing = bySource.get(sourceKey);
|
|
869
|
+
if (!existing) {
|
|
870
|
+
bySource.set(sourceKey, result);
|
|
871
|
+
} else {
|
|
872
|
+
const existingTermCount = this.countQueryTerms(existing.content, queryTerms);
|
|
873
|
+
const newTermCount = this.countQueryTerms(result.content, queryTerms);
|
|
874
|
+
if (newTermCount > existingTermCount || newTermCount === existingTermCount && result.score > existing.score) {
|
|
875
|
+
bySource.set(sourceKey, result);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return Array.from(bySource.values()).sort((a, b) => b.score - a.score);
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Count how many query terms appear in the content.
|
|
883
|
+
*/
|
|
884
|
+
countQueryTerms(content, queryTerms) {
|
|
885
|
+
const lowerContent = content.toLowerCase();
|
|
886
|
+
return queryTerms.filter((term) => lowerContent.includes(term)).length;
|
|
887
|
+
}
|
|
888
|
+
async vectorSearch(query, stores, limit, threshold) {
|
|
889
|
+
const queryVector = await this.embeddingEngine.embed(query);
|
|
890
|
+
const results = [];
|
|
891
|
+
for (const storeId of stores) {
|
|
892
|
+
const hits = await this.lanceStore.search(storeId, queryVector, limit, threshold);
|
|
893
|
+
results.push(...hits.map((r) => ({
|
|
894
|
+
id: r.id,
|
|
895
|
+
score: r.score,
|
|
896
|
+
content: r.content,
|
|
897
|
+
metadata: r.metadata
|
|
898
|
+
})));
|
|
899
|
+
}
|
|
900
|
+
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
901
|
+
}
|
|
902
|
+
async ftsSearch(query, stores, limit) {
|
|
903
|
+
const results = [];
|
|
904
|
+
for (const storeId of stores) {
|
|
905
|
+
const hits = await this.lanceStore.fullTextSearch(storeId, query, limit);
|
|
906
|
+
results.push(...hits.map((r) => ({
|
|
907
|
+
id: r.id,
|
|
908
|
+
score: r.score,
|
|
909
|
+
content: r.content,
|
|
910
|
+
metadata: r.metadata
|
|
911
|
+
})));
|
|
912
|
+
}
|
|
913
|
+
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
914
|
+
}
|
|
915
|
+
async hybridSearch(query, stores, limit, threshold) {
|
|
916
|
+
const intent = classifyQueryIntent(query);
|
|
917
|
+
const [vectorResults, ftsResults] = await Promise.all([
|
|
918
|
+
this.vectorSearch(query, stores, limit * 2, threshold),
|
|
919
|
+
this.ftsSearch(query, stores, limit * 2)
|
|
920
|
+
]);
|
|
921
|
+
const vectorRanks = /* @__PURE__ */ new Map();
|
|
922
|
+
const ftsRanks = /* @__PURE__ */ new Map();
|
|
923
|
+
const allDocs = /* @__PURE__ */ new Map();
|
|
924
|
+
vectorResults.forEach((r, i) => {
|
|
925
|
+
vectorRanks.set(r.id, i + 1);
|
|
926
|
+
allDocs.set(r.id, r);
|
|
927
|
+
});
|
|
928
|
+
ftsResults.forEach((r, i) => {
|
|
929
|
+
ftsRanks.set(r.id, i + 1);
|
|
930
|
+
if (!allDocs.has(r.id)) {
|
|
931
|
+
allDocs.set(r.id, r);
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
const rrfScores = [];
|
|
935
|
+
const { k, vectorWeight, ftsWeight } = this.rrfConfig;
|
|
936
|
+
for (const [id, result] of allDocs) {
|
|
937
|
+
const vectorRank = vectorRanks.get(id) ?? Infinity;
|
|
938
|
+
const ftsRank = ftsRanks.get(id) ?? Infinity;
|
|
939
|
+
const vectorRRF = vectorRank !== Infinity ? vectorWeight / (k + vectorRank) : 0;
|
|
940
|
+
const ftsRRF = ftsRank !== Infinity ? ftsWeight / (k + ftsRank) : 0;
|
|
941
|
+
const fileTypeBoost = this.getFileTypeBoost(
|
|
942
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
943
|
+
result.metadata["fileType"],
|
|
944
|
+
intent
|
|
945
|
+
);
|
|
946
|
+
const frameworkBoost = this.getFrameworkContextBoost(query, result);
|
|
947
|
+
const metadata = {
|
|
948
|
+
vectorRRF,
|
|
949
|
+
ftsRRF,
|
|
950
|
+
fileTypeBoost,
|
|
951
|
+
frameworkBoost
|
|
952
|
+
};
|
|
953
|
+
if (vectorRank !== Infinity) {
|
|
954
|
+
metadata.vectorRank = vectorRank;
|
|
955
|
+
}
|
|
956
|
+
if (ftsRank !== Infinity) {
|
|
957
|
+
metadata.ftsRank = ftsRank;
|
|
958
|
+
}
|
|
959
|
+
rrfScores.push({
|
|
960
|
+
id,
|
|
961
|
+
score: (vectorRRF + ftsRRF) * fileTypeBoost * frameworkBoost,
|
|
962
|
+
result,
|
|
963
|
+
metadata
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
const sorted = rrfScores.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
967
|
+
if (sorted.length > 0) {
|
|
968
|
+
const first = sorted[0];
|
|
969
|
+
const last = sorted[sorted.length - 1];
|
|
970
|
+
if (first === void 0 || last === void 0) {
|
|
971
|
+
return sorted.map((r) => ({
|
|
972
|
+
...r.result,
|
|
973
|
+
score: r.score,
|
|
974
|
+
rankingMetadata: r.metadata
|
|
975
|
+
}));
|
|
976
|
+
}
|
|
977
|
+
const maxScore = first.score;
|
|
978
|
+
const minScore = last.score;
|
|
979
|
+
const range = maxScore - minScore;
|
|
980
|
+
if (range > 0) {
|
|
981
|
+
return sorted.map((r) => ({
|
|
982
|
+
...r.result,
|
|
983
|
+
score: (r.score - minScore) / range,
|
|
984
|
+
rankingMetadata: r.metadata
|
|
985
|
+
}));
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return sorted.map((r) => ({
|
|
989
|
+
...r.result,
|
|
990
|
+
score: r.score,
|
|
991
|
+
rankingMetadata: r.metadata
|
|
992
|
+
}));
|
|
993
|
+
}
|
|
994
|
+
async searchAllStores(query, storeIds) {
|
|
995
|
+
return this.search({
|
|
996
|
+
...query,
|
|
997
|
+
stores: storeIds
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Get a score multiplier based on file type and query intent.
|
|
1002
|
+
* Documentation files get a strong boost to surface them higher.
|
|
1003
|
+
* Phase 4: Strengthened boosts for better documentation ranking.
|
|
1004
|
+
* Phase 1: Intent-based adjustments for context-aware ranking.
|
|
1005
|
+
*/
|
|
1006
|
+
getFileTypeBoost(fileType, intent) {
|
|
1007
|
+
let baseBoost;
|
|
1008
|
+
switch (fileType) {
|
|
1009
|
+
case "documentation-primary":
|
|
1010
|
+
baseBoost = 1.8;
|
|
1011
|
+
break;
|
|
1012
|
+
case "documentation":
|
|
1013
|
+
baseBoost = 1.5;
|
|
1014
|
+
break;
|
|
1015
|
+
case "example":
|
|
1016
|
+
baseBoost = 1.4;
|
|
1017
|
+
break;
|
|
1018
|
+
case "source":
|
|
1019
|
+
baseBoost = 1;
|
|
1020
|
+
break;
|
|
1021
|
+
case "source-internal":
|
|
1022
|
+
baseBoost = 0.75;
|
|
1023
|
+
break;
|
|
1024
|
+
case "test":
|
|
1025
|
+
baseBoost = 0.7;
|
|
1026
|
+
break;
|
|
1027
|
+
case "config":
|
|
1028
|
+
baseBoost = 0.5;
|
|
1029
|
+
break;
|
|
1030
|
+
default:
|
|
1031
|
+
baseBoost = 1;
|
|
1032
|
+
}
|
|
1033
|
+
const intentBoosts = INTENT_FILE_BOOSTS[intent];
|
|
1034
|
+
const intentMultiplier = intentBoosts[fileType ?? "other"] ?? 1;
|
|
1035
|
+
return baseBoost * intentMultiplier;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Get a score multiplier based on framework context.
|
|
1039
|
+
* If query mentions a framework, boost results from that framework's files.
|
|
1040
|
+
*/
|
|
1041
|
+
getFrameworkContextBoost(query, result) {
|
|
1042
|
+
const path3 = result.metadata.path ?? result.metadata.url ?? "";
|
|
1043
|
+
const content = result.content.toLowerCase();
|
|
1044
|
+
const pathLower = path3.toLowerCase();
|
|
1045
|
+
for (const { pattern, terms } of FRAMEWORK_PATTERNS) {
|
|
1046
|
+
if (pattern.test(query)) {
|
|
1047
|
+
const resultMatchesFramework = terms.some(
|
|
1048
|
+
(term) => pathLower.includes(term) || content.includes(term)
|
|
1049
|
+
);
|
|
1050
|
+
if (resultMatchesFramework) {
|
|
1051
|
+
return 1.5;
|
|
1052
|
+
} else {
|
|
1053
|
+
return 0.8;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return 1;
|
|
1058
|
+
}
|
|
1059
|
+
addProgressiveContext(result, query, detail, graph) {
|
|
1060
|
+
const enhanced = { ...result };
|
|
1061
|
+
const path3 = result.metadata.path ?? result.metadata.url ?? "unknown";
|
|
1062
|
+
const fileType = result.metadata["fileType"];
|
|
1063
|
+
const codeUnit = this.extractCodeUnitFromResult(result);
|
|
1064
|
+
const symbolName = codeUnit?.name ?? this.extractSymbolName(result.content);
|
|
1065
|
+
enhanced.summary = {
|
|
1066
|
+
type: this.inferType(fileType, codeUnit),
|
|
1067
|
+
name: symbolName,
|
|
1068
|
+
signature: codeUnit?.signature ?? "",
|
|
1069
|
+
purpose: this.generatePurpose(result.content, query),
|
|
1070
|
+
location: `${path3}${codeUnit ? ":" + String(codeUnit.startLine) : ""}`,
|
|
1071
|
+
relevanceReason: this.generateRelevanceReason(result, query)
|
|
1072
|
+
};
|
|
1073
|
+
if (detail === "contextual" || detail === "full") {
|
|
1074
|
+
const usage = this.getUsageFromGraph(graph, path3, symbolName);
|
|
1075
|
+
enhanced.context = {
|
|
1076
|
+
interfaces: this.extractInterfaces(result.content),
|
|
1077
|
+
keyImports: this.extractImports(result.content),
|
|
1078
|
+
relatedConcepts: this.extractConcepts(result.content, query),
|
|
1079
|
+
usage
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
if (detail === "full") {
|
|
1083
|
+
const relatedCode = this.getRelatedCodeFromGraph(graph, path3, symbolName);
|
|
1084
|
+
enhanced.full = {
|
|
1085
|
+
completeCode: codeUnit?.fullContent ?? result.content,
|
|
1086
|
+
relatedCode,
|
|
1087
|
+
documentation: this.extractDocumentation(result.content),
|
|
1088
|
+
tests: void 0
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
return enhanced;
|
|
1092
|
+
}
|
|
1093
|
+
extractCodeUnitFromResult(result) {
|
|
1094
|
+
const path3 = result.metadata.path;
|
|
1095
|
+
if (path3 === void 0 || path3 === "") return void 0;
|
|
1096
|
+
const ext = path3.split(".").pop() ?? "";
|
|
1097
|
+
const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext;
|
|
1098
|
+
const symbolName = this.extractSymbolName(result.content);
|
|
1099
|
+
if (symbolName === "") return void 0;
|
|
1100
|
+
return this.codeUnitService.extractCodeUnit(result.content, symbolName, language);
|
|
1101
|
+
}
|
|
1102
|
+
extractSymbolName(content) {
|
|
1103
|
+
const funcMatch = content.match(/(?:export\s+)?(?:async\s+)?function\s+(\w+)/);
|
|
1104
|
+
if (funcMatch !== null && funcMatch[1] !== void 0 && funcMatch[1] !== "") return funcMatch[1];
|
|
1105
|
+
const classMatch = content.match(/(?:export\s+)?class\s+(\w+)/);
|
|
1106
|
+
if (classMatch !== null && classMatch[1] !== void 0 && classMatch[1] !== "") return classMatch[1];
|
|
1107
|
+
const constMatch = content.match(/(?:export\s+)?const\s+(\w+)/);
|
|
1108
|
+
if (constMatch !== null && constMatch[1] !== void 0 && constMatch[1] !== "") return constMatch[1];
|
|
1109
|
+
return "(anonymous)";
|
|
1110
|
+
}
|
|
1111
|
+
inferType(fileType, codeUnit) {
|
|
1112
|
+
if (codeUnit) return codeUnit.type;
|
|
1113
|
+
if (fileType === "documentation" || fileType === "documentation-primary") return "documentation";
|
|
1114
|
+
return "function";
|
|
1115
|
+
}
|
|
1116
|
+
generatePurpose(content, query) {
|
|
1117
|
+
const docMatch = content.match(/\/\*\*\s*\n\s*\*\s*([^\n]+)/);
|
|
1118
|
+
if (docMatch !== null && docMatch[1] !== void 0 && docMatch[1] !== "") return docMatch[1].trim();
|
|
1119
|
+
const lines = content.split("\n");
|
|
1120
|
+
const queryTerms = query.toLowerCase().split(/\s+/).filter((t2) => t2.length > 2);
|
|
1121
|
+
const shouldSkip = (cleaned) => {
|
|
1122
|
+
return cleaned.startsWith("import ") || cleaned.startsWith("export ") || cleaned.startsWith("interface ") || cleaned.startsWith("type ");
|
|
1123
|
+
};
|
|
1124
|
+
const scoreLine = (cleaned) => {
|
|
1125
|
+
const lowerLine = cleaned.toLowerCase();
|
|
1126
|
+
return queryTerms.filter((term) => lowerLine.includes(term)).length;
|
|
1127
|
+
};
|
|
1128
|
+
const isMeaningful = (cleaned) => {
|
|
1129
|
+
if (cleaned.length === 0) return false;
|
|
1130
|
+
if (cleaned.startsWith("//") || cleaned.startsWith("/*")) return false;
|
|
1131
|
+
if (cleaned.startsWith("#") && cleaned.length > 3) return true;
|
|
1132
|
+
return cleaned.length >= 15;
|
|
1133
|
+
};
|
|
1134
|
+
let bestLine = null;
|
|
1135
|
+
let bestScore = 0;
|
|
1136
|
+
for (const line of lines) {
|
|
1137
|
+
const cleaned = line.trim();
|
|
1138
|
+
if (shouldSkip(cleaned) || !isMeaningful(cleaned)) continue;
|
|
1139
|
+
let score = scoreLine(cleaned);
|
|
1140
|
+
if (/[.!?]$/.test(cleaned)) {
|
|
1141
|
+
score += 0.5;
|
|
1142
|
+
}
|
|
1143
|
+
if (/\w+\([^)]*\)|=\s*\w+\(|=>/.test(cleaned)) {
|
|
1144
|
+
score += 0.6;
|
|
1145
|
+
}
|
|
1146
|
+
if (score > bestScore) {
|
|
1147
|
+
bestScore = score;
|
|
1148
|
+
bestLine = cleaned;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (bestLine !== null && bestLine !== "" && bestScore > 0) {
|
|
1152
|
+
if (bestLine.length > 150) {
|
|
1153
|
+
const firstSentence = bestLine.match(/^[^.!?]+[.!?]/);
|
|
1154
|
+
if (firstSentence && firstSentence[0].length >= 20 && firstSentence[0].length <= 150) {
|
|
1155
|
+
return firstSentence[0].trim();
|
|
1156
|
+
}
|
|
1157
|
+
return bestLine.substring(0, 147) + "...";
|
|
1158
|
+
}
|
|
1159
|
+
return bestLine;
|
|
1160
|
+
}
|
|
1161
|
+
for (const line of lines) {
|
|
1162
|
+
const cleaned = line.trim();
|
|
1163
|
+
if (shouldSkip(cleaned) || !isMeaningful(cleaned)) continue;
|
|
1164
|
+
if (cleaned.length > 150) {
|
|
1165
|
+
const firstSentence = cleaned.match(/^[^.!?]+[.!?]/);
|
|
1166
|
+
if (firstSentence && firstSentence[0].length >= 20 && firstSentence[0].length <= 150) {
|
|
1167
|
+
return firstSentence[0].trim();
|
|
1168
|
+
}
|
|
1169
|
+
return cleaned.substring(0, 147) + "...";
|
|
1170
|
+
}
|
|
1171
|
+
return cleaned;
|
|
1172
|
+
}
|
|
1173
|
+
return "Code related to query";
|
|
1174
|
+
}
|
|
1175
|
+
generateRelevanceReason(result, query) {
|
|
1176
|
+
const queryTerms = query.toLowerCase().split(/\s+/).filter((t2) => t2.length > 2);
|
|
1177
|
+
const contentLower = result.content.toLowerCase();
|
|
1178
|
+
const matchedTerms = queryTerms.filter((term) => contentLower.includes(term));
|
|
1179
|
+
if (matchedTerms.length > 0) {
|
|
1180
|
+
return `Matches: ${matchedTerms.join(", ")}`;
|
|
1181
|
+
}
|
|
1182
|
+
return "Semantically similar to query";
|
|
1183
|
+
}
|
|
1184
|
+
extractInterfaces(content) {
|
|
1185
|
+
const interfaces = [];
|
|
1186
|
+
const matches = content.matchAll(/interface\s+(\w+)/g);
|
|
1187
|
+
for (const match of matches) {
|
|
1188
|
+
if (match[1] !== void 0 && match[1] !== "") interfaces.push(match[1]);
|
|
1189
|
+
}
|
|
1190
|
+
return interfaces;
|
|
1191
|
+
}
|
|
1192
|
+
extractImports(content) {
|
|
1193
|
+
const imports = [];
|
|
1194
|
+
const matches = content.matchAll(/import\s+.*?from\s+['"]([^'"]+)['"]/g);
|
|
1195
|
+
for (const match of matches) {
|
|
1196
|
+
if (match[1] !== void 0 && match[1] !== "") imports.push(match[1]);
|
|
1197
|
+
}
|
|
1198
|
+
return imports.slice(0, 5);
|
|
1199
|
+
}
|
|
1200
|
+
extractConcepts(content, _query) {
|
|
1201
|
+
const stopwords = /* @__PURE__ */ new Set([
|
|
1202
|
+
"this",
|
|
1203
|
+
"that",
|
|
1204
|
+
"these",
|
|
1205
|
+
"those",
|
|
1206
|
+
"from",
|
|
1207
|
+
"with",
|
|
1208
|
+
"have",
|
|
1209
|
+
"will",
|
|
1210
|
+
"would",
|
|
1211
|
+
"should",
|
|
1212
|
+
"could",
|
|
1213
|
+
"about",
|
|
1214
|
+
"been",
|
|
1215
|
+
"were",
|
|
1216
|
+
"being",
|
|
1217
|
+
"function",
|
|
1218
|
+
"return",
|
|
1219
|
+
"const",
|
|
1220
|
+
"import",
|
|
1221
|
+
"export",
|
|
1222
|
+
"default",
|
|
1223
|
+
"type",
|
|
1224
|
+
"interface",
|
|
1225
|
+
"class",
|
|
1226
|
+
"extends",
|
|
1227
|
+
"implements",
|
|
1228
|
+
"async",
|
|
1229
|
+
"await",
|
|
1230
|
+
"then",
|
|
1231
|
+
"catch",
|
|
1232
|
+
"throw",
|
|
1233
|
+
"error",
|
|
1234
|
+
"undefined",
|
|
1235
|
+
"null",
|
|
1236
|
+
"true",
|
|
1237
|
+
"false",
|
|
1238
|
+
"void",
|
|
1239
|
+
"number",
|
|
1240
|
+
"string",
|
|
1241
|
+
"boolean",
|
|
1242
|
+
"object",
|
|
1243
|
+
"array",
|
|
1244
|
+
"promise",
|
|
1245
|
+
"callback",
|
|
1246
|
+
"resolve",
|
|
1247
|
+
"reject",
|
|
1248
|
+
"value",
|
|
1249
|
+
"param",
|
|
1250
|
+
"params",
|
|
1251
|
+
"args",
|
|
1252
|
+
"props",
|
|
1253
|
+
"options",
|
|
1254
|
+
"config",
|
|
1255
|
+
"data"
|
|
1256
|
+
]);
|
|
1257
|
+
const words = content.toLowerCase().match(/\b[a-z]{4,}\b/g) ?? [];
|
|
1258
|
+
const frequency = /* @__PURE__ */ new Map();
|
|
1259
|
+
for (const word of words) {
|
|
1260
|
+
if (stopwords.has(word)) continue;
|
|
1261
|
+
frequency.set(word, (frequency.get(word) ?? 0) + 1);
|
|
1262
|
+
}
|
|
1263
|
+
return Array.from(frequency.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([word]) => word);
|
|
1264
|
+
}
|
|
1265
|
+
extractDocumentation(content) {
|
|
1266
|
+
const docMatch = content.match(/\/\*\*([\s\S]*?)\*\//);
|
|
1267
|
+
if (docMatch !== null && docMatch[1] !== void 0 && docMatch[1] !== "") {
|
|
1268
|
+
return docMatch[1].split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim()).filter((line) => line.length > 0).join("\n");
|
|
1269
|
+
}
|
|
1270
|
+
return "";
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Get usage stats from code graph.
|
|
1274
|
+
* Returns default values if no graph is available.
|
|
1275
|
+
*/
|
|
1276
|
+
getUsageFromGraph(graph, filePath, symbolName) {
|
|
1277
|
+
if (!graph || symbolName === "" || symbolName === "(anonymous)") {
|
|
1278
|
+
return { calledBy: 0, calls: 0 };
|
|
1279
|
+
}
|
|
1280
|
+
const nodeId = `${filePath}:${symbolName}`;
|
|
1281
|
+
return {
|
|
1282
|
+
calledBy: graph.getCalledByCount(nodeId),
|
|
1283
|
+
calls: graph.getCallsCount(nodeId)
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Get related code from graph.
|
|
1288
|
+
* Returns callers and callees for the symbol.
|
|
1289
|
+
*/
|
|
1290
|
+
getRelatedCodeFromGraph(graph, filePath, symbolName) {
|
|
1291
|
+
if (!graph || symbolName === "" || symbolName === "(anonymous)") {
|
|
1292
|
+
return [];
|
|
1293
|
+
}
|
|
1294
|
+
const nodeId = `${filePath}:${symbolName}`;
|
|
1295
|
+
const related = [];
|
|
1296
|
+
const incoming = graph.getIncomingEdges(nodeId);
|
|
1297
|
+
for (const edge of incoming) {
|
|
1298
|
+
if (edge.type === "calls") {
|
|
1299
|
+
const [file, symbol] = this.parseNodeId(edge.from);
|
|
1300
|
+
related.push({
|
|
1301
|
+
file,
|
|
1302
|
+
summary: symbol ? `${symbol}()` : "unknown",
|
|
1303
|
+
relationship: "calls this"
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
const outgoing = graph.getEdges(nodeId);
|
|
1308
|
+
for (const edge of outgoing) {
|
|
1309
|
+
if (edge.type === "calls") {
|
|
1310
|
+
const [file, symbol] = this.parseNodeId(edge.to);
|
|
1311
|
+
related.push({
|
|
1312
|
+
file,
|
|
1313
|
+
summary: symbol ? `${symbol}()` : "unknown",
|
|
1314
|
+
relationship: "called by this"
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
return related.slice(0, 10);
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Parse a node ID into file path and symbol name.
|
|
1322
|
+
*/
|
|
1323
|
+
parseNodeId(nodeId) {
|
|
1324
|
+
const lastColon = nodeId.lastIndexOf(":");
|
|
1325
|
+
if (lastColon === -1) {
|
|
1326
|
+
return [nodeId, ""];
|
|
1327
|
+
}
|
|
1328
|
+
return [nodeId.substring(0, lastColon), nodeId.substring(lastColon + 1)];
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
|
|
1332
|
+
// src/services/index.service.ts
|
|
1333
|
+
import { readFile as readFile3, readdir } from "fs/promises";
|
|
1334
|
+
import { join as join3, extname, basename } from "path";
|
|
1335
|
+
import { createHash } from "crypto";
|
|
1336
|
+
|
|
1337
|
+
// src/services/chunking.service.ts
|
|
1338
|
+
var ChunkingService = class {
|
|
1339
|
+
chunkSize;
|
|
1340
|
+
chunkOverlap;
|
|
1341
|
+
constructor(config) {
|
|
1342
|
+
this.chunkSize = config.chunkSize;
|
|
1343
|
+
this.chunkOverlap = config.chunkOverlap;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Chunk text content. Uses semantic chunking for Markdown and code files,
|
|
1347
|
+
* falling back to sliding window for other content.
|
|
1348
|
+
*/
|
|
1349
|
+
chunk(text, filePath) {
|
|
1350
|
+
if (filePath !== void 0 && filePath !== "" && /\.md$/i.test(filePath)) {
|
|
1351
|
+
return this.chunkMarkdown(text);
|
|
1352
|
+
}
|
|
1353
|
+
if (filePath !== void 0 && filePath !== "" && /\.(ts|tsx|js|jsx)$/i.test(filePath)) {
|
|
1354
|
+
return this.chunkCode(text);
|
|
1355
|
+
}
|
|
1356
|
+
return this.chunkSlidingWindow(text);
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Semantic chunking for Markdown files.
|
|
1360
|
+
* Splits on section headers to keep related content together.
|
|
1361
|
+
*/
|
|
1362
|
+
chunkMarkdown(text) {
|
|
1363
|
+
const headerRegex = /^(#{1,4})\s+(.+)$/gm;
|
|
1364
|
+
const sections = [];
|
|
1365
|
+
let lastIndex = 0;
|
|
1366
|
+
let lastHeader = "";
|
|
1367
|
+
let match;
|
|
1368
|
+
while ((match = headerRegex.exec(text)) !== null) {
|
|
1369
|
+
if (match.index > lastIndex) {
|
|
1370
|
+
const content = text.slice(lastIndex, match.index).trim();
|
|
1371
|
+
if (content) {
|
|
1372
|
+
sections.push({
|
|
1373
|
+
header: lastHeader,
|
|
1374
|
+
content,
|
|
1375
|
+
startOffset: lastIndex
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
lastHeader = match[2] ?? "";
|
|
1380
|
+
lastIndex = match.index;
|
|
1381
|
+
}
|
|
1382
|
+
const finalContent = text.slice(lastIndex).trim();
|
|
1383
|
+
if (finalContent) {
|
|
1384
|
+
sections.push({
|
|
1385
|
+
header: lastHeader,
|
|
1386
|
+
content: finalContent,
|
|
1387
|
+
startOffset: lastIndex
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
if (sections.length === 0) {
|
|
1391
|
+
return this.chunkSlidingWindow(text);
|
|
1392
|
+
}
|
|
1393
|
+
const chunks = [];
|
|
1394
|
+
for (const section of sections) {
|
|
1395
|
+
if (section.content.length <= this.chunkSize) {
|
|
1396
|
+
chunks.push({
|
|
1397
|
+
content: section.content,
|
|
1398
|
+
chunkIndex: chunks.length,
|
|
1399
|
+
totalChunks: 0,
|
|
1400
|
+
startOffset: section.startOffset,
|
|
1401
|
+
endOffset: section.startOffset + section.content.length,
|
|
1402
|
+
sectionHeader: section.header || void 0
|
|
1403
|
+
});
|
|
1404
|
+
} else {
|
|
1405
|
+
const sectionChunks = this.chunkSlidingWindow(section.content);
|
|
1406
|
+
for (const subChunk of sectionChunks) {
|
|
1407
|
+
chunks.push({
|
|
1408
|
+
...subChunk,
|
|
1409
|
+
chunkIndex: chunks.length,
|
|
1410
|
+
startOffset: section.startOffset + subChunk.startOffset,
|
|
1411
|
+
endOffset: section.startOffset + subChunk.endOffset,
|
|
1412
|
+
sectionHeader: section.header || void 0
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
for (const chunk of chunks) {
|
|
1418
|
+
chunk.totalChunks = chunks.length;
|
|
1419
|
+
}
|
|
1420
|
+
return chunks;
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Semantic chunking for TypeScript/JavaScript code files.
|
|
1424
|
+
* Splits on top-level declarations to keep functions/classes together.
|
|
1425
|
+
*/
|
|
1426
|
+
chunkCode(text) {
|
|
1427
|
+
const declarationRegex = /^(?:\/\*\*[\s\S]*?\*\/\s*)?(?:export\s+)?(?:async\s+)?(?:function|class|interface|type|const|let|var|enum)\s+(\w+)/gm;
|
|
1428
|
+
const declarations = [];
|
|
1429
|
+
let match;
|
|
1430
|
+
while ((match = declarationRegex.exec(text)) !== null) {
|
|
1431
|
+
const name = match[1];
|
|
1432
|
+
const decl = {
|
|
1433
|
+
startOffset: match.index,
|
|
1434
|
+
endOffset: match.index
|
|
1435
|
+
};
|
|
1436
|
+
if (name !== void 0) {
|
|
1437
|
+
decl.name = name;
|
|
1438
|
+
}
|
|
1439
|
+
declarations.push(decl);
|
|
1440
|
+
}
|
|
1441
|
+
if (declarations.length === 0) {
|
|
1442
|
+
return this.chunkSlidingWindow(text);
|
|
1443
|
+
}
|
|
1444
|
+
for (let i = 0; i < declarations.length; i++) {
|
|
1445
|
+
const currentDecl = declarations[i];
|
|
1446
|
+
const nextDecl = declarations[i + 1];
|
|
1447
|
+
if (currentDecl === void 0) continue;
|
|
1448
|
+
const declText = text.slice(currentDecl.startOffset);
|
|
1449
|
+
if (/^(?:\/\*\*[\s\S]*?\*\/\s*)?(?:export\s+)?(?:async\s+)?(?:function|class|enum)\s+/m.test(declText)) {
|
|
1450
|
+
const boundary = this.findDeclarationEnd(declText);
|
|
1451
|
+
if (boundary > 0) {
|
|
1452
|
+
currentDecl.endOffset = currentDecl.startOffset + boundary;
|
|
1453
|
+
} else {
|
|
1454
|
+
currentDecl.endOffset = nextDecl !== void 0 ? nextDecl.startOffset : text.length;
|
|
1455
|
+
}
|
|
1456
|
+
} else {
|
|
1457
|
+
currentDecl.endOffset = nextDecl !== void 0 ? nextDecl.startOffset : text.length;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
const chunks = [];
|
|
1461
|
+
for (const decl of declarations) {
|
|
1462
|
+
const content = text.slice(decl.startOffset, decl.endOffset).trim();
|
|
1463
|
+
if (content.length <= this.chunkSize) {
|
|
1464
|
+
chunks.push({
|
|
1465
|
+
content,
|
|
1466
|
+
chunkIndex: chunks.length,
|
|
1467
|
+
totalChunks: 0,
|
|
1468
|
+
startOffset: decl.startOffset,
|
|
1469
|
+
endOffset: decl.endOffset,
|
|
1470
|
+
functionName: decl.name
|
|
1471
|
+
});
|
|
1472
|
+
} else {
|
|
1473
|
+
const declChunks = this.chunkSlidingWindow(content);
|
|
1474
|
+
for (const subChunk of declChunks) {
|
|
1475
|
+
chunks.push({
|
|
1476
|
+
...subChunk,
|
|
1477
|
+
chunkIndex: chunks.length,
|
|
1478
|
+
startOffset: decl.startOffset + subChunk.startOffset,
|
|
1479
|
+
endOffset: decl.startOffset + subChunk.endOffset,
|
|
1480
|
+
functionName: decl.name
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
for (const chunk of chunks) {
|
|
1486
|
+
chunk.totalChunks = chunks.length;
|
|
1487
|
+
}
|
|
1488
|
+
return chunks.length > 0 ? chunks : this.chunkSlidingWindow(text);
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Find the end of a code declaration by counting braces while ignoring
|
|
1492
|
+
* braces inside strings and comments.
|
|
1493
|
+
* Returns the offset where the declaration ends, or -1 if not found.
|
|
1494
|
+
*/
|
|
1495
|
+
findDeclarationEnd(text) {
|
|
1496
|
+
let braceCount = 0;
|
|
1497
|
+
let inString = false;
|
|
1498
|
+
let inSingleLineComment = false;
|
|
1499
|
+
let inMultiLineComment = false;
|
|
1500
|
+
let stringChar = "";
|
|
1501
|
+
let i = 0;
|
|
1502
|
+
let foundFirstBrace = false;
|
|
1503
|
+
while (i < text.length) {
|
|
1504
|
+
const char = text[i];
|
|
1505
|
+
const nextChar = i + 1 < text.length ? text[i + 1] : "";
|
|
1506
|
+
if (!inString && !inMultiLineComment && char === "/" && nextChar === "/") {
|
|
1507
|
+
inSingleLineComment = true;
|
|
1508
|
+
i += 2;
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
if (!inString && !inSingleLineComment && char === "/" && nextChar === "*") {
|
|
1512
|
+
inMultiLineComment = true;
|
|
1513
|
+
i += 2;
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
if (inMultiLineComment && char === "*" && nextChar === "/") {
|
|
1517
|
+
inMultiLineComment = false;
|
|
1518
|
+
i += 2;
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
if (inSingleLineComment && char === "\n") {
|
|
1522
|
+
inSingleLineComment = false;
|
|
1523
|
+
i++;
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
if (inSingleLineComment || inMultiLineComment) {
|
|
1527
|
+
i++;
|
|
1528
|
+
continue;
|
|
1529
|
+
}
|
|
1530
|
+
if (!inString && (char === '"' || char === "'" || char === "`")) {
|
|
1531
|
+
inString = true;
|
|
1532
|
+
stringChar = char;
|
|
1533
|
+
i++;
|
|
1534
|
+
continue;
|
|
1535
|
+
}
|
|
1536
|
+
if (inString && char === "\\") {
|
|
1537
|
+
i += 2;
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
if (inString && char === stringChar) {
|
|
1541
|
+
inString = false;
|
|
1542
|
+
stringChar = "";
|
|
1543
|
+
i++;
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
if (inString) {
|
|
1547
|
+
i++;
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
if (char === "{") {
|
|
1551
|
+
braceCount++;
|
|
1552
|
+
foundFirstBrace = true;
|
|
1553
|
+
} else if (char === "}") {
|
|
1554
|
+
braceCount--;
|
|
1555
|
+
if (foundFirstBrace && braceCount === 0) {
|
|
1556
|
+
return i + 1;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
i++;
|
|
1560
|
+
}
|
|
1561
|
+
return -1;
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Traditional sliding window chunking for non-Markdown content.
|
|
1565
|
+
*/
|
|
1566
|
+
chunkSlidingWindow(text) {
|
|
1567
|
+
if (text.length <= this.chunkSize) {
|
|
1568
|
+
return [{
|
|
1569
|
+
content: text,
|
|
1570
|
+
chunkIndex: 0,
|
|
1571
|
+
totalChunks: 1,
|
|
1572
|
+
startOffset: 0,
|
|
1573
|
+
endOffset: text.length
|
|
1574
|
+
}];
|
|
1575
|
+
}
|
|
1576
|
+
const chunks = [];
|
|
1577
|
+
const step = this.chunkSize - this.chunkOverlap;
|
|
1578
|
+
let start = 0;
|
|
1579
|
+
while (start < text.length) {
|
|
1580
|
+
const end = Math.min(start + this.chunkSize, text.length);
|
|
1581
|
+
chunks.push({
|
|
1582
|
+
content: text.slice(start, end),
|
|
1583
|
+
chunkIndex: chunks.length,
|
|
1584
|
+
totalChunks: 0,
|
|
1585
|
+
startOffset: start,
|
|
1586
|
+
endOffset: end
|
|
1587
|
+
});
|
|
1588
|
+
start += step;
|
|
1589
|
+
if (end === text.length) break;
|
|
1590
|
+
}
|
|
1591
|
+
for (const chunk of chunks) {
|
|
1592
|
+
chunk.totalChunks = chunks.length;
|
|
1593
|
+
}
|
|
1594
|
+
return chunks;
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
|
|
1598
|
+
// src/services/index.service.ts
|
|
1599
|
+
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1600
|
+
".txt",
|
|
1601
|
+
".md",
|
|
1602
|
+
".js",
|
|
1603
|
+
".ts",
|
|
1604
|
+
".jsx",
|
|
1605
|
+
".tsx",
|
|
1606
|
+
".json",
|
|
1607
|
+
".yaml",
|
|
1608
|
+
".yml",
|
|
1609
|
+
".html",
|
|
1610
|
+
".css",
|
|
1611
|
+
".scss",
|
|
1612
|
+
".less",
|
|
1613
|
+
".py",
|
|
1614
|
+
".rb",
|
|
1615
|
+
".go",
|
|
1616
|
+
".rs",
|
|
1617
|
+
".java",
|
|
1618
|
+
".c",
|
|
1619
|
+
".cpp",
|
|
1620
|
+
".h",
|
|
1621
|
+
".hpp",
|
|
1622
|
+
".sh",
|
|
1623
|
+
".bash",
|
|
1624
|
+
".zsh",
|
|
1625
|
+
".sql",
|
|
1626
|
+
".xml"
|
|
1627
|
+
]);
|
|
1628
|
+
var IndexService = class {
|
|
1629
|
+
lanceStore;
|
|
1630
|
+
embeddingEngine;
|
|
1631
|
+
chunker;
|
|
1632
|
+
codeGraphService;
|
|
1633
|
+
constructor(lanceStore, embeddingEngine, options = {}) {
|
|
1634
|
+
this.lanceStore = lanceStore;
|
|
1635
|
+
this.embeddingEngine = embeddingEngine;
|
|
1636
|
+
this.chunker = new ChunkingService({
|
|
1637
|
+
chunkSize: options.chunkSize ?? 768,
|
|
1638
|
+
chunkOverlap: options.chunkOverlap ?? 100
|
|
1639
|
+
});
|
|
1640
|
+
this.codeGraphService = options.codeGraphService;
|
|
1641
|
+
}
|
|
1642
|
+
async indexStore(store, onProgress) {
|
|
1643
|
+
try {
|
|
1644
|
+
if (store.type === "file" || store.type === "repo") {
|
|
1645
|
+
return await this.indexFileStore(store, onProgress);
|
|
1646
|
+
}
|
|
1647
|
+
return err(new Error(`Indexing not supported for store type: ${store.type}`));
|
|
1648
|
+
} catch (error) {
|
|
1649
|
+
return err(error instanceof Error ? error : new Error(String(error)));
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
async indexFileStore(store, onProgress) {
|
|
1653
|
+
const startTime = Date.now();
|
|
1654
|
+
const files = await this.scanDirectory(store.path);
|
|
1655
|
+
const documents = [];
|
|
1656
|
+
let filesProcessed = 0;
|
|
1657
|
+
const sourceFiles = [];
|
|
1658
|
+
onProgress?.({
|
|
1659
|
+
type: "start",
|
|
1660
|
+
current: 0,
|
|
1661
|
+
total: files.length,
|
|
1662
|
+
message: "Starting index"
|
|
1663
|
+
});
|
|
1664
|
+
for (const filePath of files) {
|
|
1665
|
+
const content = await readFile3(filePath, "utf-8");
|
|
1666
|
+
const fileHash = createHash("md5").update(content).digest("hex");
|
|
1667
|
+
const chunks = this.chunker.chunk(content, filePath);
|
|
1668
|
+
const ext = extname(filePath).toLowerCase();
|
|
1669
|
+
const fileName = basename(filePath).toLowerCase();
|
|
1670
|
+
const fileType = this.classifyFileType(ext, fileName, filePath);
|
|
1671
|
+
if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
|
|
1672
|
+
sourceFiles.push({ path: filePath, content });
|
|
1673
|
+
}
|
|
1674
|
+
for (const chunk of chunks) {
|
|
1675
|
+
const vector = await this.embeddingEngine.embed(chunk.content);
|
|
1676
|
+
const chunkId = chunks.length > 1 ? `${store.id}-${fileHash}-${String(chunk.chunkIndex)}` : `${store.id}-${fileHash}`;
|
|
1677
|
+
const doc = {
|
|
1678
|
+
id: createDocumentId(chunkId),
|
|
1679
|
+
content: chunk.content,
|
|
1680
|
+
vector,
|
|
1681
|
+
metadata: {
|
|
1682
|
+
type: chunks.length > 1 ? "chunk" : "file",
|
|
1683
|
+
storeId: store.id,
|
|
1684
|
+
path: filePath,
|
|
1685
|
+
indexedAt: /* @__PURE__ */ new Date(),
|
|
1686
|
+
fileHash,
|
|
1687
|
+
chunkIndex: chunk.chunkIndex,
|
|
1688
|
+
totalChunks: chunk.totalChunks,
|
|
1689
|
+
// New metadata for ranking
|
|
1690
|
+
fileType,
|
|
1691
|
+
sectionHeader: chunk.sectionHeader,
|
|
1692
|
+
functionName: chunk.functionName,
|
|
1693
|
+
hasDocComments: /\/\*\*[\s\S]*?\*\//.test(chunk.content),
|
|
1694
|
+
docSummary: chunk.docSummary
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
documents.push(doc);
|
|
1698
|
+
}
|
|
1699
|
+
filesProcessed++;
|
|
1700
|
+
onProgress?.({
|
|
1701
|
+
type: "progress",
|
|
1702
|
+
current: filesProcessed,
|
|
1703
|
+
total: files.length,
|
|
1704
|
+
message: `Indexing ${filePath}`
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
if (documents.length > 0) {
|
|
1708
|
+
await this.lanceStore.addDocuments(store.id, documents);
|
|
1709
|
+
}
|
|
1710
|
+
if (this.codeGraphService && sourceFiles.length > 0) {
|
|
1711
|
+
const graph = await this.codeGraphService.buildGraph(sourceFiles);
|
|
1712
|
+
await this.codeGraphService.saveGraph(store.id, graph);
|
|
1713
|
+
}
|
|
1714
|
+
onProgress?.({
|
|
1715
|
+
type: "complete",
|
|
1716
|
+
current: files.length,
|
|
1717
|
+
total: files.length,
|
|
1718
|
+
message: "Indexing complete"
|
|
1719
|
+
});
|
|
1720
|
+
return ok({
|
|
1721
|
+
documentsIndexed: filesProcessed,
|
|
1722
|
+
chunksCreated: documents.length,
|
|
1723
|
+
timeMs: Date.now() - startTime
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
async scanDirectory(dir) {
|
|
1727
|
+
const files = [];
|
|
1728
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
1729
|
+
for (const entry of entries) {
|
|
1730
|
+
const fullPath = join3(dir, entry.name);
|
|
1731
|
+
if (entry.isDirectory()) {
|
|
1732
|
+
if (!["node_modules", ".git", "dist", "build"].includes(entry.name)) {
|
|
1733
|
+
files.push(...await this.scanDirectory(fullPath));
|
|
1734
|
+
}
|
|
1735
|
+
} else if (entry.isFile()) {
|
|
1736
|
+
const ext = extname(entry.name).toLowerCase();
|
|
1737
|
+
if (TEXT_EXTENSIONS.has(ext)) {
|
|
1738
|
+
files.push(fullPath);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
return files;
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Classify file type for ranking purposes.
|
|
1746
|
+
* Documentation files rank higher than source code for documentation queries.
|
|
1747
|
+
* Phase 4: Enhanced to detect internal implementation files.
|
|
1748
|
+
*/
|
|
1749
|
+
classifyFileType(ext, fileName, filePath) {
|
|
1750
|
+
if (ext === ".md") {
|
|
1751
|
+
if (fileName === "changelog.md" || fileName === "changes.md" || /changelog/i.test(fileName)) {
|
|
1752
|
+
return "changelog";
|
|
1753
|
+
}
|
|
1754
|
+
if (["readme.md", "migration.md", "contributing.md"].includes(fileName)) {
|
|
1755
|
+
return "documentation-primary";
|
|
1756
|
+
}
|
|
1757
|
+
if (/\/(docs?|documentation|guides?|tutorials?|articles?)\//i.test(filePath)) {
|
|
1758
|
+
return "documentation";
|
|
1759
|
+
}
|
|
1760
|
+
return "documentation";
|
|
1761
|
+
}
|
|
1762
|
+
if (/\.(test|spec)\.[jt]sx?$/.test(fileName) || /\/__tests__\//.test(filePath)) {
|
|
1763
|
+
return "test";
|
|
1764
|
+
}
|
|
1765
|
+
if (/\/examples?\//.test(filePath) || fileName.includes("example")) {
|
|
1766
|
+
return "example";
|
|
1767
|
+
}
|
|
1768
|
+
if (/^(tsconfig|package|\.eslint|\.prettier|vite\.config|next\.config)/i.test(fileName)) {
|
|
1769
|
+
return "config";
|
|
1770
|
+
}
|
|
1771
|
+
if ([".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java"].includes(ext)) {
|
|
1772
|
+
if (this.isInternalImplementation(filePath, fileName)) {
|
|
1773
|
+
return "source-internal";
|
|
1774
|
+
}
|
|
1775
|
+
return "source";
|
|
1776
|
+
}
|
|
1777
|
+
return "other";
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Detect if a source file is internal implementation code.
|
|
1781
|
+
* Internal code should rank lower than public-facing APIs and docs.
|
|
1782
|
+
*/
|
|
1783
|
+
isInternalImplementation(filePath, fileName) {
|
|
1784
|
+
const pathLower = filePath.toLowerCase();
|
|
1785
|
+
const fileNameLower = fileName.toLowerCase();
|
|
1786
|
+
if (/\/packages\/[^/]+\/src\//.test(pathLower)) {
|
|
1787
|
+
if (fileNameLower === "index.ts" || fileNameLower === "index.js") {
|
|
1788
|
+
return false;
|
|
1789
|
+
}
|
|
1790
|
+
return true;
|
|
1791
|
+
}
|
|
1792
|
+
if (/\/(internal|lib\/core|core\/src|_internal|private)\//.test(pathLower)) {
|
|
1793
|
+
return true;
|
|
1794
|
+
}
|
|
1795
|
+
if (/\/(compiler|transforms?|parse|codegen)\//.test(pathLower) && !fileNameLower.includes("readme") && !fileNameLower.includes("index")) {
|
|
1796
|
+
return true;
|
|
1797
|
+
}
|
|
1798
|
+
return false;
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
|
|
1802
|
+
// src/services/code-graph.service.ts
|
|
1803
|
+
import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir4 } from "fs/promises";
|
|
1804
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
1805
|
+
|
|
1806
|
+
// src/analysis/code-graph.ts
|
|
1807
|
+
var CodeGraph = class {
|
|
1808
|
+
nodes = /* @__PURE__ */ new Map();
|
|
1809
|
+
edges = /* @__PURE__ */ new Map();
|
|
1810
|
+
addNodes(nodes, file) {
|
|
1811
|
+
for (const node of nodes) {
|
|
1812
|
+
const id = `${file}:${node.name}`;
|
|
1813
|
+
const graphNode = {
|
|
1814
|
+
id,
|
|
1815
|
+
file,
|
|
1816
|
+
type: node.type,
|
|
1817
|
+
name: node.name,
|
|
1818
|
+
exported: node.exported,
|
|
1819
|
+
startLine: node.startLine,
|
|
1820
|
+
endLine: node.endLine
|
|
1821
|
+
};
|
|
1822
|
+
if (node.signature !== void 0) {
|
|
1823
|
+
graphNode.signature = node.signature;
|
|
1824
|
+
}
|
|
1825
|
+
this.nodes.set(id, graphNode);
|
|
1826
|
+
if (!this.edges.has(id)) {
|
|
1827
|
+
this.edges.set(id, []);
|
|
1828
|
+
}
|
|
1829
|
+
if (node.type === "class" && node.methods !== void 0) {
|
|
1830
|
+
for (const method of node.methods) {
|
|
1831
|
+
const methodId = `${file}:${node.name}.${method.name}`;
|
|
1832
|
+
const methodNode = {
|
|
1833
|
+
id: methodId,
|
|
1834
|
+
file,
|
|
1835
|
+
type: "method",
|
|
1836
|
+
name: method.name,
|
|
1837
|
+
exported: node.exported,
|
|
1838
|
+
// Methods inherit export status from class
|
|
1839
|
+
startLine: method.startLine,
|
|
1840
|
+
endLine: method.endLine,
|
|
1841
|
+
signature: method.signature
|
|
1842
|
+
};
|
|
1843
|
+
this.nodes.set(methodId, methodNode);
|
|
1844
|
+
if (!this.edges.has(methodId)) {
|
|
1845
|
+
this.edges.set(methodId, []);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
addImport(fromFile, toFile, specifiers) {
|
|
1852
|
+
const resolvedTo = this.resolveImportPath(fromFile, toFile);
|
|
1853
|
+
for (const spec of specifiers) {
|
|
1854
|
+
const edge = {
|
|
1855
|
+
from: fromFile,
|
|
1856
|
+
to: `${resolvedTo}:${spec}`,
|
|
1857
|
+
type: "imports",
|
|
1858
|
+
confidence: 1
|
|
1859
|
+
};
|
|
1860
|
+
const edges = this.edges.get(fromFile) ?? [];
|
|
1861
|
+
edges.push(edge);
|
|
1862
|
+
this.edges.set(fromFile, edges);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
analyzeCallRelationships(code, file, functionName) {
|
|
1866
|
+
const nodeId = `${file}:${functionName}`;
|
|
1867
|
+
const callPattern = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
|
|
1868
|
+
const calls = /* @__PURE__ */ new Set();
|
|
1869
|
+
let match;
|
|
1870
|
+
while ((match = callPattern.exec(code)) !== null) {
|
|
1871
|
+
if (match[1] !== void 0 && match[1] !== "") {
|
|
1872
|
+
calls.add(match[1]);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
const edges = this.edges.get(nodeId) ?? [];
|
|
1876
|
+
for (const calledFunction of calls) {
|
|
1877
|
+
const targetNode = this.findNodeByName(calledFunction);
|
|
1878
|
+
if (targetNode) {
|
|
1879
|
+
edges.push({
|
|
1880
|
+
from: nodeId,
|
|
1881
|
+
to: targetNode.id,
|
|
1882
|
+
type: "calls",
|
|
1883
|
+
confidence: 0.8
|
|
1884
|
+
// Lower confidence for regex-based detection
|
|
1885
|
+
});
|
|
1886
|
+
} else {
|
|
1887
|
+
edges.push({
|
|
1888
|
+
from: nodeId,
|
|
1889
|
+
to: `unknown:${calledFunction}`,
|
|
1890
|
+
type: "calls",
|
|
1891
|
+
confidence: 0.5
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
this.edges.set(nodeId, edges);
|
|
1896
|
+
}
|
|
1897
|
+
getNode(id) {
|
|
1898
|
+
return this.nodes.get(id);
|
|
1899
|
+
}
|
|
1900
|
+
getEdges(nodeId) {
|
|
1901
|
+
return this.edges.get(nodeId) ?? [];
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Add an edge to the graph (used when restoring from serialized data)
|
|
1905
|
+
*/
|
|
1906
|
+
addEdge(edge) {
|
|
1907
|
+
const edges = this.edges.get(edge.from) ?? [];
|
|
1908
|
+
edges.push(edge);
|
|
1909
|
+
this.edges.set(edge.from, edges);
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Add a graph node directly (used when restoring from serialized data)
|
|
1913
|
+
*/
|
|
1914
|
+
addGraphNode(node) {
|
|
1915
|
+
this.nodes.set(node.id, node);
|
|
1916
|
+
if (!this.edges.has(node.id)) {
|
|
1917
|
+
this.edges.set(node.id, []);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Get edges where this node is the target (callers of this function)
|
|
1922
|
+
*/
|
|
1923
|
+
getIncomingEdges(nodeId) {
|
|
1924
|
+
const incoming = [];
|
|
1925
|
+
for (const edges of this.edges.values()) {
|
|
1926
|
+
for (const edge of edges) {
|
|
1927
|
+
if (edge.to === nodeId) {
|
|
1928
|
+
incoming.push(edge);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
return incoming;
|
|
1933
|
+
}
|
|
1934
|
+
/**
|
|
1935
|
+
* Count how many nodes call this node
|
|
1936
|
+
*/
|
|
1937
|
+
getCalledByCount(nodeId) {
|
|
1938
|
+
return this.getIncomingEdges(nodeId).filter((e) => e.type === "calls").length;
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Count how many nodes this node calls
|
|
1942
|
+
*/
|
|
1943
|
+
getCallsCount(nodeId) {
|
|
1944
|
+
return this.getEdges(nodeId).filter((e) => e.type === "calls").length;
|
|
1945
|
+
}
|
|
1946
|
+
getAllNodes() {
|
|
1947
|
+
return Array.from(this.nodes.values());
|
|
1948
|
+
}
|
|
1949
|
+
findNodeByName(name) {
|
|
1950
|
+
for (const node of this.nodes.values()) {
|
|
1951
|
+
if (node.name === name) {
|
|
1952
|
+
return node;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
return void 0;
|
|
1956
|
+
}
|
|
1957
|
+
resolveImportPath(fromFile, importPath) {
|
|
1958
|
+
if (importPath.startsWith(".")) {
|
|
1959
|
+
const fromDir = fromFile.split("/").slice(0, -1).join("/");
|
|
1960
|
+
const parts = importPath.split("/");
|
|
1961
|
+
let resolved = fromDir;
|
|
1962
|
+
for (const part of parts) {
|
|
1963
|
+
if (part === "..") {
|
|
1964
|
+
resolved = resolved.split("/").slice(0, -1).join("/");
|
|
1965
|
+
} else if (part !== ".") {
|
|
1966
|
+
resolved += "/" + part;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
return resolved.replace(/\.js$/, "");
|
|
1970
|
+
}
|
|
1971
|
+
return importPath;
|
|
1972
|
+
}
|
|
1973
|
+
toJSON() {
|
|
1974
|
+
const allEdges = [];
|
|
1975
|
+
for (const edges of this.edges.values()) {
|
|
1976
|
+
allEdges.push(...edges);
|
|
1977
|
+
}
|
|
1978
|
+
return {
|
|
1979
|
+
nodes: Array.from(this.nodes.values()),
|
|
1980
|
+
edges: allEdges.map((e) => ({ from: e.from, to: e.to, type: e.type }))
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
};
|
|
1984
|
+
|
|
1985
|
+
// src/analysis/ast-parser.ts
|
|
1986
|
+
import { parse } from "@babel/parser";
|
|
1987
|
+
import traverseModule from "@babel/traverse";
|
|
1988
|
+
import * as t from "@babel/types";
|
|
1989
|
+
function getTraverse(mod) {
|
|
1990
|
+
if (typeof mod === "function") {
|
|
1991
|
+
return mod;
|
|
1992
|
+
}
|
|
1993
|
+
if (mod !== null && typeof mod === "object" && "default" in mod) {
|
|
1994
|
+
const withDefault = mod;
|
|
1995
|
+
if (typeof withDefault.default === "function") {
|
|
1996
|
+
return withDefault.default;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
throw new Error("Invalid traverse module export");
|
|
2000
|
+
}
|
|
2001
|
+
var traverse = getTraverse(traverseModule);
|
|
2002
|
+
var ASTParser = class {
|
|
2003
|
+
parse(code, language) {
|
|
2004
|
+
try {
|
|
2005
|
+
const plugins = ["jsx"];
|
|
2006
|
+
if (language === "typescript") {
|
|
2007
|
+
plugins.push("typescript");
|
|
2008
|
+
}
|
|
2009
|
+
const ast = parse(code, {
|
|
2010
|
+
sourceType: "module",
|
|
2011
|
+
plugins
|
|
2012
|
+
});
|
|
2013
|
+
const nodes = [];
|
|
2014
|
+
traverse(ast, {
|
|
2015
|
+
FunctionDeclaration: (path3) => {
|
|
2016
|
+
const node = path3.node;
|
|
2017
|
+
if (!node.id) return;
|
|
2018
|
+
const exported = path3.parent.type === "ExportNamedDeclaration" || path3.parent.type === "ExportDefaultDeclaration";
|
|
2019
|
+
nodes.push({
|
|
2020
|
+
type: "function",
|
|
2021
|
+
name: node.id.name,
|
|
2022
|
+
exported,
|
|
2023
|
+
async: node.async,
|
|
2024
|
+
startLine: node.loc?.start.line ?? 0,
|
|
2025
|
+
endLine: node.loc?.end.line ?? 0,
|
|
2026
|
+
signature: this.extractFunctionSignature(node)
|
|
2027
|
+
});
|
|
2028
|
+
},
|
|
2029
|
+
ClassDeclaration: (path3) => {
|
|
2030
|
+
const node = path3.node;
|
|
2031
|
+
if (!node.id) return;
|
|
2032
|
+
const exported = path3.parent.type === "ExportNamedDeclaration" || path3.parent.type === "ExportDefaultDeclaration";
|
|
2033
|
+
const methods = [];
|
|
2034
|
+
for (const member of node.body.body) {
|
|
2035
|
+
if (t.isClassMethod(member) && t.isIdentifier(member.key)) {
|
|
2036
|
+
methods.push({
|
|
2037
|
+
name: member.key.name,
|
|
2038
|
+
async: member.async,
|
|
2039
|
+
signature: this.extractMethodSignature(member),
|
|
2040
|
+
startLine: member.loc?.start.line ?? 0,
|
|
2041
|
+
endLine: member.loc?.end.line ?? 0
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
nodes.push({
|
|
2046
|
+
type: "class",
|
|
2047
|
+
name: node.id.name,
|
|
2048
|
+
exported,
|
|
2049
|
+
startLine: node.loc?.start.line ?? 0,
|
|
2050
|
+
endLine: node.loc?.end.line ?? 0,
|
|
2051
|
+
methods
|
|
2052
|
+
});
|
|
2053
|
+
},
|
|
2054
|
+
TSInterfaceDeclaration: (path3) => {
|
|
2055
|
+
const node = path3.node;
|
|
2056
|
+
const exported = path3.parent.type === "ExportNamedDeclaration";
|
|
2057
|
+
nodes.push({
|
|
2058
|
+
type: "interface",
|
|
2059
|
+
name: node.id.name,
|
|
2060
|
+
exported,
|
|
2061
|
+
startLine: node.loc?.start.line ?? 0,
|
|
2062
|
+
endLine: node.loc?.end.line ?? 0
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
});
|
|
2066
|
+
return nodes;
|
|
2067
|
+
} catch {
|
|
2068
|
+
return [];
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
extractImports(code) {
|
|
2072
|
+
try {
|
|
2073
|
+
const ast = parse(code, {
|
|
2074
|
+
sourceType: "module",
|
|
2075
|
+
plugins: ["typescript", "jsx"]
|
|
2076
|
+
});
|
|
2077
|
+
const imports = [];
|
|
2078
|
+
traverse(ast, {
|
|
2079
|
+
ImportDeclaration: (path3) => {
|
|
2080
|
+
const node = path3.node;
|
|
2081
|
+
const specifiers = [];
|
|
2082
|
+
for (const spec of node.specifiers) {
|
|
2083
|
+
if (t.isImportDefaultSpecifier(spec)) {
|
|
2084
|
+
specifiers.push(spec.local.name);
|
|
2085
|
+
} else if (t.isImportSpecifier(spec)) {
|
|
2086
|
+
specifiers.push(spec.local.name);
|
|
2087
|
+
} else if (t.isImportNamespaceSpecifier(spec)) {
|
|
2088
|
+
specifiers.push(spec.local.name);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
imports.push({
|
|
2092
|
+
source: node.source.value,
|
|
2093
|
+
specifiers,
|
|
2094
|
+
isType: node.importKind === "type"
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
});
|
|
2098
|
+
return imports;
|
|
2099
|
+
} catch {
|
|
2100
|
+
return [];
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
extractFunctionSignature(node) {
|
|
2104
|
+
const params = node.params.map((p) => {
|
|
2105
|
+
if (t.isIdentifier(p)) return p.name;
|
|
2106
|
+
return "param";
|
|
2107
|
+
}).join(", ");
|
|
2108
|
+
return `${node.id?.name ?? "anonymous"}(${params})`;
|
|
2109
|
+
}
|
|
2110
|
+
extractMethodSignature(node) {
|
|
2111
|
+
const params = node.params.map((p) => {
|
|
2112
|
+
if (t.isIdentifier(p)) return p.name;
|
|
2113
|
+
return "param";
|
|
2114
|
+
}).join(", ");
|
|
2115
|
+
const name = t.isIdentifier(node.key) ? node.key.name : "method";
|
|
2116
|
+
return `${name}(${params})`;
|
|
2117
|
+
}
|
|
2118
|
+
};
|
|
2119
|
+
|
|
2120
|
+
// src/analysis/tree-sitter-parser.ts
|
|
2121
|
+
import Parser from "tree-sitter";
|
|
2122
|
+
import Rust from "tree-sitter-rust";
|
|
2123
|
+
import Go from "tree-sitter-go";
|
|
2124
|
+
function createRustParser() {
|
|
2125
|
+
const parser = new Parser();
|
|
2126
|
+
parser.setLanguage(Rust);
|
|
2127
|
+
return parser;
|
|
2128
|
+
}
|
|
2129
|
+
function parseRustCode(code) {
|
|
2130
|
+
try {
|
|
2131
|
+
const parser = createRustParser();
|
|
2132
|
+
return parser.parse(code);
|
|
2133
|
+
} catch {
|
|
2134
|
+
return null;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
function createGoParser() {
|
|
2138
|
+
const parser = new Parser();
|
|
2139
|
+
parser.setLanguage(Go);
|
|
2140
|
+
return parser;
|
|
2141
|
+
}
|
|
2142
|
+
function parseGoCode(code) {
|
|
2143
|
+
try {
|
|
2144
|
+
const parser = createGoParser();
|
|
2145
|
+
return parser.parse(code);
|
|
2146
|
+
} catch {
|
|
2147
|
+
return null;
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
function positionToLineNumber(position) {
|
|
2151
|
+
return position.row + 1;
|
|
2152
|
+
}
|
|
2153
|
+
function getFirstChildOfType(node, type) {
|
|
2154
|
+
return node.children.find((child) => child.type === type) ?? null;
|
|
2155
|
+
}
|
|
2156
|
+
function getChildByFieldName(node, fieldName) {
|
|
2157
|
+
return node.childForFieldName(fieldName);
|
|
2158
|
+
}
|
|
2159
|
+
function hasVisibilityModifier(node) {
|
|
2160
|
+
return node.children.some((child) => child.type === "visibility_modifier");
|
|
2161
|
+
}
|
|
2162
|
+
function isAsyncFunction(node) {
|
|
2163
|
+
return node.children.some((child) => child.type === "async" || child.text === "async");
|
|
2164
|
+
}
|
|
2165
|
+
function getFunctionSignature(node) {
|
|
2166
|
+
const nameNode = getChildByFieldName(node, "name");
|
|
2167
|
+
const parametersNode = getChildByFieldName(node, "parameters");
|
|
2168
|
+
const returnTypeNode = getChildByFieldName(node, "return_type");
|
|
2169
|
+
const typeParametersNode = getChildByFieldName(node, "type_parameters");
|
|
2170
|
+
if (nameNode === null) {
|
|
2171
|
+
return "";
|
|
2172
|
+
}
|
|
2173
|
+
let signature = nameNode.text;
|
|
2174
|
+
if (typeParametersNode !== null) {
|
|
2175
|
+
signature += typeParametersNode.text;
|
|
2176
|
+
}
|
|
2177
|
+
if (parametersNode !== null) {
|
|
2178
|
+
signature += parametersNode.text;
|
|
2179
|
+
}
|
|
2180
|
+
if (returnTypeNode !== null) {
|
|
2181
|
+
signature += " " + returnTypeNode.text;
|
|
2182
|
+
}
|
|
2183
|
+
return signature;
|
|
2184
|
+
}
|
|
2185
|
+
function queryNodesByType(tree, nodeType) {
|
|
2186
|
+
const types = Array.isArray(nodeType) ? nodeType : [nodeType];
|
|
2187
|
+
return tree.rootNode.descendantsOfType(types);
|
|
2188
|
+
}
|
|
2189
|
+
function extractImportPath(useNode) {
|
|
2190
|
+
const argumentNode = getChildByFieldName(useNode, "argument");
|
|
2191
|
+
if (argumentNode === null) {
|
|
2192
|
+
return "";
|
|
2193
|
+
}
|
|
2194
|
+
return argumentNode.text;
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
// src/analysis/rust-ast-parser.ts
|
|
2198
|
+
var RustASTParser = class {
|
|
2199
|
+
/**
|
|
2200
|
+
* Parse Rust code into CodeNode array
|
|
2201
|
+
* @param code Rust source code
|
|
2202
|
+
* @param filePath File path for error context
|
|
2203
|
+
* @returns Array of CodeNode objects representing Rust constructs
|
|
2204
|
+
*/
|
|
2205
|
+
parse(code, _filePath) {
|
|
2206
|
+
try {
|
|
2207
|
+
const tree = parseRustCode(code);
|
|
2208
|
+
if (tree === null) {
|
|
2209
|
+
return [];
|
|
2210
|
+
}
|
|
2211
|
+
const nodes = [];
|
|
2212
|
+
const functions = this.parseFunctions(tree);
|
|
2213
|
+
nodes.push(...functions);
|
|
2214
|
+
const structs = this.parseStructs(tree);
|
|
2215
|
+
nodes.push(...structs);
|
|
2216
|
+
const traits = this.parseTraits(tree);
|
|
2217
|
+
nodes.push(...traits);
|
|
2218
|
+
const types = this.parseTypeAliases(tree);
|
|
2219
|
+
nodes.push(...types);
|
|
2220
|
+
const constants = this.parseConstants(tree);
|
|
2221
|
+
nodes.push(...constants);
|
|
2222
|
+
this.parseImplBlocks(tree, nodes);
|
|
2223
|
+
return nodes;
|
|
2224
|
+
} catch {
|
|
2225
|
+
return [];
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Extract imports from Rust code
|
|
2230
|
+
* @param code Rust source code
|
|
2231
|
+
* @returns Array of ImportInfo objects
|
|
2232
|
+
*/
|
|
2233
|
+
extractImports(code) {
|
|
2234
|
+
try {
|
|
2235
|
+
const tree = parseRustCode(code);
|
|
2236
|
+
if (tree === null) {
|
|
2237
|
+
return [];
|
|
2238
|
+
}
|
|
2239
|
+
const useDeclarations = queryNodesByType(tree, "use_declaration");
|
|
2240
|
+
const imports = [];
|
|
2241
|
+
for (const useNode of useDeclarations) {
|
|
2242
|
+
const importPath = extractImportPath(useNode);
|
|
2243
|
+
if (importPath === "") {
|
|
2244
|
+
continue;
|
|
2245
|
+
}
|
|
2246
|
+
const { source, specifiers } = this.parseImportPath(importPath);
|
|
2247
|
+
imports.push({
|
|
2248
|
+
source,
|
|
2249
|
+
specifiers,
|
|
2250
|
+
isType: false
|
|
2251
|
+
// Rust doesn't distinguish type-only imports at syntax level
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
return imports;
|
|
2255
|
+
} catch {
|
|
2256
|
+
return [];
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Parse function declarations (excluding impl block methods)
|
|
2261
|
+
*/
|
|
2262
|
+
parseFunctions(tree) {
|
|
2263
|
+
const functionNodes = queryNodesByType(tree, "function_item");
|
|
2264
|
+
const nodes = [];
|
|
2265
|
+
for (const fnNode of functionNodes) {
|
|
2266
|
+
if (this.isInsideImplBlock(fnNode)) {
|
|
2267
|
+
continue;
|
|
2268
|
+
}
|
|
2269
|
+
const nameNode = getChildByFieldName(fnNode, "name");
|
|
2270
|
+
if (nameNode === null) {
|
|
2271
|
+
continue;
|
|
2272
|
+
}
|
|
2273
|
+
const name = nameNode.text;
|
|
2274
|
+
const exported = hasVisibilityModifier(fnNode);
|
|
2275
|
+
const async = isAsyncFunction(fnNode);
|
|
2276
|
+
const startLine = positionToLineNumber(fnNode.startPosition);
|
|
2277
|
+
const endLine = positionToLineNumber(fnNode.endPosition);
|
|
2278
|
+
const signature = getFunctionSignature(fnNode);
|
|
2279
|
+
nodes.push({
|
|
2280
|
+
type: "function",
|
|
2281
|
+
name,
|
|
2282
|
+
exported,
|
|
2283
|
+
async,
|
|
2284
|
+
startLine,
|
|
2285
|
+
endLine,
|
|
2286
|
+
signature
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
return nodes;
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Check if a node is inside an impl block
|
|
2293
|
+
*/
|
|
2294
|
+
isInsideImplBlock(node) {
|
|
2295
|
+
let current = node.parent;
|
|
2296
|
+
while (current !== null) {
|
|
2297
|
+
if (current.type === "impl_item") {
|
|
2298
|
+
return true;
|
|
2299
|
+
}
|
|
2300
|
+
current = current.parent;
|
|
2301
|
+
}
|
|
2302
|
+
return false;
|
|
2303
|
+
}
|
|
2304
|
+
/**
|
|
2305
|
+
* Parse struct definitions
|
|
2306
|
+
*/
|
|
2307
|
+
parseStructs(tree) {
|
|
2308
|
+
const structNodes = queryNodesByType(tree, "struct_item");
|
|
2309
|
+
const nodes = [];
|
|
2310
|
+
for (const structNode of structNodes) {
|
|
2311
|
+
const nameNode = getChildByFieldName(structNode, "name");
|
|
2312
|
+
if (nameNode === null) {
|
|
2313
|
+
continue;
|
|
2314
|
+
}
|
|
2315
|
+
const name = nameNode.text;
|
|
2316
|
+
const exported = hasVisibilityModifier(structNode);
|
|
2317
|
+
const startLine = positionToLineNumber(structNode.startPosition);
|
|
2318
|
+
const endLine = positionToLineNumber(structNode.endPosition);
|
|
2319
|
+
const typeParamsNode = getChildByFieldName(structNode, "type_parameters");
|
|
2320
|
+
const signature = typeParamsNode !== null ? `${name}${typeParamsNode.text}` : name;
|
|
2321
|
+
nodes.push({
|
|
2322
|
+
type: "class",
|
|
2323
|
+
name,
|
|
2324
|
+
exported,
|
|
2325
|
+
startLine,
|
|
2326
|
+
endLine,
|
|
2327
|
+
signature,
|
|
2328
|
+
methods: []
|
|
2329
|
+
// Will be populated by parseImplBlocks
|
|
2330
|
+
});
|
|
2331
|
+
}
|
|
2332
|
+
return nodes;
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* Parse trait definitions
|
|
2336
|
+
*/
|
|
2337
|
+
parseTraits(tree) {
|
|
2338
|
+
const traitNodes = queryNodesByType(tree, "trait_item");
|
|
2339
|
+
const nodes = [];
|
|
2340
|
+
for (const traitNode of traitNodes) {
|
|
2341
|
+
const nameNode = getChildByFieldName(traitNode, "name");
|
|
2342
|
+
if (nameNode === null) {
|
|
2343
|
+
continue;
|
|
2344
|
+
}
|
|
2345
|
+
const name = nameNode.text;
|
|
2346
|
+
const exported = hasVisibilityModifier(traitNode);
|
|
2347
|
+
const startLine = positionToLineNumber(traitNode.startPosition);
|
|
2348
|
+
const endLine = positionToLineNumber(traitNode.endPosition);
|
|
2349
|
+
const typeParamsNode = getChildByFieldName(traitNode, "type_parameters");
|
|
2350
|
+
const signature = typeParamsNode !== null ? `${name}${typeParamsNode.text}` : name;
|
|
2351
|
+
const methods = this.extractTraitMethods(traitNode);
|
|
2352
|
+
nodes.push({
|
|
2353
|
+
type: "interface",
|
|
2354
|
+
name,
|
|
2355
|
+
exported,
|
|
2356
|
+
startLine,
|
|
2357
|
+
endLine,
|
|
2358
|
+
signature,
|
|
2359
|
+
methods
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
return nodes;
|
|
2363
|
+
}
|
|
2364
|
+
/**
|
|
2365
|
+
* Parse type aliases
|
|
2366
|
+
*/
|
|
2367
|
+
parseTypeAliases(tree) {
|
|
2368
|
+
const typeNodes = queryNodesByType(tree, "type_item");
|
|
2369
|
+
const nodes = [];
|
|
2370
|
+
for (const typeNode of typeNodes) {
|
|
2371
|
+
const nameNode = getChildByFieldName(typeNode, "name");
|
|
2372
|
+
if (nameNode === null) {
|
|
2373
|
+
continue;
|
|
2374
|
+
}
|
|
2375
|
+
const name = nameNode.text;
|
|
2376
|
+
const exported = hasVisibilityModifier(typeNode);
|
|
2377
|
+
const startLine = positionToLineNumber(typeNode.startPosition);
|
|
2378
|
+
const endLine = positionToLineNumber(typeNode.endPosition);
|
|
2379
|
+
const valueNode = getChildByFieldName(typeNode, "type");
|
|
2380
|
+
const signature = valueNode !== null ? `${name} = ${valueNode.text}` : name;
|
|
2381
|
+
nodes.push({
|
|
2382
|
+
type: "type",
|
|
2383
|
+
name,
|
|
2384
|
+
exported,
|
|
2385
|
+
startLine,
|
|
2386
|
+
endLine,
|
|
2387
|
+
signature
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2390
|
+
return nodes;
|
|
2391
|
+
}
|
|
2392
|
+
/**
|
|
2393
|
+
* Parse constants and statics
|
|
2394
|
+
*/
|
|
2395
|
+
parseConstants(tree) {
|
|
2396
|
+
const constNodes = queryNodesByType(tree, ["const_item", "static_item"]);
|
|
2397
|
+
const nodes = [];
|
|
2398
|
+
for (const constNode of constNodes) {
|
|
2399
|
+
const nameNode = getChildByFieldName(constNode, "name");
|
|
2400
|
+
if (nameNode === null) {
|
|
2401
|
+
continue;
|
|
2402
|
+
}
|
|
2403
|
+
const name = nameNode.text;
|
|
2404
|
+
const exported = hasVisibilityModifier(constNode);
|
|
2405
|
+
const startLine = positionToLineNumber(constNode.startPosition);
|
|
2406
|
+
const endLine = positionToLineNumber(constNode.endPosition);
|
|
2407
|
+
const typeNode = getChildByFieldName(constNode, "type");
|
|
2408
|
+
const signature = typeNode !== null ? `${name}: ${typeNode.text}` : name;
|
|
2409
|
+
nodes.push({
|
|
2410
|
+
type: "const",
|
|
2411
|
+
name,
|
|
2412
|
+
exported,
|
|
2413
|
+
startLine,
|
|
2414
|
+
endLine,
|
|
2415
|
+
signature
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
return nodes;
|
|
2419
|
+
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Parse impl blocks and attach methods to corresponding structs
|
|
2422
|
+
*/
|
|
2423
|
+
parseImplBlocks(tree, nodes) {
|
|
2424
|
+
const implNodes = queryNodesByType(tree, "impl_item");
|
|
2425
|
+
for (const implNode of implNodes) {
|
|
2426
|
+
const typeNode = getChildByFieldName(implNode, "type");
|
|
2427
|
+
if (typeNode === null) {
|
|
2428
|
+
continue;
|
|
2429
|
+
}
|
|
2430
|
+
const typeName = typeNode.text;
|
|
2431
|
+
const methods = this.extractImplMethods(implNode);
|
|
2432
|
+
const structNode = nodes.find(
|
|
2433
|
+
(node) => node.type === "class" && node.name === typeName
|
|
2434
|
+
);
|
|
2435
|
+
if (structNode !== void 0 && structNode.methods !== void 0) {
|
|
2436
|
+
structNode.methods.push(...methods);
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
/**
|
|
2441
|
+
* Extract methods from trait definition
|
|
2442
|
+
*/
|
|
2443
|
+
extractTraitMethods(traitNode) {
|
|
2444
|
+
const methods = [];
|
|
2445
|
+
const bodyNode = getChildByFieldName(traitNode, "body");
|
|
2446
|
+
if (bodyNode === null) {
|
|
2447
|
+
return methods;
|
|
2448
|
+
}
|
|
2449
|
+
const functionSignatures = bodyNode.descendantsOfType("function_signature_item");
|
|
2450
|
+
for (const fnSigNode of functionSignatures) {
|
|
2451
|
+
const nameNode = getChildByFieldName(fnSigNode, "name");
|
|
2452
|
+
if (nameNode === null) {
|
|
2453
|
+
continue;
|
|
2454
|
+
}
|
|
2455
|
+
const name = nameNode.text;
|
|
2456
|
+
const async = isAsyncFunction(fnSigNode);
|
|
2457
|
+
const signature = getFunctionSignature(fnSigNode);
|
|
2458
|
+
const startLine = positionToLineNumber(fnSigNode.startPosition);
|
|
2459
|
+
const endLine = positionToLineNumber(fnSigNode.endPosition);
|
|
2460
|
+
methods.push({
|
|
2461
|
+
name,
|
|
2462
|
+
async,
|
|
2463
|
+
signature,
|
|
2464
|
+
startLine,
|
|
2465
|
+
endLine
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
return methods;
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Extract methods from impl block
|
|
2472
|
+
*/
|
|
2473
|
+
extractImplMethods(implNode) {
|
|
2474
|
+
const methods = [];
|
|
2475
|
+
const bodyNode = getChildByFieldName(implNode, "body");
|
|
2476
|
+
if (bodyNode === null) {
|
|
2477
|
+
return methods;
|
|
2478
|
+
}
|
|
2479
|
+
const functionItems = bodyNode.descendantsOfType("function_item");
|
|
2480
|
+
for (const fnNode of functionItems) {
|
|
2481
|
+
const nameNode = getChildByFieldName(fnNode, "name");
|
|
2482
|
+
if (nameNode === null) {
|
|
2483
|
+
continue;
|
|
2484
|
+
}
|
|
2485
|
+
const name = nameNode.text;
|
|
2486
|
+
const async = isAsyncFunction(fnNode);
|
|
2487
|
+
const signature = getFunctionSignature(fnNode);
|
|
2488
|
+
const startLine = positionToLineNumber(fnNode.startPosition);
|
|
2489
|
+
const endLine = positionToLineNumber(fnNode.endPosition);
|
|
2490
|
+
methods.push({
|
|
2491
|
+
name,
|
|
2492
|
+
async,
|
|
2493
|
+
signature,
|
|
2494
|
+
startLine,
|
|
2495
|
+
endLine
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
return methods;
|
|
2499
|
+
}
|
|
2500
|
+
/**
|
|
2501
|
+
* Parse import path into source and specifiers
|
|
2502
|
+
* Examples:
|
|
2503
|
+
* - "std::collections::HashMap" -> { source: "std::collections", specifiers: ["HashMap"] }
|
|
2504
|
+
* - "crate::utils::*" -> { source: "crate::utils", specifiers: ["*"] }
|
|
2505
|
+
* - "super::Type" -> { source: "super", specifiers: ["Type"] }
|
|
2506
|
+
*/
|
|
2507
|
+
parseImportPath(importPath) {
|
|
2508
|
+
const path3 = importPath.trim();
|
|
2509
|
+
if (path3.includes("::*")) {
|
|
2510
|
+
const source = path3.replace("::*", "");
|
|
2511
|
+
return { source, specifiers: ["*"] };
|
|
2512
|
+
}
|
|
2513
|
+
const scopedMatch = path3.match(/^(.+)::\{(.+)\}$/);
|
|
2514
|
+
if (scopedMatch !== null) {
|
|
2515
|
+
const source = scopedMatch[1] ?? "";
|
|
2516
|
+
const specifiersStr = scopedMatch[2] ?? "";
|
|
2517
|
+
const specifiers = specifiersStr.split(",").map((s) => s.trim());
|
|
2518
|
+
return { source, specifiers };
|
|
2519
|
+
}
|
|
2520
|
+
const parts = path3.split("::");
|
|
2521
|
+
if (parts.length > 1) {
|
|
2522
|
+
const specifiers = [parts[parts.length - 1] ?? ""];
|
|
2523
|
+
const source = parts.slice(0, -1).join("::");
|
|
2524
|
+
return { source, specifiers };
|
|
2525
|
+
}
|
|
2526
|
+
return { source: "", specifiers: [path3] };
|
|
2527
|
+
}
|
|
2528
|
+
};
|
|
2529
|
+
|
|
2530
|
+
// src/analysis/go-ast-parser.ts
|
|
2531
|
+
var GoASTParser = class {
|
|
2532
|
+
/**
|
|
2533
|
+
* Parse Go code into CodeNode array
|
|
2534
|
+
* @param code Go source code
|
|
2535
|
+
* @param filePath File path for error context
|
|
2536
|
+
* @returns Array of CodeNode objects representing Go constructs
|
|
2537
|
+
*/
|
|
2538
|
+
parse(code, _filePath) {
|
|
2539
|
+
try {
|
|
2540
|
+
const tree = parseGoCode(code);
|
|
2541
|
+
if (tree === null) {
|
|
2542
|
+
return [];
|
|
2543
|
+
}
|
|
2544
|
+
const nodes = [];
|
|
2545
|
+
const functions = this.parseFunctions(tree);
|
|
2546
|
+
nodes.push(...functions);
|
|
2547
|
+
const structs = this.parseStructs(tree);
|
|
2548
|
+
nodes.push(...structs);
|
|
2549
|
+
const interfaces = this.parseInterfaces(tree);
|
|
2550
|
+
nodes.push(...interfaces);
|
|
2551
|
+
const types = this.parseTypeAliases(tree);
|
|
2552
|
+
nodes.push(...types);
|
|
2553
|
+
const constants = this.parseConstants(tree);
|
|
2554
|
+
nodes.push(...constants);
|
|
2555
|
+
this.parseMethods(tree, nodes);
|
|
2556
|
+
return nodes;
|
|
2557
|
+
} catch {
|
|
2558
|
+
return [];
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
/**
|
|
2562
|
+
* Extract imports from Go code
|
|
2563
|
+
* @param code Go source code
|
|
2564
|
+
* @returns Array of ImportInfo objects
|
|
2565
|
+
*/
|
|
2566
|
+
extractImports(code) {
|
|
2567
|
+
try {
|
|
2568
|
+
const tree = parseGoCode(code);
|
|
2569
|
+
if (tree === null) {
|
|
2570
|
+
return [];
|
|
2571
|
+
}
|
|
2572
|
+
const imports = [];
|
|
2573
|
+
const importDecls = queryNodesByType(tree, "import_declaration");
|
|
2574
|
+
for (const importDecl of importDecls) {
|
|
2575
|
+
const importSpecs = importDecl.descendantsOfType("import_spec");
|
|
2576
|
+
for (const spec of importSpecs) {
|
|
2577
|
+
const pathNode = getChildByFieldName(spec, "path");
|
|
2578
|
+
if (pathNode === null) {
|
|
2579
|
+
continue;
|
|
2580
|
+
}
|
|
2581
|
+
const stringContent = pathNode.descendantsOfType("interpreted_string_literal_content")[0];
|
|
2582
|
+
const path3 = stringContent !== void 0 ? stringContent.text : pathNode.text.replace(/"/g, "");
|
|
2583
|
+
if (path3 !== "") {
|
|
2584
|
+
imports.push({
|
|
2585
|
+
source: path3,
|
|
2586
|
+
specifiers: [],
|
|
2587
|
+
isType: false
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
return imports;
|
|
2593
|
+
} catch {
|
|
2594
|
+
return [];
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
/**
|
|
2598
|
+
* Parse function declarations
|
|
2599
|
+
*/
|
|
2600
|
+
parseFunctions(tree) {
|
|
2601
|
+
const functionNodes = queryNodesByType(tree, "function_declaration");
|
|
2602
|
+
const nodes = [];
|
|
2603
|
+
for (const fnNode of functionNodes) {
|
|
2604
|
+
const nameNode = getChildByFieldName(fnNode, "name");
|
|
2605
|
+
if (nameNode === null) {
|
|
2606
|
+
continue;
|
|
2607
|
+
}
|
|
2608
|
+
const name = nameNode.text;
|
|
2609
|
+
const exported = this.isExported(name);
|
|
2610
|
+
const startLine = positionToLineNumber(fnNode.startPosition);
|
|
2611
|
+
const endLine = positionToLineNumber(fnNode.endPosition);
|
|
2612
|
+
const signature = getFunctionSignature(fnNode);
|
|
2613
|
+
nodes.push({
|
|
2614
|
+
type: "function",
|
|
2615
|
+
name,
|
|
2616
|
+
exported,
|
|
2617
|
+
async: false,
|
|
2618
|
+
startLine,
|
|
2619
|
+
endLine,
|
|
2620
|
+
signature
|
|
2621
|
+
});
|
|
2622
|
+
}
|
|
2623
|
+
return nodes;
|
|
2624
|
+
}
|
|
2625
|
+
/**
|
|
2626
|
+
* Parse struct definitions
|
|
2627
|
+
*/
|
|
2628
|
+
parseStructs(tree) {
|
|
2629
|
+
const typeDecls = queryNodesByType(tree, "type_declaration");
|
|
2630
|
+
const nodes = [];
|
|
2631
|
+
for (const typeDecl of typeDecls) {
|
|
2632
|
+
const typeSpec = getFirstChildOfType(typeDecl, "type_spec");
|
|
2633
|
+
if (typeSpec === null) {
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
const nameNode = getChildByFieldName(typeSpec, "name");
|
|
2637
|
+
const typeNode = getChildByFieldName(typeSpec, "type");
|
|
2638
|
+
if (nameNode === null || typeNode === null) {
|
|
2639
|
+
continue;
|
|
2640
|
+
}
|
|
2641
|
+
if (typeNode.type !== "struct_type") {
|
|
2642
|
+
continue;
|
|
2643
|
+
}
|
|
2644
|
+
const name = nameNode.text;
|
|
2645
|
+
const exported = this.isExported(name);
|
|
2646
|
+
const startLine = positionToLineNumber(typeDecl.startPosition);
|
|
2647
|
+
const endLine = positionToLineNumber(typeDecl.endPosition);
|
|
2648
|
+
nodes.push({
|
|
2649
|
+
type: "class",
|
|
2650
|
+
name,
|
|
2651
|
+
exported,
|
|
2652
|
+
startLine,
|
|
2653
|
+
endLine,
|
|
2654
|
+
signature: name,
|
|
2655
|
+
methods: []
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
return nodes;
|
|
2659
|
+
}
|
|
2660
|
+
/**
|
|
2661
|
+
* Parse interface definitions
|
|
2662
|
+
*/
|
|
2663
|
+
parseInterfaces(tree) {
|
|
2664
|
+
const typeDecls = queryNodesByType(tree, "type_declaration");
|
|
2665
|
+
const nodes = [];
|
|
2666
|
+
for (const typeDecl of typeDecls) {
|
|
2667
|
+
const typeSpec = getFirstChildOfType(typeDecl, "type_spec");
|
|
2668
|
+
if (typeSpec === null) {
|
|
2669
|
+
continue;
|
|
2670
|
+
}
|
|
2671
|
+
const nameNode = getChildByFieldName(typeSpec, "name");
|
|
2672
|
+
const typeNode = getChildByFieldName(typeSpec, "type");
|
|
2673
|
+
if (nameNode === null || typeNode === null) {
|
|
2674
|
+
continue;
|
|
2675
|
+
}
|
|
2676
|
+
if (typeNode.type !== "interface_type") {
|
|
2677
|
+
continue;
|
|
2678
|
+
}
|
|
2679
|
+
const name = nameNode.text;
|
|
2680
|
+
const exported = this.isExported(name);
|
|
2681
|
+
const startLine = positionToLineNumber(typeDecl.startPosition);
|
|
2682
|
+
const endLine = positionToLineNumber(typeDecl.endPosition);
|
|
2683
|
+
const methods = this.extractInterfaceMethods(typeNode);
|
|
2684
|
+
nodes.push({
|
|
2685
|
+
type: "interface",
|
|
2686
|
+
name,
|
|
2687
|
+
exported,
|
|
2688
|
+
startLine,
|
|
2689
|
+
endLine,
|
|
2690
|
+
signature: name,
|
|
2691
|
+
methods
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
2694
|
+
return nodes;
|
|
2695
|
+
}
|
|
2696
|
+
/**
|
|
2697
|
+
* Parse type aliases
|
|
2698
|
+
*/
|
|
2699
|
+
parseTypeAliases(tree) {
|
|
2700
|
+
const typeDecls = queryNodesByType(tree, "type_declaration");
|
|
2701
|
+
const nodes = [];
|
|
2702
|
+
for (const typeDecl of typeDecls) {
|
|
2703
|
+
const typeSpec = getFirstChildOfType(typeDecl, "type_spec");
|
|
2704
|
+
if (typeSpec === null) {
|
|
2705
|
+
continue;
|
|
2706
|
+
}
|
|
2707
|
+
const nameNode = getChildByFieldName(typeSpec, "name");
|
|
2708
|
+
const typeNode = getChildByFieldName(typeSpec, "type");
|
|
2709
|
+
if (nameNode === null || typeNode === null) {
|
|
2710
|
+
continue;
|
|
2711
|
+
}
|
|
2712
|
+
if (typeNode.type === "struct_type" || typeNode.type === "interface_type") {
|
|
2713
|
+
continue;
|
|
2714
|
+
}
|
|
2715
|
+
const name = nameNode.text;
|
|
2716
|
+
const exported = this.isExported(name);
|
|
2717
|
+
const startLine = positionToLineNumber(typeDecl.startPosition);
|
|
2718
|
+
const endLine = positionToLineNumber(typeDecl.endPosition);
|
|
2719
|
+
const signature = `${name} = ${typeNode.text}`;
|
|
2720
|
+
nodes.push({
|
|
2721
|
+
type: "type",
|
|
2722
|
+
name,
|
|
2723
|
+
exported,
|
|
2724
|
+
startLine,
|
|
2725
|
+
endLine,
|
|
2726
|
+
signature
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
return nodes;
|
|
2730
|
+
}
|
|
2731
|
+
/**
|
|
2732
|
+
* Parse constants and variables
|
|
2733
|
+
*/
|
|
2734
|
+
parseConstants(tree) {
|
|
2735
|
+
const nodes = [];
|
|
2736
|
+
const constDecls = queryNodesByType(tree, "const_declaration");
|
|
2737
|
+
for (const constDecl of constDecls) {
|
|
2738
|
+
const specs = constDecl.descendantsOfType("const_spec");
|
|
2739
|
+
for (const spec of specs) {
|
|
2740
|
+
const nameNode = getChildByFieldName(spec, "name");
|
|
2741
|
+
if (nameNode === null) {
|
|
2742
|
+
continue;
|
|
2743
|
+
}
|
|
2744
|
+
const name = nameNode.text;
|
|
2745
|
+
const exported = this.isExported(name);
|
|
2746
|
+
const startLine = positionToLineNumber(spec.startPosition);
|
|
2747
|
+
const endLine = positionToLineNumber(spec.endPosition);
|
|
2748
|
+
const typeNode = getChildByFieldName(spec, "type");
|
|
2749
|
+
const signature = typeNode !== null ? `${name}: ${typeNode.text}` : name;
|
|
2750
|
+
nodes.push({
|
|
2751
|
+
type: "const",
|
|
2752
|
+
name,
|
|
2753
|
+
exported,
|
|
2754
|
+
startLine,
|
|
2755
|
+
endLine,
|
|
2756
|
+
signature
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
const varDecls = queryNodesByType(tree, "var_declaration");
|
|
2761
|
+
for (const varDecl of varDecls) {
|
|
2762
|
+
const specs = varDecl.descendantsOfType("var_spec");
|
|
2763
|
+
for (const spec of specs) {
|
|
2764
|
+
const nameNode = getChildByFieldName(spec, "name");
|
|
2765
|
+
if (nameNode === null) {
|
|
2766
|
+
continue;
|
|
2767
|
+
}
|
|
2768
|
+
const name = nameNode.text;
|
|
2769
|
+
const exported = this.isExported(name);
|
|
2770
|
+
const startLine = positionToLineNumber(spec.startPosition);
|
|
2771
|
+
const endLine = positionToLineNumber(spec.endPosition);
|
|
2772
|
+
const typeNode = getChildByFieldName(spec, "type");
|
|
2773
|
+
const signature = typeNode !== null ? `${name}: ${typeNode.text}` : name;
|
|
2774
|
+
nodes.push({
|
|
2775
|
+
type: "const",
|
|
2776
|
+
name,
|
|
2777
|
+
exported,
|
|
2778
|
+
startLine,
|
|
2779
|
+
endLine,
|
|
2780
|
+
signature
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
return nodes;
|
|
2785
|
+
}
|
|
2786
|
+
/**
|
|
2787
|
+
* Parse methods and attach to corresponding structs
|
|
2788
|
+
*/
|
|
2789
|
+
parseMethods(tree, nodes) {
|
|
2790
|
+
const methodNodes = queryNodesByType(tree, "method_declaration");
|
|
2791
|
+
for (const methodNode of methodNodes) {
|
|
2792
|
+
const receiverType = this.getReceiverType(methodNode);
|
|
2793
|
+
if (receiverType === null) {
|
|
2794
|
+
continue;
|
|
2795
|
+
}
|
|
2796
|
+
const nameNode = getChildByFieldName(methodNode, "name");
|
|
2797
|
+
if (nameNode === null) {
|
|
2798
|
+
continue;
|
|
2799
|
+
}
|
|
2800
|
+
const name = nameNode.text;
|
|
2801
|
+
const signature = getFunctionSignature(methodNode);
|
|
2802
|
+
const startLine = positionToLineNumber(methodNode.startPosition);
|
|
2803
|
+
const endLine = positionToLineNumber(methodNode.endPosition);
|
|
2804
|
+
const structNode = nodes.find(
|
|
2805
|
+
(node) => node.type === "class" && node.name === receiverType
|
|
2806
|
+
);
|
|
2807
|
+
if (structNode !== void 0 && structNode.methods !== void 0) {
|
|
2808
|
+
structNode.methods.push({
|
|
2809
|
+
name,
|
|
2810
|
+
async: false,
|
|
2811
|
+
signature,
|
|
2812
|
+
startLine,
|
|
2813
|
+
endLine
|
|
2814
|
+
});
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
/**
|
|
2819
|
+
* Extract methods from interface definition
|
|
2820
|
+
*/
|
|
2821
|
+
extractInterfaceMethods(interfaceNode) {
|
|
2822
|
+
const methods = [];
|
|
2823
|
+
const methodElems = interfaceNode.descendantsOfType("method_elem");
|
|
2824
|
+
for (const methodElem of methodElems) {
|
|
2825
|
+
const nameNode = getChildByFieldName(methodElem, "name");
|
|
2826
|
+
if (nameNode === null) {
|
|
2827
|
+
continue;
|
|
2828
|
+
}
|
|
2829
|
+
const name = nameNode.text;
|
|
2830
|
+
const signature = getFunctionSignature(methodElem);
|
|
2831
|
+
const startLine = positionToLineNumber(methodElem.startPosition);
|
|
2832
|
+
const endLine = positionToLineNumber(methodElem.endPosition);
|
|
2833
|
+
methods.push({
|
|
2834
|
+
name,
|
|
2835
|
+
async: false,
|
|
2836
|
+
signature,
|
|
2837
|
+
startLine,
|
|
2838
|
+
endLine
|
|
2839
|
+
});
|
|
2840
|
+
}
|
|
2841
|
+
return methods;
|
|
2842
|
+
}
|
|
2843
|
+
/**
|
|
2844
|
+
* Get the receiver type name for a method
|
|
2845
|
+
*/
|
|
2846
|
+
getReceiverType(methodNode) {
|
|
2847
|
+
const receiverNode = getChildByFieldName(methodNode, "receiver");
|
|
2848
|
+
if (receiverNode === null) {
|
|
2849
|
+
return null;
|
|
2850
|
+
}
|
|
2851
|
+
const paramDecl = getFirstChildOfType(receiverNode, "parameter_declaration");
|
|
2852
|
+
if (paramDecl === null) {
|
|
2853
|
+
return null;
|
|
2854
|
+
}
|
|
2855
|
+
const typeNode = getChildByFieldName(paramDecl, "type");
|
|
2856
|
+
if (typeNode === null) {
|
|
2857
|
+
return null;
|
|
2858
|
+
}
|
|
2859
|
+
if (typeNode.type === "pointer_type") {
|
|
2860
|
+
const innerType = typeNode.children.find(
|
|
2861
|
+
(child) => child.type === "type_identifier"
|
|
2862
|
+
);
|
|
2863
|
+
return innerType !== void 0 ? innerType.text : null;
|
|
2864
|
+
}
|
|
2865
|
+
if (typeNode.type === "type_identifier") {
|
|
2866
|
+
return typeNode.text;
|
|
2867
|
+
}
|
|
2868
|
+
return null;
|
|
2869
|
+
}
|
|
2870
|
+
/**
|
|
2871
|
+
* Check if a name is exported (starts with uppercase letter)
|
|
2872
|
+
*/
|
|
2873
|
+
isExported(name) {
|
|
2874
|
+
if (name.length === 0) {
|
|
2875
|
+
return false;
|
|
2876
|
+
}
|
|
2877
|
+
const firstChar = name[0];
|
|
2878
|
+
if (firstChar === void 0) {
|
|
2879
|
+
return false;
|
|
2880
|
+
}
|
|
2881
|
+
return firstChar === firstChar.toUpperCase();
|
|
2882
|
+
}
|
|
2883
|
+
};
|
|
2884
|
+
|
|
2885
|
+
// src/analysis/parser-factory.ts
|
|
2886
|
+
import path2 from "path";
|
|
2887
|
+
|
|
2888
|
+
// src/analysis/python-ast-parser.ts
|
|
2889
|
+
var PythonASTParser = class {
|
|
2890
|
+
constructor(bridge) {
|
|
2891
|
+
this.bridge = bridge;
|
|
2892
|
+
}
|
|
2893
|
+
async parse(code, filePath) {
|
|
2894
|
+
const result = await this.bridge.parsePython(code, filePath);
|
|
2895
|
+
return result.nodes.map((node) => {
|
|
2896
|
+
const codeNode = {
|
|
2897
|
+
type: node.type,
|
|
2898
|
+
name: node.name,
|
|
2899
|
+
exported: node.exported,
|
|
2900
|
+
startLine: node.startLine,
|
|
2901
|
+
endLine: node.endLine
|
|
2902
|
+
};
|
|
2903
|
+
if (node.async !== void 0) {
|
|
2904
|
+
codeNode.async = node.async;
|
|
2905
|
+
}
|
|
2906
|
+
if (node.signature !== void 0) {
|
|
2907
|
+
codeNode.signature = node.signature;
|
|
2908
|
+
}
|
|
2909
|
+
if (node.methods !== void 0) {
|
|
2910
|
+
codeNode.methods = node.methods;
|
|
2911
|
+
}
|
|
2912
|
+
return codeNode;
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2916
|
+
|
|
2917
|
+
// src/analysis/parser-factory.ts
|
|
2918
|
+
var ParserFactory = class {
|
|
2919
|
+
constructor(pythonBridge) {
|
|
2920
|
+
this.pythonBridge = pythonBridge;
|
|
2921
|
+
}
|
|
2922
|
+
async parseFile(filePath, code) {
|
|
2923
|
+
const ext = path2.extname(filePath);
|
|
2924
|
+
if ([".ts", ".tsx"].includes(ext)) {
|
|
2925
|
+
const parser = new ASTParser();
|
|
2926
|
+
return parser.parse(code, "typescript");
|
|
2927
|
+
}
|
|
2928
|
+
if ([".js", ".jsx"].includes(ext)) {
|
|
2929
|
+
const parser = new ASTParser();
|
|
2930
|
+
return parser.parse(code, "javascript");
|
|
2931
|
+
}
|
|
2932
|
+
if (ext === ".py") {
|
|
2933
|
+
if (!this.pythonBridge) {
|
|
2934
|
+
throw new Error("Python bridge not available for parsing Python files");
|
|
2935
|
+
}
|
|
2936
|
+
const parser = new PythonASTParser(this.pythonBridge);
|
|
2937
|
+
return await parser.parse(code, filePath);
|
|
2938
|
+
}
|
|
2939
|
+
if (ext === ".rs") {
|
|
2940
|
+
const parser = new RustASTParser();
|
|
2941
|
+
return parser.parse(code, filePath);
|
|
2942
|
+
}
|
|
2943
|
+
if (ext === ".go") {
|
|
2944
|
+
const parser = new GoASTParser();
|
|
2945
|
+
return parser.parse(code, filePath);
|
|
2946
|
+
}
|
|
2947
|
+
return [];
|
|
2948
|
+
}
|
|
2949
|
+
};
|
|
2950
|
+
|
|
2951
|
+
// src/services/code-graph.service.ts
|
|
2952
|
+
var CodeGraphService = class {
|
|
2953
|
+
dataDir;
|
|
2954
|
+
parser;
|
|
2955
|
+
parserFactory;
|
|
2956
|
+
graphCache;
|
|
2957
|
+
constructor(dataDir, pythonBridge) {
|
|
2958
|
+
this.dataDir = dataDir;
|
|
2959
|
+
this.parser = new ASTParser();
|
|
2960
|
+
this.parserFactory = new ParserFactory(pythonBridge);
|
|
2961
|
+
this.graphCache = /* @__PURE__ */ new Map();
|
|
2962
|
+
}
|
|
2963
|
+
/**
|
|
2964
|
+
* Build a code graph from source files.
|
|
2965
|
+
*/
|
|
2966
|
+
async buildGraph(files) {
|
|
2967
|
+
const graph = new CodeGraph();
|
|
2968
|
+
for (const file of files) {
|
|
2969
|
+
const ext = file.path.split(".").pop() ?? "";
|
|
2970
|
+
if (!["ts", "tsx", "js", "jsx", "py", "rs", "go"].includes(ext)) continue;
|
|
2971
|
+
const nodes = await this.parserFactory.parseFile(file.path, file.content);
|
|
2972
|
+
graph.addNodes(nodes, file.path);
|
|
2973
|
+
if (ext === "rs") {
|
|
2974
|
+
const rustParser = new RustASTParser();
|
|
2975
|
+
const imports = rustParser.extractImports(file.content);
|
|
2976
|
+
for (const imp of imports) {
|
|
2977
|
+
if (!imp.isType) {
|
|
2978
|
+
graph.addImport(file.path, imp.source, imp.specifiers);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
} else if (ext === "go") {
|
|
2982
|
+
const goParser = new GoASTParser();
|
|
2983
|
+
const imports = goParser.extractImports(file.content);
|
|
2984
|
+
for (const imp of imports) {
|
|
2985
|
+
if (!imp.isType) {
|
|
2986
|
+
graph.addImport(file.path, imp.source, imp.specifiers);
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
} else if (ext !== "py") {
|
|
2990
|
+
const imports = this.parser.extractImports(file.content);
|
|
2991
|
+
for (const imp of imports) {
|
|
2992
|
+
if (!imp.isType) {
|
|
2993
|
+
graph.addImport(file.path, imp.source, imp.specifiers);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
for (const node of nodes) {
|
|
2998
|
+
const lines = file.content.split("\n");
|
|
2999
|
+
if (node.type === "function") {
|
|
3000
|
+
const functionCode = lines.slice(node.startLine - 1, node.endLine).join("\n");
|
|
3001
|
+
graph.analyzeCallRelationships(functionCode, file.path, node.name);
|
|
3002
|
+
} else if (node.type === "class" && node.methods !== void 0) {
|
|
3003
|
+
for (const method of node.methods) {
|
|
3004
|
+
const methodCode = lines.slice(method.startLine - 1, method.endLine).join("\n");
|
|
3005
|
+
graph.analyzeCallRelationships(methodCode, file.path, `${node.name}.${method.name}`);
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
return graph;
|
|
3011
|
+
}
|
|
3012
|
+
/**
|
|
3013
|
+
* Save a code graph for a store.
|
|
3014
|
+
*/
|
|
3015
|
+
async saveGraph(storeId, graph) {
|
|
3016
|
+
const graphPath = this.getGraphPath(storeId);
|
|
3017
|
+
await mkdir4(dirname3(graphPath), { recursive: true });
|
|
3018
|
+
const serialized = graph.toJSON();
|
|
3019
|
+
await writeFile3(graphPath, JSON.stringify(serialized, null, 2));
|
|
3020
|
+
}
|
|
3021
|
+
/**
|
|
3022
|
+
* Load a code graph for a store.
|
|
3023
|
+
* Returns undefined if no graph exists.
|
|
3024
|
+
*/
|
|
3025
|
+
async loadGraph(storeId) {
|
|
3026
|
+
const cached = this.graphCache.get(storeId);
|
|
3027
|
+
if (cached) return cached;
|
|
3028
|
+
const graphPath = this.getGraphPath(storeId);
|
|
3029
|
+
try {
|
|
3030
|
+
const content = await readFile4(graphPath, "utf-8");
|
|
3031
|
+
const parsed = JSON.parse(content);
|
|
3032
|
+
if (!this.isSerializedGraph(parsed)) {
|
|
3033
|
+
return void 0;
|
|
3034
|
+
}
|
|
3035
|
+
const serialized = parsed;
|
|
3036
|
+
const graph = new CodeGraph();
|
|
3037
|
+
for (const node of serialized.nodes) {
|
|
3038
|
+
const nodeType = this.validateNodeType(node.type);
|
|
3039
|
+
if (!nodeType) continue;
|
|
3040
|
+
if (nodeType === "method") {
|
|
3041
|
+
const graphNode = {
|
|
3042
|
+
id: node.id,
|
|
3043
|
+
file: node.file,
|
|
3044
|
+
type: "method",
|
|
3045
|
+
name: node.name,
|
|
3046
|
+
exported: node.exported,
|
|
3047
|
+
startLine: node.startLine,
|
|
3048
|
+
endLine: node.endLine
|
|
3049
|
+
};
|
|
3050
|
+
if (node.signature !== void 0) {
|
|
3051
|
+
graphNode.signature = node.signature;
|
|
3052
|
+
}
|
|
3053
|
+
graph.addGraphNode(graphNode);
|
|
3054
|
+
continue;
|
|
3055
|
+
}
|
|
3056
|
+
const codeNode = {
|
|
3057
|
+
type: nodeType,
|
|
3058
|
+
name: node.name,
|
|
3059
|
+
exported: node.exported,
|
|
3060
|
+
startLine: node.startLine,
|
|
3061
|
+
endLine: node.endLine
|
|
3062
|
+
};
|
|
3063
|
+
if (node.signature !== void 0) {
|
|
3064
|
+
codeNode.signature = node.signature;
|
|
3065
|
+
}
|
|
3066
|
+
graph.addNodes([codeNode], node.file);
|
|
3067
|
+
}
|
|
3068
|
+
for (const edge of serialized.edges) {
|
|
3069
|
+
const edgeType = this.validateEdgeType(edge.type);
|
|
3070
|
+
if (!edgeType) continue;
|
|
3071
|
+
graph.addEdge({
|
|
3072
|
+
from: edge.from,
|
|
3073
|
+
to: edge.to,
|
|
3074
|
+
type: edgeType,
|
|
3075
|
+
confidence: edge.confidence
|
|
3076
|
+
});
|
|
3077
|
+
}
|
|
3078
|
+
this.graphCache.set(storeId, graph);
|
|
3079
|
+
return graph;
|
|
3080
|
+
} catch {
|
|
3081
|
+
return void 0;
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
/**
|
|
3085
|
+
* Get usage stats for a code element.
|
|
3086
|
+
*/
|
|
3087
|
+
getUsageStats(graph, filePath, symbolName) {
|
|
3088
|
+
const nodeId = `${filePath}:${symbolName}`;
|
|
3089
|
+
return {
|
|
3090
|
+
calledBy: graph.getCalledByCount(nodeId),
|
|
3091
|
+
calls: graph.getCallsCount(nodeId)
|
|
3092
|
+
};
|
|
3093
|
+
}
|
|
3094
|
+
/**
|
|
3095
|
+
* Get related code (callers and callees) for a code element.
|
|
3096
|
+
*/
|
|
3097
|
+
getRelatedCode(graph, filePath, symbolName) {
|
|
3098
|
+
const nodeId = `${filePath}:${symbolName}`;
|
|
3099
|
+
const related = [];
|
|
3100
|
+
const incoming = graph.getIncomingEdges(nodeId);
|
|
3101
|
+
for (const edge of incoming) {
|
|
3102
|
+
if (edge.type === "calls") {
|
|
3103
|
+
related.push({ id: edge.from, relationship: "calls this" });
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
const outgoing = graph.getEdges(nodeId);
|
|
3107
|
+
for (const edge of outgoing) {
|
|
3108
|
+
if (edge.type === "calls") {
|
|
3109
|
+
related.push({ id: edge.to, relationship: "called by this" });
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
return related;
|
|
3113
|
+
}
|
|
3114
|
+
/**
|
|
3115
|
+
* Clear cached graphs.
|
|
3116
|
+
*/
|
|
3117
|
+
clearCache() {
|
|
3118
|
+
this.graphCache.clear();
|
|
3119
|
+
}
|
|
3120
|
+
getGraphPath(storeId) {
|
|
3121
|
+
return join4(this.dataDir, "graphs", `${storeId}.json`);
|
|
3122
|
+
}
|
|
3123
|
+
/**
|
|
3124
|
+
* Type guard for SerializedGraph structure.
|
|
3125
|
+
*/
|
|
3126
|
+
isSerializedGraph(value) {
|
|
3127
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3128
|
+
if (!("nodes" in value) || !("edges" in value)) return false;
|
|
3129
|
+
const obj = value;
|
|
3130
|
+
return Array.isArray(obj.nodes) && Array.isArray(obj.edges);
|
|
3131
|
+
}
|
|
3132
|
+
/**
|
|
3133
|
+
* Type guard for valid node types.
|
|
3134
|
+
*/
|
|
3135
|
+
isValidNodeType(type) {
|
|
3136
|
+
return ["function", "class", "interface", "type", "const", "method"].includes(type);
|
|
3137
|
+
}
|
|
3138
|
+
/**
|
|
3139
|
+
* Validate and return a node type, or undefined if invalid.
|
|
3140
|
+
*/
|
|
3141
|
+
validateNodeType(type) {
|
|
3142
|
+
if (this.isValidNodeType(type)) {
|
|
3143
|
+
return type;
|
|
3144
|
+
}
|
|
3145
|
+
return void 0;
|
|
3146
|
+
}
|
|
3147
|
+
/**
|
|
3148
|
+
* Type guard for valid edge types.
|
|
3149
|
+
*/
|
|
3150
|
+
isValidEdgeType(type) {
|
|
3151
|
+
return ["calls", "imports", "extends", "implements"].includes(type);
|
|
3152
|
+
}
|
|
3153
|
+
/**
|
|
3154
|
+
* Validate and return an edge type, or undefined if invalid.
|
|
3155
|
+
*/
|
|
3156
|
+
validateEdgeType(type) {
|
|
3157
|
+
if (this.isValidEdgeType(type)) {
|
|
3158
|
+
return type;
|
|
3159
|
+
}
|
|
3160
|
+
return void 0;
|
|
3161
|
+
}
|
|
3162
|
+
};
|
|
3163
|
+
|
|
3164
|
+
// src/db/lance.ts
|
|
3165
|
+
import * as lancedb from "@lancedb/lancedb";
|
|
3166
|
+
var LanceStore = class {
|
|
3167
|
+
connection = null;
|
|
3168
|
+
tables = /* @__PURE__ */ new Map();
|
|
3169
|
+
dataDir;
|
|
3170
|
+
constructor(dataDir) {
|
|
3171
|
+
this.dataDir = dataDir;
|
|
3172
|
+
}
|
|
3173
|
+
async initialize(storeId) {
|
|
3174
|
+
if (this.connection === null) {
|
|
3175
|
+
this.connection = await lancedb.connect(this.dataDir);
|
|
3176
|
+
}
|
|
3177
|
+
const tableName = this.getTableName(storeId);
|
|
3178
|
+
const tableNames = await this.connection.tableNames();
|
|
3179
|
+
if (!tableNames.includes(tableName)) {
|
|
3180
|
+
const table = await this.connection.createTable(tableName, [
|
|
3181
|
+
{
|
|
3182
|
+
id: "__init__",
|
|
3183
|
+
content: "",
|
|
3184
|
+
vector: new Array(384).fill(0),
|
|
3185
|
+
metadata: "{}"
|
|
3186
|
+
}
|
|
3187
|
+
]);
|
|
3188
|
+
await table.delete('id = "__init__"');
|
|
3189
|
+
this.tables.set(tableName, table);
|
|
3190
|
+
} else {
|
|
3191
|
+
const table = await this.connection.openTable(tableName);
|
|
3192
|
+
this.tables.set(tableName, table);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
async addDocuments(storeId, documents) {
|
|
3196
|
+
const table = await this.getTable(storeId);
|
|
3197
|
+
const lanceDocuments = documents.map((doc) => ({
|
|
3198
|
+
id: doc.id,
|
|
3199
|
+
content: doc.content,
|
|
3200
|
+
vector: [...doc.vector],
|
|
3201
|
+
metadata: JSON.stringify(doc.metadata)
|
|
3202
|
+
}));
|
|
3203
|
+
await table.add(lanceDocuments);
|
|
3204
|
+
}
|
|
3205
|
+
async deleteDocuments(storeId, documentIds) {
|
|
3206
|
+
const table = await this.getTable(storeId);
|
|
3207
|
+
const idList = documentIds.map((id) => `"${id}"`).join(", ");
|
|
3208
|
+
await table.delete(`id IN (${idList})`);
|
|
3209
|
+
}
|
|
3210
|
+
async search(storeId, vector, limit, threshold) {
|
|
3211
|
+
const table = await this.getTable(storeId);
|
|
3212
|
+
let query = table.vectorSearch(vector).limit(limit);
|
|
3213
|
+
if (threshold !== void 0) {
|
|
3214
|
+
query = query.distanceType("cosine");
|
|
3215
|
+
}
|
|
3216
|
+
const results = await query.toArray();
|
|
3217
|
+
return results.filter((r) => {
|
|
3218
|
+
if (threshold === void 0) return true;
|
|
3219
|
+
const score = 1 - r._distance;
|
|
3220
|
+
return score >= threshold;
|
|
3221
|
+
}).map((r) => ({
|
|
3222
|
+
id: createDocumentId(r.id),
|
|
3223
|
+
content: r.content,
|
|
3224
|
+
score: 1 - r._distance,
|
|
3225
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
3226
|
+
metadata: JSON.parse(r.metadata)
|
|
3227
|
+
}));
|
|
3228
|
+
}
|
|
3229
|
+
async createFtsIndex(storeId) {
|
|
3230
|
+
const table = await this.getTable(storeId);
|
|
3231
|
+
await table.createIndex("content", {
|
|
3232
|
+
config: lancedb.Index.fts()
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
3235
|
+
async fullTextSearch(storeId, query, limit) {
|
|
3236
|
+
const table = await this.getTable(storeId);
|
|
3237
|
+
try {
|
|
3238
|
+
const results = await table.search(query, "fts").limit(limit).toArray();
|
|
3239
|
+
return results.map((r) => ({
|
|
3240
|
+
id: createDocumentId(r.id),
|
|
3241
|
+
content: r.content,
|
|
3242
|
+
score: r.score,
|
|
3243
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
3244
|
+
metadata: JSON.parse(r.metadata)
|
|
3245
|
+
}));
|
|
3246
|
+
} catch {
|
|
3247
|
+
return [];
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
async deleteStore(storeId) {
|
|
3251
|
+
const tableName = this.getTableName(storeId);
|
|
3252
|
+
if (this.connection !== null) {
|
|
3253
|
+
await this.connection.dropTable(tableName);
|
|
3254
|
+
this.tables.delete(tableName);
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
getTableName(storeId) {
|
|
3258
|
+
return `documents_${storeId}`;
|
|
3259
|
+
}
|
|
3260
|
+
async getTable(storeId) {
|
|
3261
|
+
const tableName = this.getTableName(storeId);
|
|
3262
|
+
let table = this.tables.get(tableName);
|
|
3263
|
+
if (table === void 0) {
|
|
3264
|
+
await this.initialize(storeId);
|
|
3265
|
+
table = this.tables.get(tableName);
|
|
3266
|
+
}
|
|
3267
|
+
if (table === void 0) {
|
|
3268
|
+
throw new Error(`Table not found for store: ${storeId}`);
|
|
3269
|
+
}
|
|
3270
|
+
return table;
|
|
3271
|
+
}
|
|
3272
|
+
};
|
|
3273
|
+
|
|
3274
|
+
// src/db/embeddings.ts
|
|
3275
|
+
import { pipeline, env } from "@huggingface/transformers";
|
|
3276
|
+
import { homedir as homedir2 } from "os";
|
|
3277
|
+
import { join as join5 } from "path";
|
|
3278
|
+
env.cacheDir = join5(homedir2(), ".cache", "huggingface-transformers");
|
|
3279
|
+
var EmbeddingEngine = class {
|
|
3280
|
+
extractor = null;
|
|
3281
|
+
modelName;
|
|
3282
|
+
dimensions;
|
|
3283
|
+
constructor(modelName = "Xenova/all-MiniLM-L6-v2", dimensions = 384) {
|
|
3284
|
+
this.modelName = modelName;
|
|
3285
|
+
this.dimensions = dimensions;
|
|
3286
|
+
}
|
|
3287
|
+
async initialize() {
|
|
3288
|
+
if (this.extractor !== null) return;
|
|
3289
|
+
this.extractor = await pipeline("feature-extraction", this.modelName, {
|
|
3290
|
+
dtype: "fp32"
|
|
3291
|
+
});
|
|
3292
|
+
}
|
|
3293
|
+
async embed(text) {
|
|
3294
|
+
if (this.extractor === null) {
|
|
3295
|
+
await this.initialize();
|
|
3296
|
+
}
|
|
3297
|
+
if (this.extractor === null) {
|
|
3298
|
+
throw new Error("Failed to initialize embedding model");
|
|
3299
|
+
}
|
|
3300
|
+
const output = await this.extractor(text, {
|
|
3301
|
+
pooling: "mean",
|
|
3302
|
+
normalize: true
|
|
3303
|
+
});
|
|
3304
|
+
const result = Array.from(output.data);
|
|
3305
|
+
return result.map((v) => Number(v));
|
|
3306
|
+
}
|
|
3307
|
+
async embedBatch(texts) {
|
|
3308
|
+
const BATCH_SIZE = 32;
|
|
3309
|
+
const results = [];
|
|
3310
|
+
for (let i = 0; i < texts.length; i += BATCH_SIZE) {
|
|
3311
|
+
const batch = texts.slice(i, i + BATCH_SIZE);
|
|
3312
|
+
const batchResults = await Promise.all(
|
|
3313
|
+
batch.map((text) => this.embed(text))
|
|
3314
|
+
);
|
|
3315
|
+
results.push(...batchResults);
|
|
3316
|
+
if (i + BATCH_SIZE < texts.length) {
|
|
3317
|
+
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
return results;
|
|
3321
|
+
}
|
|
3322
|
+
getDimensions() {
|
|
3323
|
+
return this.dimensions;
|
|
3324
|
+
}
|
|
3325
|
+
};
|
|
3326
|
+
|
|
3327
|
+
// src/crawl/bridge.ts
|
|
3328
|
+
import { spawn as spawn2 } from "child_process";
|
|
3329
|
+
import { createInterface } from "readline";
|
|
3330
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
3331
|
+
import { ZodError } from "zod";
|
|
3332
|
+
|
|
3333
|
+
// src/crawl/schemas.ts
|
|
3334
|
+
import { z } from "zod";
|
|
3335
|
+
var CrawledLinkSchema = z.object({
|
|
3336
|
+
href: z.string(),
|
|
3337
|
+
text: z.string(),
|
|
3338
|
+
title: z.string().optional(),
|
|
3339
|
+
base_domain: z.string().optional(),
|
|
3340
|
+
head_data: z.unknown().optional(),
|
|
3341
|
+
head_extraction_status: z.unknown().optional(),
|
|
3342
|
+
head_extraction_error: z.unknown().optional(),
|
|
3343
|
+
intrinsic_score: z.number().optional(),
|
|
3344
|
+
contextual_score: z.unknown().optional(),
|
|
3345
|
+
total_score: z.unknown().optional()
|
|
3346
|
+
});
|
|
3347
|
+
var CrawlPageSchema = z.object({
|
|
3348
|
+
url: z.string(),
|
|
3349
|
+
title: z.string(),
|
|
3350
|
+
content: z.string(),
|
|
3351
|
+
links: z.array(z.string()),
|
|
3352
|
+
crawledAt: z.string()
|
|
3353
|
+
});
|
|
3354
|
+
var CrawlResultSchema = z.object({
|
|
3355
|
+
pages: z.array(CrawlPageSchema)
|
|
3356
|
+
});
|
|
3357
|
+
var HeadlessResultSchema = z.object({
|
|
3358
|
+
html: z.string(),
|
|
3359
|
+
markdown: z.string(),
|
|
3360
|
+
links: z.array(z.union([CrawledLinkSchema, z.string()]))
|
|
3361
|
+
});
|
|
3362
|
+
function validateHeadlessResult(data) {
|
|
3363
|
+
return HeadlessResultSchema.parse(data);
|
|
3364
|
+
}
|
|
3365
|
+
function validateCrawlResult(data) {
|
|
3366
|
+
return CrawlResultSchema.parse(data);
|
|
3367
|
+
}
|
|
3368
|
+
var MethodInfoSchema = z.object({
|
|
3369
|
+
name: z.string(),
|
|
3370
|
+
async: z.boolean(),
|
|
3371
|
+
signature: z.string(),
|
|
3372
|
+
startLine: z.number(),
|
|
3373
|
+
endLine: z.number(),
|
|
3374
|
+
calls: z.array(z.string())
|
|
3375
|
+
});
|
|
3376
|
+
var CodeNodeSchema = z.object({
|
|
3377
|
+
type: z.enum(["function", "class"]),
|
|
3378
|
+
name: z.string(),
|
|
3379
|
+
exported: z.boolean(),
|
|
3380
|
+
startLine: z.number(),
|
|
3381
|
+
endLine: z.number(),
|
|
3382
|
+
async: z.boolean().optional(),
|
|
3383
|
+
signature: z.string().optional(),
|
|
3384
|
+
calls: z.array(z.string()).optional(),
|
|
3385
|
+
methods: z.array(MethodInfoSchema).optional()
|
|
3386
|
+
});
|
|
3387
|
+
var ImportInfoSchema = z.object({
|
|
3388
|
+
source: z.string(),
|
|
3389
|
+
imported: z.string(),
|
|
3390
|
+
alias: z.string().optional().nullable()
|
|
3391
|
+
});
|
|
3392
|
+
var ParsePythonResultSchema = z.object({
|
|
3393
|
+
nodes: z.array(CodeNodeSchema),
|
|
3394
|
+
imports: z.array(ImportInfoSchema)
|
|
3395
|
+
});
|
|
3396
|
+
function validateParsePythonResult(data) {
|
|
3397
|
+
return ParsePythonResultSchema.parse(data);
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
// src/crawl/bridge.ts
|
|
3401
|
+
var PythonBridge = class {
|
|
3402
|
+
process = null;
|
|
3403
|
+
pending = /* @__PURE__ */ new Map();
|
|
3404
|
+
stoppingIntentionally = false;
|
|
3405
|
+
start() {
|
|
3406
|
+
if (this.process) return Promise.resolve();
|
|
3407
|
+
this.process = spawn2("python3", ["python/crawl_worker.py"], {
|
|
3408
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3409
|
+
});
|
|
3410
|
+
this.process.on("error", (err2) => {
|
|
3411
|
+
console.error("Python bridge process error:", err2);
|
|
3412
|
+
this.rejectAllPending(new Error(`Process error: ${err2.message}`));
|
|
3413
|
+
});
|
|
3414
|
+
this.process.on("exit", (code, signal) => {
|
|
3415
|
+
if (code !== 0 && code !== null) {
|
|
3416
|
+
console.error(`Python bridge process exited with code ${String(code)}`);
|
|
3417
|
+
this.rejectAllPending(new Error(`Process exited with code ${String(code)}`));
|
|
3418
|
+
} else if (signal && !this.stoppingIntentionally) {
|
|
3419
|
+
console.error(`Python bridge process killed with signal ${signal}`);
|
|
3420
|
+
this.rejectAllPending(new Error(`Process killed with signal ${signal}`));
|
|
3421
|
+
}
|
|
3422
|
+
this.process = null;
|
|
3423
|
+
this.stoppingIntentionally = false;
|
|
3424
|
+
});
|
|
3425
|
+
if (this.process.stderr) {
|
|
3426
|
+
const stderrRl = createInterface({ input: this.process.stderr });
|
|
3427
|
+
stderrRl.on("line", (line) => {
|
|
3428
|
+
console.error("Python bridge stderr:", line);
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
if (this.process.stdout === null) {
|
|
3432
|
+
this.process = null;
|
|
3433
|
+
return Promise.reject(new Error("Python bridge process stdout is null"));
|
|
3434
|
+
}
|
|
3435
|
+
const rl = createInterface({ input: this.process.stdout });
|
|
3436
|
+
rl.on("line", (line) => {
|
|
3437
|
+
if (!line.trim().startsWith("{")) {
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
try {
|
|
3441
|
+
const response = JSON.parse(line);
|
|
3442
|
+
const pending = this.pending.get(response.id);
|
|
3443
|
+
if (pending !== void 0) {
|
|
3444
|
+
if (response.error !== void 0) {
|
|
3445
|
+
clearTimeout(pending.timeout);
|
|
3446
|
+
this.pending.delete(response.id);
|
|
3447
|
+
pending.reject(new Error(response.error.message));
|
|
3448
|
+
} else if (response.result !== void 0) {
|
|
3449
|
+
clearTimeout(pending.timeout);
|
|
3450
|
+
this.pending.delete(response.id);
|
|
3451
|
+
try {
|
|
3452
|
+
let validated;
|
|
3453
|
+
if (pending.method === "crawl") {
|
|
3454
|
+
validated = validateCrawlResult(response.result);
|
|
3455
|
+
} else if (pending.method === "fetch_headless") {
|
|
3456
|
+
validated = validateHeadlessResult(response.result);
|
|
3457
|
+
} else {
|
|
3458
|
+
validated = validateParsePythonResult(response.result);
|
|
3459
|
+
}
|
|
3460
|
+
pending.resolve(validated);
|
|
3461
|
+
} catch (error) {
|
|
3462
|
+
if (error instanceof ZodError) {
|
|
3463
|
+
console.error("Python bridge response validation failed:", error.issues);
|
|
3464
|
+
console.error("Original response:", JSON.stringify(response.result));
|
|
3465
|
+
pending.reject(new Error(`Invalid response format from Python bridge: ${error.message}`));
|
|
3466
|
+
} else {
|
|
3467
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3468
|
+
pending.reject(new Error(`Response validation error: ${errorMessage}`));
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
} catch (err2) {
|
|
3474
|
+
console.error("Failed to parse JSON response from Python bridge:", err2, "Line:", line);
|
|
3475
|
+
}
|
|
3476
|
+
});
|
|
3477
|
+
return Promise.resolve();
|
|
3478
|
+
}
|
|
3479
|
+
async crawl(url, timeoutMs = 3e4) {
|
|
3480
|
+
if (!this.process) await this.start();
|
|
3481
|
+
const id = randomUUID3();
|
|
3482
|
+
const request = {
|
|
3483
|
+
jsonrpc: "2.0",
|
|
3484
|
+
id,
|
|
3485
|
+
method: "crawl",
|
|
3486
|
+
params: { url }
|
|
3487
|
+
};
|
|
3488
|
+
return new Promise((resolve3, reject) => {
|
|
3489
|
+
const timeout = setTimeout(() => {
|
|
3490
|
+
const pending = this.pending.get(id);
|
|
3491
|
+
if (pending) {
|
|
3492
|
+
this.pending.delete(id);
|
|
3493
|
+
reject(new Error(`Crawl timeout after ${String(timeoutMs)}ms for URL: ${url}`));
|
|
3494
|
+
}
|
|
3495
|
+
}, timeoutMs);
|
|
3496
|
+
this.pending.set(id, { resolve: resolve3, reject, timeout, method: "crawl" });
|
|
3497
|
+
if (this.process === null || this.process.stdin === null) {
|
|
3498
|
+
reject(new Error("Python bridge process not available"));
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
this.process.stdin.write(JSON.stringify(request) + "\n");
|
|
3502
|
+
});
|
|
3503
|
+
}
|
|
3504
|
+
async fetchHeadless(url, timeoutMs = 6e4) {
|
|
3505
|
+
if (!this.process) await this.start();
|
|
3506
|
+
const id = randomUUID3();
|
|
3507
|
+
const request = {
|
|
3508
|
+
jsonrpc: "2.0",
|
|
3509
|
+
id,
|
|
3510
|
+
method: "fetch_headless",
|
|
3511
|
+
params: { url }
|
|
3512
|
+
};
|
|
3513
|
+
return new Promise((resolve3, reject) => {
|
|
3514
|
+
const timeout = setTimeout(() => {
|
|
3515
|
+
const pending = this.pending.get(id);
|
|
3516
|
+
if (pending) {
|
|
3517
|
+
this.pending.delete(id);
|
|
3518
|
+
reject(new Error(`Headless fetch timeout after ${String(timeoutMs)}ms for URL: ${url}`));
|
|
3519
|
+
}
|
|
3520
|
+
}, timeoutMs);
|
|
3521
|
+
this.pending.set(id, { resolve: resolve3, reject, timeout, method: "fetch_headless" });
|
|
3522
|
+
if (this.process === null || this.process.stdin === null) {
|
|
3523
|
+
reject(new Error("Python bridge process not available"));
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
this.process.stdin.write(JSON.stringify(request) + "\n");
|
|
3527
|
+
});
|
|
3528
|
+
}
|
|
3529
|
+
async parsePython(code, filePath, timeoutMs = 1e4) {
|
|
3530
|
+
if (!this.process) await this.start();
|
|
3531
|
+
const id = randomUUID3();
|
|
3532
|
+
const request = {
|
|
3533
|
+
jsonrpc: "2.0",
|
|
3534
|
+
id,
|
|
3535
|
+
method: "parse_python",
|
|
3536
|
+
params: { code, filePath }
|
|
3537
|
+
};
|
|
3538
|
+
return new Promise((resolve3, reject) => {
|
|
3539
|
+
const timeout = setTimeout(() => {
|
|
3540
|
+
const pending = this.pending.get(id);
|
|
3541
|
+
if (pending) {
|
|
3542
|
+
this.pending.delete(id);
|
|
3543
|
+
reject(new Error(`Python parsing timeout after ${String(timeoutMs)}ms for file: ${filePath}`));
|
|
3544
|
+
}
|
|
3545
|
+
}, timeoutMs);
|
|
3546
|
+
this.pending.set(id, { resolve: resolve3, reject, timeout, method: "parse_python" });
|
|
3547
|
+
if (this.process === null || this.process.stdin === null) {
|
|
3548
|
+
reject(new Error("Python bridge process not available"));
|
|
3549
|
+
return;
|
|
3550
|
+
}
|
|
3551
|
+
this.process.stdin.write(JSON.stringify(request) + "\n");
|
|
3552
|
+
});
|
|
3553
|
+
}
|
|
3554
|
+
stop() {
|
|
3555
|
+
if (this.process) {
|
|
3556
|
+
this.stoppingIntentionally = true;
|
|
3557
|
+
this.rejectAllPending(new Error("Python bridge stopped"));
|
|
3558
|
+
this.process.kill();
|
|
3559
|
+
this.process = null;
|
|
3560
|
+
}
|
|
3561
|
+
return Promise.resolve();
|
|
3562
|
+
}
|
|
3563
|
+
rejectAllPending(error) {
|
|
3564
|
+
for (const pending of this.pending.values()) {
|
|
3565
|
+
clearTimeout(pending.timeout);
|
|
3566
|
+
pending.reject(error);
|
|
3567
|
+
}
|
|
3568
|
+
this.pending.clear();
|
|
3569
|
+
}
|
|
3570
|
+
};
|
|
3571
|
+
|
|
3572
|
+
// src/services/index.ts
|
|
3573
|
+
async function createServices(configPath, dataDir, projectRoot) {
|
|
3574
|
+
const config = new ConfigService(configPath, dataDir, projectRoot);
|
|
3575
|
+
const appConfig = await config.load();
|
|
3576
|
+
const resolvedDataDir = config.resolveDataDir();
|
|
3577
|
+
const lance = new LanceStore(resolvedDataDir);
|
|
3578
|
+
const embeddings = new EmbeddingEngine(
|
|
3579
|
+
appConfig.embedding.model,
|
|
3580
|
+
appConfig.embedding.dimensions
|
|
3581
|
+
);
|
|
3582
|
+
await embeddings.initialize();
|
|
3583
|
+
const store = new StoreService(resolvedDataDir);
|
|
3584
|
+
await store.initialize();
|
|
3585
|
+
const pythonBridge = new PythonBridge();
|
|
3586
|
+
await pythonBridge.start();
|
|
3587
|
+
const codeGraph = new CodeGraphService(resolvedDataDir, pythonBridge);
|
|
3588
|
+
const search = new SearchService(lance, embeddings, void 0, codeGraph);
|
|
3589
|
+
const index = new IndexService(lance, embeddings, { codeGraphService: codeGraph });
|
|
3590
|
+
return {
|
|
3591
|
+
config,
|
|
3592
|
+
store,
|
|
3593
|
+
search,
|
|
3594
|
+
index,
|
|
3595
|
+
lance,
|
|
3596
|
+
embeddings,
|
|
3597
|
+
codeGraph,
|
|
3598
|
+
pythonBridge
|
|
3599
|
+
};
|
|
3600
|
+
}
|
|
3601
|
+
async function destroyServices(services) {
|
|
3602
|
+
await services.pythonBridge.stop();
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
export {
|
|
3606
|
+
createStoreId,
|
|
3607
|
+
createDocumentId,
|
|
3608
|
+
ok,
|
|
3609
|
+
err,
|
|
3610
|
+
extractRepoName,
|
|
3611
|
+
ASTParser,
|
|
3612
|
+
PythonBridge,
|
|
3613
|
+
JobService,
|
|
3614
|
+
createServices,
|
|
3615
|
+
destroyServices
|
|
3616
|
+
};
|
|
3617
|
+
//# sourceMappingURL=chunk-5QMHZUC4.js.map
|