sofia-cli 0.1.1
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/.github/agents/copilot-instructions.md +39 -0
- package/.github/agents/speckit.analyze.agent.md +184 -0
- package/.github/agents/speckit.checklist.agent.md +294 -0
- package/.github/agents/speckit.clarify.agent.md +181 -0
- package/.github/agents/speckit.constitution.agent.md +84 -0
- package/.github/agents/speckit.implement.agent.md +135 -0
- package/.github/agents/speckit.plan.agent.md +90 -0
- package/.github/agents/speckit.specify.agent.md +258 -0
- package/.github/agents/speckit.tasks.agent.md +137 -0
- package/.github/agents/speckit.taskstoissues.agent.md +30 -0
- package/.github/copilot-instructions.md +257 -0
- package/.github/prompts/speckit.analyze.prompt.md +3 -0
- package/.github/prompts/speckit.checklist.prompt.md +3 -0
- package/.github/prompts/speckit.clarify.prompt.md +3 -0
- package/.github/prompts/speckit.constitution.prompt.md +3 -0
- package/.github/prompts/speckit.implement.prompt.md +3 -0
- package/.github/prompts/speckit.plan.prompt.md +3 -0
- package/.github/prompts/speckit.specify.prompt.md +3 -0
- package/.github/prompts/speckit.tasks.prompt.md +3 -0
- package/.github/prompts/speckit.taskstoissues.prompt.md +3 -0
- package/.github/workflows/ci.yml +38 -0
- package/.prettierrc +6 -0
- package/.specify/memory/constitution.md +181 -0
- package/.specify/scripts/bash/check-prerequisites.sh +166 -0
- package/.specify/scripts/bash/common.sh +156 -0
- package/.specify/scripts/bash/create-new-feature.sh +297 -0
- package/.specify/scripts/bash/setup-plan.sh +61 -0
- package/.specify/scripts/bash/update-agent-context.sh +810 -0
- package/.specify/templates/agent-file-template.md +28 -0
- package/.specify/templates/checklist-template.md +40 -0
- package/.specify/templates/constitution-template.md +50 -0
- package/.specify/templates/plan-template.md +113 -0
- package/.specify/templates/spec-template.md +115 -0
- package/.specify/templates/tasks-template.md +251 -0
- package/.vscode/mcp.json +42 -0
- package/.vscode/settings.json +19 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/LICENSE +21 -0
- package/README.md +213 -0
- package/dist/src/cli/developCommand.js +240 -0
- package/dist/src/cli/directCommands.js +143 -0
- package/dist/src/cli/envLoader.js +16 -0
- package/dist/src/cli/exportCommand.js +53 -0
- package/dist/src/cli/index.js +203 -0
- package/dist/src/cli/ioContext.js +109 -0
- package/dist/src/cli/preflight.js +57 -0
- package/dist/src/cli/statusCommand.js +110 -0
- package/dist/src/cli/workshopCommand.js +400 -0
- package/dist/src/develop/checkpointState.js +86 -0
- package/dist/src/develop/codeGenerator.js +319 -0
- package/dist/src/develop/dynamicScaffolder.js +226 -0
- package/dist/src/develop/githubMcpAdapter.js +122 -0
- package/dist/src/develop/index.js +15 -0
- package/dist/src/develop/mcpContextEnricher.js +195 -0
- package/dist/src/develop/pocScaffolder.js +542 -0
- package/dist/src/develop/ralphLoop.js +659 -0
- package/dist/src/develop/templateRegistry.js +364 -0
- package/dist/src/develop/testRunner.js +202 -0
- package/dist/src/logging/logger.js +58 -0
- package/dist/src/loop/conversationLoop.js +227 -0
- package/dist/src/loop/phaseSummarizer.js +87 -0
- package/dist/src/mcp/mcpManager.js +267 -0
- package/dist/src/mcp/mcpTransport.js +391 -0
- package/dist/src/mcp/retryPolicy.js +47 -0
- package/dist/src/mcp/webSearch.js +254 -0
- package/dist/src/phases/contextSummarizer.js +101 -0
- package/dist/src/phases/discoveryEnricher.js +156 -0
- package/dist/src/phases/phaseExtractors.js +222 -0
- package/dist/src/phases/phaseHandlers.js +328 -0
- package/dist/src/prompts/design.md +51 -0
- package/dist/src/prompts/develop-boundary.md +51 -0
- package/dist/src/prompts/develop.md +111 -0
- package/dist/src/prompts/discover.md +58 -0
- package/dist/src/prompts/ideate.md +56 -0
- package/dist/src/prompts/plan.md +51 -0
- package/dist/src/prompts/promptLoader.js +167 -0
- package/dist/src/prompts/promptLoader.ts +198 -0
- package/dist/src/prompts/select.md +47 -0
- package/dist/src/prompts/summarize/README.md +8 -0
- package/dist/src/prompts/summarize/design-summary.md +37 -0
- package/dist/src/prompts/summarize/develop-summary.md +25 -0
- package/dist/src/prompts/summarize/ideate-summary.md +27 -0
- package/dist/src/prompts/summarize/plan-summary.md +27 -0
- package/dist/src/prompts/summarize/select-summary.md +21 -0
- package/dist/src/prompts/system.md +28 -0
- package/dist/src/sessions/exportPaths.js +22 -0
- package/dist/src/sessions/exportWriter.js +406 -0
- package/dist/src/sessions/sessionManager.js +81 -0
- package/dist/src/sessions/sessionStore.js +65 -0
- package/dist/src/shared/activitySpinner.js +91 -0
- package/dist/src/shared/copilotClient.js +129 -0
- package/dist/src/shared/data/cards.json +1249 -0
- package/dist/src/shared/data/cardsLoader.js +51 -0
- package/dist/src/shared/errorClassifier.js +120 -0
- package/dist/src/shared/events.js +28 -0
- package/dist/src/shared/markdownRenderer.js +34 -0
- package/dist/src/shared/schemas/session.js +265 -0
- package/dist/src/shared/tableRenderer.js +20 -0
- package/dist/src/vendor/chalk.js +2 -0
- package/dist/src/vendor/cli-table3.js +3 -0
- package/dist/src/vendor/commander.js +2 -0
- package/dist/src/vendor/marked-terminal.js +3 -0
- package/dist/src/vendor/marked.js +2 -0
- package/dist/src/vendor/ora.js +2 -0
- package/dist/src/vendor/pino.js +2 -0
- package/dist/src/vendor/zod.js +2 -0
- package/dist/tests/e2e/developE2e.spec.js +126 -0
- package/dist/tests/e2e/developFailureE2e.spec.js +247 -0
- package/dist/tests/e2e/developPty.spec.js +75 -0
- package/dist/tests/e2e/discoveryWebSearchRelevance.spec.js +84 -0
- package/dist/tests/e2e/harness.spec.js +83 -0
- package/dist/tests/e2e/mcpLive.spec.js +120 -0
- package/dist/tests/e2e/newSession.e2e.spec.js +177 -0
- package/dist/tests/e2e/ralphLoopEnrichmentComparison.spec.js +62 -0
- package/dist/tests/e2e/workiqEnrichment.spec.js +56 -0
- package/dist/tests/e2e/zavaSimulation.spec.js +452 -0
- package/dist/tests/fixtures/test-fixture-project/src/add.js +3 -0
- package/dist/tests/fixtures/test-fixture-project/tests/failing.test.js +6 -0
- package/dist/tests/fixtures/test-fixture-project/tests/hanging.test.js +8 -0
- package/dist/tests/fixtures/test-fixture-project/tests/passing.test.js +10 -0
- package/dist/tests/fixtures/test-fixture-project/vitest.config.js +6 -0
- package/dist/tests/integration/autoStartConversation.spec.js +138 -0
- package/dist/tests/integration/defaultCommand.spec.js +147 -0
- package/dist/tests/integration/directCommandNonTty.spec.js +224 -0
- package/dist/tests/integration/directCommandTty.spec.js +151 -0
- package/dist/tests/integration/discoveryEnrichmentFlow.spec.js +175 -0
- package/dist/tests/integration/exportArtifacts.spec.js +202 -0
- package/dist/tests/integration/exportFallbackFlow.spec.js +99 -0
- package/dist/tests/integration/mcpDegradationFlow.spec.js +190 -0
- package/dist/tests/integration/mcpTransportFlow.spec.js +139 -0
- package/dist/tests/integration/newSessionFlow.spec.js +343 -0
- package/dist/tests/integration/pocGithubMcp.spec.js +186 -0
- package/dist/tests/integration/pocLocalFallback.spec.js +171 -0
- package/dist/tests/integration/pocScaffold.spec.js +163 -0
- package/dist/tests/integration/ralphLoopFlow.spec.js +359 -0
- package/dist/tests/integration/ralphLoopPartial.spec.js +368 -0
- package/dist/tests/integration/resumeAndBacktrack.spec.js +247 -0
- package/dist/tests/integration/spinnerLifecycle.spec.js +220 -0
- package/dist/tests/integration/summarizationFlow.spec.js +115 -0
- package/dist/tests/integration/testRunnerReal.spec.js +52 -0
- package/dist/tests/integration/webSearchAgent.spec.js +128 -0
- package/dist/tests/live/copilotSdkLive.spec.js +107 -0
- package/dist/tests/live/zavaFullWorkshop.spec.js +392 -0
- package/dist/tests/setup/loadEnv.js +3 -0
- package/dist/tests/unit/cli/developCommand.spec.js +567 -0
- package/dist/tests/unit/cli/directCommands.spec.js +279 -0
- package/dist/tests/unit/cli/envLoader.spec.js +58 -0
- package/dist/tests/unit/cli/ioContext.spec.js +119 -0
- package/dist/tests/unit/cli/preflight.spec.js +108 -0
- package/dist/tests/unit/cli/statusCommand.spec.js +111 -0
- package/dist/tests/unit/cli/workshopClientFallback.spec.js +80 -0
- package/dist/tests/unit/cli/workshopCommand.spec.js +329 -0
- package/dist/tests/unit/config/vitestEnvSetup.spec.js +13 -0
- package/dist/tests/unit/develop/checkpointState.spec.js +315 -0
- package/dist/tests/unit/develop/codeGenerator.spec.js +355 -0
- package/dist/tests/unit/develop/githubMcpAdapter.spec.js +231 -0
- package/dist/tests/unit/develop/mcpContextEnricher.spec.js +433 -0
- package/dist/tests/unit/develop/outputValidator.spec.js +119 -0
- package/dist/tests/unit/develop/pocScaffolder.spec.js +353 -0
- package/dist/tests/unit/develop/ralphLoop.spec.js +1248 -0
- package/dist/tests/unit/develop/templateRegistry.spec.js +85 -0
- package/dist/tests/unit/develop/testRunner.spec.js +249 -0
- package/dist/tests/unit/infraBicep.spec.js +92 -0
- package/dist/tests/unit/infraDeploy.spec.js +82 -0
- package/dist/tests/unit/infraTeardown.spec.js +63 -0
- package/dist/tests/unit/logging/logger.spec.js +43 -0
- package/dist/tests/unit/loop/conversationLoop.spec.js +592 -0
- package/dist/tests/unit/loop/phaseSummarizer.spec.js +141 -0
- package/dist/tests/unit/loop/streamingMarkdown.spec.js +147 -0
- package/dist/tests/unit/mcp/mcpManager.spec.js +279 -0
- package/dist/tests/unit/mcp/mcpTransport.spec.js +529 -0
- package/dist/tests/unit/mcp/retryPolicy.spec.js +218 -0
- package/dist/tests/unit/mcp/timeoutValidation.spec.js +46 -0
- package/dist/tests/unit/mcp/webSearch.spec.js +567 -0
- package/dist/tests/unit/phases/contextSummarizer.spec.js +140 -0
- package/dist/tests/unit/phases/discoveryEnricher.repeatCalls.spec.js +93 -0
- package/dist/tests/unit/phases/discoveryEnricher.spec.js +411 -0
- package/dist/tests/unit/phases/phaseExtractors.spec.js +352 -0
- package/dist/tests/unit/phases/phaseHandlers.spec.js +425 -0
- package/dist/tests/unit/prompts/promptLoader.spec.js +118 -0
- package/dist/tests/unit/schemas/pocSchemas.spec.js +412 -0
- package/dist/tests/unit/schemas/session.spec.js +257 -0
- package/dist/tests/unit/sessions/exportPaths.spec.js +31 -0
- package/dist/tests/unit/sessions/exportWriter.spec.js +655 -0
- package/dist/tests/unit/sessions/sessionManager.spec.js +151 -0
- package/dist/tests/unit/sessions/sessionStore.spec.js +116 -0
- package/dist/tests/unit/shared/activitySpinner.spec.js +175 -0
- package/dist/tests/unit/shared/cardsLoader.spec.js +76 -0
- package/dist/tests/unit/shared/copilotClient.spec.js +155 -0
- package/dist/tests/unit/shared/errorClassifier.spec.js +131 -0
- package/dist/tests/unit/shared/events.spec.js +55 -0
- package/dist/tests/unit/shared/markdownRenderer.spec.js +35 -0
- package/dist/tests/unit/shared/markdownRendererChunks.spec.js +70 -0
- package/dist/tests/unit/shared/tableRenderer.spec.js +34 -0
- package/dist/vitest.config.js +14 -0
- package/dist/vitest.live.config.js +18 -0
- package/docs/README.md +35 -0
- package/docs/architecture.md +169 -0
- package/docs/cli-usage.md +207 -0
- package/docs/environment.md +66 -0
- package/docs/export-format.md +146 -0
- package/docs/session-model.md +113 -0
- package/eslint.config.js +35 -0
- package/infra/deploy.sh +193 -0
- package/infra/gather-env.sh +211 -0
- package/infra/main.bicep +90 -0
- package/infra/main.bicepparam +18 -0
- package/infra/resources.bicep +134 -0
- package/infra/teardown.sh +114 -0
- package/package.json +63 -0
- package/specs/001-cli-workshop-rebuild/checklists/requirements.md +35 -0
- package/specs/001-cli-workshop-rebuild/contracts/cli.md +59 -0
- package/specs/001-cli-workshop-rebuild/contracts/export-summary-json.md +23 -0
- package/specs/001-cli-workshop-rebuild/contracts/session-json.md +30 -0
- package/specs/001-cli-workshop-rebuild/data-model.md +210 -0
- package/specs/001-cli-workshop-rebuild/plan.md +361 -0
- package/specs/001-cli-workshop-rebuild/quickstart.md +83 -0
- package/specs/001-cli-workshop-rebuild/research.md +116 -0
- package/specs/001-cli-workshop-rebuild/spec.md +240 -0
- package/specs/001-cli-workshop-rebuild/tasks.md +476 -0
- package/specs/002-poc-generation/contracts/poc-output.md +172 -0
- package/specs/002-poc-generation/contracts/ralph-loop.md +113 -0
- package/specs/002-poc-generation/data-model.md +172 -0
- package/specs/002-poc-generation/plan.md +109 -0
- package/specs/002-poc-generation/quickstart.md +97 -0
- package/specs/002-poc-generation/research.md +786 -0
- package/specs/002-poc-generation/spec.md +81 -0
- package/specs/002-poc-generation/tasks-fix.md +198 -0
- package/specs/002-poc-generation/tasks.md +252 -0
- package/specs/003-mcp-transport-integration/checklists/requirements.md +37 -0
- package/specs/003-mcp-transport-integration/contracts/context-enricher.md +220 -0
- package/specs/003-mcp-transport-integration/contracts/discovery-enricher.md +267 -0
- package/specs/003-mcp-transport-integration/contracts/github-adapter.md +149 -0
- package/specs/003-mcp-transport-integration/contracts/mcp-transport.md +288 -0
- package/specs/003-mcp-transport-integration/data-model.md +326 -0
- package/specs/003-mcp-transport-integration/plan.md +114 -0
- package/specs/003-mcp-transport-integration/quickstart.md +311 -0
- package/specs/003-mcp-transport-integration/research.md +395 -0
- package/specs/003-mcp-transport-integration/spec.md +234 -0
- package/specs/003-mcp-transport-integration/tasks.md +324 -0
- package/specs/003-next-spec-gaps.md +150 -0
- package/specs/004-dev-resume-hardening/checklists/requirements.md +37 -0
- package/specs/004-dev-resume-hardening/contracts/cli.md +160 -0
- package/specs/004-dev-resume-hardening/data-model.md +321 -0
- package/specs/004-dev-resume-hardening/plan.md +107 -0
- package/specs/004-dev-resume-hardening/quickstart.md +115 -0
- package/specs/004-dev-resume-hardening/research.md +142 -0
- package/specs/004-dev-resume-hardening/spec.md +221 -0
- package/specs/004-dev-resume-hardening/tasks.md +333 -0
- package/specs/005-ai-search-deploy/checklists/requirements.md +39 -0
- package/specs/005-ai-search-deploy/contracts/web-search-tool.md +241 -0
- package/specs/005-ai-search-deploy/data-model.md +130 -0
- package/specs/005-ai-search-deploy/plan.md +93 -0
- package/specs/005-ai-search-deploy/quickstart.md +96 -0
- package/specs/005-ai-search-deploy/research.md +187 -0
- package/specs/005-ai-search-deploy/spec.md +143 -0
- package/specs/005-ai-search-deploy/tasks.md +284 -0
- package/specs/006-workshop-extraction-fixes/checklists/requirements.md +61 -0
- package/specs/006-workshop-extraction-fixes/contracts/summarization-and-export.md +131 -0
- package/specs/006-workshop-extraction-fixes/data-model.md +149 -0
- package/specs/006-workshop-extraction-fixes/plan.md +123 -0
- package/specs/006-workshop-extraction-fixes/quickstart.md +101 -0
- package/specs/006-workshop-extraction-fixes/research.md +143 -0
- package/specs/006-workshop-extraction-fixes/spec.md +210 -0
- package/specs/006-workshop-extraction-fixes/tasks.md +316 -0
- package/src/cli/developCommand.ts +308 -0
- package/src/cli/directCommands.ts +195 -0
- package/src/cli/envLoader.ts +17 -0
- package/src/cli/exportCommand.ts +65 -0
- package/src/cli/index.ts +249 -0
- package/src/cli/ioContext.ts +139 -0
- package/src/cli/preflight.ts +86 -0
- package/src/cli/statusCommand.ts +118 -0
- package/src/cli/workshopCommand.ts +496 -0
- package/src/develop/checkpointState.ts +121 -0
- package/src/develop/codeGenerator.ts +402 -0
- package/src/develop/dynamicScaffolder.ts +284 -0
- package/src/develop/githubMcpAdapter.ts +199 -0
- package/src/develop/index.ts +34 -0
- package/src/develop/mcpContextEnricher.ts +279 -0
- package/src/develop/pocScaffolder.ts +646 -0
- package/src/develop/ralphLoop.ts +1044 -0
- package/src/develop/templateRegistry.ts +427 -0
- package/src/develop/testRunner.ts +276 -0
- package/src/logging/logger.ts +73 -0
- package/src/loop/conversationLoop.ts +355 -0
- package/src/loop/phaseSummarizer.ts +114 -0
- package/src/mcp/mcpManager.ts +365 -0
- package/src/mcp/mcpTransport.ts +562 -0
- package/src/mcp/retryPolicy.ts +87 -0
- package/src/mcp/webSearch.ts +388 -0
- package/src/originalPrompts/design_thinking.md +178 -0
- package/src/originalPrompts/design_thinking_persona.md +76 -0
- package/src/originalPrompts/document_generator_example.md +77 -0
- package/src/originalPrompts/document_generator_persona.md +47 -0
- package/src/originalPrompts/facilitator_persona.md +125 -0
- package/src/originalPrompts/guardrails.md +47 -0
- package/src/phases/contextSummarizer.ts +154 -0
- package/src/phases/discoveryEnricher.ts +223 -0
- package/src/phases/phaseExtractors.ts +247 -0
- package/src/phases/phaseHandlers.ts +450 -0
- package/src/prompts/design.md +51 -0
- package/src/prompts/develop-boundary.md +51 -0
- package/src/prompts/develop.md +111 -0
- package/src/prompts/discover.md +58 -0
- package/src/prompts/ideate.md +56 -0
- package/src/prompts/plan.md +51 -0
- package/src/prompts/promptLoader.ts +198 -0
- package/src/prompts/select.md +47 -0
- package/src/prompts/summarize/README.md +8 -0
- package/src/prompts/summarize/design-summary.md +37 -0
- package/src/prompts/summarize/develop-summary.md +25 -0
- package/src/prompts/summarize/ideate-summary.md +27 -0
- package/src/prompts/summarize/plan-summary.md +27 -0
- package/src/prompts/summarize/select-summary.md +21 -0
- package/src/prompts/system.md +28 -0
- package/src/sessions/exportPaths.ts +28 -0
- package/src/sessions/exportWriter.ts +490 -0
- package/src/sessions/sessionManager.ts +119 -0
- package/src/sessions/sessionStore.ts +69 -0
- package/src/shared/activitySpinner.ts +108 -0
- package/src/shared/copilotClient.ts +291 -0
- package/src/shared/data/cards.json +1249 -0
- package/src/shared/data/cardsLoader.ts +70 -0
- package/src/shared/errorClassifier.ts +160 -0
- package/src/shared/events.ts +103 -0
- package/src/shared/markdownRenderer.ts +44 -0
- package/src/shared/schemas/session.ts +346 -0
- package/src/shared/tableRenderer.ts +28 -0
- package/src/types/marked-terminal.d.ts +5 -0
- package/src/vendor/chalk.ts +2 -0
- package/src/vendor/cli-table3.ts +3 -0
- package/src/vendor/commander.ts +2 -0
- package/src/vendor/marked-terminal.ts +3 -0
- package/src/vendor/marked.ts +2 -0
- package/src/vendor/ora.ts +2 -0
- package/src/vendor/pino.ts +3 -0
- package/src/vendor/zod.ts +3 -0
- package/tests/e2e/developE2e.spec.ts +152 -0
- package/tests/e2e/developFailureE2e.spec.ts +289 -0
- package/tests/e2e/developPty.spec.ts +86 -0
- package/tests/e2e/discoveryWebSearchRelevance.spec.ts +103 -0
- package/tests/e2e/harness.spec.ts +104 -0
- package/tests/e2e/mcpLive.spec.ts +149 -0
- package/tests/e2e/newSession.e2e.spec.ts +245 -0
- package/tests/e2e/ralphLoopEnrichmentComparison.spec.ts +70 -0
- package/tests/e2e/workiqEnrichment.spec.ts +72 -0
- package/tests/e2e/zava-assessment/agent-interaction-script.md +258 -0
- package/tests/e2e/zava-assessment/company-profile.md +98 -0
- package/tests/e2e/zava-assessment/expected-results-checklist.md +454 -0
- package/tests/e2e/zavaSimulation.spec.ts +511 -0
- package/tests/fixtures/completedSession.json +141 -0
- package/tests/fixtures/test-fixture-project/package-lock.json +1585 -0
- package/tests/fixtures/test-fixture-project/package.json +12 -0
- package/tests/fixtures/test-fixture-project/src/add.ts +3 -0
- package/tests/fixtures/test-fixture-project/tests/failing.test.ts +7 -0
- package/tests/fixtures/test-fixture-project/tests/hanging.test.ts +9 -0
- package/tests/fixtures/test-fixture-project/tests/passing.test.ts +13 -0
- package/tests/fixtures/test-fixture-project/vitest.config.ts +7 -0
- package/tests/integration/autoStartConversation.spec.ts +168 -0
- package/tests/integration/defaultCommand.spec.ts +179 -0
- package/tests/integration/directCommandNonTty.spec.ts +260 -0
- package/tests/integration/directCommandTty.spec.ts +185 -0
- package/tests/integration/discoveryEnrichmentFlow.spec.ts +209 -0
- package/tests/integration/exportArtifacts.spec.ts +232 -0
- package/tests/integration/exportFallbackFlow.spec.ts +115 -0
- package/tests/integration/mcpDegradationFlow.spec.ts +231 -0
- package/tests/integration/mcpTransportFlow.spec.ts +178 -0
- package/tests/integration/newSessionFlow.spec.ts +406 -0
- package/tests/integration/pocGithubMcp.spec.ts +224 -0
- package/tests/integration/pocLocalFallback.spec.ts +205 -0
- package/tests/integration/pocScaffold.spec.ts +220 -0
- package/tests/integration/ralphLoopFlow.spec.ts +430 -0
- package/tests/integration/ralphLoopPartial.spec.ts +416 -0
- package/tests/integration/resumeAndBacktrack.spec.ts +278 -0
- package/tests/integration/spinnerLifecycle.spec.ts +270 -0
- package/tests/integration/summarizationFlow.spec.ts +135 -0
- package/tests/integration/testRunnerReal.spec.ts +63 -0
- package/tests/integration/webSearchAgent.spec.ts +155 -0
- package/tests/live/copilotSdkLive.spec.ts +149 -0
- package/tests/live/zavaFullWorkshop.spec.ts +515 -0
- package/tests/setup/loadEnv.ts +5 -0
- package/tests/unit/cli/developCommand.spec.ts +679 -0
- package/tests/unit/cli/directCommands.spec.ts +325 -0
- package/tests/unit/cli/envLoader.spec.ts +73 -0
- package/tests/unit/cli/ioContext.spec.ts +148 -0
- package/tests/unit/cli/preflight.spec.ts +125 -0
- package/tests/unit/cli/statusCommand.spec.ts +134 -0
- package/tests/unit/cli/workshopClientFallback.spec.ts +100 -0
- package/tests/unit/cli/workshopCommand.spec.ts +378 -0
- package/tests/unit/config/vitestEnvSetup.spec.ts +24 -0
- package/tests/unit/develop/checkpointState.spec.ts +378 -0
- package/tests/unit/develop/codeGenerator.spec.ts +447 -0
- package/tests/unit/develop/githubMcpAdapter.spec.ts +283 -0
- package/tests/unit/develop/mcpContextEnricher.spec.ts +564 -0
- package/tests/unit/develop/outputValidator.spec.ts +134 -0
- package/tests/unit/develop/pocScaffolder.spec.ts +451 -0
- package/tests/unit/develop/ralphLoop.spec.ts +1439 -0
- package/tests/unit/develop/templateRegistry.spec.ts +106 -0
- package/tests/unit/develop/testRunner.spec.ts +294 -0
- package/tests/unit/infraBicep.spec.ts +116 -0
- package/tests/unit/infraDeploy.spec.ts +102 -0
- package/tests/unit/infraTeardown.spec.ts +77 -0
- package/tests/unit/logging/logger.spec.ts +50 -0
- package/tests/unit/loop/conversationLoop.spec.ts +719 -0
- package/tests/unit/loop/phaseSummarizer.spec.ts +169 -0
- package/tests/unit/loop/streamingMarkdown.spec.ts +180 -0
- package/tests/unit/mcp/mcpManager.spec.ts +336 -0
- package/tests/unit/mcp/mcpTransport.spec.ts +689 -0
- package/tests/unit/mcp/retryPolicy.spec.ts +278 -0
- package/tests/unit/mcp/timeoutValidation.spec.ts +55 -0
- package/tests/unit/mcp/webSearch.spec.ts +718 -0
- package/tests/unit/phases/contextSummarizer.spec.ts +158 -0
- package/tests/unit/phases/discoveryEnricher.repeatCalls.spec.ts +125 -0
- package/tests/unit/phases/discoveryEnricher.spec.ts +512 -0
- package/tests/unit/phases/phaseExtractors.spec.ts +406 -0
- package/tests/unit/phases/phaseHandlers.spec.ts +483 -0
- package/tests/unit/prompts/promptLoader.spec.ts +144 -0
- package/tests/unit/schemas/pocSchemas.spec.ts +457 -0
- package/tests/unit/schemas/session.spec.ts +328 -0
- package/tests/unit/sessions/exportPaths.spec.ts +38 -0
- package/tests/unit/sessions/exportWriter.spec.ts +737 -0
- package/tests/unit/sessions/sessionManager.spec.ts +174 -0
- package/tests/unit/sessions/sessionStore.spec.ts +136 -0
- package/tests/unit/shared/activitySpinner.spec.ts +211 -0
- package/tests/unit/shared/cardsLoader.spec.ts +89 -0
- package/tests/unit/shared/copilotClient.spec.ts +185 -0
- package/tests/unit/shared/errorClassifier.spec.ts +152 -0
- package/tests/unit/shared/events.spec.ts +71 -0
- package/tests/unit/shared/markdownRenderer.spec.ts +42 -0
- package/tests/unit/shared/markdownRendererChunks.spec.ts +83 -0
- package/tests/unit/shared/tableRenderer.spec.ts +38 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +15 -0
- package/vitest.live.config.ts +19 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { createActivityEvent } from '../shared/events.js';
|
|
2
|
+
import { renderMarkdown } from '../shared/markdownRenderer.js';
|
|
3
|
+
import { createNoOpSpinner } from '../shared/activitySpinner.js';
|
|
4
|
+
import { phaseSummarize } from './phaseSummarizer.js';
|
|
5
|
+
// ── ConversationLoop ─────────────────────────────────────────────────────────
|
|
6
|
+
export class ConversationLoop {
|
|
7
|
+
aborted = false;
|
|
8
|
+
session;
|
|
9
|
+
client;
|
|
10
|
+
io;
|
|
11
|
+
handler;
|
|
12
|
+
onEvent;
|
|
13
|
+
onSessionUpdate;
|
|
14
|
+
initialMessage;
|
|
15
|
+
spinner;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.client = options.client;
|
|
18
|
+
this.io = options.io;
|
|
19
|
+
this.session = { ...options.session };
|
|
20
|
+
this.handler = options.phaseHandler;
|
|
21
|
+
this.onEvent = options.onEvent ?? (() => { });
|
|
22
|
+
this.onSessionUpdate = options.onSessionUpdate ?? (async () => { });
|
|
23
|
+
this.initialMessage = options.initialMessage;
|
|
24
|
+
this.spinner = options.spinner ?? createNoOpSpinner();
|
|
25
|
+
}
|
|
26
|
+
/** Run the conversation loop for the current phase. */
|
|
27
|
+
async run() {
|
|
28
|
+
this.setupSignalHandler();
|
|
29
|
+
let systemPrompt = this.handler.buildSystemPrompt(this.session);
|
|
30
|
+
// FR-007b, FR-007c: Inject phase boundary instruction to prevent LLM drift
|
|
31
|
+
systemPrompt += `\n\nYou are in the ${this.handler.phase} phase. Do NOT introduce or begin the next phase. The user will be offered a decision gate when this phase is complete.`;
|
|
32
|
+
// Inject prior conversation history into the system prompt when resuming
|
|
33
|
+
// so the LLM has context from previous turns in this phase.
|
|
34
|
+
const priorTurns = (this.session.turns ?? []).filter((t) => t.phase === this.handler.phase);
|
|
35
|
+
if (priorTurns.length > 0) {
|
|
36
|
+
const historyBlock = priorTurns.map((t) => `[${t.role}]: ${t.content}`).join('\n\n');
|
|
37
|
+
systemPrompt += `\n\n## Previous conversation history\n\n${historyBlock}`;
|
|
38
|
+
}
|
|
39
|
+
const references = this.handler.getReferences?.(this.session) ?? [];
|
|
40
|
+
const sessionOpts = { systemPrompt, references };
|
|
41
|
+
const conversationSession = await this.client.createSession(sessionOpts);
|
|
42
|
+
this.emitEvent(createActivityEvent(`Starting ${this.handler.phase} phase`));
|
|
43
|
+
// Auto-start: send initial message to LLM before waiting for user input
|
|
44
|
+
if (this.initialMessage) {
|
|
45
|
+
const response = await this.streamResponse(conversationSession, {
|
|
46
|
+
role: 'user',
|
|
47
|
+
content: this.initialMessage,
|
|
48
|
+
});
|
|
49
|
+
const now = new Date().toISOString();
|
|
50
|
+
const turns = this.session.turns ?? [];
|
|
51
|
+
turns.push({
|
|
52
|
+
phase: this.handler.phase,
|
|
53
|
+
sequence: turns.length + 1,
|
|
54
|
+
role: 'user',
|
|
55
|
+
content: this.initialMessage,
|
|
56
|
+
timestamp: now,
|
|
57
|
+
}, {
|
|
58
|
+
phase: this.handler.phase,
|
|
59
|
+
sequence: turns.length + 2,
|
|
60
|
+
role: 'assistant',
|
|
61
|
+
content: response,
|
|
62
|
+
timestamp: now,
|
|
63
|
+
});
|
|
64
|
+
const updates = this.handler.extractResult(this.session, response);
|
|
65
|
+
this.session = {
|
|
66
|
+
...this.session,
|
|
67
|
+
...updates,
|
|
68
|
+
turns,
|
|
69
|
+
updatedAt: now,
|
|
70
|
+
};
|
|
71
|
+
// Run async post-extraction hook if present (e.g., discovery enrichment)
|
|
72
|
+
if (this.handler.postExtract) {
|
|
73
|
+
const postUpdates = await this.handler.postExtract(this.session);
|
|
74
|
+
this.session = { ...this.session, ...postUpdates, updatedAt: new Date().toISOString() };
|
|
75
|
+
}
|
|
76
|
+
await this.onSessionUpdate(this.session);
|
|
77
|
+
}
|
|
78
|
+
// Main conversation loop
|
|
79
|
+
while (!this.aborted) {
|
|
80
|
+
const userInput = await this.io.readInput(`[${this.handler.phase}] > `);
|
|
81
|
+
if (userInput === null) {
|
|
82
|
+
// EOF / Ctrl+D — treat as "done" signal
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
const trimmed = userInput.trim();
|
|
86
|
+
if (trimmed.toLowerCase() === 'done' || trimmed === '') {
|
|
87
|
+
// Check if handler considers the phase complete
|
|
88
|
+
if (this.handler.isComplete?.(this.session, '') !== false) {
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
// Otherwise continue the conversation
|
|
92
|
+
}
|
|
93
|
+
// Send user message and stream response
|
|
94
|
+
const response = await this.streamResponse(conversationSession, {
|
|
95
|
+
role: 'user',
|
|
96
|
+
content: trimmed,
|
|
97
|
+
});
|
|
98
|
+
// Accumulate turn history
|
|
99
|
+
const now = new Date().toISOString();
|
|
100
|
+
const turns = this.session.turns ?? [];
|
|
101
|
+
turns.push({
|
|
102
|
+
phase: this.handler.phase,
|
|
103
|
+
sequence: turns.length + 1,
|
|
104
|
+
role: 'user',
|
|
105
|
+
content: trimmed,
|
|
106
|
+
timestamp: now,
|
|
107
|
+
}, {
|
|
108
|
+
phase: this.handler.phase,
|
|
109
|
+
sequence: turns.length + 2,
|
|
110
|
+
role: 'assistant',
|
|
111
|
+
content: response,
|
|
112
|
+
timestamp: now,
|
|
113
|
+
});
|
|
114
|
+
// Extract structured data from the response
|
|
115
|
+
const updates = this.handler.extractResult(this.session, response);
|
|
116
|
+
this.session = {
|
|
117
|
+
...this.session,
|
|
118
|
+
...updates,
|
|
119
|
+
turns,
|
|
120
|
+
updatedAt: now,
|
|
121
|
+
};
|
|
122
|
+
// Run async post-extraction hook if present (e.g., discovery enrichment)
|
|
123
|
+
if (this.handler.postExtract) {
|
|
124
|
+
const postUpdates = await this.handler.postExtract(this.session);
|
|
125
|
+
this.session = { ...this.session, ...postUpdates, updatedAt: new Date().toISOString() };
|
|
126
|
+
}
|
|
127
|
+
// Persist after every turn (FR-039a)
|
|
128
|
+
await this.onSessionUpdate(this.session);
|
|
129
|
+
}
|
|
130
|
+
// FR-006: Post-phase summarization fallback
|
|
131
|
+
// If structured data wasn't extracted during conversation, try a dedicated summarization call
|
|
132
|
+
try {
|
|
133
|
+
const summarizationUpdates = await phaseSummarize(this.client, this.handler.phase, this.session, this.handler);
|
|
134
|
+
if (Object.keys(summarizationUpdates).length > 0) {
|
|
135
|
+
this.session = {
|
|
136
|
+
...this.session,
|
|
137
|
+
...summarizationUpdates,
|
|
138
|
+
updatedAt: new Date().toISOString(),
|
|
139
|
+
};
|
|
140
|
+
await this.onSessionUpdate(this.session);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Summarization failure is non-fatal
|
|
145
|
+
}
|
|
146
|
+
return this.session;
|
|
147
|
+
}
|
|
148
|
+
/** Stream response from the LLM and render incrementally. */
|
|
149
|
+
async streamResponse(session, message) {
|
|
150
|
+
const chunks = [];
|
|
151
|
+
let firstTextDelta = true;
|
|
152
|
+
// Start "Thinking..." spinner before sending
|
|
153
|
+
this.spinner.startThinking();
|
|
154
|
+
try {
|
|
155
|
+
for await (const event of session.send(message)) {
|
|
156
|
+
// Check if user pressed Ctrl+C during streaming
|
|
157
|
+
if (this.aborted)
|
|
158
|
+
break;
|
|
159
|
+
this.emitEvent(event);
|
|
160
|
+
if (event.type === 'TextDelta') {
|
|
161
|
+
// Stop spinner on first text output
|
|
162
|
+
if (firstTextDelta) {
|
|
163
|
+
this.spinner.stop();
|
|
164
|
+
firstTextDelta = false;
|
|
165
|
+
}
|
|
166
|
+
chunks.push(event.text);
|
|
167
|
+
if (!this.io.isJsonMode) {
|
|
168
|
+
// Render markdown for TTY, raw for non-TTY
|
|
169
|
+
if (this.io.isTTY) {
|
|
170
|
+
this.io.write(renderMarkdown(event.text, { isTTY: true }));
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
this.io.write(event.text);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else if (event.type === 'Activity') {
|
|
178
|
+
this.io.writeActivity(event.message);
|
|
179
|
+
}
|
|
180
|
+
else if (event.type === 'ToolCall') {
|
|
181
|
+
this.spinner.startToolCall(event.toolName);
|
|
182
|
+
}
|
|
183
|
+
else if (event.type === 'ToolResult') {
|
|
184
|
+
const summary = typeof event.result === 'string'
|
|
185
|
+
? event.result
|
|
186
|
+
: JSON.stringify(event.result).slice(0, 120);
|
|
187
|
+
this.spinner.completeToolCall(event.toolName, summary);
|
|
188
|
+
this.io.writeToolSummary(event.toolName, summary);
|
|
189
|
+
// Resume thinking spinner in case more processing follows
|
|
190
|
+
this.spinner.startThinking();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
// Always stop spinner — prevents cursor-hidden state on errors
|
|
196
|
+
this.spinner.stop();
|
|
197
|
+
}
|
|
198
|
+
const fullResponse = chunks.join('');
|
|
199
|
+
// In JSON mode, output the full rendered response
|
|
200
|
+
if (this.io.isJsonMode) {
|
|
201
|
+
this.io.write(JSON.stringify({ phase: this.handler.phase, content: fullResponse }));
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
this.io.write('\n');
|
|
205
|
+
}
|
|
206
|
+
return fullResponse;
|
|
207
|
+
}
|
|
208
|
+
emitEvent(event) {
|
|
209
|
+
this.onEvent(event);
|
|
210
|
+
}
|
|
211
|
+
setupSignalHandler() {
|
|
212
|
+
const handler = () => {
|
|
213
|
+
this.aborted = true;
|
|
214
|
+
this.emitEvent(createActivityEvent('Ctrl+C received — finishing current turn'));
|
|
215
|
+
};
|
|
216
|
+
// Avoid MaxListenersExceededWarning when many loops are created in tests
|
|
217
|
+
const current = process.listenerCount('SIGINT');
|
|
218
|
+
if (current >= 10) {
|
|
219
|
+
process.setMaxListeners(current + 1);
|
|
220
|
+
}
|
|
221
|
+
process.once('SIGINT', handler);
|
|
222
|
+
}
|
|
223
|
+
/** Get the current session state. */
|
|
224
|
+
getSession() {
|
|
225
|
+
return { ...this.session };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { loadSummarizationPrompt } from '../prompts/promptLoader.js';
|
|
2
|
+
// ── Phase → session field mapping ────────────────────────────────────────────
|
|
3
|
+
const PHASE_SESSION_FIELD = {
|
|
4
|
+
Discover: 'businessContext',
|
|
5
|
+
Ideate: 'ideas',
|
|
6
|
+
Design: 'evaluation',
|
|
7
|
+
Select: 'selection',
|
|
8
|
+
Plan: 'plan',
|
|
9
|
+
Develop: 'poc',
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Check whether a phase needs post-phase summarization.
|
|
13
|
+
* Returns true if the session field for this phase is null/undefined.
|
|
14
|
+
*/
|
|
15
|
+
export function needsSummarization(phase, session) {
|
|
16
|
+
const fieldName = PHASE_SESSION_FIELD[phase];
|
|
17
|
+
if (!fieldName)
|
|
18
|
+
return false;
|
|
19
|
+
const value = session[fieldName];
|
|
20
|
+
return value === undefined || value === null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build a concatenated transcript from conversation turns for a specific phase.
|
|
24
|
+
*/
|
|
25
|
+
export function buildPhaseTranscript(phase, session) {
|
|
26
|
+
const turns = (session.turns ?? []).filter((t) => t.phase === phase);
|
|
27
|
+
if (turns.length === 0)
|
|
28
|
+
return '';
|
|
29
|
+
return turns.map((t) => `[${t.role}]: ${t.content}`).join('\n\n');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Post-phase summarization call.
|
|
33
|
+
*
|
|
34
|
+
* If the expected structured field for `phase` is still null after the
|
|
35
|
+
* conversation loop, makes a one-shot LLM call to extract it from the
|
|
36
|
+
* full transcript.
|
|
37
|
+
*
|
|
38
|
+
* Returns partial session updates (may be empty if extraction still fails).
|
|
39
|
+
* Never throws — summarization is a best-effort fallback.
|
|
40
|
+
*/
|
|
41
|
+
export async function phaseSummarize(client, phase, session, handler) {
|
|
42
|
+
// Skip if field is already populated
|
|
43
|
+
if (!needsSummarization(phase, session)) {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
// Build the transcript
|
|
47
|
+
const transcript = buildPhaseTranscript(phase, session);
|
|
48
|
+
if (!transcript) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
// Load the phase-specific summarization prompt
|
|
53
|
+
const systemPrompt = await loadSummarizationPrompt(phase);
|
|
54
|
+
// Create a new session for the summarization call (avoids polluting context)
|
|
55
|
+
const summarizationSession = await client.createSession({ systemPrompt });
|
|
56
|
+
// Send the transcript as a single user message and collect the full response
|
|
57
|
+
const chunks = [];
|
|
58
|
+
for await (const event of summarizationSession.send({
|
|
59
|
+
role: 'user',
|
|
60
|
+
content: transcript,
|
|
61
|
+
})) {
|
|
62
|
+
if (event.type === 'TextDelta') {
|
|
63
|
+
chunks.push(event.text);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const response = chunks.join('');
|
|
67
|
+
// Extract structured data using the phase handler's extractResult
|
|
68
|
+
const updates = handler.extractResult(session, response);
|
|
69
|
+
// For Design phase, also extract Mermaid diagram (FR-007a)
|
|
70
|
+
// Store in plan.architectureNotes if available (session schema doesn't have
|
|
71
|
+
// a dedicated architectureDiagram field)
|
|
72
|
+
if (phase === 'Design') {
|
|
73
|
+
const mermaidMatch = response.match(/```mermaid\s*\n([\s\S]*?)\n```/);
|
|
74
|
+
if (mermaidMatch) {
|
|
75
|
+
const diagram = mermaidMatch[1].trim();
|
|
76
|
+
if (!updates.plan) {
|
|
77
|
+
updates.plan = { milestones: [], architectureNotes: diagram };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return updates;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Summarization failure is non-fatal — log and return empty
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Manager.
|
|
3
|
+
*
|
|
4
|
+
* Loads .vscode/mcp.json configuration, manages MCP server connections,
|
|
5
|
+
* lists available tools, classifies errors, and dispatches real MCP tool calls
|
|
6
|
+
* via the transport layer.
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { createTransport, StdioMcpTransport } from './mcpTransport.js';
|
|
10
|
+
import { withRetry } from './retryPolicy.js';
|
|
11
|
+
const ERROR_CODE_MAP = {
|
|
12
|
+
ECONNREFUSED: 'connection-refused',
|
|
13
|
+
ENOTFOUND: 'dns-failure',
|
|
14
|
+
ETIMEDOUT: 'timeout',
|
|
15
|
+
ECONNRESET: 'connection-refused',
|
|
16
|
+
ERR_TLS_CERT_ALTNAME_INVALID: 'auth-failure',
|
|
17
|
+
};
|
|
18
|
+
export function classifyMcpError(err) {
|
|
19
|
+
if (err instanceof Error) {
|
|
20
|
+
const code = err.code;
|
|
21
|
+
if (code && code in ERROR_CODE_MAP) {
|
|
22
|
+
return ERROR_CODE_MAP[code];
|
|
23
|
+
}
|
|
24
|
+
// AbortError from AbortController / fetch timeouts
|
|
25
|
+
if (err instanceof DOMException && err.name === 'AbortError') {
|
|
26
|
+
return 'timeout';
|
|
27
|
+
}
|
|
28
|
+
// Message-based timeout detection
|
|
29
|
+
if (/timed?\s*out/i.test(err.message)) {
|
|
30
|
+
return 'timeout';
|
|
31
|
+
}
|
|
32
|
+
if (err.message.includes('401') || err.message.includes('403')) {
|
|
33
|
+
return 'auth-failure';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return 'unknown';
|
|
37
|
+
}
|
|
38
|
+
// ── Config loader ────────────────────────────────────────────────────────────
|
|
39
|
+
/**
|
|
40
|
+
* Load and normalize MCP configuration from a JSON file.
|
|
41
|
+
* Returns empty servers if the file doesn't exist.
|
|
42
|
+
*/
|
|
43
|
+
export async function loadMcpConfig(configPath) {
|
|
44
|
+
try {
|
|
45
|
+
const raw = await readFile(configPath, 'utf-8');
|
|
46
|
+
// Strip JSONC comments — only full-line // comments and block comments
|
|
47
|
+
// Avoid stripping // inside string values (e.g. URLs like https://)
|
|
48
|
+
const stripped = raw
|
|
49
|
+
.replace(/^\s*\/\/.*$/gm, '') // full-line // comments
|
|
50
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // block comments
|
|
51
|
+
.replace(/,(\s*[}\]])/g, '$1'); // trailing commas
|
|
52
|
+
const parsed = JSON.parse(stripped);
|
|
53
|
+
const servers = {};
|
|
54
|
+
if (parsed.servers && typeof parsed.servers === 'object') {
|
|
55
|
+
for (const [name, config] of Object.entries(parsed.servers)) {
|
|
56
|
+
const cfg = config;
|
|
57
|
+
if (cfg.url && typeof cfg.url === 'string') {
|
|
58
|
+
servers[name] = {
|
|
59
|
+
name,
|
|
60
|
+
type: 'http',
|
|
61
|
+
url: cfg.url,
|
|
62
|
+
...(cfg.headers && typeof cfg.headers === 'object'
|
|
63
|
+
? { headers: cfg.headers }
|
|
64
|
+
: {}),
|
|
65
|
+
...(Array.isArray(cfg.tools) ? { tools: cfg.tools } : {}),
|
|
66
|
+
...(typeof cfg.timeout === 'number' ? { timeout: cfg.timeout } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
else if (cfg.command && typeof cfg.command === 'string') {
|
|
70
|
+
servers[name] = {
|
|
71
|
+
name,
|
|
72
|
+
type: 'stdio',
|
|
73
|
+
command: cfg.command,
|
|
74
|
+
args: Array.isArray(cfg.args) ? cfg.args : [],
|
|
75
|
+
...(cfg.env && typeof cfg.env === 'object'
|
|
76
|
+
? { env: cfg.env }
|
|
77
|
+
: {}),
|
|
78
|
+
...(typeof cfg.cwd === 'string' ? { cwd: cfg.cwd } : {}),
|
|
79
|
+
...(Array.isArray(cfg.tools) ? { tools: cfg.tools } : {}),
|
|
80
|
+
...(typeof cfg.timeout === 'number' ? { timeout: cfg.timeout } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { servers };
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
|
|
89
|
+
return { servers: {} };
|
|
90
|
+
}
|
|
91
|
+
// Re-throw parse or other errors
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Convert sofIA's `McpConfig` to the shape expected by the Copilot SDK's
|
|
97
|
+
* `SessionConfig.mcpServers` (`Record<string, MCPServerConfig>`).
|
|
98
|
+
*
|
|
99
|
+
* This bridges `.vscode/mcp.json` → SDK `createSession({ mcpServers })`.
|
|
100
|
+
* The SDK types are `MCPLocalServerConfig` and `MCPRemoteServerConfig`.
|
|
101
|
+
*/
|
|
102
|
+
export function toSdkMcpServers(config) {
|
|
103
|
+
const result = {};
|
|
104
|
+
for (const [name, server] of Object.entries(config.servers)) {
|
|
105
|
+
if (server.type === 'stdio') {
|
|
106
|
+
result[name] = {
|
|
107
|
+
type: 'stdio',
|
|
108
|
+
command: server.command,
|
|
109
|
+
args: server.args,
|
|
110
|
+
tools: server.tools ?? ['*'],
|
|
111
|
+
...(server.env ? { env: server.env } : {}),
|
|
112
|
+
...(server.cwd ? { cwd: server.cwd } : {}),
|
|
113
|
+
...(server.timeout ? { timeout: server.timeout } : {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
else if (server.type === 'http') {
|
|
117
|
+
result[name] = {
|
|
118
|
+
type: 'http',
|
|
119
|
+
url: server.url,
|
|
120
|
+
tools: server.tools ?? ['*'],
|
|
121
|
+
...(server.headers ? { headers: server.headers } : {}),
|
|
122
|
+
...(server.timeout ? { timeout: server.timeout } : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
// ── Default Timeouts ─────────────────────────────────────────────────────────
|
|
129
|
+
const DEFAULT_TIMEOUTS = {
|
|
130
|
+
github: 60_000,
|
|
131
|
+
context7: 30_000,
|
|
132
|
+
azure: 30_000,
|
|
133
|
+
workiq: 30_000,
|
|
134
|
+
'microsoftdocs/mcp': 30_000,
|
|
135
|
+
};
|
|
136
|
+
const FALLBACK_TIMEOUT = 30_000;
|
|
137
|
+
// ── McpManager ───────────────────────────────────────────────────────────────
|
|
138
|
+
export class McpManager {
|
|
139
|
+
config;
|
|
140
|
+
connectedServers = new Set();
|
|
141
|
+
transports = new Map();
|
|
142
|
+
logger;
|
|
143
|
+
constructor(config, logger) {
|
|
144
|
+
this.config = config;
|
|
145
|
+
this.logger = logger;
|
|
146
|
+
}
|
|
147
|
+
/** List all configured server names. */
|
|
148
|
+
listServers() {
|
|
149
|
+
return Object.keys(this.config.servers);
|
|
150
|
+
}
|
|
151
|
+
/** Get configuration for a specific server. */
|
|
152
|
+
getServerConfig(name) {
|
|
153
|
+
return this.config.servers[name];
|
|
154
|
+
}
|
|
155
|
+
/** Check if a server is currently connected (available). */
|
|
156
|
+
isAvailable(name) {
|
|
157
|
+
return this.connectedServers.has(name);
|
|
158
|
+
}
|
|
159
|
+
/** Mark a server as connected. Used by the SDK integration layer. */
|
|
160
|
+
markConnected(name) {
|
|
161
|
+
if (this.config.servers[name]) {
|
|
162
|
+
this.connectedServers.add(name);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Mark a server as disconnected. */
|
|
166
|
+
markDisconnected(name) {
|
|
167
|
+
this.connectedServers.delete(name);
|
|
168
|
+
}
|
|
169
|
+
/** Get all server configs as an array. */
|
|
170
|
+
getAllConfigs() {
|
|
171
|
+
return Object.values(this.config.servers);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Call a tool on a named MCP server.
|
|
175
|
+
*
|
|
176
|
+
* Dispatches to the correct transport (stdio or HTTP), applies retry policy
|
|
177
|
+
* for transient errors, and normalizes the response.
|
|
178
|
+
*
|
|
179
|
+
* @param serverName The MCP server name (e.g., 'github', 'context7', 'azure')
|
|
180
|
+
* @param toolName The tool to call on that server
|
|
181
|
+
* @param args Arguments for the tool
|
|
182
|
+
* @param options Optional timeout and retry configuration
|
|
183
|
+
* @returns The tool response as a parsed object
|
|
184
|
+
*/
|
|
185
|
+
async callTool(serverName, toolName, args, options) {
|
|
186
|
+
if (!this.isAvailable(serverName)) {
|
|
187
|
+
throw new Error(`MCP server '${serverName}' is not available`);
|
|
188
|
+
}
|
|
189
|
+
const serverConfig = this.config.servers[serverName];
|
|
190
|
+
if (!serverConfig) {
|
|
191
|
+
throw new Error(`Unknown MCP server: ${serverName}`);
|
|
192
|
+
}
|
|
193
|
+
// Get or create transport
|
|
194
|
+
const transport = this.getOrCreateTransport(serverName, serverConfig);
|
|
195
|
+
// Connect stdio transports on first use
|
|
196
|
+
if (transport instanceof StdioMcpTransport && !transport.isConnected()) {
|
|
197
|
+
await transport.connect();
|
|
198
|
+
}
|
|
199
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUTS[serverName] ?? FALLBACK_TIMEOUT;
|
|
200
|
+
try {
|
|
201
|
+
let response;
|
|
202
|
+
if (options?.retryOnTransient !== false) {
|
|
203
|
+
response = await withRetry(() => transport.callTool(toolName, args, timeoutMs), {
|
|
204
|
+
serverName,
|
|
205
|
+
toolName,
|
|
206
|
+
logger: this.logger,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
response = await transport.callTool(toolName, args, timeoutMs);
|
|
211
|
+
}
|
|
212
|
+
// Normalize content to Record<string, unknown>
|
|
213
|
+
const content = response.content;
|
|
214
|
+
if (typeof content === 'string') {
|
|
215
|
+
// Try to parse JSON string; if not JSON, wrap as { text: content }
|
|
216
|
+
try {
|
|
217
|
+
const parsed = JSON.parse(content);
|
|
218
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
219
|
+
return parsed;
|
|
220
|
+
}
|
|
221
|
+
return { text: content };
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return { text: content };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return content;
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
this.markDisconnected(serverName);
|
|
231
|
+
throw err;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Disconnect all cached transports and clear the registry.
|
|
236
|
+
*/
|
|
237
|
+
async disconnectAll() {
|
|
238
|
+
const disconnections = [];
|
|
239
|
+
for (const transport of this.transports.values()) {
|
|
240
|
+
disconnections.push(transport.disconnect());
|
|
241
|
+
}
|
|
242
|
+
await Promise.allSettled(disconnections);
|
|
243
|
+
this.transports.clear();
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Get or lazily create a transport for the given server.
|
|
247
|
+
*/
|
|
248
|
+
getOrCreateTransport(serverName, config) {
|
|
249
|
+
let transport = this.transports.get(serverName);
|
|
250
|
+
if (!transport) {
|
|
251
|
+
const logger = this.logger ??
|
|
252
|
+
{
|
|
253
|
+
info: () => { },
|
|
254
|
+
warn: () => { },
|
|
255
|
+
error: () => { },
|
|
256
|
+
debug: () => { },
|
|
257
|
+
trace: () => { },
|
|
258
|
+
fatal: () => { },
|
|
259
|
+
child: () => logger,
|
|
260
|
+
level: 'silent',
|
|
261
|
+
};
|
|
262
|
+
transport = createTransport(config, logger);
|
|
263
|
+
this.transports.set(serverName, transport);
|
|
264
|
+
}
|
|
265
|
+
return transport;
|
|
266
|
+
}
|
|
267
|
+
}
|