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,320 @@
|
|
|
1
|
+
// edge-collector.ts — Forge LSP Bridge
|
|
2
|
+
// On file save, uses VS Code's built-in call hierarchy API to collect
|
|
3
|
+
// type-resolved CALLS edges from the TypeScript Language Server.
|
|
4
|
+
// Edges are batched and flushed to the forge server after a debounce period.
|
|
5
|
+
|
|
6
|
+
import * as vscode from 'vscode';
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Types
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export interface LspEdge {
|
|
13
|
+
from_file: string;
|
|
14
|
+
from_symbol: string;
|
|
15
|
+
to_file: string;
|
|
16
|
+
to_symbol: string;
|
|
17
|
+
edge_type: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type FlushCallback = (edges: LspEdge[]) => Promise<void>;
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// EdgeCollector
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
export class EdgeCollector {
|
|
27
|
+
private pendingEdges: LspEdge[] = [];
|
|
28
|
+
private debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
29
|
+
private debounceMs: number;
|
|
30
|
+
private readonly flushCallback: FlushCallback;
|
|
31
|
+
private flushing = false;
|
|
32
|
+
|
|
33
|
+
constructor(debounceMs: number, flushCallback: FlushCallback) {
|
|
34
|
+
this.debounceMs = debounceMs;
|
|
35
|
+
this.flushCallback = flushCallback;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Update the debounce interval (e.g. when the user changes settings).
|
|
40
|
+
*/
|
|
41
|
+
setDebounceMs(ms: number): void {
|
|
42
|
+
this.debounceMs = ms;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Collect call-hierarchy edges for all functions/methods in a saved document.
|
|
47
|
+
*/
|
|
48
|
+
async collectFromDocument(document: vscode.TextDocument): Promise<void> {
|
|
49
|
+
const uri = document.uri;
|
|
50
|
+
|
|
51
|
+
// Get all document symbols to find functions, methods, constructors
|
|
52
|
+
const symbols = await this.getCallableSymbols(uri);
|
|
53
|
+
|
|
54
|
+
if (symbols.length === 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// For each callable symbol, prepare a call hierarchy and collect outgoing calls
|
|
59
|
+
for (const sym of symbols) {
|
|
60
|
+
try {
|
|
61
|
+
const edges = await this.collectOutgoingCalls(uri, sym);
|
|
62
|
+
if (edges.length > 0) {
|
|
63
|
+
this.pendingEdges.push(...edges);
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Individual symbol failures are non-fatal — skip and continue.
|
|
67
|
+
// This can happen if the TS language server hasn't finished loading
|
|
68
|
+
// or if the symbol is in an unsupported position.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Reset the debounce timer
|
|
73
|
+
this.scheduleFlush();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Force an immediate flush of all pending edges, regardless of debounce.
|
|
78
|
+
*/
|
|
79
|
+
async flushNow(): Promise<void> {
|
|
80
|
+
this.cancelDebounce();
|
|
81
|
+
await this.doFlush();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// -------------------------------------------------------------------------
|
|
85
|
+
// Symbol discovery
|
|
86
|
+
// -------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Flatten the document symbol tree and return only callable symbols
|
|
90
|
+
* (functions, methods, constructors).
|
|
91
|
+
*/
|
|
92
|
+
private async getCallableSymbols(
|
|
93
|
+
uri: vscode.Uri,
|
|
94
|
+
): Promise<FlatSymbol[]> {
|
|
95
|
+
let rawSymbols: vscode.DocumentSymbol[] | undefined;
|
|
96
|
+
try {
|
|
97
|
+
rawSymbols = await vscode.commands.executeCommand<vscode.DocumentSymbol[]>(
|
|
98
|
+
'vscode.executeDocumentSymbolProvider',
|
|
99
|
+
uri,
|
|
100
|
+
);
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!rawSymbols || rawSymbols.length === 0) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const result: FlatSymbol[] = [];
|
|
110
|
+
this.flattenSymbols(rawSymbols, uri, result, undefined);
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Recursively flatten nested DocumentSymbols into a flat list of callable
|
|
116
|
+
* symbols. Tracks parent name to build qualified symbol names like
|
|
117
|
+
* "ClassName.methodName".
|
|
118
|
+
*/
|
|
119
|
+
private flattenSymbols(
|
|
120
|
+
symbols: vscode.DocumentSymbol[],
|
|
121
|
+
uri: vscode.Uri,
|
|
122
|
+
out: FlatSymbol[],
|
|
123
|
+
parentName: string | undefined,
|
|
124
|
+
): void {
|
|
125
|
+
for (const sym of symbols) {
|
|
126
|
+
const qualifiedName = parentName
|
|
127
|
+
? `${parentName}.${sym.name}`
|
|
128
|
+
: sym.name;
|
|
129
|
+
|
|
130
|
+
if (isCallable(sym.kind)) {
|
|
131
|
+
out.push({
|
|
132
|
+
name: qualifiedName,
|
|
133
|
+
kind: sym.kind,
|
|
134
|
+
range: sym.selectionRange,
|
|
135
|
+
uri,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Recurse into children (e.g. methods inside a class)
|
|
140
|
+
if (sym.children && sym.children.length > 0) {
|
|
141
|
+
const nextParent =
|
|
142
|
+
sym.kind === vscode.SymbolKind.Class ||
|
|
143
|
+
sym.kind === vscode.SymbolKind.Interface ||
|
|
144
|
+
sym.kind === vscode.SymbolKind.Namespace ||
|
|
145
|
+
sym.kind === vscode.SymbolKind.Module
|
|
146
|
+
? qualifiedName
|
|
147
|
+
: parentName;
|
|
148
|
+
this.flattenSymbols(sym.children, uri, out, nextParent);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// -------------------------------------------------------------------------
|
|
154
|
+
// Call hierarchy collection
|
|
155
|
+
// -------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* For a single callable symbol, prepare a call hierarchy item and collect
|
|
159
|
+
* its outgoing calls. Returns LspEdge[] for "CALLS" relationships.
|
|
160
|
+
*/
|
|
161
|
+
private async collectOutgoingCalls(
|
|
162
|
+
uri: vscode.Uri,
|
|
163
|
+
sym: FlatSymbol,
|
|
164
|
+
): Promise<LspEdge[]> {
|
|
165
|
+
// Prepare call hierarchy at the symbol's selection position
|
|
166
|
+
const position = sym.range.start;
|
|
167
|
+
let callItems: vscode.CallHierarchyItem[] | undefined;
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
callItems = await vscode.commands.executeCommand<vscode.CallHierarchyItem[]>(
|
|
171
|
+
'vscode.prepareCallHierarchy',
|
|
172
|
+
uri,
|
|
173
|
+
position,
|
|
174
|
+
);
|
|
175
|
+
} catch {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!callItems || callItems.length === 0) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const edges: LspEdge[] = [];
|
|
184
|
+
|
|
185
|
+
for (const item of callItems) {
|
|
186
|
+
let outgoingCalls: vscode.CallHierarchyOutgoingCall[] | undefined;
|
|
187
|
+
try {
|
|
188
|
+
outgoingCalls = await vscode.commands.executeCommand<vscode.CallHierarchyOutgoingCall[]>(
|
|
189
|
+
'vscode.provideOutgoingCalls',
|
|
190
|
+
item,
|
|
191
|
+
);
|
|
192
|
+
} catch {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!outgoingCalls || outgoingCalls.length === 0) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const fromFile = normalizeFilePath(item.uri.fsPath);
|
|
201
|
+
const fromSymbol = item.name;
|
|
202
|
+
|
|
203
|
+
for (const call of outgoingCalls) {
|
|
204
|
+
const toFile = normalizeFilePath(call.to.uri.fsPath);
|
|
205
|
+
const toSymbol = call.to.name;
|
|
206
|
+
|
|
207
|
+
// Skip self-references and node_modules
|
|
208
|
+
if (fromSymbol === toSymbol && fromFile === toFile) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (toFile.includes('node_modules')) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
edges.push({
|
|
216
|
+
from_file: fromFile,
|
|
217
|
+
from_symbol: fromSymbol,
|
|
218
|
+
to_file: toFile,
|
|
219
|
+
to_symbol: toSymbol,
|
|
220
|
+
edge_type: 'CALLS',
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return edges;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// -------------------------------------------------------------------------
|
|
229
|
+
// Debounced flush
|
|
230
|
+
// -------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
private scheduleFlush(): void {
|
|
233
|
+
this.cancelDebounce();
|
|
234
|
+
this.debounceTimer = setTimeout(() => {
|
|
235
|
+
void this.doFlush();
|
|
236
|
+
}, this.debounceMs);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private cancelDebounce(): void {
|
|
240
|
+
if (this.debounceTimer !== undefined) {
|
|
241
|
+
clearTimeout(this.debounceTimer);
|
|
242
|
+
this.debounceTimer = undefined;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private async doFlush(): Promise<void> {
|
|
247
|
+
if (this.flushing) {
|
|
248
|
+
// Re-schedule if a flush is already in progress
|
|
249
|
+
this.scheduleFlush();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (this.pendingEdges.length === 0) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Take ownership of the pending edges
|
|
258
|
+
const batch = this.pendingEdges;
|
|
259
|
+
this.pendingEdges = [];
|
|
260
|
+
this.flushing = true;
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
// Deduplicate edges within the batch
|
|
264
|
+
const deduplicated = deduplicateEdges(batch);
|
|
265
|
+
await this.flushCallback(deduplicated);
|
|
266
|
+
} catch {
|
|
267
|
+
// If flush fails, re-queue the edges for the next attempt
|
|
268
|
+
this.pendingEdges.unshift(...batch);
|
|
269
|
+
} finally {
|
|
270
|
+
this.flushing = false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// Helpers
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
interface FlatSymbol {
|
|
280
|
+
name: string;
|
|
281
|
+
kind: vscode.SymbolKind;
|
|
282
|
+
range: vscode.Range;
|
|
283
|
+
uri: vscode.Uri;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Returns true if the symbol kind represents something that can make calls.
|
|
288
|
+
*/
|
|
289
|
+
function isCallable(kind: vscode.SymbolKind): boolean {
|
|
290
|
+
return (
|
|
291
|
+
kind === vscode.SymbolKind.Function ||
|
|
292
|
+
kind === vscode.SymbolKind.Method ||
|
|
293
|
+
kind === vscode.SymbolKind.Constructor
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Normalize file paths to use forward slashes for consistency.
|
|
299
|
+
*/
|
|
300
|
+
function normalizeFilePath(filePath: string): string {
|
|
301
|
+
return filePath.replace(/\\/g, '/');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Remove duplicate edges within a batch (same from/to file+symbol+type).
|
|
306
|
+
*/
|
|
307
|
+
function deduplicateEdges(edges: LspEdge[]): LspEdge[] {
|
|
308
|
+
const seen = new Set<string>();
|
|
309
|
+
const result: LspEdge[] = [];
|
|
310
|
+
|
|
311
|
+
for (const edge of edges) {
|
|
312
|
+
const key = `${edge.from_file}:${edge.from_symbol}->${edge.to_file}:${edge.to_symbol}:${edge.edge_type}`;
|
|
313
|
+
if (!seen.has(key)) {
|
|
314
|
+
seen.add(key);
|
|
315
|
+
result.push(edge);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// extension.ts — Forge LSP Bridge
|
|
2
|
+
// VS Code extension entry point. Hooks into file-save events for TS/JS files,
|
|
3
|
+
// collects LSP-resolved call edges via the edge collector, and flushes them
|
|
4
|
+
// to the dk-forge MCP server via the forge client.
|
|
5
|
+
|
|
6
|
+
import * as vscode from 'vscode';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import { EdgeCollector } from './edge-collector';
|
|
10
|
+
import { ForgeClient } from './forge-client';
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// State
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
let edgeCollector: EdgeCollector | undefined;
|
|
17
|
+
let forgeClient: ForgeClient | undefined;
|
|
18
|
+
let statusBarItem: vscode.StatusBarItem | undefined;
|
|
19
|
+
let saveListener: vscode.Disposable | undefined;
|
|
20
|
+
let enabled = true;
|
|
21
|
+
let repoId = '';
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Activation
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
export async function activate(context: vscode.ExtensionContext): Promise<void> {
|
|
28
|
+
const config = vscode.workspace.getConfiguration('forge.lspBridge');
|
|
29
|
+
enabled = config.get<boolean>('enabled', true);
|
|
30
|
+
|
|
31
|
+
// Auto-detect repo ID from .forge/manifest.yaml if not configured
|
|
32
|
+
repoId = config.get<string>('repoId', '') || '';
|
|
33
|
+
if (!repoId) {
|
|
34
|
+
repoId = await detectRepoId();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const debounceMs = config.get<number>('debounceMs', 5000);
|
|
38
|
+
const mcpServerPath = config.get<string>('mcpServerPath', '') || '';
|
|
39
|
+
|
|
40
|
+
// Initialize forge MCP client
|
|
41
|
+
forgeClient = new ForgeClient(mcpServerPath);
|
|
42
|
+
|
|
43
|
+
// Initialize edge collector with flush callback
|
|
44
|
+
edgeCollector = new EdgeCollector(debounceMs, async (edges) => {
|
|
45
|
+
if (!forgeClient || !repoId) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const result = await forgeClient.submitLspEdges(repoId, edges);
|
|
50
|
+
if (result && typeof result.accepted === 'number') {
|
|
51
|
+
log(`Flushed ${result.accepted} edges (${result.failed ?? 0} failed)`);
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
log(`Flush failed: ${String(err)}`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Status bar item
|
|
59
|
+
statusBarItem = vscode.window.createStatusBarItem(
|
|
60
|
+
vscode.StatusBarAlignment.Right,
|
|
61
|
+
50,
|
|
62
|
+
);
|
|
63
|
+
statusBarItem.command = 'forge.lspBridge.toggle';
|
|
64
|
+
updateStatusBar();
|
|
65
|
+
statusBarItem.show();
|
|
66
|
+
context.subscriptions.push(statusBarItem);
|
|
67
|
+
|
|
68
|
+
// Register the save listener
|
|
69
|
+
if (enabled) {
|
|
70
|
+
registerSaveListener(context);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Register commands
|
|
74
|
+
context.subscriptions.push(
|
|
75
|
+
vscode.commands.registerCommand('forge.lspBridge.toggle', () => {
|
|
76
|
+
enabled = !enabled;
|
|
77
|
+
if (enabled) {
|
|
78
|
+
registerSaveListener(context);
|
|
79
|
+
log('Enabled');
|
|
80
|
+
} else {
|
|
81
|
+
disposeSaveListener();
|
|
82
|
+
log('Disabled');
|
|
83
|
+
}
|
|
84
|
+
updateStatusBar();
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
context.subscriptions.push(
|
|
89
|
+
vscode.commands.registerCommand('forge.lspBridge.flush', async () => {
|
|
90
|
+
if (!edgeCollector) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
await edgeCollector.flushNow();
|
|
94
|
+
log('Manual flush complete');
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Watch for configuration changes
|
|
99
|
+
context.subscriptions.push(
|
|
100
|
+
vscode.workspace.onDidChangeConfiguration((e) => {
|
|
101
|
+
if (e.affectsConfiguration('forge.lspBridge')) {
|
|
102
|
+
const newConfig = vscode.workspace.getConfiguration('forge.lspBridge');
|
|
103
|
+
const newEnabled = newConfig.get<boolean>('enabled', true);
|
|
104
|
+
const newRepoId = newConfig.get<string>('repoId', '') || '';
|
|
105
|
+
const newDebounce = newConfig.get<number>('debounceMs', 5000);
|
|
106
|
+
|
|
107
|
+
if (newRepoId) {
|
|
108
|
+
repoId = newRepoId;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (edgeCollector) {
|
|
112
|
+
edgeCollector.setDebounceMs(newDebounce);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (newEnabled !== enabled) {
|
|
116
|
+
enabled = newEnabled;
|
|
117
|
+
if (enabled) {
|
|
118
|
+
registerSaveListener(context);
|
|
119
|
+
} else {
|
|
120
|
+
disposeSaveListener();
|
|
121
|
+
}
|
|
122
|
+
updateStatusBar();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
log(`Activated (repoId: ${repoId || '<none>'}, debounce: ${debounceMs}ms)`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Deactivation
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
export async function deactivate(): Promise<void> {
|
|
136
|
+
disposeSaveListener();
|
|
137
|
+
|
|
138
|
+
// Flush any remaining edges
|
|
139
|
+
if (edgeCollector) {
|
|
140
|
+
try {
|
|
141
|
+
await edgeCollector.flushNow();
|
|
142
|
+
} catch {
|
|
143
|
+
// Ignore errors during deactivation
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Dispose the forge client (kills the child process)
|
|
148
|
+
if (forgeClient) {
|
|
149
|
+
forgeClient.dispose();
|
|
150
|
+
forgeClient = undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (statusBarItem) {
|
|
154
|
+
statusBarItem.dispose();
|
|
155
|
+
statusBarItem = undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
edgeCollector = undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Internal helpers
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
const SUPPORTED_LANGUAGES = new Set([
|
|
166
|
+
'typescript',
|
|
167
|
+
'javascript',
|
|
168
|
+
'typescriptreact',
|
|
169
|
+
'javascriptreact',
|
|
170
|
+
]);
|
|
171
|
+
|
|
172
|
+
function registerSaveListener(context: vscode.ExtensionContext): void {
|
|
173
|
+
disposeSaveListener();
|
|
174
|
+
|
|
175
|
+
saveListener = vscode.workspace.onDidSaveTextDocument(async (document) => {
|
|
176
|
+
if (!enabled || !edgeCollector) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!SUPPORTED_LANGUAGES.has(document.languageId)) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
await edgeCollector.collectFromDocument(document);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
// Never let a collection error propagate to the save flow
|
|
188
|
+
log(`Collection error for ${document.fileName}: ${String(err)}`);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
context.subscriptions.push(saveListener);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function disposeSaveListener(): void {
|
|
196
|
+
if (saveListener) {
|
|
197
|
+
saveListener.dispose();
|
|
198
|
+
saveListener = undefined;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function updateStatusBar(): void {
|
|
203
|
+
if (!statusBarItem) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (enabled) {
|
|
207
|
+
statusBarItem.text = '$(symbol-method) Forge LSP';
|
|
208
|
+
statusBarItem.tooltip = 'Forge LSP Bridge: Active — click to toggle';
|
|
209
|
+
statusBarItem.backgroundColor = undefined;
|
|
210
|
+
} else {
|
|
211
|
+
statusBarItem.text = '$(circle-slash) Forge LSP';
|
|
212
|
+
statusBarItem.tooltip = 'Forge LSP Bridge: Disabled — click to toggle';
|
|
213
|
+
statusBarItem.backgroundColor = new vscode.ThemeColor(
|
|
214
|
+
'statusBarItem.warningBackground',
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Detect the repo ID from .forge/manifest.yaml in the workspace root.
|
|
221
|
+
* Falls back to the workspace folder name, or empty string.
|
|
222
|
+
*/
|
|
223
|
+
async function detectRepoId(): Promise<string> {
|
|
224
|
+
const folders = vscode.workspace.workspaceFolders;
|
|
225
|
+
if (!folders || folders.length === 0) {
|
|
226
|
+
return '';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const folder of folders) {
|
|
230
|
+
const manifestPath = path.join(folder.uri.fsPath, '.forge', 'manifest.yaml');
|
|
231
|
+
try {
|
|
232
|
+
if (fs.existsSync(manifestPath)) {
|
|
233
|
+
const content = fs.readFileSync(manifestPath, 'utf8');
|
|
234
|
+
// Simple YAML parsing — look for "name: <value>" line
|
|
235
|
+
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
236
|
+
if (nameMatch && nameMatch[1]) {
|
|
237
|
+
const name = nameMatch[1].trim();
|
|
238
|
+
if (name) {
|
|
239
|
+
return name;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Check for repo_id_override
|
|
243
|
+
const overrideMatch = content.match(/^repo_id_override:\s*(.+)$/m);
|
|
244
|
+
if (overrideMatch && overrideMatch[1]) {
|
|
245
|
+
const override = overrideMatch[1].trim();
|
|
246
|
+
if (override && override !== 'null') {
|
|
247
|
+
return override;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch {
|
|
252
|
+
// Silently continue if the manifest can't be read
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Fallback: use the first workspace folder name
|
|
257
|
+
return folders[0]?.name ?? '';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Logging
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
const outputChannel = vscode.window.createOutputChannel('Forge LSP Bridge');
|
|
265
|
+
|
|
266
|
+
function log(message: string): void {
|
|
267
|
+
const timestamp = new Date().toISOString();
|
|
268
|
+
outputChannel.appendLine(`[${timestamp}] ${message}`);
|
|
269
|
+
}
|