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,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context summarizer tests.
|
|
3
|
+
*
|
|
4
|
+
* Tests for buildSummarizedContext() and renderSummarizedContext()
|
|
5
|
+
* that project structured session fields into compact markdown
|
|
6
|
+
* for injection into phase system prompts.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
|
+
import { buildSummarizedContext, renderSummarizedContext, } from '../../../src/phases/contextSummarizer.js';
|
|
10
|
+
function emptySession(overrides) {
|
|
11
|
+
return {
|
|
12
|
+
sessionId: 'test-1',
|
|
13
|
+
schemaVersion: '1.0.0',
|
|
14
|
+
createdAt: '2025-01-01T00:00:00Z',
|
|
15
|
+
updatedAt: '2025-01-01T00:00:00Z',
|
|
16
|
+
phase: 'Discover',
|
|
17
|
+
status: 'Active',
|
|
18
|
+
participants: [],
|
|
19
|
+
artifacts: { generatedFiles: [] },
|
|
20
|
+
turns: [],
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
describe('buildSummarizedContext', () => {
|
|
25
|
+
it('returns empty context for empty session', () => {
|
|
26
|
+
const ctx = buildSummarizedContext(emptySession());
|
|
27
|
+
expect(ctx.businessSummary).toBeUndefined();
|
|
28
|
+
expect(ctx.challenges).toBeUndefined();
|
|
29
|
+
expect(ctx.ideaSummaries).toBeUndefined();
|
|
30
|
+
expect(ctx.evaluationSummary).toBeUndefined();
|
|
31
|
+
expect(ctx.selectionSummary).toBeUndefined();
|
|
32
|
+
expect(ctx.planMilestones).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
it('projects all fields from a full session', () => {
|
|
35
|
+
const session = emptySession({
|
|
36
|
+
businessContext: {
|
|
37
|
+
businessDescription: 'Zava Industries',
|
|
38
|
+
challenges: ['Scaling', 'Automation'],
|
|
39
|
+
},
|
|
40
|
+
topic: { topicArea: 'Customer Support' },
|
|
41
|
+
workflow: {
|
|
42
|
+
activities: [
|
|
43
|
+
{ id: 's1', name: 'Intake' },
|
|
44
|
+
{ id: 's2', name: 'Processing' },
|
|
45
|
+
],
|
|
46
|
+
edges: [],
|
|
47
|
+
},
|
|
48
|
+
ideas: [
|
|
49
|
+
{ id: 'idea-1', title: 'AI Bot', description: 'Automate support', workflowStepIds: ['s1'] },
|
|
50
|
+
],
|
|
51
|
+
evaluation: {
|
|
52
|
+
method: 'feasibility-value-matrix',
|
|
53
|
+
ideas: [{ ideaId: 'idea-1', feasibility: 8, value: 9 }],
|
|
54
|
+
},
|
|
55
|
+
selection: {
|
|
56
|
+
ideaId: 'idea-1',
|
|
57
|
+
selectionRationale: 'Best fit',
|
|
58
|
+
confirmedByUser: true,
|
|
59
|
+
},
|
|
60
|
+
plan: {
|
|
61
|
+
milestones: [
|
|
62
|
+
{ id: 'm1', title: 'Phase 1', items: ['Task 1'] },
|
|
63
|
+
],
|
|
64
|
+
architectureNotes: 'Node.js + TypeScript',
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
const ctx = buildSummarizedContext(session);
|
|
68
|
+
expect(ctx.businessSummary).toBe('Zava Industries');
|
|
69
|
+
expect(ctx.challenges).toEqual(['Scaling', 'Automation']);
|
|
70
|
+
expect(ctx.topicArea).toBe('Customer Support');
|
|
71
|
+
expect(ctx.workflowSteps).toEqual(['Intake', 'Processing']);
|
|
72
|
+
expect(ctx.ideaSummaries).toHaveLength(1);
|
|
73
|
+
expect(ctx.ideaSummaries[0].title).toBe('AI Bot');
|
|
74
|
+
expect(ctx.evaluationSummary).toContain('feasibility-value-matrix');
|
|
75
|
+
expect(ctx.selectionSummary).toContain('idea-1');
|
|
76
|
+
expect(ctx.planMilestones).toEqual(['Phase 1']);
|
|
77
|
+
expect(ctx.architectureNotes).toBe('Node.js + TypeScript');
|
|
78
|
+
});
|
|
79
|
+
it('gracefully handles null fields', () => {
|
|
80
|
+
const session = emptySession({
|
|
81
|
+
businessContext: {
|
|
82
|
+
businessDescription: 'Test Inc',
|
|
83
|
+
challenges: [],
|
|
84
|
+
},
|
|
85
|
+
// Everything else is null/undefined
|
|
86
|
+
});
|
|
87
|
+
const ctx = buildSummarizedContext(session);
|
|
88
|
+
expect(ctx.businessSummary).toBe('Test Inc');
|
|
89
|
+
expect(ctx.ideaSummaries).toBeUndefined();
|
|
90
|
+
expect(ctx.evaluationSummary).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
it('includes discovery enrichment highlights', () => {
|
|
93
|
+
const session = emptySession({
|
|
94
|
+
discovery: {
|
|
95
|
+
enrichment: {
|
|
96
|
+
industryTrends: ['AI adoption growing', 'Cloud migration'],
|
|
97
|
+
companyNews: ['Raised Series B'],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
const ctx = buildSummarizedContext(session);
|
|
102
|
+
expect(ctx.enrichmentHighlights).toBeDefined();
|
|
103
|
+
expect(ctx.enrichmentHighlights).toContain('AI adoption growing');
|
|
104
|
+
expect(ctx.enrichmentHighlights).toContain('Raised Series B');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
describe('renderSummarizedContext', () => {
|
|
108
|
+
it('returns empty string for empty context', () => {
|
|
109
|
+
expect(renderSummarizedContext({})).toBe('');
|
|
110
|
+
});
|
|
111
|
+
it('renders markdown with all sections', () => {
|
|
112
|
+
const markdown = renderSummarizedContext({
|
|
113
|
+
businessSummary: 'Zava Industries',
|
|
114
|
+
challenges: ['Scaling'],
|
|
115
|
+
topicArea: 'Support',
|
|
116
|
+
workflowSteps: ['Intake', 'Processing'],
|
|
117
|
+
ideaSummaries: [{ id: 'idea-1', title: 'AI Bot', description: 'Automate' }],
|
|
118
|
+
evaluationSummary: 'Method: feasibility-value-matrix, 1 ideas evaluated',
|
|
119
|
+
selectionSummary: 'Selected: idea-1',
|
|
120
|
+
planMilestones: ['Phase 1'],
|
|
121
|
+
architectureNotes: 'Node.js',
|
|
122
|
+
});
|
|
123
|
+
expect(markdown).toContain('## Prior Phase Context');
|
|
124
|
+
expect(markdown).toContain('### Business Context');
|
|
125
|
+
expect(markdown).toContain('Zava Industries');
|
|
126
|
+
expect(markdown).toContain('### Workflow');
|
|
127
|
+
expect(markdown).toContain('### Ideas');
|
|
128
|
+
expect(markdown).toContain('### Evaluation');
|
|
129
|
+
expect(markdown).toContain('### Selection');
|
|
130
|
+
expect(markdown).toContain('### Plan');
|
|
131
|
+
});
|
|
132
|
+
it('omits sections when data is absent', () => {
|
|
133
|
+
const markdown = renderSummarizedContext({
|
|
134
|
+
businessSummary: 'Test Inc',
|
|
135
|
+
});
|
|
136
|
+
expect(markdown).toContain('Test Inc');
|
|
137
|
+
expect(markdown).not.toContain('### Ideas');
|
|
138
|
+
expect(markdown).not.toContain('### Plan');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T041 focused: Discovery enricher repeated calls (simplified live test).
|
|
3
|
+
*
|
|
4
|
+
* Reproduces the exact scenario from T041 but with detailed logging
|
|
5
|
+
* to isolate web search behavior across repeated enrichFromWebSearch() calls.
|
|
6
|
+
*
|
|
7
|
+
* Gated behind SOFIA_LIVE_MCP_TESTS=true to skip in CI.
|
|
8
|
+
*
|
|
9
|
+
* Run with:
|
|
10
|
+
* SOFIA_LIVE_MCP_TESTS=true \
|
|
11
|
+
* FOUNDRY_PROJECT_ENDPOINT=... \
|
|
12
|
+
* FOUNDRY_MODEL_DEPLOYMENT_NAME=... \
|
|
13
|
+
* npm test -- tests/unit/phases/discoveryEnricher.repeatCalls.spec.ts
|
|
14
|
+
*/
|
|
15
|
+
import { describe, it, expect } from 'vitest';
|
|
16
|
+
const LIVE = process.env.SOFIA_LIVE_MCP_TESTS === 'true';
|
|
17
|
+
const TEST_COMPANIES = [
|
|
18
|
+
{
|
|
19
|
+
summary: '"Nestlé" is a global food and beverage company headquartered in Switzerland.',
|
|
20
|
+
keywords: ['food', 'beverage', 'switzerland'],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
summary: '"Zara" is a global retail company headquartered in Spain.',
|
|
24
|
+
keywords: ['retail', 'fashion', 'spain'],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
summary: '"Microsoft Corporation" is a global technology company headquartered in Redmond, Washington.',
|
|
28
|
+
keywords: ['technology', 'software', 'cloud'],
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
describe.skipIf(!LIVE)('DiscoveryEnricher repeated web search calls (T041 focused)', () => {
|
|
32
|
+
it('enrichFromWebSearch returns results for each company when called sequentially', async () => {
|
|
33
|
+
const { DiscoveryEnricher } = await import('../../../src/phases/discoveryEnricher.js');
|
|
34
|
+
const { createWebSearchTool } = await import('../../../src/mcp/webSearch.js');
|
|
35
|
+
const webSearchFn = createWebSearchTool({
|
|
36
|
+
projectEndpoint: process.env.FOUNDRY_PROJECT_ENDPOINT,
|
|
37
|
+
modelDeploymentName: process.env.FOUNDRY_MODEL_DEPLOYMENT_NAME,
|
|
38
|
+
});
|
|
39
|
+
const webSearchClient = {
|
|
40
|
+
search: async (query) => {
|
|
41
|
+
console.log(` [WebSearch] Query: "${query}"`);
|
|
42
|
+
const result = await webSearchFn(query);
|
|
43
|
+
console.log(` [WebSearch] Results: ${result.results.length} items, degraded=${result.degraded}`);
|
|
44
|
+
if (result.error) {
|
|
45
|
+
console.log(` [WebSearch] Error: ${result.error}`);
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
const enricher = new DiscoveryEnricher();
|
|
51
|
+
console.log('\n=== T041 Focused: Sequential enrichFromWebSearch Calls ===\n');
|
|
52
|
+
const allResults = [];
|
|
53
|
+
for (let idx = 0; idx < TEST_COMPANIES.length; idx++) {
|
|
54
|
+
const company = TEST_COMPANIES[idx];
|
|
55
|
+
console.log(`\n[${idx + 1}/${TEST_COMPANIES.length}] Processing: ${company.summary.split('"')[1]}`);
|
|
56
|
+
const enrichment = await enricher.enrichFromWebSearch(company.summary, webSearchClient);
|
|
57
|
+
const content = [
|
|
58
|
+
...(enrichment.companyNews ?? []),
|
|
59
|
+
...(enrichment.competitorInfo ?? []),
|
|
60
|
+
...(enrichment.industryTrends ?? []),
|
|
61
|
+
];
|
|
62
|
+
const allText = content.join(' ').toLowerCase();
|
|
63
|
+
const hasRelevant = company.keywords.some((kw) => allText.includes(kw.toLowerCase()));
|
|
64
|
+
console.log(` [Enrichment] companyNews: ${enrichment.companyNews?.length ?? 0}`);
|
|
65
|
+
console.log(` [Enrichment] competitorInfo: ${enrichment.competitorInfo?.length ?? 0}`);
|
|
66
|
+
console.log(` [Enrichment] industryTrends: ${enrichment.industryTrends?.length ?? 0}`);
|
|
67
|
+
console.log(` [Enrichment] Total snippets: ${content.length}`);
|
|
68
|
+
console.log(` [Enrichment] Has relevant keyword: ${hasRelevant}`);
|
|
69
|
+
if (content.length > 0) {
|
|
70
|
+
console.log(` [Enrichment] First snippet: ${content[0].slice(0, 80)}...`);
|
|
71
|
+
}
|
|
72
|
+
allResults.push({
|
|
73
|
+
company: company.summary.split('"')[1],
|
|
74
|
+
snippetCount: content.length,
|
|
75
|
+
hasRelevantKeyword: hasRelevant,
|
|
76
|
+
rawContent: content,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
console.log('\n=== Summary ===\n');
|
|
80
|
+
let relevantCount = 0;
|
|
81
|
+
for (const r of allResults) {
|
|
82
|
+
const status = r.hasRelevantKeyword && r.snippetCount > 0 ? '✓' : '✗';
|
|
83
|
+
console.log(`${status} ${r.company}: ${r.snippetCount} snippets, relevant=${r.hasRelevantKeyword}`);
|
|
84
|
+
if (r.hasRelevantKeyword && r.snippetCount > 0) {
|
|
85
|
+
relevantCount++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
console.log(`\nResult: ${relevantCount}/${TEST_COMPANIES.length} companies have relevant results`);
|
|
89
|
+
console.log(`Expected: >= 2 for pass (simplified from T041's 3/5 threshold)\n`);
|
|
90
|
+
// Simplified threshold: at least 2/3
|
|
91
|
+
expect(relevantCount).toBeGreaterThanOrEqual(2);
|
|
92
|
+
}, 180_000); // 3 minute timeout for live queries
|
|
93
|
+
});
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for DiscoveryEnricher — US3 web search enrichment
|
|
3
|
+
* and US4 WorkIQ enrichment.
|
|
4
|
+
*
|
|
5
|
+
* T026: enrichFromWebSearch() basic functionality
|
|
6
|
+
* T027: User consent prompt integration
|
|
7
|
+
* T028: Session schema integration
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
10
|
+
import { DiscoveryEnricher } from '../../../src/phases/discoveryEnricher.js';
|
|
11
|
+
import { DiscoveryEnrichmentSchema, workshopSessionSchema, } from '../../../src/shared/schemas/session.js';
|
|
12
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
13
|
+
function makeWebSearchClient(results = [], opts) {
|
|
14
|
+
return {
|
|
15
|
+
search: opts?.throws
|
|
16
|
+
? vi.fn().mockRejectedValue(new Error('search failed'))
|
|
17
|
+
: vi.fn().mockResolvedValue({
|
|
18
|
+
results,
|
|
19
|
+
degraded: opts?.degraded ?? false,
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function makeLoopIO(overrides) {
|
|
24
|
+
return {
|
|
25
|
+
write: vi.fn(),
|
|
26
|
+
writeActivity: vi.fn(),
|
|
27
|
+
writeToolSummary: vi.fn(),
|
|
28
|
+
readInput: vi.fn().mockResolvedValue('n'),
|
|
29
|
+
showDecisionGate: vi.fn().mockResolvedValue({ choice: 'continue' }),
|
|
30
|
+
isJsonMode: false,
|
|
31
|
+
isTTY: true,
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function makeMcpManager(overrides) {
|
|
36
|
+
return {
|
|
37
|
+
isAvailable: vi.fn().mockReturnValue(false),
|
|
38
|
+
callTool: vi.fn().mockResolvedValue({}),
|
|
39
|
+
markConnected: vi.fn(),
|
|
40
|
+
markDisconnected: vi.fn(),
|
|
41
|
+
disconnectAll: vi.fn().mockResolvedValue(undefined),
|
|
42
|
+
...overrides,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// ── T026: enrichFromWebSearch() ──────────────────────────────────────────────
|
|
46
|
+
describe('DiscoveryEnricher', () => {
|
|
47
|
+
let enricher;
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
enricher = new DiscoveryEnricher();
|
|
50
|
+
});
|
|
51
|
+
describe('enrichFromWebSearch() (T026)', () => {
|
|
52
|
+
it('calls webSearchClient.search with company-news, competitor, and industry-trends queries', async () => {
|
|
53
|
+
const searchFn = vi.fn().mockResolvedValue({ results: [], degraded: false });
|
|
54
|
+
const client = { search: searchFn };
|
|
55
|
+
await enricher.enrichFromWebSearch('Acme Corp makes widgets', client);
|
|
56
|
+
// Should make 3 search calls (company news, competitors, industry trends)
|
|
57
|
+
expect(searchFn).toHaveBeenCalledTimes(3);
|
|
58
|
+
// First query should mention company/news
|
|
59
|
+
expect(searchFn.mock.calls[0][0]).toMatch(/acme/i);
|
|
60
|
+
expect(searchFn.mock.calls[0][0]).toMatch(/news/i);
|
|
61
|
+
// Second should mention competitors/market
|
|
62
|
+
expect(searchFn.mock.calls[1][0]).toMatch(/competitor|market/i);
|
|
63
|
+
// Third should mention trends
|
|
64
|
+
expect(searchFn.mock.calls[2][0]).toMatch(/trend/i);
|
|
65
|
+
});
|
|
66
|
+
it('populates companyNews, competitorInfo, industryTrends from results', async () => {
|
|
67
|
+
const newsResults = [
|
|
68
|
+
{ title: 'Acme raises $10M', url: 'https://a.com', snippet: 'Acme Corp raised $10M' },
|
|
69
|
+
{ title: 'Acme launches product', url: 'https://b.com', snippet: 'New product launched' },
|
|
70
|
+
];
|
|
71
|
+
const competitorResults = [
|
|
72
|
+
{ title: 'Widget Inc grows', url: 'https://c.com', snippet: 'Widget Inc sees 20% growth' },
|
|
73
|
+
];
|
|
74
|
+
const trendResults = [
|
|
75
|
+
{
|
|
76
|
+
title: 'AI in manufacturing',
|
|
77
|
+
url: 'https://d.com',
|
|
78
|
+
snippet: 'AI transforms manufacturing',
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
const searchFn = vi
|
|
82
|
+
.fn()
|
|
83
|
+
.mockResolvedValueOnce({ results: newsResults, degraded: false })
|
|
84
|
+
.mockResolvedValueOnce({ results: competitorResults, degraded: false })
|
|
85
|
+
.mockResolvedValueOnce({ results: trendResults, degraded: false });
|
|
86
|
+
const result = await enricher.enrichFromWebSearch('Acme Corp makes widgets', {
|
|
87
|
+
search: searchFn,
|
|
88
|
+
});
|
|
89
|
+
expect(result.companyNews).toHaveLength(2);
|
|
90
|
+
expect(result.companyNews[0]).toContain('Acme raises $10M');
|
|
91
|
+
expect(result.competitorInfo ?? []).toHaveLength(0);
|
|
92
|
+
expect(result.industryTrends ?? []).toHaveLength(0);
|
|
93
|
+
});
|
|
94
|
+
it('stops after first successful query to reduce web search calls', async () => {
|
|
95
|
+
const searchFn = vi
|
|
96
|
+
.fn()
|
|
97
|
+
.mockResolvedValueOnce({
|
|
98
|
+
results: [{ title: 'Acme news', url: 'https://a.com', snippet: 'Acme in manufacturing' }],
|
|
99
|
+
degraded: false,
|
|
100
|
+
})
|
|
101
|
+
.mockResolvedValue({
|
|
102
|
+
results: [{ title: 'Should not be used', url: 'https://b.com', snippet: 'unused' }],
|
|
103
|
+
degraded: false,
|
|
104
|
+
});
|
|
105
|
+
const result = await enricher.enrichFromWebSearch('Acme Corp makes widgets', {
|
|
106
|
+
search: searchFn,
|
|
107
|
+
});
|
|
108
|
+
expect(searchFn).toHaveBeenCalledTimes(1);
|
|
109
|
+
expect(result.companyNews).toHaveLength(1);
|
|
110
|
+
expect(result.competitorInfo ?? []).toHaveLength(0);
|
|
111
|
+
expect(result.industryTrends ?? []).toHaveLength(0);
|
|
112
|
+
});
|
|
113
|
+
it('returns gracefully with empty arrays when search throws', async () => {
|
|
114
|
+
const client = makeWebSearchClient([], { throws: true });
|
|
115
|
+
const result = await enricher.enrichFromWebSearch('Acme Corp', client);
|
|
116
|
+
// Should not throw, should return empty-ish object
|
|
117
|
+
expect(result.companyNews ?? []).toHaveLength(0);
|
|
118
|
+
expect(result.competitorInfo ?? []).toHaveLength(0);
|
|
119
|
+
expect(result.industryTrends ?? []).toHaveLength(0);
|
|
120
|
+
});
|
|
121
|
+
it('returns empty when search result is degraded', async () => {
|
|
122
|
+
const searchFn = vi.fn().mockResolvedValue({ results: [], degraded: true });
|
|
123
|
+
const client = { search: searchFn };
|
|
124
|
+
const result = await enricher.enrichFromWebSearch('Acme Corp', client);
|
|
125
|
+
expect(result.companyNews ?? []).toHaveLength(0);
|
|
126
|
+
expect(result.competitorInfo ?? []).toHaveLength(0);
|
|
127
|
+
});
|
|
128
|
+
it('sets sourcesUsed to ["websearch"] when search succeeds', async () => {
|
|
129
|
+
const newsResults = [{ title: 'News', url: 'https://a.com', snippet: 'snippet' }];
|
|
130
|
+
const searchFn = vi.fn().mockResolvedValue({ results: newsResults, degraded: false });
|
|
131
|
+
const client = { search: searchFn };
|
|
132
|
+
const result = await enricher.enrichFromWebSearch('Acme Corp', client);
|
|
133
|
+
expect(result.sourcesUsed).toContain('websearch');
|
|
134
|
+
});
|
|
135
|
+
it('sets enrichedAt to an ISO 8601 timestamp', async () => {
|
|
136
|
+
const newsResults = [{ title: 'News', url: 'https://a.com', snippet: 'snippet' }];
|
|
137
|
+
const searchFn = vi.fn().mockResolvedValue({ results: newsResults, degraded: false });
|
|
138
|
+
const client = { search: searchFn };
|
|
139
|
+
const result = await enricher.enrichFromWebSearch('Acme Corp', client);
|
|
140
|
+
expect(result.enrichedAt).toBeDefined();
|
|
141
|
+
// Validate it's a parseable date
|
|
142
|
+
expect(new Date(result.enrichedAt).toISOString()).toBe(result.enrichedAt);
|
|
143
|
+
});
|
|
144
|
+
it('caps array fields at 10 items', async () => {
|
|
145
|
+
// Return 15 results per query
|
|
146
|
+
const bigResults = Array.from({ length: 15 }, (_, i) => ({
|
|
147
|
+
title: `Title ${i}`,
|
|
148
|
+
url: `https://example.com/${i}`,
|
|
149
|
+
snippet: `Snippet ${i}`,
|
|
150
|
+
}));
|
|
151
|
+
const searchFn = vi.fn().mockResolvedValue({ results: bigResults, degraded: false });
|
|
152
|
+
const client = { search: searchFn };
|
|
153
|
+
const result = await enricher.enrichFromWebSearch('Acme Corp', client);
|
|
154
|
+
expect((result.companyNews ?? []).length).toBeLessThanOrEqual(10);
|
|
155
|
+
expect((result.competitorInfo ?? []).length).toBeLessThanOrEqual(10);
|
|
156
|
+
expect((result.industryTrends ?? []).length).toBeLessThanOrEqual(10);
|
|
157
|
+
});
|
|
158
|
+
it('populates webSearchResults with combined snippets', async () => {
|
|
159
|
+
const newsResults = [
|
|
160
|
+
{ title: 'News', url: 'https://a.com', snippet: 'Company news snippet' },
|
|
161
|
+
];
|
|
162
|
+
const searchFn = vi.fn().mockResolvedValue({ results: newsResults, degraded: false });
|
|
163
|
+
const client = { search: searchFn };
|
|
164
|
+
const result = await enricher.enrichFromWebSearch('Acme Corp', client);
|
|
165
|
+
expect(result.webSearchResults).toBeDefined();
|
|
166
|
+
expect(result.webSearchResults).toContain('Company news snippet');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
// ── T027: enrich() user consent / prompt flow ────────────────────────────
|
|
170
|
+
describe('enrich() consent flow (T027)', () => {
|
|
171
|
+
it('does not prompt for web search when webSearchClient is not provided', async () => {
|
|
172
|
+
const io = makeLoopIO();
|
|
173
|
+
const mcpManager = makeMcpManager();
|
|
174
|
+
await enricher.enrich({
|
|
175
|
+
companySummary: 'Acme Corp',
|
|
176
|
+
mcpManager,
|
|
177
|
+
io,
|
|
178
|
+
});
|
|
179
|
+
// readInput should not be called for web search consent
|
|
180
|
+
// (web search consent is implicit when webSearchClient is provided)
|
|
181
|
+
expect(io.readInput).not.toHaveBeenCalled();
|
|
182
|
+
});
|
|
183
|
+
it('runs web search when webSearchClient is provided', async () => {
|
|
184
|
+
const searchFn = vi.fn().mockResolvedValue({
|
|
185
|
+
results: [{ title: 'News', url: 'https://a.com', snippet: 'snip' }],
|
|
186
|
+
degraded: false,
|
|
187
|
+
});
|
|
188
|
+
const io = makeLoopIO();
|
|
189
|
+
const mcpManager = makeMcpManager();
|
|
190
|
+
const result = await enricher.enrich({
|
|
191
|
+
companySummary: 'Acme Corp',
|
|
192
|
+
mcpManager,
|
|
193
|
+
io,
|
|
194
|
+
webSearchClient: { search: searchFn },
|
|
195
|
+
});
|
|
196
|
+
expect(searchFn).toHaveBeenCalled();
|
|
197
|
+
expect(result.sourcesUsed).toContain('websearch');
|
|
198
|
+
});
|
|
199
|
+
it('returns valid DiscoveryEnrichment when all sources unavailable', async () => {
|
|
200
|
+
const io = makeLoopIO();
|
|
201
|
+
const mcpManager = makeMcpManager();
|
|
202
|
+
const result = await enricher.enrich({
|
|
203
|
+
companySummary: 'Acme Corp',
|
|
204
|
+
mcpManager,
|
|
205
|
+
io,
|
|
206
|
+
});
|
|
207
|
+
// Should return a valid (possibly empty) enrichment — no crash
|
|
208
|
+
const parsed = DiscoveryEnrichmentSchema.safeParse(result);
|
|
209
|
+
expect(parsed.success).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
// ── T028: Session schema integration ─────────────────────────────────────
|
|
213
|
+
describe('session schema integration (T028)', () => {
|
|
214
|
+
it('DiscoveryEnrichmentSchema parses valid enrichment', () => {
|
|
215
|
+
const valid = {
|
|
216
|
+
webSearchResults: 'Some results',
|
|
217
|
+
companyNews: ['News 1', 'News 2'],
|
|
218
|
+
competitorInfo: ['Competitor A'],
|
|
219
|
+
industryTrends: ['Trend 1'],
|
|
220
|
+
enrichedAt: new Date().toISOString(),
|
|
221
|
+
sourcesUsed: ['websearch'],
|
|
222
|
+
};
|
|
223
|
+
const parsed = DiscoveryEnrichmentSchema.safeParse(valid);
|
|
224
|
+
expect(parsed.success).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
it('validates enrichedAt is ISO 8601 when present', () => {
|
|
227
|
+
const invalidDate = {
|
|
228
|
+
enrichedAt: 'not-a-date',
|
|
229
|
+
sourcesUsed: ['websearch'],
|
|
230
|
+
};
|
|
231
|
+
const parsed = DiscoveryEnrichmentSchema.safeParse(invalidDate);
|
|
232
|
+
expect(parsed.success).toBe(false);
|
|
233
|
+
});
|
|
234
|
+
it('validates sourcesUsed entries are lowercase strings', () => {
|
|
235
|
+
const valid = {
|
|
236
|
+
sourcesUsed: ['websearch', 'workiq'],
|
|
237
|
+
};
|
|
238
|
+
const parsed = DiscoveryEnrichmentSchema.safeParse(valid);
|
|
239
|
+
expect(parsed.success).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
it('session with discovery.enrichment round-trips through workshopSessionSchema.parse()', () => {
|
|
242
|
+
const session = {
|
|
243
|
+
sessionId: 'test-123',
|
|
244
|
+
schemaVersion: '0.3.0',
|
|
245
|
+
createdAt: new Date().toISOString(),
|
|
246
|
+
updatedAt: new Date().toISOString(),
|
|
247
|
+
phase: 'Discover',
|
|
248
|
+
status: 'Active',
|
|
249
|
+
participants: [],
|
|
250
|
+
artifacts: { generatedFiles: [] },
|
|
251
|
+
discovery: {
|
|
252
|
+
enrichment: {
|
|
253
|
+
companyNews: ['News headline'],
|
|
254
|
+
competitorInfo: ['Competitor X'],
|
|
255
|
+
industryTrends: ['AI trend'],
|
|
256
|
+
enrichedAt: new Date().toISOString(),
|
|
257
|
+
sourcesUsed: ['websearch'],
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
const parsed = workshopSessionSchema.safeParse(session);
|
|
262
|
+
expect(parsed.success).toBe(true);
|
|
263
|
+
if (parsed.success) {
|
|
264
|
+
expect(parsed.data.discovery?.enrichment?.companyNews).toEqual(['News headline']);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
it('rejects array fields exceeding max 10 items', () => {
|
|
268
|
+
const tooMany = {
|
|
269
|
+
companyNews: Array.from({ length: 11 }, (_, i) => `News ${i}`),
|
|
270
|
+
};
|
|
271
|
+
const parsed = DiscoveryEnrichmentSchema.safeParse(tooMany);
|
|
272
|
+
expect(parsed.success).toBe(false);
|
|
273
|
+
});
|
|
274
|
+
it('accepts completely empty enrichment', () => {
|
|
275
|
+
const parsed = DiscoveryEnrichmentSchema.safeParse({});
|
|
276
|
+
expect(parsed.success).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
// ── T034: enrichFromWorkIQ() ──────────────────────────────────────────────
|
|
280
|
+
describe('enrichFromWorkIQ() (T034)', () => {
|
|
281
|
+
it('prompts user for consent via io.readInput before any callTool call', async () => {
|
|
282
|
+
const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('n') });
|
|
283
|
+
const mcpManager = makeMcpManager({
|
|
284
|
+
isAvailable: vi.fn().mockReturnValue(true),
|
|
285
|
+
});
|
|
286
|
+
await enricher.enrichFromWorkIQ('Acme Corp', mcpManager, io);
|
|
287
|
+
expect(io.readInput).toHaveBeenCalled();
|
|
288
|
+
// Should have asked before calling callTool
|
|
289
|
+
expect(mcpManager.callTool).not.toHaveBeenCalled();
|
|
290
|
+
});
|
|
291
|
+
it('calls mcpManager.callTool when user consents with "y"', async () => {
|
|
292
|
+
const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('y') });
|
|
293
|
+
const mcpManager = makeMcpManager({
|
|
294
|
+
isAvailable: vi.fn().mockReturnValue(true),
|
|
295
|
+
callTool: vi.fn().mockResolvedValue({
|
|
296
|
+
teamExpertise: ['TypeScript', 'React'],
|
|
297
|
+
collaborationPatterns: ['Daily standups'],
|
|
298
|
+
documentationGaps: ['API docs missing'],
|
|
299
|
+
}),
|
|
300
|
+
});
|
|
301
|
+
const result = await enricher.enrichFromWorkIQ('Acme Corp', mcpManager, io);
|
|
302
|
+
expect(mcpManager.callTool).toHaveBeenCalledWith('workiq', 'analyze_team', expect.objectContaining({ summary: 'Acme Corp' }), { timeoutMs: 30_000 });
|
|
303
|
+
expect(result.workiqInsights).toBeDefined();
|
|
304
|
+
expect(result.workiqInsights.teamExpertise).toContain('TypeScript');
|
|
305
|
+
expect(result.workiqInsights.collaborationPatterns).toContain('Daily standups');
|
|
306
|
+
expect(result.workiqInsights.documentationGaps).toContain('API docs missing');
|
|
307
|
+
expect(result.sourcesUsed).toContain('workiq');
|
|
308
|
+
});
|
|
309
|
+
it('returns empty workiqInsights when user declines', async () => {
|
|
310
|
+
const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('n') });
|
|
311
|
+
const mcpManager = makeMcpManager({
|
|
312
|
+
isAvailable: vi.fn().mockReturnValue(true),
|
|
313
|
+
});
|
|
314
|
+
const result = await enricher.enrichFromWorkIQ('Acme Corp', mcpManager, io);
|
|
315
|
+
expect(result.workiqInsights).toBeUndefined();
|
|
316
|
+
expect(mcpManager.callTool).not.toHaveBeenCalled();
|
|
317
|
+
});
|
|
318
|
+
it('returns empty workiqInsights when user presses Enter (default No)', async () => {
|
|
319
|
+
const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('') });
|
|
320
|
+
const mcpManager = makeMcpManager({
|
|
321
|
+
isAvailable: vi.fn().mockReturnValue(true),
|
|
322
|
+
});
|
|
323
|
+
const result = await enricher.enrichFromWorkIQ('Acme Corp', mcpManager, io);
|
|
324
|
+
expect(result.workiqInsights).toBeUndefined();
|
|
325
|
+
expect(mcpManager.callTool).not.toHaveBeenCalled();
|
|
326
|
+
});
|
|
327
|
+
it('returns empty workiqInsights gracefully when callTool throws', async () => {
|
|
328
|
+
const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('y') });
|
|
329
|
+
const mcpManager = makeMcpManager({
|
|
330
|
+
isAvailable: vi.fn().mockReturnValue(true),
|
|
331
|
+
callTool: vi.fn().mockRejectedValue(new Error('WorkIQ timeout')),
|
|
332
|
+
});
|
|
333
|
+
const result = await enricher.enrichFromWorkIQ('Acme Corp', mcpManager, io);
|
|
334
|
+
// Should not throw
|
|
335
|
+
expect(result.workiqInsights).toBeUndefined();
|
|
336
|
+
});
|
|
337
|
+
it('extracts insights from response.insights fallback (split by newline)', async () => {
|
|
338
|
+
const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('y') });
|
|
339
|
+
const mcpManager = makeMcpManager({
|
|
340
|
+
isAvailable: vi.fn().mockReturnValue(true),
|
|
341
|
+
callTool: vi.fn().mockResolvedValue({
|
|
342
|
+
insights: 'Expert in TypeScript\nDaily standups\nMissing API docs',
|
|
343
|
+
}),
|
|
344
|
+
});
|
|
345
|
+
const result = await enricher.enrichFromWorkIQ('Acme Corp', mcpManager, io);
|
|
346
|
+
expect(result.workiqInsights).toBeDefined();
|
|
347
|
+
// When response has only 'insights' string, it gets split
|
|
348
|
+
expect(result.workiqInsights.teamExpertise).toBeDefined();
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
// ── T035: enrich() orchestrator with WorkIQ ──────────────────────────────
|
|
352
|
+
describe('enrich() orchestrator with WorkIQ (T035)', () => {
|
|
353
|
+
it('calls enrichFromWorkIQ when WorkIQ is available', async () => {
|
|
354
|
+
const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('y') });
|
|
355
|
+
const mcpManager = makeMcpManager({
|
|
356
|
+
isAvailable: vi.fn().mockReturnValue(true),
|
|
357
|
+
callTool: vi.fn().mockResolvedValue({
|
|
358
|
+
teamExpertise: ['Node.js'],
|
|
359
|
+
collaborationPatterns: ['Async reviews'],
|
|
360
|
+
documentationGaps: ['Onboarding docs'],
|
|
361
|
+
}),
|
|
362
|
+
});
|
|
363
|
+
const result = await enricher.enrich({
|
|
364
|
+
companySummary: 'Acme Corp makes widgets',
|
|
365
|
+
mcpManager,
|
|
366
|
+
io,
|
|
367
|
+
});
|
|
368
|
+
expect(result.workiqInsights).toBeDefined();
|
|
369
|
+
expect(result.sourcesUsed).toContain('workiq');
|
|
370
|
+
});
|
|
371
|
+
it('merges WorkIQ results with web search results', async () => {
|
|
372
|
+
const searchFn = vi.fn().mockResolvedValue({
|
|
373
|
+
results: [{ title: 'News', url: 'https://a.com', snippet: 'snip' }],
|
|
374
|
+
degraded: false,
|
|
375
|
+
});
|
|
376
|
+
const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('y') });
|
|
377
|
+
const mcpManager = makeMcpManager({
|
|
378
|
+
isAvailable: vi.fn().mockReturnValue(true),
|
|
379
|
+
callTool: vi.fn().mockResolvedValue({
|
|
380
|
+
teamExpertise: ['Python'],
|
|
381
|
+
collaborationPatterns: [],
|
|
382
|
+
documentationGaps: [],
|
|
383
|
+
}),
|
|
384
|
+
});
|
|
385
|
+
const result = await enricher.enrich({
|
|
386
|
+
companySummary: 'Acme Corp',
|
|
387
|
+
mcpManager,
|
|
388
|
+
io,
|
|
389
|
+
webSearchClient: { search: searchFn },
|
|
390
|
+
});
|
|
391
|
+
expect(result.sourcesUsed).toContain('websearch');
|
|
392
|
+
expect(result.sourcesUsed).toContain('workiq');
|
|
393
|
+
expect(result.companyNews).toBeDefined();
|
|
394
|
+
expect(result.workiqInsights).toBeDefined();
|
|
395
|
+
});
|
|
396
|
+
it('returns valid DiscoveryEnrichment with all empty fields when WorkIQ fails', async () => {
|
|
397
|
+
const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('y') });
|
|
398
|
+
const mcpManager = makeMcpManager({
|
|
399
|
+
isAvailable: vi.fn().mockReturnValue(true),
|
|
400
|
+
callTool: vi.fn().mockRejectedValue(new Error('WorkIQ down')),
|
|
401
|
+
});
|
|
402
|
+
const result = await enricher.enrich({
|
|
403
|
+
companySummary: 'Acme Corp',
|
|
404
|
+
mcpManager,
|
|
405
|
+
io,
|
|
406
|
+
});
|
|
407
|
+
const parsed = DiscoveryEnrichmentSchema.safeParse(result);
|
|
408
|
+
expect(parsed.success).toBe(true);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
});
|