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,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T032: Unit tests for GitHubMcpAdapter.
|
|
3
|
+
*
|
|
4
|
+
* Verifies:
|
|
5
|
+
* - isAvailable() checks McpManager.isAvailable('github')
|
|
6
|
+
* - createRepository() calls MCP tool
|
|
7
|
+
* - pushFiles() commits and pushes
|
|
8
|
+
* - Graceful fallback returns { available: false } when MCP unavailable
|
|
9
|
+
*/
|
|
10
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
11
|
+
import { GitHubMcpAdapter } from '../../../src/develop/githubMcpAdapter.js';
|
|
12
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
13
|
+
function makeMcpManager(githubAvailable, callToolImpl) {
|
|
14
|
+
return {
|
|
15
|
+
isAvailable: (name) => name === 'github' && githubAvailable,
|
|
16
|
+
listServers: () => [],
|
|
17
|
+
getServerConfig: () => undefined,
|
|
18
|
+
markConnected: () => { },
|
|
19
|
+
markDisconnected: () => { },
|
|
20
|
+
getAllConfigs: () => [],
|
|
21
|
+
callTool: callToolImpl
|
|
22
|
+
? vi.fn(callToolImpl)
|
|
23
|
+
: vi.fn().mockRejectedValue(new Error('not wired')),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
describe('GitHubMcpAdapter', () => {
|
|
27
|
+
describe('isAvailable()', () => {
|
|
28
|
+
it('returns true when McpManager.isAvailable("github") is true', () => {
|
|
29
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(true));
|
|
30
|
+
expect(adapter.isAvailable()).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
it('returns false when GitHub MCP is not available', () => {
|
|
33
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(false));
|
|
34
|
+
expect(adapter.isAvailable()).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('createRepository()', () => {
|
|
38
|
+
it('calls mcpManager.callTool with create_repository and returns parsed result', async () => {
|
|
39
|
+
const callTool = vi.fn().mockResolvedValue({
|
|
40
|
+
html_url: 'https://github.com/acme/poc-route-optimizer',
|
|
41
|
+
name: 'poc-route-optimizer',
|
|
42
|
+
});
|
|
43
|
+
const mgr = makeMcpManager(true, callTool);
|
|
44
|
+
const adapter = new GitHubMcpAdapter(mgr);
|
|
45
|
+
const result = await adapter.createRepository({
|
|
46
|
+
name: 'poc-route-optimizer',
|
|
47
|
+
description: 'AI route optimization PoC',
|
|
48
|
+
});
|
|
49
|
+
expect(result.available).toBe(true);
|
|
50
|
+
if (result.available) {
|
|
51
|
+
expect(result.repoUrl).toBe('https://github.com/acme/poc-route-optimizer');
|
|
52
|
+
expect(result.repoName).toBe('poc-route-optimizer');
|
|
53
|
+
}
|
|
54
|
+
expect(callTool).toHaveBeenCalledWith('github', 'create_repository', {
|
|
55
|
+
name: 'poc-route-optimizer',
|
|
56
|
+
description: 'AI route optimization PoC',
|
|
57
|
+
private: true,
|
|
58
|
+
}, { timeoutMs: 60_000 });
|
|
59
|
+
});
|
|
60
|
+
it('returns { available: false } when GitHub MCP is unavailable', async () => {
|
|
61
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(false));
|
|
62
|
+
const result = await adapter.createRepository({
|
|
63
|
+
name: 'poc-route-optimizer',
|
|
64
|
+
});
|
|
65
|
+
expect(result.available).toBe(false);
|
|
66
|
+
if (!result.available) {
|
|
67
|
+
expect(result.reason).toBeDefined();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
it('returns { available: false } when callTool throws', async () => {
|
|
71
|
+
const callTool = vi.fn().mockRejectedValue(new Error('MCP callTool not yet wired'));
|
|
72
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
|
|
73
|
+
const result = await adapter.createRepository({ name: 'poc-fail' });
|
|
74
|
+
expect(result.available).toBe(false);
|
|
75
|
+
if (!result.available) {
|
|
76
|
+
expect(result.reason).toContain('not yet wired');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
it('stores repo URL after successful creation', async () => {
|
|
80
|
+
const callTool = vi.fn().mockResolvedValue({
|
|
81
|
+
html_url: 'https://github.com/acme/my-poc',
|
|
82
|
+
name: 'my-poc',
|
|
83
|
+
});
|
|
84
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
|
|
85
|
+
await adapter.createRepository({ name: 'my-poc' });
|
|
86
|
+
expect(adapter.getRepoUrl()).toBe('https://github.com/acme/my-poc');
|
|
87
|
+
});
|
|
88
|
+
it('returns { available: false } when response has no URL', async () => {
|
|
89
|
+
const callTool = vi.fn().mockResolvedValue({ id: 123 });
|
|
90
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
|
|
91
|
+
const result = await adapter.createRepository({ name: 'no-url' });
|
|
92
|
+
expect(result.available).toBe(false);
|
|
93
|
+
if (!result.available) {
|
|
94
|
+
expect(result.reason).toContain('missing repository URL');
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe('pushFiles()', () => {
|
|
99
|
+
it('calls mcpManager.callTool with push_files and returns commitSha', async () => {
|
|
100
|
+
const callTool = vi.fn().mockResolvedValue({ sha: 'abc123ff' });
|
|
101
|
+
const mgr = makeMcpManager(true, callTool);
|
|
102
|
+
const adapter = new GitHubMcpAdapter(mgr);
|
|
103
|
+
const result = await adapter.pushFiles({
|
|
104
|
+
repoUrl: 'https://github.com/acme/my-poc',
|
|
105
|
+
files: [
|
|
106
|
+
{ path: 'src/index.ts', content: 'export function main() {}' },
|
|
107
|
+
{ path: 'package.json', content: '{"name":"my-poc"}' },
|
|
108
|
+
],
|
|
109
|
+
commitMessage: 'chore: initial scaffold',
|
|
110
|
+
});
|
|
111
|
+
expect(result.available).toBe(true);
|
|
112
|
+
if (result.available) {
|
|
113
|
+
expect(result.commitSha).toBe('abc123ff');
|
|
114
|
+
}
|
|
115
|
+
expect(callTool).toHaveBeenCalledWith('github', 'push_files', {
|
|
116
|
+
owner: 'acme',
|
|
117
|
+
repo: 'my-poc',
|
|
118
|
+
files: [
|
|
119
|
+
{ path: 'src/index.ts', content: 'export function main() {}' },
|
|
120
|
+
{ path: 'package.json', content: '{"name":"my-poc"}' },
|
|
121
|
+
],
|
|
122
|
+
message: 'chore: initial scaffold',
|
|
123
|
+
branch: 'main',
|
|
124
|
+
}, { timeoutMs: 60_000 });
|
|
125
|
+
});
|
|
126
|
+
it('returns { available: false } when GitHub MCP is unavailable', async () => {
|
|
127
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(false));
|
|
128
|
+
const result = await adapter.pushFiles({
|
|
129
|
+
repoUrl: 'https://github.com/acme/my-poc',
|
|
130
|
+
files: [],
|
|
131
|
+
commitMessage: 'test',
|
|
132
|
+
});
|
|
133
|
+
expect(result.available).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
it('returns { available: false } when callTool throws', async () => {
|
|
136
|
+
const callTool = vi.fn().mockRejectedValue(new Error('push failed'));
|
|
137
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
|
|
138
|
+
const result = await adapter.pushFiles({
|
|
139
|
+
repoUrl: 'https://github.com/acme/my-poc',
|
|
140
|
+
files: [{ path: 'a.ts', content: '' }],
|
|
141
|
+
commitMessage: 'boom',
|
|
142
|
+
});
|
|
143
|
+
expect(result.available).toBe(false);
|
|
144
|
+
if (!result.available) {
|
|
145
|
+
expect(result.reason).toContain('push failed');
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
it('handles empty files array', async () => {
|
|
149
|
+
const callTool = vi.fn().mockResolvedValue({ sha: 'empty00' });
|
|
150
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
|
|
151
|
+
const result = await adapter.pushFiles({
|
|
152
|
+
repoUrl: 'https://github.com/acme/my-poc',
|
|
153
|
+
files: [],
|
|
154
|
+
commitMessage: 'empty commit',
|
|
155
|
+
});
|
|
156
|
+
expect(result.available).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('getRepoUrl()', () => {
|
|
160
|
+
it('returns undefined before createRepository is called', () => {
|
|
161
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(true));
|
|
162
|
+
expect(adapter.getRepoUrl()).toBeUndefined();
|
|
163
|
+
});
|
|
164
|
+
it('returns URL after successful createRepository', async () => {
|
|
165
|
+
const callTool = vi.fn().mockResolvedValue({
|
|
166
|
+
html_url: 'https://github.com/acme/test-poc',
|
|
167
|
+
name: 'test-poc',
|
|
168
|
+
});
|
|
169
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
|
|
170
|
+
await adapter.createRepository({ name: 'test-poc' });
|
|
171
|
+
expect(adapter.getRepoUrl()).toBe('https://github.com/acme/test-poc');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
// ── T008: Contract tests per contracts/github-adapter.md ────────────────
|
|
175
|
+
describe('createRepository() — contract: URL fallback chain', () => {
|
|
176
|
+
it('falls back to response.url when html_url is missing', async () => {
|
|
177
|
+
const callTool = vi.fn().mockResolvedValue({
|
|
178
|
+
url: 'https://api.github.com/repos/acme/poc',
|
|
179
|
+
name: 'poc',
|
|
180
|
+
});
|
|
181
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
|
|
182
|
+
const result = await adapter.createRepository({ name: 'poc' });
|
|
183
|
+
expect(result.available).toBe(true);
|
|
184
|
+
if (result.available) {
|
|
185
|
+
expect(result.repoUrl).toBe('https://api.github.com/repos/acme/poc');
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
it('falls back to response.clone_url when html_url and url are missing', async () => {
|
|
189
|
+
const callTool = vi.fn().mockResolvedValue({
|
|
190
|
+
clone_url: 'https://github.com/acme/poc.git',
|
|
191
|
+
name: 'poc',
|
|
192
|
+
});
|
|
193
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
|
|
194
|
+
const result = await adapter.createRepository({ name: 'poc' });
|
|
195
|
+
expect(result.available).toBe(true);
|
|
196
|
+
if (result.available) {
|
|
197
|
+
expect(result.repoUrl).toBe('https://github.com/acme/poc.git');
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
it('extracts repoName from response.full_name as fallback', async () => {
|
|
201
|
+
const callTool = vi.fn().mockResolvedValue({
|
|
202
|
+
html_url: 'https://github.com/acme/poc',
|
|
203
|
+
full_name: 'acme/poc',
|
|
204
|
+
});
|
|
205
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
|
|
206
|
+
const result = await adapter.createRepository({ name: 'poc' });
|
|
207
|
+
expect(result.available).toBe(true);
|
|
208
|
+
if (result.available) {
|
|
209
|
+
// name not in response → full_name fallback returns 'acme/poc'
|
|
210
|
+
expect(result.repoName).toBe('acme/poc');
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
describe('pushFiles() — contract: commitSha extraction', () => {
|
|
215
|
+
it('extracts commitSha from response.commit.sha as fallback', async () => {
|
|
216
|
+
const callTool = vi.fn().mockResolvedValue({
|
|
217
|
+
commit: { sha: 'nested-sha-abc' },
|
|
218
|
+
});
|
|
219
|
+
const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
|
|
220
|
+
const result = await adapter.pushFiles({
|
|
221
|
+
repoUrl: 'https://github.com/acme/poc',
|
|
222
|
+
files: [{ path: 'index.ts', content: 'hello' }],
|
|
223
|
+
commitMessage: 'init',
|
|
224
|
+
});
|
|
225
|
+
expect(result.available).toBe(true);
|
|
226
|
+
if (result.available) {
|
|
227
|
+
expect(result.commitSha).toBe('nested-sha-abc');
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T047: Unit tests for McpContextEnricher.
|
|
3
|
+
*
|
|
4
|
+
* Verifies:
|
|
5
|
+
* - Queries Context7 for library docs when dependencies listed in plan
|
|
6
|
+
* - Queries Azure MCP when plan mentions Azure services
|
|
7
|
+
* - Calls web.search when stuckIterations > 0
|
|
8
|
+
* - Falls back gracefully when MCP services unavailable
|
|
9
|
+
* - Returns structured context string suitable for prompt injection
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
12
|
+
import { McpContextEnricher } from '../../../src/develop/mcpContextEnricher.js';
|
|
13
|
+
// ── Mock web search ───────────────────────────────────────────────────────────
|
|
14
|
+
vi.mock('../../../src/mcp/webSearch.js', () => ({
|
|
15
|
+
isWebSearchConfigured: vi.fn(() => false),
|
|
16
|
+
}));
|
|
17
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
18
|
+
function makeMcpManager(availableServers = [], callToolImpl) {
|
|
19
|
+
return {
|
|
20
|
+
isAvailable: (name) => availableServers.includes(name),
|
|
21
|
+
listServers: () => availableServers,
|
|
22
|
+
getServerConfig: () => undefined,
|
|
23
|
+
markConnected: () => { },
|
|
24
|
+
markDisconnected: () => { },
|
|
25
|
+
getAllConfigs: () => [],
|
|
26
|
+
callTool: callToolImpl
|
|
27
|
+
? vi.fn(callToolImpl)
|
|
28
|
+
: vi.fn().mockRejectedValue(new Error('not wired')),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
describe('McpContextEnricher', () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.clearAllMocks();
|
|
34
|
+
});
|
|
35
|
+
describe('constructor', () => {
|
|
36
|
+
it('stores mcpManager reference', () => {
|
|
37
|
+
const manager = makeMcpManager();
|
|
38
|
+
const enricher = new McpContextEnricher(manager);
|
|
39
|
+
expect(enricher.mcpManager).toBe(manager);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('enrich() — Context7', () => {
|
|
43
|
+
it('queries Context7 when available and dependencies listed', async () => {
|
|
44
|
+
const callTool = vi
|
|
45
|
+
.fn()
|
|
46
|
+
.mockResolvedValueOnce({ libraryId: 'express-lib-id' })
|
|
47
|
+
.mockResolvedValueOnce({ content: 'Express.js API docs here' })
|
|
48
|
+
.mockResolvedValueOnce({ libraryId: 'zod-lib-id' })
|
|
49
|
+
.mockResolvedValueOnce({ content: 'Zod schema validation docs' });
|
|
50
|
+
const manager = makeMcpManager(['context7'], callTool);
|
|
51
|
+
const enricher = new McpContextEnricher(manager);
|
|
52
|
+
const result = await enricher.enrich({
|
|
53
|
+
mcpManager: manager,
|
|
54
|
+
dependencies: ['express', 'zod'],
|
|
55
|
+
});
|
|
56
|
+
expect(result.combined).toBeTruthy();
|
|
57
|
+
expect(result.libraryDocs).toBeDefined();
|
|
58
|
+
// Should have called resolve-library-id and query-docs for each dep
|
|
59
|
+
expect(callTool).toHaveBeenCalledWith('context7', 'resolve-library-id', {
|
|
60
|
+
libraryName: 'express',
|
|
61
|
+
}, { timeoutMs: 30_000 });
|
|
62
|
+
expect(callTool).toHaveBeenCalledWith('context7', 'query-docs', {
|
|
63
|
+
libraryId: 'express-lib-id',
|
|
64
|
+
topic: 'express',
|
|
65
|
+
}, { timeoutMs: 30_000 });
|
|
66
|
+
});
|
|
67
|
+
it('falls back to npmjs link when callTool throws for a dependency', async () => {
|
|
68
|
+
const callTool = vi.fn().mockRejectedValue(new Error('not wired'));
|
|
69
|
+
const manager = makeMcpManager(['context7'], callTool);
|
|
70
|
+
const enricher = new McpContextEnricher(manager);
|
|
71
|
+
const result = await enricher.enrich({
|
|
72
|
+
mcpManager: manager,
|
|
73
|
+
dependencies: ['express'],
|
|
74
|
+
});
|
|
75
|
+
expect(result.libraryDocs).toBeDefined();
|
|
76
|
+
expect(result.libraryDocs).toContain('npmjs.com/package/express');
|
|
77
|
+
});
|
|
78
|
+
it('skips Context7 when not available', async () => {
|
|
79
|
+
const manager = makeMcpManager([]); // no context7
|
|
80
|
+
const enricher = new McpContextEnricher(manager);
|
|
81
|
+
const result = await enricher.enrich({
|
|
82
|
+
mcpManager: manager,
|
|
83
|
+
dependencies: ['express', 'zod'],
|
|
84
|
+
});
|
|
85
|
+
expect(result.libraryDocs).toBeUndefined();
|
|
86
|
+
expect(result.combined).toBe('');
|
|
87
|
+
});
|
|
88
|
+
it('skips Context7 when no dependencies listed', async () => {
|
|
89
|
+
const manager = makeMcpManager(['context7']);
|
|
90
|
+
const enricher = new McpContextEnricher(manager);
|
|
91
|
+
const result = await enricher.enrich({
|
|
92
|
+
mcpManager: manager,
|
|
93
|
+
dependencies: [],
|
|
94
|
+
});
|
|
95
|
+
expect(result.libraryDocs).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
it('filters out type-only packages from Context7 queries', async () => {
|
|
98
|
+
const callTool = vi
|
|
99
|
+
.fn()
|
|
100
|
+
.mockResolvedValueOnce({ libraryId: 'express-id' })
|
|
101
|
+
.mockResolvedValueOnce({ content: 'Express docs' });
|
|
102
|
+
const manager = makeMcpManager(['context7'], callTool);
|
|
103
|
+
const enricher = new McpContextEnricher(manager);
|
|
104
|
+
const result = await enricher.enrich({
|
|
105
|
+
mcpManager: manager,
|
|
106
|
+
dependencies: ['@types/node', 'typescript', 'vitest', 'express'],
|
|
107
|
+
});
|
|
108
|
+
expect(result.libraryDocs).toBeDefined();
|
|
109
|
+
if (result.libraryDocs) {
|
|
110
|
+
expect(result.libraryDocs).toContain('express');
|
|
111
|
+
expect(result.libraryDocs).not.toContain('@types/node');
|
|
112
|
+
expect(result.libraryDocs).not.toContain('typescript');
|
|
113
|
+
}
|
|
114
|
+
// Only express should trigger callTool calls (2 calls: resolve + query)
|
|
115
|
+
expect(callTool).toHaveBeenCalledTimes(2);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe('enrich() — Azure MCP', () => {
|
|
119
|
+
it('calls mcpManager.callTool for Azure documentation when available', async () => {
|
|
120
|
+
const callTool = vi.fn().mockResolvedValue({
|
|
121
|
+
content: 'Use managed identity for Cosmos DB authentication.',
|
|
122
|
+
});
|
|
123
|
+
const manager = makeMcpManager(['azure'], callTool);
|
|
124
|
+
const enricher = new McpContextEnricher(manager);
|
|
125
|
+
const result = await enricher.enrich({
|
|
126
|
+
mcpManager: manager,
|
|
127
|
+
architectureNotes: 'Use Azure Cosmos DB for data storage and Azure OpenAI for inference.',
|
|
128
|
+
});
|
|
129
|
+
expect(result.azureGuidance).toBeDefined();
|
|
130
|
+
expect(result.azureGuidance).toContain('managed identity');
|
|
131
|
+
expect(callTool).toHaveBeenCalledWith('azure', 'documentation', expect.objectContaining({
|
|
132
|
+
query: expect.stringContaining('cosmos db'),
|
|
133
|
+
}), { timeoutMs: 30_000 });
|
|
134
|
+
});
|
|
135
|
+
it('falls back to static guidance when callTool throws', async () => {
|
|
136
|
+
const callTool = vi.fn().mockRejectedValue(new Error('not wired'));
|
|
137
|
+
const manager = makeMcpManager(['azure'], callTool);
|
|
138
|
+
const enricher = new McpContextEnricher(manager);
|
|
139
|
+
const result = await enricher.enrich({
|
|
140
|
+
mcpManager: manager,
|
|
141
|
+
architectureNotes: 'Use Azure Cosmos DB for data storage.',
|
|
142
|
+
});
|
|
143
|
+
expect(result.azureGuidance).toBeDefined();
|
|
144
|
+
expect(result.azureGuidance).toContain('Detected Azure services');
|
|
145
|
+
expect(result.combined).toContain('Azure');
|
|
146
|
+
});
|
|
147
|
+
it('skips Azure MCP when not available', async () => {
|
|
148
|
+
const manager = makeMcpManager([]);
|
|
149
|
+
const enricher = new McpContextEnricher(manager);
|
|
150
|
+
const result = await enricher.enrich({
|
|
151
|
+
mcpManager: manager,
|
|
152
|
+
architectureNotes: 'Use Azure Cosmos DB for data storage.',
|
|
153
|
+
});
|
|
154
|
+
expect(result.azureGuidance).toBeUndefined();
|
|
155
|
+
});
|
|
156
|
+
it('skips Azure MCP when plan does not mention Azure services', async () => {
|
|
157
|
+
const manager = makeMcpManager(['azure']);
|
|
158
|
+
const enricher = new McpContextEnricher(manager);
|
|
159
|
+
const result = await enricher.enrich({
|
|
160
|
+
mcpManager: manager,
|
|
161
|
+
architectureNotes: 'Use PostgreSQL and Express. No cloud dependencies.',
|
|
162
|
+
});
|
|
163
|
+
expect(result.azureGuidance).toBeUndefined();
|
|
164
|
+
});
|
|
165
|
+
it('detects various Azure keywords', async () => {
|
|
166
|
+
const callTool = vi.fn().mockRejectedValue(new Error('not wired'));
|
|
167
|
+
const manager = makeMcpManager(['azure'], callTool);
|
|
168
|
+
const enricher = new McpContextEnricher(manager);
|
|
169
|
+
const azureKeywords = ['cosmos db', 'blob storage', 'service bus', 'key vault'];
|
|
170
|
+
for (const keyword of azureKeywords) {
|
|
171
|
+
const result = await enricher.enrich({
|
|
172
|
+
mcpManager: manager,
|
|
173
|
+
architectureNotes: `Use ${keyword} for the implementation.`,
|
|
174
|
+
});
|
|
175
|
+
expect(result.azureGuidance, `Expected Azure guidance for keyword: ${keyword}`).toBeDefined();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
describe('enrich() — web.search', () => {
|
|
180
|
+
it('calls web.search when configured and stuckIterations >= 2', async () => {
|
|
181
|
+
const { isWebSearchConfigured } = await import('../../../src/mcp/webSearch.js');
|
|
182
|
+
vi.mocked(isWebSearchConfigured).mockReturnValue(true);
|
|
183
|
+
const manager = makeMcpManager([]);
|
|
184
|
+
const enricher = new McpContextEnricher(manager);
|
|
185
|
+
const result = await enricher.enrich({
|
|
186
|
+
mcpManager: manager,
|
|
187
|
+
stuckIterations: 2,
|
|
188
|
+
failingTests: ['suite > test A fails with TypeError'],
|
|
189
|
+
});
|
|
190
|
+
expect(result.webSearchResults).toBeDefined();
|
|
191
|
+
});
|
|
192
|
+
it('skips web.search when stuckIterations < 2', async () => {
|
|
193
|
+
const { isWebSearchConfigured } = await import('../../../src/mcp/webSearch.js');
|
|
194
|
+
vi.mocked(isWebSearchConfigured).mockReturnValue(true);
|
|
195
|
+
const manager = makeMcpManager([]);
|
|
196
|
+
const enricher = new McpContextEnricher(manager);
|
|
197
|
+
const result = await enricher.enrich({
|
|
198
|
+
mcpManager: manager,
|
|
199
|
+
stuckIterations: 1,
|
|
200
|
+
failingTests: ['suite > test A'],
|
|
201
|
+
});
|
|
202
|
+
expect(result.webSearchResults).toBeUndefined();
|
|
203
|
+
});
|
|
204
|
+
it('skips web.search when not configured', async () => {
|
|
205
|
+
const { isWebSearchConfigured } = await import('../../../src/mcp/webSearch.js');
|
|
206
|
+
vi.mocked(isWebSearchConfigured).mockReturnValue(false);
|
|
207
|
+
const manager = makeMcpManager([]);
|
|
208
|
+
const enricher = new McpContextEnricher(manager);
|
|
209
|
+
const result = await enricher.enrich({
|
|
210
|
+
mcpManager: manager,
|
|
211
|
+
stuckIterations: 3,
|
|
212
|
+
failingTests: ['test fails'],
|
|
213
|
+
});
|
|
214
|
+
expect(result.webSearchResults).toBeUndefined();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
describe('graceful degradation', () => {
|
|
218
|
+
it('returns empty context when all services unavailable', async () => {
|
|
219
|
+
const manager = makeMcpManager([]);
|
|
220
|
+
const enricher = new McpContextEnricher(manager);
|
|
221
|
+
const result = await enricher.enrich({
|
|
222
|
+
mcpManager: manager,
|
|
223
|
+
dependencies: ['express'],
|
|
224
|
+
architectureNotes: 'Use Azure and express',
|
|
225
|
+
stuckIterations: 5,
|
|
226
|
+
failingTests: ['test fails'],
|
|
227
|
+
});
|
|
228
|
+
// All services unavailable (web search mocked to false, no MCP servers)
|
|
229
|
+
expect(result.combined).toBe('');
|
|
230
|
+
});
|
|
231
|
+
it('returns combined context string when multiple services respond', async () => {
|
|
232
|
+
const { isWebSearchConfigured } = await import('../../../src/mcp/webSearch.js');
|
|
233
|
+
vi.mocked(isWebSearchConfigured).mockReturnValue(true);
|
|
234
|
+
const callTool = vi
|
|
235
|
+
.fn()
|
|
236
|
+
// Context7 resolve + query for 'express'
|
|
237
|
+
.mockResolvedValueOnce({ libraryId: 'express-id' })
|
|
238
|
+
.mockResolvedValueOnce({ content: 'Express framework docs' })
|
|
239
|
+
// Azure documentation
|
|
240
|
+
.mockResolvedValueOnce({ content: 'Azure Cosmos DB guidance' });
|
|
241
|
+
const manager = makeMcpManager(['context7', 'azure'], callTool);
|
|
242
|
+
const enricher = new McpContextEnricher(manager);
|
|
243
|
+
const result = await enricher.enrich({
|
|
244
|
+
mcpManager: manager,
|
|
245
|
+
dependencies: ['express'],
|
|
246
|
+
architectureNotes: 'Use Azure Cosmos DB',
|
|
247
|
+
stuckIterations: 2,
|
|
248
|
+
failingTests: ['test A fails'],
|
|
249
|
+
});
|
|
250
|
+
// combined should include sections from multiple services
|
|
251
|
+
expect(result.combined.length).toBeGreaterThan(0);
|
|
252
|
+
// Should have at least some context
|
|
253
|
+
const hasMultipleSections = (result.combined.match(/###/g) ?? []).length >= 1;
|
|
254
|
+
expect(hasMultipleSections).toBe(true);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
// ── T009: Contract tests per contracts/context-enricher.md ──────────────
|
|
258
|
+
describe('queryContext7 — contract: response field fallbacks', () => {
|
|
259
|
+
it('uses response.id as fallback when response.libraryId is missing', async () => {
|
|
260
|
+
const callTool = vi
|
|
261
|
+
.fn()
|
|
262
|
+
.mockResolvedValueOnce({ id: '/expressjs/express' }) // fallback field
|
|
263
|
+
.mockResolvedValueOnce({ content: 'Express docs from id fallback' });
|
|
264
|
+
const manager = makeMcpManager(['context7'], callTool);
|
|
265
|
+
const enricher = new McpContextEnricher(manager);
|
|
266
|
+
const result = await enricher.enrich({
|
|
267
|
+
mcpManager: manager,
|
|
268
|
+
dependencies: ['express'],
|
|
269
|
+
});
|
|
270
|
+
expect(result.libraryDocs).toBeDefined();
|
|
271
|
+
expect(result.libraryDocs).toContain('Express docs from id fallback');
|
|
272
|
+
// Should have called query-docs with the resolved id
|
|
273
|
+
expect(callTool).toHaveBeenCalledWith('context7', 'query-docs', {
|
|
274
|
+
libraryId: '/expressjs/express',
|
|
275
|
+
topic: 'express',
|
|
276
|
+
}, { timeoutMs: 30_000 });
|
|
277
|
+
});
|
|
278
|
+
it('uses response.text as fallback content when response.content is missing', async () => {
|
|
279
|
+
const callTool = vi
|
|
280
|
+
.fn()
|
|
281
|
+
.mockResolvedValueOnce({ libraryId: 'zod-id' })
|
|
282
|
+
.mockResolvedValueOnce({ text: 'Zod docs from text fallback' }); // text fallback
|
|
283
|
+
const manager = makeMcpManager(['context7'], callTool);
|
|
284
|
+
const enricher = new McpContextEnricher(manager);
|
|
285
|
+
const result = await enricher.enrich({
|
|
286
|
+
mcpManager: manager,
|
|
287
|
+
dependencies: ['zod'],
|
|
288
|
+
});
|
|
289
|
+
expect(result.libraryDocs).toBeDefined();
|
|
290
|
+
expect(result.libraryDocs).toContain('Zod docs from text fallback');
|
|
291
|
+
});
|
|
292
|
+
it('processes max 5 non-skipped dependencies', async () => {
|
|
293
|
+
const callTool = vi.fn().mockResolvedValue({ libraryId: 'lib-id', content: 'docs' });
|
|
294
|
+
const manager = makeMcpManager(['context7'], callTool);
|
|
295
|
+
const enricher = new McpContextEnricher(manager);
|
|
296
|
+
const deps = ['dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6', 'dep7'];
|
|
297
|
+
await enricher.enrich({
|
|
298
|
+
mcpManager: manager,
|
|
299
|
+
dependencies: deps,
|
|
300
|
+
});
|
|
301
|
+
// Only 5 non-skipped deps should be processed (2 calls each: resolve + query)
|
|
302
|
+
expect(callTool).toHaveBeenCalledTimes(10); // 5 * 2
|
|
303
|
+
});
|
|
304
|
+
it('falls back to npmjs link when both libraryId and id are missing', async () => {
|
|
305
|
+
const callTool = vi.fn().mockResolvedValueOnce({}); // no libraryId, no id
|
|
306
|
+
const manager = makeMcpManager(['context7'], callTool);
|
|
307
|
+
const enricher = new McpContextEnricher(manager);
|
|
308
|
+
const result = await enricher.enrich({
|
|
309
|
+
mcpManager: manager,
|
|
310
|
+
dependencies: ['unknown-pkg'],
|
|
311
|
+
});
|
|
312
|
+
expect(result.libraryDocs).toBeDefined();
|
|
313
|
+
expect(result.libraryDocs).toContain('npmjs.com/package/unknown-pkg');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
describe('queryAzureMcp — contract: response field fallbacks', () => {
|
|
317
|
+
it('uses response.text as fallback when response.content is missing', async () => {
|
|
318
|
+
const callTool = vi.fn().mockResolvedValue({
|
|
319
|
+
text: 'Azure guidance from text fallback',
|
|
320
|
+
});
|
|
321
|
+
const manager = makeMcpManager(['azure'], callTool);
|
|
322
|
+
const enricher = new McpContextEnricher(manager);
|
|
323
|
+
const result = await enricher.enrich({
|
|
324
|
+
mcpManager: manager,
|
|
325
|
+
architectureNotes: 'Use Azure Cosmos DB',
|
|
326
|
+
});
|
|
327
|
+
expect(result.azureGuidance).toBeDefined();
|
|
328
|
+
expect(result.azureGuidance).toContain('Azure guidance from text fallback');
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
describe('queryWebSearch — contract: MCP-first then fallback', () => {
|
|
332
|
+
it('tries MCP callTool websearch before Azure AI Foundry bridge', async () => {
|
|
333
|
+
const { isWebSearchConfigured } = await import('../../../src/mcp/webSearch.js');
|
|
334
|
+
vi.mocked(isWebSearchConfigured).mockReturnValue(true);
|
|
335
|
+
const callTool = vi.fn().mockResolvedValue({
|
|
336
|
+
content: 'MCP search results here',
|
|
337
|
+
});
|
|
338
|
+
const manager = makeMcpManager(['websearch'], callTool);
|
|
339
|
+
const enricher = new McpContextEnricher(manager);
|
|
340
|
+
const result = await enricher.enrich({
|
|
341
|
+
mcpManager: manager,
|
|
342
|
+
stuckIterations: 2,
|
|
343
|
+
failingTests: ['test A fails with TypeError'],
|
|
344
|
+
});
|
|
345
|
+
expect(result.webSearchResults).toContain('MCP search results here');
|
|
346
|
+
expect(callTool).toHaveBeenCalledWith('websearch', 'search', expect.objectContaining({
|
|
347
|
+
query: expect.stringContaining('how to fix'),
|
|
348
|
+
}), { timeoutMs: 30_000 });
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
// ── T010: Web search gating tests ───────────────────────────────────────
|
|
352
|
+
describe('enrich() — web search stuckIterations gating', () => {
|
|
353
|
+
it('MUST NOT invoke queryWebSearch when stuckIterations < 2', async () => {
|
|
354
|
+
const { isWebSearchConfigured } = await import('../../../src/mcp/webSearch.js');
|
|
355
|
+
vi.mocked(isWebSearchConfigured).mockReturnValue(true);
|
|
356
|
+
const callTool = vi.fn().mockResolvedValue({ content: 'should not be called' });
|
|
357
|
+
const manager = makeMcpManager(['websearch'], callTool);
|
|
358
|
+
const enricher = new McpContextEnricher(manager);
|
|
359
|
+
const result = await enricher.enrich({
|
|
360
|
+
mcpManager: manager,
|
|
361
|
+
stuckIterations: 1,
|
|
362
|
+
failingTests: ['test A fails'],
|
|
363
|
+
});
|
|
364
|
+
expect(result.webSearchResults).toBeUndefined();
|
|
365
|
+
// websearch callTool should NOT have been called
|
|
366
|
+
const webSearchCalls = callTool.mock.calls.filter((c) => c[0] === 'websearch');
|
|
367
|
+
expect(webSearchCalls).toHaveLength(0);
|
|
368
|
+
});
|
|
369
|
+
it('MUST invoke queryWebSearch when stuckIterations >= 2', async () => {
|
|
370
|
+
const { isWebSearchConfigured } = await import('../../../src/mcp/webSearch.js');
|
|
371
|
+
vi.mocked(isWebSearchConfigured).mockReturnValue(true);
|
|
372
|
+
const callTool = vi.fn().mockResolvedValue({ content: 'web search results' });
|
|
373
|
+
const manager = makeMcpManager(['websearch'], callTool);
|
|
374
|
+
const enricher = new McpContextEnricher(manager);
|
|
375
|
+
const result = await enricher.enrich({
|
|
376
|
+
mcpManager: manager,
|
|
377
|
+
stuckIterations: 2,
|
|
378
|
+
failingTests: ['test B fails with ReferenceError'],
|
|
379
|
+
});
|
|
380
|
+
expect(result.webSearchResults).toBeDefined();
|
|
381
|
+
const webSearchCalls = callTool.mock.calls.filter((c) => c[0] === 'websearch');
|
|
382
|
+
expect(webSearchCalls.length).toBeGreaterThan(0);
|
|
383
|
+
});
|
|
384
|
+
it('MUST invoke queryWebSearch when stuckIterations is 3', async () => {
|
|
385
|
+
const { isWebSearchConfigured } = await import('../../../src/mcp/webSearch.js');
|
|
386
|
+
vi.mocked(isWebSearchConfigured).mockReturnValue(true);
|
|
387
|
+
const callTool = vi.fn().mockResolvedValue({ content: 'search data' });
|
|
388
|
+
const manager = makeMcpManager(['websearch'], callTool);
|
|
389
|
+
const enricher = new McpContextEnricher(manager);
|
|
390
|
+
const result = await enricher.enrich({
|
|
391
|
+
mcpManager: manager,
|
|
392
|
+
stuckIterations: 3,
|
|
393
|
+
failingTests: ['test C'],
|
|
394
|
+
});
|
|
395
|
+
expect(result.webSearchResults).toBeDefined();
|
|
396
|
+
});
|
|
397
|
+
it('does not invoke queryWebSearch when failingTests is empty', async () => {
|
|
398
|
+
const { isWebSearchConfigured } = await import('../../../src/mcp/webSearch.js');
|
|
399
|
+
vi.mocked(isWebSearchConfigured).mockReturnValue(true);
|
|
400
|
+
const callTool = vi.fn();
|
|
401
|
+
const manager = makeMcpManager(['websearch'], callTool);
|
|
402
|
+
const enricher = new McpContextEnricher(manager);
|
|
403
|
+
const result = await enricher.enrich({
|
|
404
|
+
mcpManager: manager,
|
|
405
|
+
stuckIterations: 5,
|
|
406
|
+
failingTests: [],
|
|
407
|
+
});
|
|
408
|
+
expect(result.webSearchResults).toBeUndefined();
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
describe('enrich() — concurrent execution', () => {
|
|
412
|
+
it('runs queryContext7 and queryAzureMcp (results combined)', async () => {
|
|
413
|
+
const callTool = vi
|
|
414
|
+
.fn()
|
|
415
|
+
// Context7 resolve + query
|
|
416
|
+
.mockResolvedValueOnce({ libraryId: 'express-id' })
|
|
417
|
+
.mockResolvedValueOnce({ content: 'Express docs' })
|
|
418
|
+
// Azure documentation
|
|
419
|
+
.mockResolvedValueOnce({ content: 'Azure best practices' });
|
|
420
|
+
const manager = makeMcpManager(['context7', 'azure'], callTool);
|
|
421
|
+
const enricher = new McpContextEnricher(manager);
|
|
422
|
+
const result = await enricher.enrich({
|
|
423
|
+
mcpManager: manager,
|
|
424
|
+
dependencies: ['express'],
|
|
425
|
+
architectureNotes: 'Use Azure Cosmos DB for storage',
|
|
426
|
+
});
|
|
427
|
+
expect(result.libraryDocs).toBeDefined();
|
|
428
|
+
expect(result.azureGuidance).toBeDefined();
|
|
429
|
+
expect(result.combined).toContain('Library Documentation');
|
|
430
|
+
expect(result.combined).toContain('Azure');
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
});
|