forge-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/worktree-create.sh +64 -0
- package/.claude/hooks/worktree-remove.sh +57 -0
- package/.claude/settings.local.json +29 -0
- package/.forge/knowledge/conventions.yaml +1 -0
- package/.forge/knowledge/decisions.yaml +1 -0
- package/.forge/knowledge/gotchas.yaml +1 -0
- package/.forge/knowledge/patterns.yaml +1 -0
- package/.forge/manifest.yaml +6 -0
- package/CLAUDE.md +144 -0
- package/bin/setup-forge.sh +132 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +553 -0
- package/dist/cli.js.map +1 -0
- package/dist/context/codebase.d.ts +57 -0
- package/dist/context/codebase.d.ts.map +1 -0
- package/dist/context/codebase.js +301 -0
- package/dist/context/codebase.js.map +1 -0
- package/dist/context/injector.d.ts +147 -0
- package/dist/context/injector.d.ts.map +1 -0
- package/dist/context/injector.js +533 -0
- package/dist/context/injector.js.map +1 -0
- package/dist/context/memory.d.ts +32 -0
- package/dist/context/memory.d.ts.map +1 -0
- package/dist/context/memory.js +140 -0
- package/dist/context/memory.js.map +1 -0
- package/dist/context/session-index.d.ts +54 -0
- package/dist/context/session-index.d.ts.map +1 -0
- package/dist/context/session-index.js +265 -0
- package/dist/context/session-index.js.map +1 -0
- package/dist/context/session.d.ts +42 -0
- package/dist/context/session.d.ts.map +1 -0
- package/dist/context/session.js +121 -0
- package/dist/context/session.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/ingestion/chunker.d.ts +19 -0
- package/dist/ingestion/chunker.d.ts.map +1 -0
- package/dist/ingestion/chunker.js +189 -0
- package/dist/ingestion/chunker.js.map +1 -0
- package/dist/ingestion/embedder.d.ts +45 -0
- package/dist/ingestion/embedder.d.ts.map +1 -0
- package/dist/ingestion/embedder.js +152 -0
- package/dist/ingestion/embedder.js.map +1 -0
- package/dist/ingestion/git-analyzer.d.ts +77 -0
- package/dist/ingestion/git-analyzer.d.ts.map +1 -0
- package/dist/ingestion/git-analyzer.js +437 -0
- package/dist/ingestion/git-analyzer.js.map +1 -0
- package/dist/ingestion/indexer.d.ts +79 -0
- package/dist/ingestion/indexer.d.ts.map +1 -0
- package/dist/ingestion/indexer.js +766 -0
- package/dist/ingestion/indexer.js.map +1 -0
- package/dist/ingestion/markdown-chunker.d.ts +19 -0
- package/dist/ingestion/markdown-chunker.d.ts.map +1 -0
- package/dist/ingestion/markdown-chunker.js +243 -0
- package/dist/ingestion/markdown-chunker.js.map +1 -0
- package/dist/ingestion/markdown-knowledge.d.ts +21 -0
- package/dist/ingestion/markdown-knowledge.d.ts.map +1 -0
- package/dist/ingestion/markdown-knowledge.js +129 -0
- package/dist/ingestion/markdown-knowledge.js.map +1 -0
- package/dist/ingestion/parser.d.ts +20 -0
- package/dist/ingestion/parser.d.ts.map +1 -0
- package/dist/ingestion/parser.js +429 -0
- package/dist/ingestion/parser.js.map +1 -0
- package/dist/ingestion/watcher.d.ts +28 -0
- package/dist/ingestion/watcher.d.ts.map +1 -0
- package/dist/ingestion/watcher.js +147 -0
- package/dist/ingestion/watcher.js.map +1 -0
- package/dist/knowledge/hydrator.d.ts +37 -0
- package/dist/knowledge/hydrator.d.ts.map +1 -0
- package/dist/knowledge/hydrator.js +220 -0
- package/dist/knowledge/hydrator.js.map +1 -0
- package/dist/knowledge/registry.d.ts +129 -0
- package/dist/knowledge/registry.d.ts.map +1 -0
- package/dist/knowledge/registry.js +361 -0
- package/dist/knowledge/registry.js.map +1 -0
- package/dist/knowledge/search.d.ts +114 -0
- package/dist/knowledge/search.d.ts.map +1 -0
- package/dist/knowledge/search.js +428 -0
- package/dist/knowledge/search.js.map +1 -0
- package/dist/knowledge/store.d.ts +76 -0
- package/dist/knowledge/store.d.ts.map +1 -0
- package/dist/knowledge/store.js +230 -0
- package/dist/knowledge/store.js.map +1 -0
- package/dist/learning/confidence.d.ts +30 -0
- package/dist/learning/confidence.d.ts.map +1 -0
- package/dist/learning/confidence.js +165 -0
- package/dist/learning/confidence.js.map +1 -0
- package/dist/learning/patterns.d.ts +52 -0
- package/dist/learning/patterns.d.ts.map +1 -0
- package/dist/learning/patterns.js +290 -0
- package/dist/learning/patterns.js.map +1 -0
- package/dist/learning/trajectory.d.ts +55 -0
- package/dist/learning/trajectory.d.ts.map +1 -0
- package/dist/learning/trajectory.js +200 -0
- package/dist/learning/trajectory.js.map +1 -0
- package/dist/memory/memory-compat.d.ts +100 -0
- package/dist/memory/memory-compat.d.ts.map +1 -0
- package/dist/memory/memory-compat.js +146 -0
- package/dist/memory/memory-compat.js.map +1 -0
- package/dist/memory/observation-store.d.ts +57 -0
- package/dist/memory/observation-store.d.ts.map +1 -0
- package/dist/memory/observation-store.js +154 -0
- package/dist/memory/observation-store.js.map +1 -0
- package/dist/memory/session-tracker.d.ts +81 -0
- package/dist/memory/session-tracker.d.ts.map +1 -0
- package/dist/memory/session-tracker.js +262 -0
- package/dist/memory/session-tracker.js.map +1 -0
- package/dist/pipeline/engine.d.ts +179 -0
- package/dist/pipeline/engine.d.ts.map +1 -0
- package/dist/pipeline/engine.js +691 -0
- package/dist/pipeline/engine.js.map +1 -0
- package/dist/pipeline/events.d.ts +54 -0
- package/dist/pipeline/events.d.ts.map +1 -0
- package/dist/pipeline/events.js +157 -0
- package/dist/pipeline/events.js.map +1 -0
- package/dist/pipeline/parallel.d.ts +83 -0
- package/dist/pipeline/parallel.d.ts.map +1 -0
- package/dist/pipeline/parallel.js +277 -0
- package/dist/pipeline/parallel.js.map +1 -0
- package/dist/pipeline/state-machine.d.ts +65 -0
- package/dist/pipeline/state-machine.d.ts.map +1 -0
- package/dist/pipeline/state-machine.js +176 -0
- package/dist/pipeline/state-machine.js.map +1 -0
- package/dist/query/graph-queries.d.ts +84 -0
- package/dist/query/graph-queries.d.ts.map +1 -0
- package/dist/query/graph-queries.js +216 -0
- package/dist/query/graph-queries.js.map +1 -0
- package/dist/query/hybrid-search.d.ts +34 -0
- package/dist/query/hybrid-search.d.ts.map +1 -0
- package/dist/query/hybrid-search.js +263 -0
- package/dist/query/hybrid-search.js.map +1 -0
- package/dist/query/intent-detector.d.ts +35 -0
- package/dist/query/intent-detector.d.ts.map +1 -0
- package/dist/query/intent-detector.js +115 -0
- package/dist/query/intent-detector.js.map +1 -0
- package/dist/query/ranking.d.ts +57 -0
- package/dist/query/ranking.d.ts.map +1 -0
- package/dist/query/ranking.js +109 -0
- package/dist/query/ranking.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +291 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/falkordb-store.d.ts +73 -0
- package/dist/storage/falkordb-store.d.ts.map +1 -0
- package/dist/storage/falkordb-store.js +346 -0
- package/dist/storage/falkordb-store.js.map +1 -0
- package/dist/storage/file-cache.d.ts +32 -0
- package/dist/storage/file-cache.d.ts.map +1 -0
- package/dist/storage/file-cache.js +115 -0
- package/dist/storage/file-cache.js.map +1 -0
- package/dist/storage/interfaces.d.ts +151 -0
- package/dist/storage/interfaces.d.ts.map +1 -0
- package/dist/storage/interfaces.js +7 -0
- package/dist/storage/interfaces.js.map +1 -0
- package/dist/storage/qdrant-store.d.ts +110 -0
- package/dist/storage/qdrant-store.d.ts.map +1 -0
- package/dist/storage/qdrant-store.js +467 -0
- package/dist/storage/qdrant-store.js.map +1 -0
- package/dist/storage/schema.d.ts +4 -0
- package/dist/storage/schema.d.ts.map +1 -0
- package/dist/storage/schema.js +136 -0
- package/dist/storage/schema.js.map +1 -0
- package/dist/storage/sqlite.d.ts +35 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +132 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/tools/collaboration-tools.d.ts +111 -0
- package/dist/tools/collaboration-tools.d.ts.map +1 -0
- package/dist/tools/collaboration-tools.js +174 -0
- package/dist/tools/collaboration-tools.js.map +1 -0
- package/dist/tools/context-tools.d.ts +293 -0
- package/dist/tools/context-tools.d.ts.map +1 -0
- package/dist/tools/context-tools.js +437 -0
- package/dist/tools/context-tools.js.map +1 -0
- package/dist/tools/graph-tools.d.ts +129 -0
- package/dist/tools/graph-tools.d.ts.map +1 -0
- package/dist/tools/graph-tools.js +237 -0
- package/dist/tools/graph-tools.js.map +1 -0
- package/dist/tools/ingestion-tools.d.ts +96 -0
- package/dist/tools/ingestion-tools.d.ts.map +1 -0
- package/dist/tools/ingestion-tools.js +90 -0
- package/dist/tools/ingestion-tools.js.map +1 -0
- package/dist/tools/learning-tools.d.ts +168 -0
- package/dist/tools/learning-tools.d.ts.map +1 -0
- package/dist/tools/learning-tools.js +158 -0
- package/dist/tools/learning-tools.js.map +1 -0
- package/dist/tools/memory-tools.d.ts +183 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +197 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/tools/phase-tools.d.ts +954 -0
- package/dist/tools/phase-tools.d.ts.map +1 -0
- package/dist/tools/phase-tools.js +1215 -0
- package/dist/tools/phase-tools.js.map +1 -0
- package/dist/tools/pipeline-tools.d.ts +140 -0
- package/dist/tools/pipeline-tools.d.ts.map +1 -0
- package/dist/tools/pipeline-tools.js +162 -0
- package/dist/tools/pipeline-tools.js.map +1 -0
- package/dist/tools/registration-tools.d.ts +220 -0
- package/dist/tools/registration-tools.d.ts.map +1 -0
- package/dist/tools/registration-tools.js +391 -0
- package/dist/tools/registration-tools.js.map +1 -0
- package/dist/util/circuit-breaker.d.ts +75 -0
- package/dist/util/circuit-breaker.d.ts.map +1 -0
- package/dist/util/circuit-breaker.js +159 -0
- package/dist/util/circuit-breaker.js.map +1 -0
- package/dist/util/config.d.ts +23 -0
- package/dist/util/config.d.ts.map +1 -0
- package/dist/util/config.js +164 -0
- package/dist/util/config.js.map +1 -0
- package/dist/util/logger.d.ts +13 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +45 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/token-counter.d.ts +24 -0
- package/dist/util/token-counter.d.ts.map +1 -0
- package/dist/util/token-counter.js +48 -0
- package/dist/util/token-counter.js.map +1 -0
- package/dist/util/types.d.ts +525 -0
- package/dist/util/types.d.ts.map +1 -0
- package/dist/util/types.js +5 -0
- package/dist/util/types.js.map +1 -0
- package/docker-compose.yml +20 -0
- package/docs/plans/2026-02-27-swarm-coordination/architecture.md +203 -0
- package/docs/plans/2026-02-27-swarm-coordination/vision.md +57 -0
- package/docs/plans/completed/2026-02-26-forge-plugin-bundling/architecture.md +1 -0
- package/docs/plans/completed/2026-02-26-forge-plugin-bundling/vision.md +300 -0
- package/docs/plans/completed/2026-02-27-forge-swarm-learning/architecture.md +480 -0
- package/docs/plans/completed/2026-02-27-forge-swarm-learning/verification-checklist.md +462 -0
- package/docs/plans/completed/2026-02-27-git-history-atlassian/git-jira-plan.md +181 -0
- package/package.json +39 -0
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/.mcp.json +15 -0
- package/plugin/README.md +134 -0
- package/plugin/agents/architect.md +367 -0
- package/plugin/agents/backend-specialist.md +263 -0
- package/plugin/agents/brainstormer.md +122 -0
- package/plugin/agents/data-specialist.md +266 -0
- package/plugin/agents/designer.md +408 -0
- package/plugin/agents/frontend-specialist.md +241 -0
- package/plugin/agents/inspector.md +406 -0
- package/plugin/agents/knowledge-keeper.md +443 -0
- package/plugin/agents/platform-engineer.md +326 -0
- package/plugin/agents/product-manager.md +268 -0
- package/plugin/agents/product-owner.md +438 -0
- package/plugin/agents/pulse-checker.md +73 -0
- package/plugin/agents/qa-strategist.md +500 -0
- package/plugin/agents/self-improver.md +310 -0
- package/plugin/agents/strategist.md +360 -0
- package/plugin/agents/supervisor.md +380 -0
- package/plugin/commands/brainstorm.md +25 -0
- package/plugin/commands/forge.md +88 -0
- package/plugin/docs/atlassian-integration.md +110 -0
- package/plugin/docs/workflow.md +126 -0
- package/plugin/skills/agent-development/.skillfish.json +10 -0
- package/plugin/skills/agent-development/SKILL.md +415 -0
- package/plugin/skills/agent-development/examples/agent-creation-prompt.md +238 -0
- package/plugin/skills/agent-development/examples/complete-agent-examples.md +427 -0
- package/plugin/skills/agent-development/references/agent-creation-system-prompt.md +207 -0
- package/plugin/skills/agent-development/references/system-prompt-design.md +411 -0
- package/plugin/skills/agent-development/references/triggering-examples.md +491 -0
- package/plugin/skills/agent-development/scripts/validate-agent.sh +217 -0
- package/plugin/skills/agent-handoff/SKILL.md +335 -0
- package/plugin/skills/anti-stub/SKILL.md +317 -0
- package/plugin/skills/brainstorm/SKILL.md +31 -0
- package/plugin/skills/debugging/SKILL.md +276 -0
- package/plugin/skills/fix/SKILL.md +62 -0
- package/plugin/skills/frontend-design/.skillfish.json +10 -0
- package/plugin/skills/frontend-design/SKILL.md +42 -0
- package/plugin/skills/gotchas/SKILL.md +61 -0
- package/plugin/skills/graph-orchestrator/SKILL.md +38 -0
- package/plugin/skills/history/SKILL.md +58 -0
- package/plugin/skills/impact/SKILL.md +59 -0
- package/plugin/skills/implementation-execution/SKILL.md +291 -0
- package/plugin/skills/index-repo/SKILL.md +55 -0
- package/plugin/skills/interviewing/SKILL.md +225 -0
- package/plugin/skills/knowledge-curation/SKILL.md +393 -0
- package/plugin/skills/learn/SKILL.md +69 -0
- package/plugin/skills/mcp-integration/.skillfish.json +10 -0
- package/plugin/skills/mcp-integration/SKILL.md +554 -0
- package/plugin/skills/mcp-integration/examples/http-server.json +20 -0
- package/plugin/skills/mcp-integration/examples/sse-server.json +19 -0
- package/plugin/skills/mcp-integration/examples/stdio-server.json +26 -0
- package/plugin/skills/mcp-integration/references/authentication.md +549 -0
- package/plugin/skills/mcp-integration/references/server-types.md +536 -0
- package/plugin/skills/mcp-integration/references/tool-usage.md +538 -0
- package/plugin/skills/nestjs/.skillfish.json +10 -0
- package/plugin/skills/nestjs/SKILL.md +669 -0
- package/plugin/skills/nestjs/drizzle-reference.md +1894 -0
- package/plugin/skills/nestjs/reference.md +1447 -0
- package/plugin/skills/nestjs/workflow-optimization.md +229 -0
- package/plugin/skills/parallel-dispatch/SKILL.md +308 -0
- package/plugin/skills/project-discovery/SKILL.md +304 -0
- package/plugin/skills/search/SKILL.md +56 -0
- package/plugin/skills/security-audit/SKILL.md +362 -0
- package/plugin/skills/skill-development/.skillfish.json +10 -0
- package/plugin/skills/skill-development/SKILL.md +637 -0
- package/plugin/skills/skill-development/references/skill-creator-original.md +209 -0
- package/plugin/skills/tdd/SKILL.md +273 -0
- package/plugin/skills/terminal-presentation/SKILL.md +395 -0
- package/plugin/skills/test-strategy/SKILL.md +365 -0
- package/plugin/skills/verification-protocol/SKILL.md +256 -0
- package/plugin/skills/visual-explainer/CHANGELOG.md +97 -0
- package/plugin/skills/visual-explainer/LICENSE +21 -0
- package/plugin/skills/visual-explainer/README.md +137 -0
- package/plugin/skills/visual-explainer/SKILL.md +352 -0
- package/plugin/skills/visual-explainer/banner.png +0 -0
- package/plugin/skills/visual-explainer/package.json +11 -0
- package/plugin/skills/visual-explainer/prompts/diff-review.md +68 -0
- package/plugin/skills/visual-explainer/prompts/fact-check.md +63 -0
- package/plugin/skills/visual-explainer/prompts/generate-slides.md +18 -0
- package/plugin/skills/visual-explainer/prompts/generate-web-diagram.md +10 -0
- package/plugin/skills/visual-explainer/prompts/plan-review.md +86 -0
- package/plugin/skills/visual-explainer/prompts/project-recap.md +61 -0
- package/plugin/skills/visual-explainer/references/css-patterns.md +1188 -0
- package/plugin/skills/visual-explainer/references/libraries.md +470 -0
- package/plugin/skills/visual-explainer/references/responsive-nav.md +212 -0
- package/plugin/skills/visual-explainer/references/slide-patterns.md +1403 -0
- package/plugin/skills/visual-explainer/templates/architecture.html +596 -0
- package/plugin/skills/visual-explainer/templates/data-table.html +540 -0
- package/plugin/skills/visual-explainer/templates/mermaid-flowchart.html +435 -0
- package/plugin/skills/visual-explainer/templates/slide-deck.html +913 -0
- package/src/cli.ts +655 -0
- package/src/context/.gitkeep +0 -0
- package/src/context/codebase.ts +393 -0
- package/src/context/injector.ts +797 -0
- package/src/context/memory.ts +187 -0
- package/src/context/session-index.ts +327 -0
- package/src/context/session.ts +152 -0
- package/src/index.ts +47 -0
- package/src/ingestion/.gitkeep +0 -0
- package/src/ingestion/chunker.ts +277 -0
- package/src/ingestion/embedder.ts +167 -0
- package/src/ingestion/git-analyzer.ts +545 -0
- package/src/ingestion/indexer.ts +984 -0
- package/src/ingestion/markdown-chunker.ts +337 -0
- package/src/ingestion/markdown-knowledge.ts +175 -0
- package/src/ingestion/parser.ts +475 -0
- package/src/ingestion/watcher.ts +182 -0
- package/src/knowledge/.gitkeep +0 -0
- package/src/knowledge/hydrator.ts +246 -0
- package/src/knowledge/registry.ts +463 -0
- package/src/knowledge/search.ts +565 -0
- package/src/knowledge/store.ts +262 -0
- package/src/learning/.gitkeep +0 -0
- package/src/learning/confidence.ts +193 -0
- package/src/learning/patterns.ts +360 -0
- package/src/learning/trajectory.ts +268 -0
- package/src/memory/.gitkeep +0 -0
- package/src/memory/memory-compat.ts +233 -0
- package/src/memory/observation-store.ts +224 -0
- package/src/memory/session-tracker.ts +332 -0
- package/src/pipeline/.gitkeep +0 -0
- package/src/pipeline/engine.ts +1139 -0
- package/src/pipeline/events.ts +253 -0
- package/src/pipeline/parallel.ts +394 -0
- package/src/pipeline/state-machine.ts +199 -0
- package/src/query/.gitkeep +0 -0
- package/src/query/graph-queries.ts +262 -0
- package/src/query/hybrid-search.ts +337 -0
- package/src/query/intent-detector.ts +131 -0
- package/src/query/ranking.ts +161 -0
- package/src/server.ts +352 -0
- package/src/storage/.gitkeep +0 -0
- package/src/storage/falkordb-store.ts +388 -0
- package/src/storage/file-cache.ts +141 -0
- package/src/storage/interfaces.ts +201 -0
- package/src/storage/qdrant-store.ts +557 -0
- package/src/storage/schema.ts +139 -0
- package/src/storage/sqlite.ts +168 -0
- package/src/tools/.gitkeep +0 -0
- package/src/tools/collaboration-tools.ts +208 -0
- package/src/tools/context-tools.ts +493 -0
- package/src/tools/graph-tools.ts +295 -0
- package/src/tools/ingestion-tools.ts +122 -0
- package/src/tools/learning-tools.ts +181 -0
- package/src/tools/memory-tools.ts +234 -0
- package/src/tools/phase-tools.ts +1452 -0
- package/src/tools/pipeline-tools.ts +188 -0
- package/src/tools/registration-tools.ts +450 -0
- package/src/util/.gitkeep +0 -0
- package/src/util/circuit-breaker.ts +193 -0
- package/src/util/config.ts +177 -0
- package/src/util/logger.ts +53 -0
- package/src/util/token-counter.ts +52 -0
- package/src/util/types.ts +710 -0
- package/tests/context/.gitkeep +0 -0
- package/tests/integration/.gitkeep +0 -0
- package/tests/knowledge/.gitkeep +0 -0
- package/tests/learning/.gitkeep +0 -0
- package/tests/pipeline/.gitkeep +0 -0
- package/tests/tools/.gitkeep +0 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +10 -0
- package/vscode-extension/.vscodeignore +7 -0
- package/vscode-extension/README.md +43 -0
- package/vscode-extension/out/edge-collector.js +274 -0
- package/vscode-extension/out/edge-collector.js.map +1 -0
- package/vscode-extension/out/extension.js +264 -0
- package/vscode-extension/out/extension.js.map +1 -0
- package/vscode-extension/out/forge-client.js +318 -0
- package/vscode-extension/out/forge-client.js.map +1 -0
- package/vscode-extension/package-lock.json +59 -0
- package/vscode-extension/package.json +71 -0
- package/vscode-extension/src/edge-collector.ts +320 -0
- package/vscode-extension/src/extension.ts +269 -0
- package/vscode-extension/src/forge-client.ts +364 -0
- package/vscode-extension/tsconfig.json +19 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qdrant implementation of VectorStore.
|
|
3
|
+
* Connects to localhost:6333 via REST API.
|
|
4
|
+
* Collections:
|
|
5
|
+
* - code_chunks (384-dim cosine)
|
|
6
|
+
* - observations (384-dim cosine)
|
|
7
|
+
* - knowledge (384-dim cosine) — pipeline-specific, stores KnowledgePayload items
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { QdrantClient } from '@qdrant/js-client-rest';
|
|
11
|
+
import type { VectorStore, VectorSearchOptions } from './interfaces.js';
|
|
12
|
+
import type {
|
|
13
|
+
CodeChunkPayload,
|
|
14
|
+
ObservationPayload,
|
|
15
|
+
GitCommitPayload,
|
|
16
|
+
VectorSearchResult,
|
|
17
|
+
} from '../util/types.js';
|
|
18
|
+
import { logger } from '../util/logger.js';
|
|
19
|
+
import { CircuitBreaker } from '../util/circuit-breaker.js';
|
|
20
|
+
|
|
21
|
+
const CODE_CHUNKS_COLLECTION = 'code_chunks';
|
|
22
|
+
const OBSERVATIONS_COLLECTION = 'observations';
|
|
23
|
+
const KNOWLEDGE_COLLECTION = 'knowledge';
|
|
24
|
+
const GIT_COMMITS_COLLECTION = 'git_commits';
|
|
25
|
+
|
|
26
|
+
export class QdrantVectorStore implements VectorStore {
|
|
27
|
+
private client: QdrantClient;
|
|
28
|
+
private readonly url: string;
|
|
29
|
+
private readonly breaker: CircuitBreaker;
|
|
30
|
+
|
|
31
|
+
constructor(url: string = 'http://localhost:6333') {
|
|
32
|
+
this.url = url;
|
|
33
|
+
this.client = new QdrantClient({ url });
|
|
34
|
+
this.breaker = new CircuitBreaker({
|
|
35
|
+
name: 'qdrant',
|
|
36
|
+
failureThreshold: 5,
|
|
37
|
+
resetTimeoutMs: 30_000,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async connect(): Promise<void> {
|
|
42
|
+
// Qdrant REST client doesn't need an explicit connection - just validate health
|
|
43
|
+
const healthy = await this.isHealthy();
|
|
44
|
+
if (!healthy) {
|
|
45
|
+
throw new Error(`Qdrant not reachable at ${this.url}`);
|
|
46
|
+
}
|
|
47
|
+
logger.info('Qdrant connected', { url: this.url });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async disconnect(): Promise<void> {
|
|
51
|
+
// No persistent connection to close for REST client
|
|
52
|
+
logger.info('Qdrant disconnected');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async isHealthy(): Promise<boolean> {
|
|
56
|
+
// Circuit breaker short-circuits when OPEN — skip the HTTP health probe
|
|
57
|
+
if (!this.breaker.isHealthy()) return false;
|
|
58
|
+
try {
|
|
59
|
+
// getCollections() is the most reliable health probe for qdrant-js-client-rest:
|
|
60
|
+
// it performs a real HTTP request and returns if the server is reachable.
|
|
61
|
+
await this.client.getCollections();
|
|
62
|
+
return true;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async ensureCollections(dimension: number): Promise<void> {
|
|
69
|
+
const hnsw = { m: 16, ef_construct: 100 };
|
|
70
|
+
const onDisk = true;
|
|
71
|
+
|
|
72
|
+
// Create code_chunks collection
|
|
73
|
+
await this.ensureCollection(CODE_CHUNKS_COLLECTION, dimension, hnsw, onDisk, [
|
|
74
|
+
{ field_name: 'repo_id', field_schema: 'keyword' },
|
|
75
|
+
{ field_name: 'file_path', field_schema: 'keyword' },
|
|
76
|
+
{ field_name: 'entity_type', field_schema: 'keyword' },
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
// Create observations collection
|
|
80
|
+
await this.ensureCollection(OBSERVATIONS_COLLECTION, dimension, hnsw, onDisk, [
|
|
81
|
+
{ field_name: 'repo_id', field_schema: 'keyword' },
|
|
82
|
+
{ field_name: 'session_id', field_schema: 'keyword' },
|
|
83
|
+
{ field_name: 'namespace', field_schema: 'keyword' },
|
|
84
|
+
{ field_name: 'key', field_schema: 'keyword' },
|
|
85
|
+
{ field_name: 'source', field_schema: 'keyword' },
|
|
86
|
+
{ field_name: 'type', field_schema: 'keyword' },
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
// Create knowledge collection (pipeline-specific)
|
|
90
|
+
await this.ensureCollection(KNOWLEDGE_COLLECTION, dimension, hnsw, onDisk, [
|
|
91
|
+
{ field_name: 'category', field_schema: 'keyword' },
|
|
92
|
+
{ field_name: 'repo_id', field_schema: 'keyword' },
|
|
93
|
+
{ field_name: 'stack_tags', field_schema: 'keyword' },
|
|
94
|
+
{ field_name: 'confidence', field_schema: 'float' },
|
|
95
|
+
{ field_name: 'sharing', field_schema: 'keyword' },
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
// Create git_commits collection (Phase 3 - git enrichment)
|
|
99
|
+
await this.ensureCollection(GIT_COMMITS_COLLECTION, dimension, hnsw, onDisk, [
|
|
100
|
+
{ field_name: 'repo_id', field_schema: 'keyword' },
|
|
101
|
+
{ field_name: 'commit_hash', field_schema: 'keyword' },
|
|
102
|
+
{ field_name: 'author', field_schema: 'keyword' },
|
|
103
|
+
{ field_name: 'timestamp', field_schema: 'integer' },
|
|
104
|
+
{ field_name: 'file_paths', field_schema: 'keyword' },
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
logger.info('Qdrant collections ensured', { dimension });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private async ensureCollection(
|
|
111
|
+
name: string,
|
|
112
|
+
dimension: number,
|
|
113
|
+
hnsw: { m: number; ef_construct: number },
|
|
114
|
+
onDisk: boolean,
|
|
115
|
+
payloadIndexes: Array<{ field_name: string; field_schema: string }>
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
try {
|
|
118
|
+
await this.client.getCollection(name);
|
|
119
|
+
logger.debug(`Collection ${name} already exists`);
|
|
120
|
+
} catch {
|
|
121
|
+
// Collection does not exist - create it
|
|
122
|
+
await this.client.createCollection(name, {
|
|
123
|
+
vectors: {
|
|
124
|
+
size: dimension,
|
|
125
|
+
distance: 'Cosine',
|
|
126
|
+
on_disk: onDisk,
|
|
127
|
+
},
|
|
128
|
+
hnsw_config: {
|
|
129
|
+
m: hnsw.m,
|
|
130
|
+
ef_construct: hnsw.ef_construct,
|
|
131
|
+
on_disk: true,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
logger.info(`Created Qdrant collection: ${name}`);
|
|
135
|
+
|
|
136
|
+
// Create payload indexes
|
|
137
|
+
for (const idx of payloadIndexes) {
|
|
138
|
+
try {
|
|
139
|
+
await this.client.createPayloadIndex(name, {
|
|
140
|
+
field_name: idx.field_name,
|
|
141
|
+
field_schema: idx.field_schema as 'keyword' | 'float',
|
|
142
|
+
});
|
|
143
|
+
} catch (err) {
|
|
144
|
+
logger.warn(`Failed to create payload index ${idx.field_name} on ${name}`, { error: String(err) });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async upsertCodeChunks(
|
|
151
|
+
chunks: Array<{ id: string; vector: number[]; payload: CodeChunkPayload }>
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
if (chunks.length === 0) return;
|
|
154
|
+
|
|
155
|
+
const points = chunks.map(chunk => ({
|
|
156
|
+
id: this.uuidToNumeric(chunk.id),
|
|
157
|
+
vector: chunk.vector,
|
|
158
|
+
payload: chunk.payload as unknown as Record<string, unknown>,
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
await this.client.upsert(CODE_CHUNKS_COLLECTION, {
|
|
162
|
+
wait: true,
|
|
163
|
+
points,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
logger.debug(`Upserted ${chunks.length} code chunks to Qdrant`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async upsertObservation(id: string, vector: number[], payload: ObservationPayload): Promise<void> {
|
|
170
|
+
await this.client.upsert(OBSERVATIONS_COLLECTION, {
|
|
171
|
+
wait: true,
|
|
172
|
+
points: [{
|
|
173
|
+
id: this.uuidToNumeric(id),
|
|
174
|
+
vector,
|
|
175
|
+
payload: payload as unknown as Record<string, unknown>,
|
|
176
|
+
}],
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async searchCodeChunks(queryVector: number[], options: VectorSearchOptions): Promise<VectorSearchResult[]> {
|
|
181
|
+
const { limit = 20, filter, scoreThreshold = 0 } = options;
|
|
182
|
+
|
|
183
|
+
const params: Parameters<typeof this.client.search>[1] = {
|
|
184
|
+
vector: queryVector,
|
|
185
|
+
limit,
|
|
186
|
+
score_threshold: scoreThreshold,
|
|
187
|
+
with_payload: true,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
if (filter) {
|
|
191
|
+
params.filter = this.buildFilter(filter);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const results = await this.breaker.execute(() =>
|
|
195
|
+
this.client.search(CODE_CHUNKS_COLLECTION, params)
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
return results.map(r => ({
|
|
199
|
+
id: String(r.id),
|
|
200
|
+
score: r.score,
|
|
201
|
+
payload: r.payload as unknown as CodeChunkPayload,
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async searchObservations(queryVector: number[], options: VectorSearchOptions): Promise<VectorSearchResult[]> {
|
|
206
|
+
const { limit = 10, filter, scoreThreshold = 0.3 } = options;
|
|
207
|
+
|
|
208
|
+
const params: Parameters<typeof this.client.search>[1] = {
|
|
209
|
+
vector: queryVector,
|
|
210
|
+
limit,
|
|
211
|
+
score_threshold: scoreThreshold,
|
|
212
|
+
with_payload: true,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (filter) {
|
|
216
|
+
params.filter = this.buildFilter(filter);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const results = await this.breaker.execute(() =>
|
|
220
|
+
this.client.search(OBSERVATIONS_COLLECTION, params)
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
return results.map(r => ({
|
|
224
|
+
id: String(r.id),
|
|
225
|
+
score: r.score,
|
|
226
|
+
payload: r.payload as unknown as ObservationPayload,
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async deleteFileChunks(filePath: string, repoId: string): Promise<void> {
|
|
231
|
+
await this.client.delete(CODE_CHUNKS_COLLECTION, {
|
|
232
|
+
wait: true,
|
|
233
|
+
filter: {
|
|
234
|
+
must: [
|
|
235
|
+
{ key: 'file_path', match: { value: filePath } },
|
|
236
|
+
{ key: 'repo_id', match: { value: repoId } },
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async deleteRepoChunks(repoId: string): Promise<void> {
|
|
243
|
+
await this.client.delete(CODE_CHUNKS_COLLECTION, {
|
|
244
|
+
wait: true,
|
|
245
|
+
filter: {
|
|
246
|
+
must: [{ key: 'repo_id', match: { value: repoId } }],
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async getCounts(): Promise<{ codeChunks: number; observations: number }> {
|
|
252
|
+
try {
|
|
253
|
+
const [codeInfo, obsInfo] = await Promise.all([
|
|
254
|
+
this.client.getCollection(CODE_CHUNKS_COLLECTION),
|
|
255
|
+
this.client.getCollection(OBSERVATIONS_COLLECTION),
|
|
256
|
+
]);
|
|
257
|
+
return {
|
|
258
|
+
codeChunks: codeInfo.points_count ?? 0,
|
|
259
|
+
observations: obsInfo.points_count ?? 0,
|
|
260
|
+
};
|
|
261
|
+
} catch {
|
|
262
|
+
return { codeChunks: 0, observations: 0 };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async getObservation(id: string): Promise<ObservationPayload | null> {
|
|
267
|
+
try {
|
|
268
|
+
const results = await this.client.retrieve(OBSERVATIONS_COLLECTION, {
|
|
269
|
+
ids: [this.uuidToNumeric(id)],
|
|
270
|
+
with_payload: true,
|
|
271
|
+
});
|
|
272
|
+
if (results.length === 0) return null;
|
|
273
|
+
return results[0]!.payload as unknown as ObservationPayload;
|
|
274
|
+
} catch {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async updateObservation(id: string, payload: Partial<ObservationPayload>): Promise<void> {
|
|
280
|
+
await this.client.setPayload(OBSERVATIONS_COLLECTION, {
|
|
281
|
+
wait: true,
|
|
282
|
+
points: [this.uuidToNumeric(id)],
|
|
283
|
+
payload: payload as unknown as Record<string, unknown>,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async filterObservations(filter: Record<string, unknown>, limit = 10): Promise<VectorSearchResult[]> {
|
|
288
|
+
const results = await this.client.scroll(OBSERVATIONS_COLLECTION, {
|
|
289
|
+
filter: this.buildFilter(filter),
|
|
290
|
+
limit,
|
|
291
|
+
with_payload: true,
|
|
292
|
+
with_vector: false,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return (results.points ?? []).map(r => ({
|
|
296
|
+
id: String(r.id),
|
|
297
|
+
score: 1.0,
|
|
298
|
+
payload: r.payload as unknown as ObservationPayload,
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ============================================================
|
|
303
|
+
// Knowledge collection helpers (pipeline-specific)
|
|
304
|
+
// ============================================================
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Search the knowledge collection by vector similarity.
|
|
308
|
+
*/
|
|
309
|
+
async searchKnowledge(
|
|
310
|
+
queryVector: number[],
|
|
311
|
+
options: VectorSearchOptions
|
|
312
|
+
): Promise<VectorSearchResult[]> {
|
|
313
|
+
const { limit = 10, filter, scoreThreshold = 0.3 } = options;
|
|
314
|
+
|
|
315
|
+
const params: Parameters<typeof this.client.search>[1] = {
|
|
316
|
+
vector: queryVector,
|
|
317
|
+
limit,
|
|
318
|
+
score_threshold: scoreThreshold,
|
|
319
|
+
with_payload: true,
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
if (filter) {
|
|
323
|
+
params.filter = this.buildFilter(filter);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const results = await this.breaker.execute(() =>
|
|
327
|
+
this.client.search(KNOWLEDGE_COLLECTION, params)
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
return results.map(r => ({
|
|
331
|
+
id: String(r.id),
|
|
332
|
+
score: r.score,
|
|
333
|
+
payload: r.payload as Record<string, unknown>,
|
|
334
|
+
})) as unknown as VectorSearchResult[];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Upsert a knowledge item into the knowledge collection.
|
|
339
|
+
*/
|
|
340
|
+
async upsertKnowledge(
|
|
341
|
+
id: string,
|
|
342
|
+
vector: number[],
|
|
343
|
+
payload: Record<string, unknown>
|
|
344
|
+
): Promise<void> {
|
|
345
|
+
await this.client.upsert(KNOWLEDGE_COLLECTION, {
|
|
346
|
+
wait: true,
|
|
347
|
+
points: [{
|
|
348
|
+
id: this.uuidToNumeric(id),
|
|
349
|
+
vector,
|
|
350
|
+
payload,
|
|
351
|
+
}],
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Delete a knowledge item from the knowledge collection.
|
|
357
|
+
*/
|
|
358
|
+
async deleteKnowledge(id: string): Promise<void> {
|
|
359
|
+
await this.client.delete(KNOWLEDGE_COLLECTION, {
|
|
360
|
+
wait: true,
|
|
361
|
+
points: [this.uuidToNumeric(id)],
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Delete ALL knowledge items for a given repo_id from the knowledge collection.
|
|
367
|
+
* Used by KnowledgeHydrator.removeRepo() before re-hydrating to handle
|
|
368
|
+
* item deletions (items removed from YAML should disappear from Qdrant).
|
|
369
|
+
*/
|
|
370
|
+
async deleteKnowledgeByRepo(repoId: string): Promise<void> {
|
|
371
|
+
await this.client.delete(KNOWLEDGE_COLLECTION, {
|
|
372
|
+
wait: true,
|
|
373
|
+
filter: {
|
|
374
|
+
must: [{ key: 'repo_id', match: { value: repoId } }],
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
logger.debug('QdrantVectorStore: deleted knowledge points for repo', { repoId });
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Retrieve a single knowledge item payload by its Qdrant point ID.
|
|
382
|
+
* Returns null if the item does not exist.
|
|
383
|
+
* Used by KnowledgeSearch.boostConfidence() to read current confidence value.
|
|
384
|
+
*/
|
|
385
|
+
async getKnowledge(id: string): Promise<Record<string, unknown> | null> {
|
|
386
|
+
try {
|
|
387
|
+
const results = await this.client.retrieve(KNOWLEDGE_COLLECTION, {
|
|
388
|
+
ids: [this.uuidToNumeric(id)],
|
|
389
|
+
with_payload: true,
|
|
390
|
+
});
|
|
391
|
+
if (results.length === 0) return null;
|
|
392
|
+
return results[0]!.payload as Record<string, unknown> ?? null;
|
|
393
|
+
} catch {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Partially update a knowledge item's payload fields without changing the vector.
|
|
400
|
+
* Used by KnowledgeSearch.boostConfidence() to update accessed_at, access_count,
|
|
401
|
+
* and confidence without re-embedding.
|
|
402
|
+
*/
|
|
403
|
+
async setKnowledgePayload(id: string, patch: Record<string, unknown>): Promise<void> {
|
|
404
|
+
await this.client.setPayload(KNOWLEDGE_COLLECTION, {
|
|
405
|
+
wait: true,
|
|
406
|
+
points: [this.uuidToNumeric(id)],
|
|
407
|
+
payload: patch,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Filter-scroll the knowledge collection without a semantic query.
|
|
413
|
+
* Used by KnowledgeSearch.getGotchas() and getPatterns() to enumerate items
|
|
414
|
+
* by category without needing an embedding.
|
|
415
|
+
*
|
|
416
|
+
* Returns up to `limit` items matching the filter, each as { id, payload }.
|
|
417
|
+
*/
|
|
418
|
+
async filterKnowledge(
|
|
419
|
+
filter: Record<string, unknown>,
|
|
420
|
+
limit: number,
|
|
421
|
+
): Promise<Array<{ id: string; payload: Record<string, unknown> }>> {
|
|
422
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
423
|
+
const results = await this.client.scroll(KNOWLEDGE_COLLECTION, {
|
|
424
|
+
filter: filter as unknown as any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
425
|
+
limit,
|
|
426
|
+
with_payload: true,
|
|
427
|
+
with_vector: false,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
return (results.points ?? []).map((r) => ({
|
|
431
|
+
id: String(r.id),
|
|
432
|
+
payload: r.payload as Record<string, unknown>,
|
|
433
|
+
}));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Get count of knowledge items.
|
|
438
|
+
*/
|
|
439
|
+
async getKnowledgeCount(): Promise<number> {
|
|
440
|
+
try {
|
|
441
|
+
const info = await this.client.getCollection(KNOWLEDGE_COLLECTION);
|
|
442
|
+
return info.points_count ?? 0;
|
|
443
|
+
} catch {
|
|
444
|
+
return 0;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ============================================================
|
|
449
|
+
// Git commits collection helpers (Phase 3 - git enrichment)
|
|
450
|
+
// ============================================================
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Upsert git commit embeddings into the git_commits collection.
|
|
454
|
+
* Each commit message is embedded and stored with metadata for semantic search.
|
|
455
|
+
*/
|
|
456
|
+
async upsertGitCommits(
|
|
457
|
+
commits: Array<{ id: string; vector: number[]; payload: GitCommitPayload }>
|
|
458
|
+
): Promise<void> {
|
|
459
|
+
if (commits.length === 0) return;
|
|
460
|
+
|
|
461
|
+
const points = commits.map(c => ({
|
|
462
|
+
id: this.uuidToNumeric(c.id),
|
|
463
|
+
vector: c.vector,
|
|
464
|
+
payload: c.payload as unknown as Record<string, unknown>,
|
|
465
|
+
}));
|
|
466
|
+
|
|
467
|
+
// Upsert in batches of 100 to avoid overwhelming the server
|
|
468
|
+
const BATCH = 100;
|
|
469
|
+
for (let i = 0; i < points.length; i += BATCH) {
|
|
470
|
+
const batch = points.slice(i, i + BATCH);
|
|
471
|
+
await this.client.upsert(GIT_COMMITS_COLLECTION, {
|
|
472
|
+
wait: true,
|
|
473
|
+
points: batch,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
logger.debug(`Upserted ${commits.length} git commits to Qdrant`);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Semantic search over git commit messages.
|
|
482
|
+
* Returns commits matching the query vector, optionally filtered by repo_id.
|
|
483
|
+
*/
|
|
484
|
+
async searchGitCommits(
|
|
485
|
+
queryVector: number[],
|
|
486
|
+
repoId?: string,
|
|
487
|
+
limit: number = 20
|
|
488
|
+
): Promise<VectorSearchResult[]> {
|
|
489
|
+
const params: Parameters<typeof this.client.search>[1] = {
|
|
490
|
+
vector: queryVector,
|
|
491
|
+
limit,
|
|
492
|
+
score_threshold: 0.2,
|
|
493
|
+
with_payload: true,
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
if (repoId) {
|
|
497
|
+
params.filter = this.buildFilter({ repo_id: repoId });
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const results = await this.breaker.execute(() =>
|
|
501
|
+
this.client.search(GIT_COMMITS_COLLECTION, params)
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
return results.map(r => ({
|
|
505
|
+
id: String(r.id),
|
|
506
|
+
score: r.score,
|
|
507
|
+
payload: r.payload as unknown as GitCommitPayload,
|
|
508
|
+
})) as unknown as VectorSearchResult[];
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Delete all git commit embeddings for a given repo.
|
|
513
|
+
* Used during re-indexing to avoid stale commit data.
|
|
514
|
+
*/
|
|
515
|
+
async deleteGitCommitsByRepo(repoId: string): Promise<void> {
|
|
516
|
+
await this.client.delete(GIT_COMMITS_COLLECTION, {
|
|
517
|
+
wait: true,
|
|
518
|
+
filter: {
|
|
519
|
+
must: [{ key: 'repo_id', match: { value: repoId } }],
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
logger.debug('QdrantVectorStore: deleted git commits for repo', { repoId });
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private buildFilter(filter: Record<string, unknown>): Record<string, unknown> {
|
|
526
|
+
// Simple filter builder: { repo_id: 'x', namespace: 'y' } -> Qdrant must filter
|
|
527
|
+
const must = Object.entries(filter)
|
|
528
|
+
.filter(([, v]) => v !== undefined && v !== null)
|
|
529
|
+
.map(([key, value]) => ({
|
|
530
|
+
key,
|
|
531
|
+
match: { value },
|
|
532
|
+
}));
|
|
533
|
+
|
|
534
|
+
return must.length > 0 ? { must } : {};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Convert UUID string to a numeric ID for Qdrant.
|
|
539
|
+
* Qdrant supports both UUID strings and unsigned 64-bit integers as point IDs.
|
|
540
|
+
* We use UUID format directly as Qdrant v1.x supports UUID string IDs.
|
|
541
|
+
*/
|
|
542
|
+
private uuidToNumeric(id: string): string {
|
|
543
|
+
// Qdrant supports UUID string IDs - return as-is
|
|
544
|
+
// Ensure it's a valid UUID format; if not, pad it
|
|
545
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) {
|
|
546
|
+
return id;
|
|
547
|
+
}
|
|
548
|
+
// Hash to a deterministic UUID-like format
|
|
549
|
+
let hash = 0;
|
|
550
|
+
for (let i = 0; i < id.length; i++) {
|
|
551
|
+
hash = ((hash << 5) - hash) + id.charCodeAt(i);
|
|
552
|
+
hash |= 0;
|
|
553
|
+
}
|
|
554
|
+
const absHash = Math.abs(hash);
|
|
555
|
+
return `00000000-0000-4000-8000-${String(absHash).padStart(12, '0')}`;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// SQL DDL for the dk-forge-server SQLite database.
|
|
2
|
+
// All knowledge/vector data lives in Qdrant — this file covers pipeline state only.
|
|
3
|
+
//
|
|
4
|
+
// Design notes:
|
|
5
|
+
// - Timestamps are INTEGER (Unix milliseconds) throughout — no TEXT dates.
|
|
6
|
+
// - JSON columns are TEXT in SQLite; callers parse/serialize them explicitly.
|
|
7
|
+
// - Foreign keys are declared; the PipelineDB class enables FK enforcement
|
|
8
|
+
// via PRAGMA foreign_keys=ON at connection time.
|
|
9
|
+
// - Every CREATE TABLE uses IF NOT EXISTS so initialize() is idempotent.
|
|
10
|
+
// - Every CREATE INDEX uses IF NOT EXISTS for the same reason.
|
|
11
|
+
|
|
12
|
+
export const SCHEMA_SQL = `
|
|
13
|
+
-- Repo registry
|
|
14
|
+
CREATE TABLE IF NOT EXISTS repos (
|
|
15
|
+
id TEXT PRIMARY KEY,
|
|
16
|
+
name TEXT NOT NULL,
|
|
17
|
+
path TEXT NOT NULL,
|
|
18
|
+
remote TEXT,
|
|
19
|
+
stack TEXT NOT NULL DEFAULT '[]',
|
|
20
|
+
sharing TEXT NOT NULL DEFAULT 'private',
|
|
21
|
+
org TEXT,
|
|
22
|
+
registered_at INTEGER NOT NULL,
|
|
23
|
+
last_seen_at INTEGER NOT NULL,
|
|
24
|
+
manifest_hash TEXT
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
-- Pipeline projects
|
|
28
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
29
|
+
id TEXT PRIMARY KEY,
|
|
30
|
+
repo_id TEXT NOT NULL REFERENCES repos(id),
|
|
31
|
+
name TEXT NOT NULL,
|
|
32
|
+
description TEXT,
|
|
33
|
+
current_phase TEXT NOT NULL DEFAULT 'idle',
|
|
34
|
+
tier TEXT NOT NULL DEFAULT 'full',
|
|
35
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
36
|
+
created_at INTEGER NOT NULL,
|
|
37
|
+
updated_at INTEGER NOT NULL,
|
|
38
|
+
metadata TEXT NOT NULL DEFAULT '{}'
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
-- Pipeline events (event sourcing log)
|
|
42
|
+
CREATE TABLE IF NOT EXISTS pipeline_events (
|
|
43
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
44
|
+
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
45
|
+
event_type TEXT NOT NULL,
|
|
46
|
+
from_phase TEXT,
|
|
47
|
+
to_phase TEXT,
|
|
48
|
+
agent TEXT,
|
|
49
|
+
payload TEXT NOT NULL DEFAULT '{}',
|
|
50
|
+
created_at INTEGER NOT NULL
|
|
51
|
+
);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_events_project
|
|
53
|
+
ON pipeline_events(project_id, created_at);
|
|
54
|
+
|
|
55
|
+
-- Phase outputs
|
|
56
|
+
CREATE TABLE IF NOT EXISTS phase_outputs (
|
|
57
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
58
|
+
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
59
|
+
phase TEXT NOT NULL,
|
|
60
|
+
output_type TEXT NOT NULL,
|
|
61
|
+
content TEXT NOT NULL,
|
|
62
|
+
file_path TEXT,
|
|
63
|
+
created_at INTEGER NOT NULL
|
|
64
|
+
);
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_outputs_project_phase
|
|
66
|
+
ON phase_outputs(project_id, phase);
|
|
67
|
+
|
|
68
|
+
-- Trajectories
|
|
69
|
+
CREATE TABLE IF NOT EXISTS trajectories (
|
|
70
|
+
id TEXT PRIMARY KEY,
|
|
71
|
+
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
72
|
+
phase TEXT NOT NULL,
|
|
73
|
+
agent TEXT NOT NULL,
|
|
74
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
75
|
+
started_at INTEGER NOT NULL,
|
|
76
|
+
completed_at INTEGER,
|
|
77
|
+
success INTEGER,
|
|
78
|
+
feedback TEXT
|
|
79
|
+
);
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_trajectories_project
|
|
81
|
+
ON trajectories(project_id, phase);
|
|
82
|
+
|
|
83
|
+
-- Trajectory steps
|
|
84
|
+
CREATE TABLE IF NOT EXISTS trajectory_steps (
|
|
85
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
86
|
+
trajectory_id TEXT NOT NULL REFERENCES trajectories(id),
|
|
87
|
+
action TEXT NOT NULL,
|
|
88
|
+
result TEXT,
|
|
89
|
+
quality_score REAL,
|
|
90
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
91
|
+
created_at INTEGER NOT NULL
|
|
92
|
+
);
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_steps_trajectory
|
|
94
|
+
ON trajectory_steps(trajectory_id, created_at);
|
|
95
|
+
|
|
96
|
+
-- Sessions
|
|
97
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
98
|
+
id TEXT NOT NULL,
|
|
99
|
+
project_id TEXT REFERENCES projects(id),
|
|
100
|
+
repo_id TEXT REFERENCES repos(id),
|
|
101
|
+
started_at INTEGER NOT NULL,
|
|
102
|
+
ended_at INTEGER,
|
|
103
|
+
state TEXT NOT NULL DEFAULT '{}',
|
|
104
|
+
observation_count INTEGER NOT NULL DEFAULT 0,
|
|
105
|
+
PRIMARY KEY (id)
|
|
106
|
+
);
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project
|
|
108
|
+
ON sessions(project_id);
|
|
109
|
+
|
|
110
|
+
-- Parallel work claims
|
|
111
|
+
CREATE TABLE IF NOT EXISTS claims (
|
|
112
|
+
id TEXT PRIMARY KEY,
|
|
113
|
+
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
114
|
+
module_name TEXT NOT NULL,
|
|
115
|
+
agent TEXT NOT NULL,
|
|
116
|
+
branch TEXT,
|
|
117
|
+
status TEXT NOT NULL DEFAULT 'claimed',
|
|
118
|
+
claimed_at INTEGER NOT NULL,
|
|
119
|
+
completed_at INTEGER,
|
|
120
|
+
result TEXT
|
|
121
|
+
);
|
|
122
|
+
CREATE INDEX IF NOT EXISTS idx_claims_project
|
|
123
|
+
ON claims(project_id, status);
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
// Individual table DDL strings — useful for tests that need to create a
|
|
127
|
+
// single table in isolation without executing the full schema.
|
|
128
|
+
export const TABLE_NAMES = [
|
|
129
|
+
'repos',
|
|
130
|
+
'projects',
|
|
131
|
+
'pipeline_events',
|
|
132
|
+
'phase_outputs',
|
|
133
|
+
'trajectories',
|
|
134
|
+
'trajectory_steps',
|
|
135
|
+
'sessions',
|
|
136
|
+
'claims',
|
|
137
|
+
] as const;
|
|
138
|
+
|
|
139
|
+
export type TableName = (typeof TABLE_NAMES)[number];
|