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,360 @@
|
|
|
1
|
+
// PatternExtractor — B11
|
|
2
|
+
//
|
|
3
|
+
// Extracts learnable knowledge items from completed project trajectories.
|
|
4
|
+
// Called by the knowledge_collection phase tool handler after a project
|
|
5
|
+
// reaches the inspection->completed transition.
|
|
6
|
+
//
|
|
7
|
+
// Two operations:
|
|
8
|
+
//
|
|
9
|
+
// extractFromProject(projectId)
|
|
10
|
+
// Reads all trajectory steps for the project, looks for high-quality
|
|
11
|
+
// steps (qualityScore >= 0.7) and steps with "gotcha"-like language,
|
|
12
|
+
// and promotes the best observations to KnowledgeItem records.
|
|
13
|
+
// Writes to both the YAML store (durable) and Qdrant (searchable).
|
|
14
|
+
//
|
|
15
|
+
// promoteToKnowledge(observation, category, repoId, ...)
|
|
16
|
+
// One-shot promotion: takes a raw observation string and creates a
|
|
17
|
+
// full KnowledgeItem with embedding, persisting to YAML and Qdrant.
|
|
18
|
+
// Used by the knowledge-keeper agent for manual promotion.
|
|
19
|
+
|
|
20
|
+
import { randomUUID } from 'node:crypto';
|
|
21
|
+
import type { PipelineDB } from '../storage/sqlite.js';
|
|
22
|
+
import type { KnowledgeYamlStore } from '../knowledge/store.js';
|
|
23
|
+
import type { KnowledgeSearch } from '../knowledge/search.js';
|
|
24
|
+
import type {
|
|
25
|
+
KnowledgeItem,
|
|
26
|
+
KnowledgeCategory,
|
|
27
|
+
Phase,
|
|
28
|
+
PipelinePhase,
|
|
29
|
+
TrajectoryStepRow,
|
|
30
|
+
TrajectoryRow,
|
|
31
|
+
} from '../util/types.js';
|
|
32
|
+
import { embedText } from '../ingestion/embedder.js';
|
|
33
|
+
import { logger } from '../util/logger.js';
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Constants
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/** Minimum quality score for a step to be considered a candidate. */
|
|
40
|
+
const QUALITY_THRESHOLD = 0.7;
|
|
41
|
+
|
|
42
|
+
/** Words that strongly suggest a step contains a gotcha / pain point. */
|
|
43
|
+
const GOTCHA_KEYWORDS = [
|
|
44
|
+
'gotcha',
|
|
45
|
+
'bug',
|
|
46
|
+
'fix',
|
|
47
|
+
'broken',
|
|
48
|
+
'failed',
|
|
49
|
+
'error',
|
|
50
|
+
'issue',
|
|
51
|
+
'problem',
|
|
52
|
+
'mistake',
|
|
53
|
+
'avoid',
|
|
54
|
+
'warning',
|
|
55
|
+
'careful',
|
|
56
|
+
'unexpected',
|
|
57
|
+
'pitfall',
|
|
58
|
+
'caveat',
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
/** Words that suggest a step contains a positive pattern. */
|
|
62
|
+
const PATTERN_KEYWORDS = [
|
|
63
|
+
'pattern',
|
|
64
|
+
'approach',
|
|
65
|
+
'works',
|
|
66
|
+
'solution',
|
|
67
|
+
'discovered',
|
|
68
|
+
'learned',
|
|
69
|
+
'efficient',
|
|
70
|
+
'best',
|
|
71
|
+
'prefer',
|
|
72
|
+
'recommend',
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// PatternExtractor
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
export class PatternExtractor {
|
|
80
|
+
constructor(
|
|
81
|
+
private readonly db: PipelineDB,
|
|
82
|
+
private readonly knowledgeStore: KnowledgeYamlStore,
|
|
83
|
+
private readonly knowledgeSearch: KnowledgeSearch,
|
|
84
|
+
private readonly embedder: typeof embedText,
|
|
85
|
+
) {}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Extract knowledge from all trajectory steps of a completed project.
|
|
89
|
+
*
|
|
90
|
+
* Algorithm:
|
|
91
|
+
* 1. Load all trajectories + their steps for the project.
|
|
92
|
+
* 2. Collect steps that are either:
|
|
93
|
+
* a. High quality (qualityScore >= QUALITY_THRESHOLD), or
|
|
94
|
+
* b. Contain gotcha-like language in action/result text.
|
|
95
|
+
* 3. For each candidate step, determine whether it looks like a gotcha
|
|
96
|
+
* or a pattern (keyword matching).
|
|
97
|
+
* 4. Deduplicate against existing knowledge (semantic similarity check).
|
|
98
|
+
* 5. Promote unique candidates to KnowledgeItem records.
|
|
99
|
+
* 6. Return the list of newly created items.
|
|
100
|
+
*/
|
|
101
|
+
async extractFromProject(projectId: string): Promise<KnowledgeItem[]> {
|
|
102
|
+
// Load all trajectories for the project
|
|
103
|
+
const trajectoryRows = this.db.all<TrajectoryRow>(
|
|
104
|
+
`SELECT * FROM trajectories WHERE project_id = ? ORDER BY started_at ASC`,
|
|
105
|
+
[projectId],
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (trajectoryRows.length === 0) {
|
|
109
|
+
logger.debug('PatternExtractor: no trajectories for project', { projectId });
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Load all steps for these trajectories in one query
|
|
114
|
+
const trajectoryIds = trajectoryRows.map((t) => t.id);
|
|
115
|
+
const placeholders = trajectoryIds.map(() => '?').join(', ');
|
|
116
|
+
const stepRows = this.db.all<TrajectoryStepRow>(
|
|
117
|
+
`SELECT * FROM trajectory_steps WHERE trajectory_id IN (${placeholders}) ORDER BY created_at ASC`,
|
|
118
|
+
trajectoryIds,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Build a map from trajectory_id -> trajectory for phase/agent lookup
|
|
122
|
+
const trajectoryMap = new Map<string, TrajectoryRow>(
|
|
123
|
+
trajectoryRows.map((t) => [t.id, t]),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Identify candidate steps
|
|
127
|
+
const candidates = stepRows.filter((step) => {
|
|
128
|
+
const hasHighQuality =
|
|
129
|
+
step.quality_score !== null && step.quality_score >= QUALITY_THRESHOLD;
|
|
130
|
+
const text = `${step.action} ${step.result ?? ''}`.toLowerCase();
|
|
131
|
+
const hasGotchaLanguage = GOTCHA_KEYWORDS.some((kw) => text.includes(kw));
|
|
132
|
+
const hasPatternLanguage = PATTERN_KEYWORDS.some((kw) => text.includes(kw));
|
|
133
|
+
return hasHighQuality || hasGotchaLanguage || hasPatternLanguage;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (candidates.length === 0) {
|
|
137
|
+
logger.debug('PatternExtractor: no candidate steps found', { projectId });
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Look up repo_id from the project
|
|
142
|
+
const projectRow = this.db.get<{ repo_id: string }>(
|
|
143
|
+
`SELECT repo_id FROM projects WHERE id = ?`,
|
|
144
|
+
[projectId],
|
|
145
|
+
);
|
|
146
|
+
const repoId = projectRow?.repo_id ?? 'unknown';
|
|
147
|
+
|
|
148
|
+
// Promote each candidate (with deduplication)
|
|
149
|
+
const promoted: KnowledgeItem[] = [];
|
|
150
|
+
|
|
151
|
+
for (const step of candidates) {
|
|
152
|
+
const traj = trajectoryMap.get(step.trajectory_id);
|
|
153
|
+
const observation = buildObservationText(step);
|
|
154
|
+
const category = classifyCategory(step);
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
// Check for near-duplicate in existing knowledge
|
|
158
|
+
const isDuplicate = await this.isDuplicateKnowledge(observation, category);
|
|
159
|
+
if (isDuplicate) {
|
|
160
|
+
logger.debug('PatternExtractor: skipping duplicate', {
|
|
161
|
+
action: step.action.slice(0, 60),
|
|
162
|
+
});
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const item = await this.promoteToKnowledge(
|
|
167
|
+
observation,
|
|
168
|
+
category,
|
|
169
|
+
repoId,
|
|
170
|
+
traj ? (traj.phase as Phase) : undefined,
|
|
171
|
+
traj?.agent,
|
|
172
|
+
);
|
|
173
|
+
promoted.push(item);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
// Non-fatal: log and continue to next candidate
|
|
176
|
+
logger.warn('PatternExtractor: failed to promote step', {
|
|
177
|
+
trajectoryId: step.trajectory_id,
|
|
178
|
+
error: String(err),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
logger.info('PatternExtractor: extraction complete', {
|
|
184
|
+
projectId,
|
|
185
|
+
candidateCount: candidates.length,
|
|
186
|
+
promotedCount: promoted.length,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return promoted;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Promote a raw observation string to a full KnowledgeItem.
|
|
194
|
+
*
|
|
195
|
+
* Steps:
|
|
196
|
+
* 1. Synthesise a title from the first sentence of the observation.
|
|
197
|
+
* 2. Build a KnowledgeItem with appropriate defaults.
|
|
198
|
+
* 3. Add to the YAML store (the git-tracked source of truth).
|
|
199
|
+
* 4. Embed and upsert to Qdrant via hydrateItem() (if available).
|
|
200
|
+
* 5. Return the new item.
|
|
201
|
+
*/
|
|
202
|
+
async promoteToKnowledge(
|
|
203
|
+
observation: string,
|
|
204
|
+
category: KnowledgeCategory,
|
|
205
|
+
repoId: string,
|
|
206
|
+
phase?: Phase | PipelinePhase,
|
|
207
|
+
agent?: string,
|
|
208
|
+
): Promise<KnowledgeItem> {
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
const prefix = categoryPrefix(category);
|
|
211
|
+
const id = `${prefix}-${randomUUID().slice(0, 8)}`;
|
|
212
|
+
const title = deriveTitle(observation, category);
|
|
213
|
+
|
|
214
|
+
const item: KnowledgeItem = {
|
|
215
|
+
id,
|
|
216
|
+
title,
|
|
217
|
+
content: observation,
|
|
218
|
+
stack_tags: [], // Extracted without stack context; caller can enrich
|
|
219
|
+
confidence: 0.6, // New items start at moderate confidence
|
|
220
|
+
source: 'agent',
|
|
221
|
+
// Phase values from both Phase and PipelinePhase unions are valid strings
|
|
222
|
+
// in YAML. We cast to satisfy the KnowledgeItem type declaration.
|
|
223
|
+
source_phase: (phase ?? null) as PipelinePhase | null,
|
|
224
|
+
source_agent: agent ?? null,
|
|
225
|
+
created_at: now,
|
|
226
|
+
updated_at: now,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Persist to YAML (source of truth)
|
|
230
|
+
this.knowledgeStore.addItem(item);
|
|
231
|
+
|
|
232
|
+
// Embed and upsert to Qdrant via the KnowledgeSearch's backing store
|
|
233
|
+
// The KnowledgeSearch class does not expose hydrateItem directly — we
|
|
234
|
+
// go through the embedder + vectorStore path manually.
|
|
235
|
+
try {
|
|
236
|
+
const text = `${title} ${observation}`;
|
|
237
|
+
const vector = await this.embedder(text);
|
|
238
|
+
|
|
239
|
+
// Access the backing QdrantVectorStore from KnowledgeSearch
|
|
240
|
+
const store = (this.knowledgeSearch as unknown as {
|
|
241
|
+
vectorStore?: { upsertKnowledge?: (id: string, vector: number[], payload: Record<string, unknown>) => Promise<void> };
|
|
242
|
+
}).vectorStore;
|
|
243
|
+
|
|
244
|
+
if (store?.upsertKnowledge) {
|
|
245
|
+
await store.upsertKnowledge(id, vector, {
|
|
246
|
+
id,
|
|
247
|
+
repo_id: repoId,
|
|
248
|
+
category,
|
|
249
|
+
title,
|
|
250
|
+
content: observation,
|
|
251
|
+
stack_tags: [],
|
|
252
|
+
confidence: 0.6,
|
|
253
|
+
source: 'agent',
|
|
254
|
+
source_phase: phase ?? null,
|
|
255
|
+
source_agent: agent ?? null,
|
|
256
|
+
sharing: 'private',
|
|
257
|
+
created_at: now,
|
|
258
|
+
updated_at: now,
|
|
259
|
+
accessed_at: now,
|
|
260
|
+
access_count: 0,
|
|
261
|
+
});
|
|
262
|
+
logger.debug('PatternExtractor: item upserted to Qdrant', { id });
|
|
263
|
+
} else {
|
|
264
|
+
logger.debug('PatternExtractor: Qdrant upsert unavailable, YAML only', { id });
|
|
265
|
+
}
|
|
266
|
+
} catch (err) {
|
|
267
|
+
// Non-fatal: YAML write succeeded, Qdrant will be synced on next hydration
|
|
268
|
+
logger.warn('PatternExtractor: Qdrant upsert failed (YAML write succeeded)', {
|
|
269
|
+
id,
|
|
270
|
+
error: String(err),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
logger.info('PatternExtractor: knowledge item promoted', {
|
|
275
|
+
id,
|
|
276
|
+
category,
|
|
277
|
+
title,
|
|
278
|
+
repoId,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return item;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// Private helpers
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Check whether the observation text is semantically too close to an
|
|
290
|
+
* existing knowledge item (similarity > 0.92). If so, we skip promotion
|
|
291
|
+
* to avoid building up near-duplicate entries over many projects.
|
|
292
|
+
*/
|
|
293
|
+
private async isDuplicateKnowledge(
|
|
294
|
+
observation: string,
|
|
295
|
+
category: KnowledgeCategory,
|
|
296
|
+
): Promise<boolean> {
|
|
297
|
+
try {
|
|
298
|
+
const results = await this.knowledgeSearch.search(observation, {
|
|
299
|
+
category,
|
|
300
|
+
limit: 1,
|
|
301
|
+
min_confidence: 0.0,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
return results.length > 0 && results[0]!.relevance_score > 0.92;
|
|
305
|
+
} catch {
|
|
306
|
+
// If search fails, assume not duplicate so we don't lose knowledge
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
// Internal utilities
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
function buildObservationText(step: TrajectoryStepRow): string {
|
|
317
|
+
const parts: string[] = [step.action];
|
|
318
|
+
if (step.result) parts.push(step.result);
|
|
319
|
+
return parts.join('\n').trim();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function classifyCategory(step: TrajectoryStepRow): KnowledgeCategory {
|
|
323
|
+
const text = `${step.action} ${step.result ?? ''}`.toLowerCase();
|
|
324
|
+
const gotchaScore = GOTCHA_KEYWORDS.filter((kw) => text.includes(kw)).length;
|
|
325
|
+
const patternScore = PATTERN_KEYWORDS.filter((kw) => text.includes(kw)).length;
|
|
326
|
+
|
|
327
|
+
if (gotchaScore > patternScore) return 'gotcha';
|
|
328
|
+
if (patternScore > gotchaScore) return 'pattern';
|
|
329
|
+
|
|
330
|
+
// If quality score is high, lean toward "pattern" (positive finding)
|
|
331
|
+
if (step.quality_score !== null && step.quality_score >= QUALITY_THRESHOLD) {
|
|
332
|
+
return 'pattern';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return 'gotcha';
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function categoryPrefix(category: KnowledgeCategory): string {
|
|
339
|
+
switch (category) {
|
|
340
|
+
case 'gotcha': return 'gotcha';
|
|
341
|
+
case 'pattern': return 'pattern';
|
|
342
|
+
case 'decision': return 'decision';
|
|
343
|
+
case 'convention': return 'convention';
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function deriveTitle(observation: string, category: KnowledgeCategory): string {
|
|
348
|
+
// Use the first sentence (up to 80 chars) as the title
|
|
349
|
+
const firstSentence = observation.split(/[.!?\n]/)[0]?.trim() ?? observation;
|
|
350
|
+
const truncated = firstSentence.length > 80
|
|
351
|
+
? firstSentence.slice(0, 77) + '...'
|
|
352
|
+
: firstSentence;
|
|
353
|
+
|
|
354
|
+
// Prefix with category for clarity when browsing YAML files
|
|
355
|
+
const prefix = category.charAt(0).toUpperCase() + category.slice(1);
|
|
356
|
+
return `${prefix}: ${truncated}`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Re-export keyword arrays so callers (e.g., tests) can reason about classification
|
|
360
|
+
export { GOTCHA_KEYWORDS, PATTERN_KEYWORDS, QUALITY_THRESHOLD };
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// TrajectoryRecorder — B10
|
|
2
|
+
//
|
|
3
|
+
// Server-side automatic trajectory recording. Agents do NOT call this directly
|
|
4
|
+
// — the pipeline tool handlers call startTrajectory() and recordStep() as a
|
|
5
|
+
// side effect of processing forge.start_* and forge.submit_* tool calls.
|
|
6
|
+
//
|
|
7
|
+
// Storage: SQLite `trajectories` and `trajectory_steps` tables.
|
|
8
|
+
// All operations are synchronous (better-sqlite3). No async needed.
|
|
9
|
+
//
|
|
10
|
+
// Design notes:
|
|
11
|
+
// - startTrajectory() is safe to call multiple times for the same
|
|
12
|
+
// (projectId, phase, agent) tuple — it creates a fresh trajectory each
|
|
13
|
+
// time, returning the new ID. Old active trajectories for the same project
|
|
14
|
+
// are left in the DB as historical records.
|
|
15
|
+
// - recordStep() silently returns if `trajectoryId` is not found. This
|
|
16
|
+
// prevents a missing trajectory from crashing a tool handler.
|
|
17
|
+
// - completeTrajectory() and failTrajectory() are idempotent — they only
|
|
18
|
+
// update rows whose status is 'active'.
|
|
19
|
+
|
|
20
|
+
import { randomUUID } from 'node:crypto';
|
|
21
|
+
import type { PipelineDB } from '../storage/sqlite.js';
|
|
22
|
+
import type {
|
|
23
|
+
Trajectory,
|
|
24
|
+
TrajectoryRow,
|
|
25
|
+
TrajectoryStep,
|
|
26
|
+
TrajectoryStepRow,
|
|
27
|
+
TrajectoryStepMetadata,
|
|
28
|
+
PipelinePhase,
|
|
29
|
+
} from '../util/types.js';
|
|
30
|
+
import { logger } from '../util/logger.js';
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Row deserializers
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
function rowToTrajectory(row: TrajectoryRow): Trajectory {
|
|
37
|
+
return {
|
|
38
|
+
id: row.id,
|
|
39
|
+
projectId: row.project_id,
|
|
40
|
+
phase: row.phase as PipelinePhase,
|
|
41
|
+
agent: row.agent,
|
|
42
|
+
status: row.status,
|
|
43
|
+
startedAt: row.started_at,
|
|
44
|
+
completedAt: row.completed_at ?? null,
|
|
45
|
+
success: row.success === null ? null : row.success === 1,
|
|
46
|
+
feedback: row.feedback ?? null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function rowToStep(row: TrajectoryStepRow): TrajectoryStep {
|
|
51
|
+
let metadata: TrajectoryStepMetadata = {};
|
|
52
|
+
try {
|
|
53
|
+
metadata = JSON.parse(row.metadata) as TrajectoryStepMetadata;
|
|
54
|
+
} catch {
|
|
55
|
+
metadata = {};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
id: row.id,
|
|
59
|
+
trajectoryId: row.trajectory_id,
|
|
60
|
+
action: row.action,
|
|
61
|
+
result: row.result ?? null,
|
|
62
|
+
qualityScore: row.quality_score ?? null,
|
|
63
|
+
metadata,
|
|
64
|
+
createdAt: row.created_at,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// TrajectoryRecorder
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
export class TrajectoryRecorder {
|
|
73
|
+
constructor(private readonly db: PipelineDB) {}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Start a new trajectory for the given project / phase / agent.
|
|
77
|
+
* Returns the UUID of the newly created trajectory.
|
|
78
|
+
*
|
|
79
|
+
* Side-effect: any previously active trajectory for this project is NOT
|
|
80
|
+
* auto-closed — they are left as historical data. The caller is responsible
|
|
81
|
+
* for calling completeTrajectory() or failTrajectory() when appropriate.
|
|
82
|
+
*/
|
|
83
|
+
startTrajectory(projectId: string, phase: PipelinePhase, agent: string): string {
|
|
84
|
+
const id = randomUUID();
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
|
|
87
|
+
this.db.run(
|
|
88
|
+
`INSERT INTO trajectories (id, project_id, phase, agent, status, started_at, completed_at, success, feedback)
|
|
89
|
+
VALUES (?, ?, ?, ?, 'active', ?, NULL, NULL, NULL)`,
|
|
90
|
+
[id, projectId, phase, agent, now],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
logger.debug('TrajectoryRecorder: trajectory started', {
|
|
94
|
+
id,
|
|
95
|
+
projectId,
|
|
96
|
+
phase,
|
|
97
|
+
agent,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return id;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Record a step in an existing trajectory.
|
|
105
|
+
* Silently returns when the trajectoryId does not exist in the DB — this
|
|
106
|
+
* prevents missing trajectories from surfacing as user-facing errors.
|
|
107
|
+
*/
|
|
108
|
+
recordStep(
|
|
109
|
+
trajectoryId: string,
|
|
110
|
+
action: string,
|
|
111
|
+
result?: string,
|
|
112
|
+
qualityScore?: number,
|
|
113
|
+
metadata?: Record<string, unknown>,
|
|
114
|
+
): void {
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
this.db.run(
|
|
119
|
+
`INSERT INTO trajectory_steps (trajectory_id, action, result, quality_score, metadata, created_at)
|
|
120
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
121
|
+
[
|
|
122
|
+
trajectoryId,
|
|
123
|
+
action,
|
|
124
|
+
result ?? null,
|
|
125
|
+
qualityScore ?? null,
|
|
126
|
+
JSON.stringify(metadata ?? {}),
|
|
127
|
+
now,
|
|
128
|
+
],
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
logger.debug('TrajectoryRecorder: step recorded', {
|
|
132
|
+
trajectoryId,
|
|
133
|
+
action: action.slice(0, 80),
|
|
134
|
+
qualityScore,
|
|
135
|
+
});
|
|
136
|
+
} catch (err) {
|
|
137
|
+
// Non-fatal — step recording must never crash the tool handler
|
|
138
|
+
logger.warn('TrajectoryRecorder: failed to record step (non-fatal)', {
|
|
139
|
+
trajectoryId,
|
|
140
|
+
error: String(err),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Mark a trajectory as successfully completed.
|
|
147
|
+
* Idempotent — only updates rows with status='active'.
|
|
148
|
+
*/
|
|
149
|
+
completeTrajectory(trajectoryId: string, feedback?: string): void {
|
|
150
|
+
this.db.run(
|
|
151
|
+
`UPDATE trajectories
|
|
152
|
+
SET status = 'complete', completed_at = ?, success = 1, feedback = ?
|
|
153
|
+
WHERE id = ? AND status = 'active'`,
|
|
154
|
+
[Date.now(), feedback ?? null, trajectoryId],
|
|
155
|
+
);
|
|
156
|
+
logger.debug('TrajectoryRecorder: trajectory completed', { trajectoryId });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Mark a trajectory as failed.
|
|
161
|
+
* Idempotent — only updates rows with status='active'.
|
|
162
|
+
*/
|
|
163
|
+
failTrajectory(trajectoryId: string, feedback?: string): void {
|
|
164
|
+
this.db.run(
|
|
165
|
+
`UPDATE trajectories
|
|
166
|
+
SET status = 'failed', completed_at = ?, success = 0, feedback = ?
|
|
167
|
+
WHERE id = ? AND status = 'active'`,
|
|
168
|
+
[Date.now(), feedback ?? null, trajectoryId],
|
|
169
|
+
);
|
|
170
|
+
logger.debug('TrajectoryRecorder: trajectory failed', { trajectoryId });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Return the most recent active trajectory for a project, or null.
|
|
175
|
+
* "Active" means status = 'active'.
|
|
176
|
+
*/
|
|
177
|
+
getActiveTrajectory(projectId: string): Trajectory | null {
|
|
178
|
+
const row = this.db.get<TrajectoryRow>(
|
|
179
|
+
`SELECT * FROM trajectories
|
|
180
|
+
WHERE project_id = ? AND status = 'active'
|
|
181
|
+
ORDER BY started_at DESC LIMIT 1`,
|
|
182
|
+
[projectId],
|
|
183
|
+
);
|
|
184
|
+
return row ? rowToTrajectory(row) : null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Return all trajectories for a project, ordered by started_at ascending.
|
|
189
|
+
*/
|
|
190
|
+
getTrajectories(projectId: string): Trajectory[] {
|
|
191
|
+
const rows = this.db.all<TrajectoryRow>(
|
|
192
|
+
`SELECT * FROM trajectories WHERE project_id = ? ORDER BY started_at ASC`,
|
|
193
|
+
[projectId],
|
|
194
|
+
);
|
|
195
|
+
return rows.map(rowToTrajectory);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Return all steps for a trajectory, ordered by created_at ascending.
|
|
200
|
+
*/
|
|
201
|
+
getSteps(trajectoryId: string): TrajectoryStep[] {
|
|
202
|
+
const rows = this.db.all<TrajectoryStepRow>(
|
|
203
|
+
`SELECT * FROM trajectory_steps WHERE trajectory_id = ? ORDER BY created_at ASC`,
|
|
204
|
+
[trajectoryId],
|
|
205
|
+
);
|
|
206
|
+
return rows.map(rowToStep);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Produce a summary of all trajectory activity for a project.
|
|
211
|
+
* Used by the knowledge_collection phase to feed the PatternExtractor.
|
|
212
|
+
*/
|
|
213
|
+
getSummary(projectId: string): {
|
|
214
|
+
totalSteps: number;
|
|
215
|
+
phasesCompleted: number;
|
|
216
|
+
cycles: Record<string, number>;
|
|
217
|
+
durationMinutes: number;
|
|
218
|
+
} {
|
|
219
|
+
const trajectories = this.getTrajectories(projectId);
|
|
220
|
+
|
|
221
|
+
if (trajectories.length === 0) {
|
|
222
|
+
return {
|
|
223
|
+
totalSteps: 0,
|
|
224
|
+
phasesCompleted: 0,
|
|
225
|
+
cycles: {},
|
|
226
|
+
durationMinutes: 0,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Count total steps across all trajectories
|
|
231
|
+
const totalStepsRow = this.db.get<{ cnt: number }>(
|
|
232
|
+
`SELECT COUNT(*) as cnt FROM trajectory_steps ts
|
|
233
|
+
JOIN trajectories t ON ts.trajectory_id = t.id
|
|
234
|
+
WHERE t.project_id = ?`,
|
|
235
|
+
[projectId],
|
|
236
|
+
);
|
|
237
|
+
const totalSteps = totalStepsRow?.cnt ?? 0;
|
|
238
|
+
|
|
239
|
+
// Count completed phases
|
|
240
|
+
const phasesCompleted = trajectories.filter(
|
|
241
|
+
(t) => t.status === 'complete' && t.success === true,
|
|
242
|
+
).length;
|
|
243
|
+
|
|
244
|
+
// Count cycles: phases that appear more than once in completed trajectories
|
|
245
|
+
const phaseCompletions = trajectories
|
|
246
|
+
.filter((t) => t.status === 'complete')
|
|
247
|
+
.reduce<Record<string, number>>((acc, t) => {
|
|
248
|
+
acc[t.phase] = (acc[t.phase] ?? 0) + 1;
|
|
249
|
+
return acc;
|
|
250
|
+
}, {});
|
|
251
|
+
|
|
252
|
+
const cycles: Record<string, number> = {};
|
|
253
|
+
for (const [phase, count] of Object.entries(phaseCompletions)) {
|
|
254
|
+
if (count > 1) {
|
|
255
|
+
cycles[phase] = count - 1; // cycles = extra runs beyond the first
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Duration: from first startedAt to last completedAt (or now if still active)
|
|
260
|
+
const firstStart = Math.min(...trajectories.map((t) => t.startedAt));
|
|
261
|
+
const lastEnd = Math.max(
|
|
262
|
+
...trajectories.map((t) => t.completedAt ?? Date.now()),
|
|
263
|
+
);
|
|
264
|
+
const durationMinutes = Math.round((lastEnd - firstStart) / 60_000);
|
|
265
|
+
|
|
266
|
+
return { totalSteps, phasesCompleted, cycles, durationMinutes };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
File without changes
|