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,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T004/T005: Unit tests for McpTransport implementations.
|
|
3
|
+
*
|
|
4
|
+
* Verifies:
|
|
5
|
+
* - HttpMcpTransport: JSON-RPC framing, auth headers, timeout, error classification
|
|
6
|
+
* - StdioMcpTransport: subprocess spawn, initialize handshake, pending request lifecycle
|
|
7
|
+
* - createTransport() factory: creates correct implementation per config type
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
10
|
+
import { EventEmitter, PassThrough } from 'node:stream';
|
|
11
|
+
import { HttpMcpTransport, StdioMcpTransport, McpTransportError, createTransport, } from '../../../src/mcp/mcpTransport.js';
|
|
12
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
13
|
+
function makeLogger() {
|
|
14
|
+
return {
|
|
15
|
+
info: vi.fn(),
|
|
16
|
+
warn: vi.fn(),
|
|
17
|
+
error: vi.fn(),
|
|
18
|
+
debug: vi.fn(),
|
|
19
|
+
trace: vi.fn(),
|
|
20
|
+
fatal: vi.fn(),
|
|
21
|
+
child: vi.fn().mockReturnThis(),
|
|
22
|
+
level: 'silent',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function makeHttpConfig(overrides) {
|
|
26
|
+
return {
|
|
27
|
+
name: 'test-http',
|
|
28
|
+
type: 'http',
|
|
29
|
+
url: 'https://api.example.com/mcp',
|
|
30
|
+
...overrides,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function makeStdioConfig(overrides) {
|
|
34
|
+
return {
|
|
35
|
+
name: 'test-stdio',
|
|
36
|
+
type: 'stdio',
|
|
37
|
+
command: 'node',
|
|
38
|
+
args: ['server.js'],
|
|
39
|
+
...overrides,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// ── HttpMcpTransport Tests (T004) ────────────────────────────────────────────
|
|
43
|
+
describe('HttpMcpTransport', () => {
|
|
44
|
+
let originalFetch;
|
|
45
|
+
let originalToken;
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
originalFetch = globalThis.fetch;
|
|
48
|
+
originalToken = process.env.GITHUB_TOKEN;
|
|
49
|
+
});
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
globalThis.fetch = originalFetch;
|
|
52
|
+
if (originalToken !== undefined) {
|
|
53
|
+
process.env.GITHUB_TOKEN = originalToken;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
delete process.env.GITHUB_TOKEN;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
it('sends correct JSON-RPC tools/call framing', async () => {
|
|
60
|
+
let capturedBody;
|
|
61
|
+
globalThis.fetch = vi.fn(async (_url, init) => {
|
|
62
|
+
capturedBody = JSON.parse(init.body);
|
|
63
|
+
return new Response(JSON.stringify({
|
|
64
|
+
jsonrpc: '2.0',
|
|
65
|
+
id: 1,
|
|
66
|
+
result: { content: [{ type: 'text', text: '{"ok":true}' }] },
|
|
67
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
68
|
+
});
|
|
69
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
70
|
+
await transport.callTool('search', { query: 'test' }, 5000);
|
|
71
|
+
expect(capturedBody).toEqual({
|
|
72
|
+
jsonrpc: '2.0',
|
|
73
|
+
id: expect.any(Number),
|
|
74
|
+
method: 'tools/call',
|
|
75
|
+
params: { name: 'search', arguments: { query: 'test' } },
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
it('sets Authorization Bearer header from GITHUB_TOKEN', async () => {
|
|
79
|
+
process.env.GITHUB_TOKEN = 'test-token-abc';
|
|
80
|
+
let capturedHeaders = {};
|
|
81
|
+
globalThis.fetch = vi.fn(async (_url, init) => {
|
|
82
|
+
capturedHeaders = Object.fromEntries(Object.entries(init.headers));
|
|
83
|
+
return new Response(JSON.stringify({
|
|
84
|
+
jsonrpc: '2.0',
|
|
85
|
+
id: 1,
|
|
86
|
+
result: { data: 'ok' },
|
|
87
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
88
|
+
});
|
|
89
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
90
|
+
await transport.callTool('test-tool', {}, 5000);
|
|
91
|
+
expect(capturedHeaders['Authorization']).toBe('Bearer test-token-abc');
|
|
92
|
+
});
|
|
93
|
+
it('does not set Authorization header when GITHUB_TOKEN is unset', async () => {
|
|
94
|
+
delete process.env.GITHUB_TOKEN;
|
|
95
|
+
let capturedHeaders = {};
|
|
96
|
+
globalThis.fetch = vi.fn(async (_url, init) => {
|
|
97
|
+
capturedHeaders = Object.fromEntries(Object.entries(init.headers));
|
|
98
|
+
return new Response(JSON.stringify({
|
|
99
|
+
jsonrpc: '2.0',
|
|
100
|
+
id: 1,
|
|
101
|
+
result: {},
|
|
102
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
103
|
+
});
|
|
104
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
105
|
+
await transport.callTool('test-tool', {}, 5000);
|
|
106
|
+
expect(capturedHeaders['Authorization']).toBeUndefined();
|
|
107
|
+
});
|
|
108
|
+
it('throws McpTransportError with timeout classification on AbortController timeout', async () => {
|
|
109
|
+
globalThis.fetch = vi.fn(async (_url, init) => {
|
|
110
|
+
// Simulate slow server — wait for abort signal
|
|
111
|
+
return new Promise((_resolve, reject) => {
|
|
112
|
+
const signal = init.signal;
|
|
113
|
+
if (signal) {
|
|
114
|
+
signal.addEventListener('abort', () => {
|
|
115
|
+
const err = new Error('The operation was aborted');
|
|
116
|
+
err.name = 'AbortError';
|
|
117
|
+
reject(err);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
123
|
+
await expect(transport.callTool('slow-tool', {}, 50)).rejects.toThrow(McpTransportError);
|
|
124
|
+
try {
|
|
125
|
+
await transport.callTool('slow-tool', {}, 50);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
expect(err).toBeInstanceOf(McpTransportError);
|
|
129
|
+
expect(err.serverName).toBe('test-http');
|
|
130
|
+
expect(err.code).toBe('ETIMEDOUT');
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
it('classifies HTTP 401 as auth-failure error', async () => {
|
|
134
|
+
globalThis.fetch = vi.fn(async () => {
|
|
135
|
+
return new Response('Unauthorized', { status: 401 });
|
|
136
|
+
});
|
|
137
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
138
|
+
await expect(transport.callTool('secure-tool', {}, 5000)).rejects.toThrow(McpTransportError);
|
|
139
|
+
try {
|
|
140
|
+
await transport.callTool('secure-tool', {}, 5000);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
expect(err).toBeInstanceOf(McpTransportError);
|
|
144
|
+
expect(err.message).toContain('authentication failed');
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
it('classifies HTTP 403 as auth-failure error', async () => {
|
|
148
|
+
globalThis.fetch = vi.fn(async () => {
|
|
149
|
+
return new Response('Forbidden', { status: 403 });
|
|
150
|
+
});
|
|
151
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
152
|
+
await expect(transport.callTool('forbidden-tool', {}, 5000)).rejects.toThrow(McpTransportError);
|
|
153
|
+
try {
|
|
154
|
+
await transport.callTool('forbidden-tool', {}, 5000);
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
expect(err).toBeInstanceOf(McpTransportError);
|
|
158
|
+
expect(err.message).toContain('authentication failed');
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
it('throws McpTransportError on HTTP 5xx server error', async () => {
|
|
162
|
+
globalThis.fetch = vi.fn(async () => {
|
|
163
|
+
return new Response('Internal Server Error', { status: 500 });
|
|
164
|
+
});
|
|
165
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
166
|
+
await expect(transport.callTool('error-tool', {}, 5000)).rejects.toThrow(McpTransportError);
|
|
167
|
+
try {
|
|
168
|
+
await transport.callTool('error-tool', {}, 5000);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
expect(err).toBeInstanceOf(McpTransportError);
|
|
172
|
+
expect(err.message).toContain('server error');
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
it('throws McpTransportError on non-JSON response body', async () => {
|
|
176
|
+
globalThis.fetch = vi.fn(async () => {
|
|
177
|
+
return new Response('not json at all', {
|
|
178
|
+
status: 200,
|
|
179
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
183
|
+
await expect(transport.callTool('bad-format', {}, 5000)).rejects.toThrow(McpTransportError);
|
|
184
|
+
try {
|
|
185
|
+
await transport.callTool('bad-format', {}, 5000);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
expect(err).toBeInstanceOf(McpTransportError);
|
|
189
|
+
expect(err.message).toContain('Non-JSON response');
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
it('throws McpTransportError on JSON-RPC error in response', async () => {
|
|
193
|
+
globalThis.fetch = vi.fn(async () => {
|
|
194
|
+
return new Response(JSON.stringify({
|
|
195
|
+
jsonrpc: '2.0',
|
|
196
|
+
id: 1,
|
|
197
|
+
error: { code: -32601, message: 'Method not found' },
|
|
198
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
199
|
+
});
|
|
200
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
201
|
+
await expect(transport.callTool('missing-tool', {}, 5000)).rejects.toThrow(McpTransportError);
|
|
202
|
+
try {
|
|
203
|
+
await transport.callTool('missing-tool', {}, 5000);
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
expect(err).toBeInstanceOf(McpTransportError);
|
|
207
|
+
expect(err.message).toContain('Method not found');
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
it('normalizes JSON-RPC validation errors with JSON details', async () => {
|
|
211
|
+
globalThis.fetch = vi.fn(async () => {
|
|
212
|
+
return new Response(JSON.stringify({
|
|
213
|
+
jsonrpc: '2.0',
|
|
214
|
+
id: 1,
|
|
215
|
+
error: {
|
|
216
|
+
code: -32602,
|
|
217
|
+
message: 'MCP error -32602: Input validation error: Invalid arguments for tool resolve-library-id: [{"path":["query"],"message":"Invalid input: expected string, received undefined"}]',
|
|
218
|
+
},
|
|
219
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
220
|
+
});
|
|
221
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
222
|
+
await expect(transport.callTool('resolve-library-id', {}, 5000)).rejects.toThrow(McpTransportError);
|
|
223
|
+
try {
|
|
224
|
+
await transport.callTool('resolve-library-id', {}, 5000);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
expect(err).toBeInstanceOf(McpTransportError);
|
|
228
|
+
expect(err.message).not.toContain('[{');
|
|
229
|
+
expect(err.message).toContain('query: Invalid input');
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
it('extracts content from result.content[0].text', async () => {
|
|
233
|
+
globalThis.fetch = vi.fn(async () => {
|
|
234
|
+
return new Response(JSON.stringify({
|
|
235
|
+
jsonrpc: '2.0',
|
|
236
|
+
id: 1,
|
|
237
|
+
result: { content: [{ type: 'text', text: '{"found": true}' }] },
|
|
238
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
239
|
+
});
|
|
240
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
241
|
+
const response = await transport.callTool('search', {}, 5000);
|
|
242
|
+
expect(response.content).toBe('{"found": true}');
|
|
243
|
+
});
|
|
244
|
+
it('returns result directly when no content array', async () => {
|
|
245
|
+
globalThis.fetch = vi.fn(async () => {
|
|
246
|
+
return new Response(JSON.stringify({
|
|
247
|
+
jsonrpc: '2.0',
|
|
248
|
+
id: 1,
|
|
249
|
+
result: { key: 'value', nested: { a: 1 } },
|
|
250
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
251
|
+
});
|
|
252
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
253
|
+
const response = await transport.callTool('data', {}, 5000);
|
|
254
|
+
expect(response.content).toEqual({ key: 'value', nested: { a: 1 } });
|
|
255
|
+
});
|
|
256
|
+
it('includes config headers in request', async () => {
|
|
257
|
+
let capturedHeaders = {};
|
|
258
|
+
globalThis.fetch = vi.fn(async (_url, init) => {
|
|
259
|
+
capturedHeaders = Object.fromEntries(Object.entries(init.headers));
|
|
260
|
+
return new Response(JSON.stringify({
|
|
261
|
+
jsonrpc: '2.0',
|
|
262
|
+
id: 1,
|
|
263
|
+
result: {},
|
|
264
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
265
|
+
});
|
|
266
|
+
delete process.env.GITHUB_TOKEN;
|
|
267
|
+
const transport = new HttpMcpTransport(makeHttpConfig({ headers: { 'X-Custom': 'value' } }), makeLogger());
|
|
268
|
+
await transport.callTool('tool', {}, 5000);
|
|
269
|
+
expect(capturedHeaders['X-Custom']).toBe('value');
|
|
270
|
+
expect(capturedHeaders['Content-Type']).toBe('application/json');
|
|
271
|
+
});
|
|
272
|
+
it('isConnected() always returns true (HTTP is stateless)', () => {
|
|
273
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
274
|
+
expect(transport.isConnected()).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
it('disconnect() is a no-op', async () => {
|
|
277
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
278
|
+
await expect(transport.disconnect()).resolves.toBeUndefined();
|
|
279
|
+
expect(transport.isConnected()).toBe(true);
|
|
280
|
+
});
|
|
281
|
+
it('includes raw response in ToolCallResponse', async () => {
|
|
282
|
+
const rawResult = { content: [{ type: 'text', text: 'hello' }] };
|
|
283
|
+
globalThis.fetch = vi.fn(async () => {
|
|
284
|
+
return new Response(JSON.stringify({
|
|
285
|
+
jsonrpc: '2.0',
|
|
286
|
+
id: 1,
|
|
287
|
+
result: rawResult,
|
|
288
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
289
|
+
});
|
|
290
|
+
const transport = new HttpMcpTransport(makeHttpConfig(), makeLogger());
|
|
291
|
+
const response = await transport.callTool('tool', {}, 5000);
|
|
292
|
+
expect(response.raw).toBeDefined();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
// ── StdioMcpTransport Tests (T005) ──────────────────────────────────────────
|
|
296
|
+
// We mock child_process.spawn to avoid real subprocess spawning
|
|
297
|
+
vi.mock('node:child_process', () => {
|
|
298
|
+
return {
|
|
299
|
+
spawn: vi.fn(),
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
describe('StdioMcpTransport', () => {
|
|
303
|
+
let mockSpawn;
|
|
304
|
+
beforeEach(async () => {
|
|
305
|
+
const cp = await import('node:child_process');
|
|
306
|
+
mockSpawn = vi.mocked(cp.spawn);
|
|
307
|
+
mockSpawn.mockReset();
|
|
308
|
+
});
|
|
309
|
+
/**
|
|
310
|
+
* Create a mock child process with stdin/stdout/stderr streams
|
|
311
|
+
* and event emitter capabilities.
|
|
312
|
+
*/
|
|
313
|
+
function createMockProcess() {
|
|
314
|
+
const stdout = new PassThrough();
|
|
315
|
+
const stdin = { write: vi.fn(), end: vi.fn() };
|
|
316
|
+
const stderr = new PassThrough();
|
|
317
|
+
const proc = new EventEmitter();
|
|
318
|
+
Object.assign(proc, {
|
|
319
|
+
stdout,
|
|
320
|
+
stdin,
|
|
321
|
+
stderr,
|
|
322
|
+
pid: 12345,
|
|
323
|
+
kill: vi.fn(),
|
|
324
|
+
_stdout: stdout,
|
|
325
|
+
_stdin: stdin,
|
|
326
|
+
});
|
|
327
|
+
return proc;
|
|
328
|
+
}
|
|
329
|
+
it('spawns subprocess with correct command and args', async () => {
|
|
330
|
+
const proc = createMockProcess();
|
|
331
|
+
mockSpawn.mockReturnValue(proc);
|
|
332
|
+
const config = makeStdioConfig({ command: 'npx', args: ['-y', 'my-mcp-server'] });
|
|
333
|
+
const transport = new StdioMcpTransport(config, makeLogger());
|
|
334
|
+
// Start connect but don't await — we need to send the response
|
|
335
|
+
const connectPromise = transport.connect();
|
|
336
|
+
// Simulate init response
|
|
337
|
+
setTimeout(() => {
|
|
338
|
+
proc._stdout.write(JSON.stringify({ jsonrpc: '2.0', id: 1, result: { ok: true } }) + '\n');
|
|
339
|
+
}, 10);
|
|
340
|
+
await connectPromise;
|
|
341
|
+
expect(mockSpawn).toHaveBeenCalledWith('npx', ['-y', 'my-mcp-server'], expect.objectContaining({
|
|
342
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
343
|
+
}));
|
|
344
|
+
});
|
|
345
|
+
it('sends initialize JSON-RPC request during connect', async () => {
|
|
346
|
+
const proc = createMockProcess();
|
|
347
|
+
mockSpawn.mockReturnValue(proc);
|
|
348
|
+
const transport = new StdioMcpTransport(makeStdioConfig(), makeLogger());
|
|
349
|
+
const connectPromise = transport.connect();
|
|
350
|
+
setTimeout(() => {
|
|
351
|
+
proc._stdout.write(JSON.stringify({ jsonrpc: '2.0', id: 1, result: {} }) + '\n');
|
|
352
|
+
}, 10);
|
|
353
|
+
await connectPromise;
|
|
354
|
+
expect(proc._stdin.write).toHaveBeenCalled();
|
|
355
|
+
const written = proc._stdin.write.mock.calls[0][0];
|
|
356
|
+
const parsed = JSON.parse(written.trim());
|
|
357
|
+
expect(parsed).toEqual({
|
|
358
|
+
jsonrpc: '2.0',
|
|
359
|
+
id: 1,
|
|
360
|
+
method: 'initialize',
|
|
361
|
+
params: {
|
|
362
|
+
protocolVersion: '1.0',
|
|
363
|
+
clientInfo: { name: 'sofIA', version: '0.1.0' },
|
|
364
|
+
capabilities: {},
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
it('resolves callTool on matching response id', async () => {
|
|
369
|
+
const proc = createMockProcess();
|
|
370
|
+
mockSpawn.mockReturnValue(proc);
|
|
371
|
+
const transport = new StdioMcpTransport(makeStdioConfig(), makeLogger());
|
|
372
|
+
const connectPromise = transport.connect();
|
|
373
|
+
setTimeout(() => {
|
|
374
|
+
proc._stdout.write(JSON.stringify({ jsonrpc: '2.0', id: 1, result: {} }) + '\n');
|
|
375
|
+
}, 10);
|
|
376
|
+
await connectPromise;
|
|
377
|
+
// Now callTool — the next id will be 2
|
|
378
|
+
const callPromise = transport.callTool('test-tool', { arg: 'value' }, 5000);
|
|
379
|
+
setTimeout(() => {
|
|
380
|
+
proc._stdout.write(JSON.stringify({
|
|
381
|
+
jsonrpc: '2.0',
|
|
382
|
+
id: 2,
|
|
383
|
+
result: { content: [{ type: 'text', text: '{"result":"ok"}' }] },
|
|
384
|
+
}) + '\n');
|
|
385
|
+
}, 10);
|
|
386
|
+
const response = await callPromise;
|
|
387
|
+
expect(response.content).toBe('{"result":"ok"}');
|
|
388
|
+
});
|
|
389
|
+
it('rejects all pending requests when subprocess exits', async () => {
|
|
390
|
+
const proc = createMockProcess();
|
|
391
|
+
mockSpawn.mockReturnValue(proc);
|
|
392
|
+
const transport = new StdioMcpTransport(makeStdioConfig(), makeLogger());
|
|
393
|
+
const connectPromise = transport.connect();
|
|
394
|
+
setTimeout(() => {
|
|
395
|
+
proc._stdout.write(JSON.stringify({ jsonrpc: '2.0', id: 1, result: {} }) + '\n');
|
|
396
|
+
}, 10);
|
|
397
|
+
await connectPromise;
|
|
398
|
+
// Start a call that will hang
|
|
399
|
+
const callPromise = transport.callTool('will-fail', {}, 30000);
|
|
400
|
+
// Simulate subprocess exit
|
|
401
|
+
setTimeout(() => {
|
|
402
|
+
proc.emit('exit', 1);
|
|
403
|
+
}, 10);
|
|
404
|
+
await expect(callPromise).rejects.toThrow(McpTransportError);
|
|
405
|
+
try {
|
|
406
|
+
await callPromise;
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
expect(err).toBeInstanceOf(McpTransportError);
|
|
410
|
+
expect(err.message).toContain('exited');
|
|
411
|
+
expect(err.code).toBe('ECONNREFUSED');
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
it('rejects with timeout on failed handshake (5 second timeout)', async () => {
|
|
415
|
+
const proc = createMockProcess();
|
|
416
|
+
mockSpawn.mockReturnValue(proc);
|
|
417
|
+
const transport = new StdioMcpTransport(makeStdioConfig(), makeLogger());
|
|
418
|
+
// Don't send any response — let it time out
|
|
419
|
+
// We'll use a much shorter timeout for the test by checking the error
|
|
420
|
+
// We can't wait 5 real seconds, so we'll use fake timers
|
|
421
|
+
vi.useFakeTimers();
|
|
422
|
+
const connectPromise = transport.connect();
|
|
423
|
+
// Advance past the 5-second timeout
|
|
424
|
+
vi.advanceTimersByTime(5100);
|
|
425
|
+
await expect(connectPromise).rejects.toThrow(McpTransportError);
|
|
426
|
+
try {
|
|
427
|
+
await connectPromise;
|
|
428
|
+
}
|
|
429
|
+
catch (err) {
|
|
430
|
+
expect(err).toBeInstanceOf(McpTransportError);
|
|
431
|
+
expect(err.message).toContain('timed out');
|
|
432
|
+
expect(err.message).toContain('5 seconds');
|
|
433
|
+
expect(err.code).toBe('ETIMEDOUT');
|
|
434
|
+
}
|
|
435
|
+
vi.useRealTimers();
|
|
436
|
+
});
|
|
437
|
+
it('throws when callTool is called before connect', async () => {
|
|
438
|
+
const transport = new StdioMcpTransport(makeStdioConfig(), makeLogger());
|
|
439
|
+
await expect(transport.callTool('tool', {}, 5000)).rejects.toThrow(McpTransportError);
|
|
440
|
+
try {
|
|
441
|
+
await transport.callTool('tool', {}, 5000);
|
|
442
|
+
}
|
|
443
|
+
catch (err) {
|
|
444
|
+
expect(err.message).toContain('not connected');
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
it('rejects pending request on callTool timeout', async () => {
|
|
448
|
+
const proc = createMockProcess();
|
|
449
|
+
mockSpawn.mockReturnValue(proc);
|
|
450
|
+
const transport = new StdioMcpTransport(makeStdioConfig(), makeLogger());
|
|
451
|
+
const connectPromise = transport.connect();
|
|
452
|
+
setTimeout(() => {
|
|
453
|
+
proc._stdout.write(JSON.stringify({ jsonrpc: '2.0', id: 1, result: {} }) + '\n');
|
|
454
|
+
}, 10);
|
|
455
|
+
await connectPromise;
|
|
456
|
+
// callTool with very short timeout — process never responds
|
|
457
|
+
vi.useFakeTimers();
|
|
458
|
+
const callPromise = transport.callTool('slow-tool', {}, 100);
|
|
459
|
+
vi.advanceTimersByTime(150);
|
|
460
|
+
await expect(callPromise).rejects.toThrow(McpTransportError);
|
|
461
|
+
try {
|
|
462
|
+
await callPromise;
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
expect(err.message).toContain('timed out');
|
|
466
|
+
expect(err.code).toBe('ETIMEDOUT');
|
|
467
|
+
}
|
|
468
|
+
vi.useRealTimers();
|
|
469
|
+
});
|
|
470
|
+
it('disconnect rejects pending and kills subprocess', async () => {
|
|
471
|
+
const proc = createMockProcess();
|
|
472
|
+
mockSpawn.mockReturnValue(proc);
|
|
473
|
+
const transport = new StdioMcpTransport(makeStdioConfig(), makeLogger());
|
|
474
|
+
const connectPromise = transport.connect();
|
|
475
|
+
setTimeout(() => {
|
|
476
|
+
proc._stdout.write(JSON.stringify({ jsonrpc: '2.0', id: 1, result: {} }) + '\n');
|
|
477
|
+
}, 10);
|
|
478
|
+
await connectPromise;
|
|
479
|
+
expect(transport.isConnected()).toBe(true);
|
|
480
|
+
await transport.disconnect();
|
|
481
|
+
expect(transport.isConnected()).toBe(false);
|
|
482
|
+
expect(proc.kill).toHaveBeenCalledWith('SIGTERM');
|
|
483
|
+
});
|
|
484
|
+
it('passes env variables to subprocess', async () => {
|
|
485
|
+
const proc = createMockProcess();
|
|
486
|
+
mockSpawn.mockReturnValue(proc);
|
|
487
|
+
const config = makeStdioConfig({
|
|
488
|
+
env: { MY_VAR: 'test_value' },
|
|
489
|
+
});
|
|
490
|
+
const transport = new StdioMcpTransport(config, makeLogger());
|
|
491
|
+
const connectPromise = transport.connect();
|
|
492
|
+
setTimeout(() => {
|
|
493
|
+
proc._stdout.write(JSON.stringify({ jsonrpc: '2.0', id: 1, result: {} }) + '\n');
|
|
494
|
+
}, 10);
|
|
495
|
+
await connectPromise;
|
|
496
|
+
const spawnCall = mockSpawn.mock.calls[0];
|
|
497
|
+
const spawnOptions = spawnCall[2];
|
|
498
|
+
expect(spawnOptions.env).toHaveProperty('MY_VAR', 'test_value');
|
|
499
|
+
});
|
|
500
|
+
it('skips non-JSON stdout lines without error', async () => {
|
|
501
|
+
const proc = createMockProcess();
|
|
502
|
+
mockSpawn.mockReturnValue(proc);
|
|
503
|
+
const logger = makeLogger();
|
|
504
|
+
const transport = new StdioMcpTransport(makeStdioConfig(), logger);
|
|
505
|
+
const connectPromise = transport.connect();
|
|
506
|
+
// Send garbage then the real response
|
|
507
|
+
proc._stdout.write('some garbage output\n');
|
|
508
|
+
setTimeout(() => {
|
|
509
|
+
proc._stdout.write(JSON.stringify({ jsonrpc: '2.0', id: 1, result: {} }) + '\n');
|
|
510
|
+
}, 10);
|
|
511
|
+
await connectPromise;
|
|
512
|
+
expect(logger.debug).toHaveBeenCalled();
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
// ── createTransport() Factory Tests ─────────────────────────────────────────
|
|
516
|
+
describe('createTransport()', () => {
|
|
517
|
+
it('creates HttpMcpTransport for http type config', () => {
|
|
518
|
+
const transport = createTransport(makeHttpConfig(), makeLogger());
|
|
519
|
+
expect(transport).toBeInstanceOf(HttpMcpTransport);
|
|
520
|
+
});
|
|
521
|
+
it('creates StdioMcpTransport for stdio type config', () => {
|
|
522
|
+
const transport = createTransport(makeStdioConfig(), makeLogger());
|
|
523
|
+
expect(transport).toBeInstanceOf(StdioMcpTransport);
|
|
524
|
+
});
|
|
525
|
+
it('throws for unsupported transport type', () => {
|
|
526
|
+
const badConfig = { name: 'bad', type: 'grpc' };
|
|
527
|
+
expect(() => createTransport(badConfig, makeLogger())).toThrow('Unsupported MCP transport type');
|
|
528
|
+
});
|
|
529
|
+
});
|