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,13 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { add } from '../src/add.js';
|
|
4
|
+
|
|
5
|
+
describe('add', () => {
|
|
6
|
+
it('adds two numbers', () => {
|
|
7
|
+
expect(add(1, 2)).toBe(3);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('adds negative numbers', () => {
|
|
11
|
+
expect(add(-1, -2)).toBe(-3);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T075: Integration test for auto-start conversation wiring.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that the workshop flow sends an initial message at phase start
|
|
5
|
+
* so the LLM speaks first, and the user never has to initiate.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
|
|
12
|
+
import { ConversationLoop } from '../../src/loop/conversationLoop.js';
|
|
13
|
+
import type { LoopIO, DecisionGateResult } from '../../src/loop/conversationLoop.js';
|
|
14
|
+
import { createFakeCopilotClient } from '../../src/shared/copilotClient.js';
|
|
15
|
+
import type { WorkshopSession, PhaseValue } from '../../src/shared/schemas/session.js';
|
|
16
|
+
import { SessionStore } from '../../src/sessions/sessionStore.js';
|
|
17
|
+
import { createPhaseHandler } from '../../src/phases/phaseHandlers.js';
|
|
18
|
+
|
|
19
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function createTestSession(overrides?: Partial<WorkshopSession>): WorkshopSession {
|
|
22
|
+
const now = new Date().toISOString();
|
|
23
|
+
return {
|
|
24
|
+
sessionId: 'test-autostart',
|
|
25
|
+
schemaVersion: '1.0.0',
|
|
26
|
+
createdAt: now,
|
|
27
|
+
updatedAt: now,
|
|
28
|
+
phase: 'Discover',
|
|
29
|
+
status: 'Active',
|
|
30
|
+
participants: [],
|
|
31
|
+
artifacts: { generatedFiles: [] },
|
|
32
|
+
turns: [],
|
|
33
|
+
...overrides,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createScriptedIO(
|
|
38
|
+
inputs: (string | null)[],
|
|
39
|
+
decisionGateChoice: DecisionGateResult = { choice: 'continue' },
|
|
40
|
+
): LoopIO & { output: string[]; activityLog: string[] } {
|
|
41
|
+
let inputIdx = 0;
|
|
42
|
+
const output: string[] = [];
|
|
43
|
+
const activityLog: string[] = [];
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
write(text: string) {
|
|
47
|
+
output.push(text);
|
|
48
|
+
},
|
|
49
|
+
writeActivity(text: string) {
|
|
50
|
+
activityLog.push(text);
|
|
51
|
+
},
|
|
52
|
+
writeToolSummary(_toolName: string, _summary: string) {
|
|
53
|
+
// no-op
|
|
54
|
+
},
|
|
55
|
+
async readInput(_prompt?: string): Promise<string | null> {
|
|
56
|
+
if (inputIdx >= inputs.length) return null;
|
|
57
|
+
return inputs[inputIdx++];
|
|
58
|
+
},
|
|
59
|
+
async showDecisionGate(_phase: PhaseValue): Promise<DecisionGateResult> {
|
|
60
|
+
return decisionGateChoice;
|
|
61
|
+
},
|
|
62
|
+
isJsonMode: false,
|
|
63
|
+
isTTY: true,
|
|
64
|
+
output,
|
|
65
|
+
activityLog,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
describe('Auto-start conversation integration (T075)', () => {
|
|
72
|
+
let tmpDir: string;
|
|
73
|
+
let store: SessionStore;
|
|
74
|
+
|
|
75
|
+
beforeEach(async () => {
|
|
76
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'sofia-autostart-'));
|
|
77
|
+
store = new SessionStore(tmpDir);
|
|
78
|
+
process.removeAllListeners('SIGINT');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
afterEach(async () => {
|
|
82
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('workshop flow sends initial message at phase start, LLM speaks first', async () => {
|
|
86
|
+
const session = createTestSession();
|
|
87
|
+
await store.save(session);
|
|
88
|
+
|
|
89
|
+
const client = createFakeCopilotClient([
|
|
90
|
+
{ role: 'assistant', content: 'Welcome! Tell me about your business and challenges.' },
|
|
91
|
+
{ role: 'assistant', content: 'Thanks for sharing. Let me identify key topics.' },
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
const io = createScriptedIO(['We sell widgets online']);
|
|
95
|
+
|
|
96
|
+
const handler = createPhaseHandler('Discover');
|
|
97
|
+
await handler._preload();
|
|
98
|
+
|
|
99
|
+
// Get initial message from handler
|
|
100
|
+
const initialMessage = handler.getInitialMessage!(session);
|
|
101
|
+
expect(initialMessage).toBeDefined();
|
|
102
|
+
|
|
103
|
+
const loop = new ConversationLoop({
|
|
104
|
+
client,
|
|
105
|
+
io,
|
|
106
|
+
session,
|
|
107
|
+
phaseHandler: handler,
|
|
108
|
+
initialMessage,
|
|
109
|
+
onSessionUpdate: async (s) => {
|
|
110
|
+
await store.save(s);
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const result = await loop.run();
|
|
115
|
+
|
|
116
|
+
// First turn should be the auto-start initial message, not user input
|
|
117
|
+
expect(result.turns).toBeDefined();
|
|
118
|
+
expect(result.turns!.length).toBeGreaterThanOrEqual(2);
|
|
119
|
+
expect(result.turns![0].role).toBe('user');
|
|
120
|
+
expect(result.turns![0].content).toBe(initialMessage);
|
|
121
|
+
expect(result.turns![1].role).toBe('assistant');
|
|
122
|
+
expect(result.turns![1].content).toBe('Welcome! Tell me about your business and challenges.');
|
|
123
|
+
|
|
124
|
+
// LLM greeting should appear in output
|
|
125
|
+
const allOutput = io.output.join('');
|
|
126
|
+
expect(allOutput).toContain('Welcome!');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('auto-start with resumed session includes progress context', async () => {
|
|
130
|
+
const session = createTestSession({
|
|
131
|
+
turns: [
|
|
132
|
+
{ phase: 'Discover', sequence: 1, role: 'user', content: 'Previous input', timestamp: '2025-01-01T00:00:00Z' },
|
|
133
|
+
{ phase: 'Discover', sequence: 2, role: 'assistant', content: 'Previous response', timestamp: '2025-01-01T00:00:00Z' },
|
|
134
|
+
],
|
|
135
|
+
});
|
|
136
|
+
const originalTurnCount = session.turns!.length;
|
|
137
|
+
await store.save(session);
|
|
138
|
+
|
|
139
|
+
const client = createFakeCopilotClient([
|
|
140
|
+
{ role: 'assistant', content: 'Welcome back! Last time we discussed your business. Let me continue.' },
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
const io = createScriptedIO([]);
|
|
144
|
+
|
|
145
|
+
const handler = createPhaseHandler('Discover');
|
|
146
|
+
await handler._preload();
|
|
147
|
+
|
|
148
|
+
const initialMessage = handler.getInitialMessage!(session);
|
|
149
|
+
expect(initialMessage).toBeDefined();
|
|
150
|
+
// Resumed session message should be different from new session message
|
|
151
|
+
const newSessionMsg = handler.getInitialMessage!(createTestSession());
|
|
152
|
+
expect(initialMessage).not.toBe(newSessionMsg);
|
|
153
|
+
|
|
154
|
+
const loop = new ConversationLoop({
|
|
155
|
+
client,
|
|
156
|
+
io,
|
|
157
|
+
session,
|
|
158
|
+
phaseHandler: handler,
|
|
159
|
+
initialMessage,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const result = await loop.run();
|
|
163
|
+
|
|
164
|
+
// Should have the auto-start turns appended to existing turns
|
|
165
|
+
// Note: the loop mutates the turns array in-place, so we compare against the captured count
|
|
166
|
+
expect(result.turns!.length).toBeGreaterThan(originalTurnCount);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T070: Integration tests for default command behavior (FR-004).
|
|
3
|
+
*
|
|
4
|
+
* Verifies:
|
|
5
|
+
* - `sofia` with no subcommand enters the workshop flow (default action)
|
|
6
|
+
* - `sofia workshop` still works as an alias
|
|
7
|
+
* - `--help` shows workshop options (--new-session, --phase, --retry) at top level
|
|
8
|
+
* - `sofia status` and `sofia export` subcommands continue to work after restructure
|
|
9
|
+
*/
|
|
10
|
+
import { describe, it, expect, vi, type Mock, beforeEach, afterEach } from 'vitest';
|
|
11
|
+
|
|
12
|
+
import type { CliHandlers } from '../../src/cli/index.js';
|
|
13
|
+
|
|
14
|
+
type HandlerMock = Mock<CliHandlers['workshopHandler']>;
|
|
15
|
+
|
|
16
|
+
// Mock all heavy dependencies so we only test CLI argument routing
|
|
17
|
+
vi.mock('../../src/sessions/sessionStore.js', () => ({
|
|
18
|
+
createDefaultStore: vi.fn(() => ({
|
|
19
|
+
list: vi.fn(async () => []),
|
|
20
|
+
exists: vi.fn(async () => false),
|
|
21
|
+
save: vi.fn(async () => {}),
|
|
22
|
+
load: vi.fn(async () => ({})),
|
|
23
|
+
})),
|
|
24
|
+
SessionStore: vi.fn(),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock('../../src/shared/copilotClient.js', () => ({
|
|
28
|
+
createCopilotClient: vi.fn(async () => ({
|
|
29
|
+
createSession: vi.fn(async () => ({
|
|
30
|
+
send: vi.fn(async function* () {
|
|
31
|
+
yield { type: 'TextDelta', text: 'hello' };
|
|
32
|
+
}),
|
|
33
|
+
})),
|
|
34
|
+
})),
|
|
35
|
+
createFakeCopilotClient: vi.fn(),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
vi.mock('../../src/logging/logger.js', () => ({
|
|
39
|
+
getLogger: vi.fn(() => ({
|
|
40
|
+
info: vi.fn(),
|
|
41
|
+
warn: vi.fn(),
|
|
42
|
+
error: vi.fn(),
|
|
43
|
+
debug: vi.fn(),
|
|
44
|
+
})),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
vi.mock('../../src/cli/ioContext.js', () => ({
|
|
48
|
+
createLoopIO: vi.fn(() => ({
|
|
49
|
+
write: vi.fn(),
|
|
50
|
+
writeActivity: vi.fn(),
|
|
51
|
+
readInput: vi.fn(async () => null),
|
|
52
|
+
showDecisionGate: vi.fn(async () => ({ choice: 'exit' })),
|
|
53
|
+
isJsonMode: false,
|
|
54
|
+
isTTY: false,
|
|
55
|
+
})),
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
describe('Default command behavior (T070)', () => {
|
|
59
|
+
let workshopSpy: HandlerMock;
|
|
60
|
+
let statusSpy: HandlerMock;
|
|
61
|
+
let exportSpy: HandlerMock;
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
workshopSpy = vi.fn<CliHandlers['workshopHandler']>(async () => {});
|
|
65
|
+
statusSpy = vi.fn<CliHandlers['statusHandler']>(async () => {});
|
|
66
|
+
exportSpy = vi.fn<CliHandlers['exportHandler']>(async () => {});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
vi.restoreAllMocks();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('sofia with no subcommand invokes the workshop handler (default action)', async () => {
|
|
74
|
+
// Import buildCli which should expose the program without auto-parsing
|
|
75
|
+
const { buildCli } = await import('../../src/cli/index.js');
|
|
76
|
+
const program = buildCli({
|
|
77
|
+
workshopHandler: workshopSpy,
|
|
78
|
+
statusHandler: statusSpy,
|
|
79
|
+
exportHandler: exportSpy,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Parse with no subcommand: `sofia`
|
|
83
|
+
await program.parseAsync(['node', 'sofia']);
|
|
84
|
+
|
|
85
|
+
expect(workshopSpy).toHaveBeenCalledTimes(1);
|
|
86
|
+
expect(statusSpy).not.toHaveBeenCalled();
|
|
87
|
+
expect(exportSpy).not.toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('sofia workshop still works as alias', async () => {
|
|
91
|
+
const { buildCli } = await import('../../src/cli/index.js');
|
|
92
|
+
const program = buildCli({
|
|
93
|
+
workshopHandler: workshopSpy,
|
|
94
|
+
statusHandler: statusSpy,
|
|
95
|
+
exportHandler: exportSpy,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await program.parseAsync(['node', 'sofia', 'workshop']);
|
|
99
|
+
|
|
100
|
+
expect(workshopSpy).toHaveBeenCalledTimes(1);
|
|
101
|
+
expect(statusSpy).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('--help shows workshop options (--new-session, --phase, --retry) at top level', async () => {
|
|
105
|
+
const { buildCli } = await import('../../src/cli/index.js');
|
|
106
|
+
const program = buildCli({
|
|
107
|
+
workshopHandler: workshopSpy,
|
|
108
|
+
statusHandler: statusSpy,
|
|
109
|
+
exportHandler: exportSpy,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const helpText = program.helpInformation();
|
|
113
|
+
|
|
114
|
+
expect(helpText).toContain('--new-session');
|
|
115
|
+
expect(helpText).toContain('--phase');
|
|
116
|
+
expect(helpText).toContain('--retry');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('sofia status routes to status handler', async () => {
|
|
120
|
+
const { buildCli } = await import('../../src/cli/index.js');
|
|
121
|
+
const program = buildCli({
|
|
122
|
+
workshopHandler: workshopSpy,
|
|
123
|
+
statusHandler: statusSpy,
|
|
124
|
+
exportHandler: exportSpy,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await program.parseAsync(['node', 'sofia', 'status']);
|
|
128
|
+
|
|
129
|
+
expect(statusSpy).toHaveBeenCalledTimes(1);
|
|
130
|
+
expect(workshopSpy).not.toHaveBeenCalled();
|
|
131
|
+
expect(exportSpy).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('sofia export routes to export handler', async () => {
|
|
135
|
+
const { buildCli } = await import('../../src/cli/index.js');
|
|
136
|
+
const program = buildCli({
|
|
137
|
+
workshopHandler: workshopSpy,
|
|
138
|
+
statusHandler: statusSpy,
|
|
139
|
+
exportHandler: exportSpy,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
await program.parseAsync(['node', 'sofia', 'export']);
|
|
143
|
+
|
|
144
|
+
expect(exportSpy).toHaveBeenCalledTimes(1);
|
|
145
|
+
expect(workshopSpy).not.toHaveBeenCalled();
|
|
146
|
+
expect(statusSpy).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('top-level --new-session is passed through to workshop handler', async () => {
|
|
150
|
+
const { buildCli } = await import('../../src/cli/index.js');
|
|
151
|
+
const program = buildCli({
|
|
152
|
+
workshopHandler: workshopSpy,
|
|
153
|
+
statusHandler: statusSpy,
|
|
154
|
+
exportHandler: exportSpy,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
await program.parseAsync(['node', 'sofia', '--new-session']);
|
|
158
|
+
|
|
159
|
+
expect(workshopSpy).toHaveBeenCalledTimes(1);
|
|
160
|
+
const mergedOpts = workshopSpy.mock.calls[0][0];
|
|
161
|
+
expect(mergedOpts.newSession).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('top-level --session and --phase triggers direct command mode options', async () => {
|
|
165
|
+
const { buildCli } = await import('../../src/cli/index.js');
|
|
166
|
+
const program = buildCli({
|
|
167
|
+
workshopHandler: workshopSpy,
|
|
168
|
+
statusHandler: statusSpy,
|
|
169
|
+
exportHandler: exportSpy,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await program.parseAsync(['node', 'sofia', '--session', 's123', '--phase', 'Ideate']);
|
|
173
|
+
|
|
174
|
+
expect(workshopSpy).toHaveBeenCalledTimes(1);
|
|
175
|
+
const opts = workshopSpy.mock.calls[0][0];
|
|
176
|
+
expect(opts.session).toBe('s123');
|
|
177
|
+
expect(opts.phase).toBe('Ideate');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test: Direct command non-TTY mode (T046)
|
|
3
|
+
*
|
|
4
|
+
* Tests the direct command entrypoint in non-TTY / automation contexts.
|
|
5
|
+
*
|
|
6
|
+
* Verifies:
|
|
7
|
+
* - Fails fast with non-zero exit code when --session is missing
|
|
8
|
+
* - Fails fast when --phase is missing in non-interactive mode
|
|
9
|
+
* - JSON-only stdout when --json specified (no human text leaks)
|
|
10
|
+
* - Activity/telemetry goes to the activity log (stderr equivalent), not stdout
|
|
11
|
+
* - Retry flag retries transient failures the specified number of times
|
|
12
|
+
* - Actionable error messages in JSON format
|
|
13
|
+
*/
|
|
14
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
15
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { tmpdir } from 'node:os';
|
|
18
|
+
|
|
19
|
+
import type { LoopIO, DecisionGateResult } from '../../src/loop/conversationLoop.js';
|
|
20
|
+
import { createFakeCopilotClient } from '../../src/shared/copilotClient.js';
|
|
21
|
+
import type { WorkshopSession, PhaseValue } from '../../src/shared/schemas/session.js';
|
|
22
|
+
import { SessionStore } from '../../src/sessions/sessionStore.js';
|
|
23
|
+
import {
|
|
24
|
+
runDirectCommand,
|
|
25
|
+
} from '../../src/cli/directCommands.js';
|
|
26
|
+
|
|
27
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
function createTestSession(overrides?: Partial<WorkshopSession>): WorkshopSession {
|
|
30
|
+
const now = new Date().toISOString();
|
|
31
|
+
return {
|
|
32
|
+
sessionId: 'test-direct-nontty',
|
|
33
|
+
schemaVersion: '1.0.0',
|
|
34
|
+
createdAt: now,
|
|
35
|
+
updatedAt: now,
|
|
36
|
+
phase: 'Discover',
|
|
37
|
+
status: 'Active',
|
|
38
|
+
participants: [],
|
|
39
|
+
artifacts: { generatedFiles: [] },
|
|
40
|
+
turns: [],
|
|
41
|
+
...overrides,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createNonTtyIO(
|
|
46
|
+
decisionGateChoice: DecisionGateResult = { choice: 'continue' },
|
|
47
|
+
inputs: (string | null)[] = [null],
|
|
48
|
+
): LoopIO & { output: string[]; activityLog: string[] } {
|
|
49
|
+
let inputIdx = 0;
|
|
50
|
+
const output: string[] = [];
|
|
51
|
+
const activityLog: string[] = [];
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
write(text: string) { output.push(text); },
|
|
55
|
+
writeActivity(text: string) { activityLog.push(text); },
|
|
56
|
+
writeToolSummary(_toolName: string, _summary: string) {},
|
|
57
|
+
async readInput(_prompt?: string): Promise<string | null> {
|
|
58
|
+
if (inputIdx >= inputs.length) return null;
|
|
59
|
+
return inputs[inputIdx++];
|
|
60
|
+
},
|
|
61
|
+
async showDecisionGate(_phase: PhaseValue): Promise<DecisionGateResult> {
|
|
62
|
+
return decisionGateChoice;
|
|
63
|
+
},
|
|
64
|
+
isJsonMode: true,
|
|
65
|
+
isTTY: false,
|
|
66
|
+
output,
|
|
67
|
+
activityLog,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
describe('Direct command non-TTY mode', () => {
|
|
74
|
+
let tmpDir: string;
|
|
75
|
+
let store: SessionStore;
|
|
76
|
+
|
|
77
|
+
beforeEach(async () => {
|
|
78
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'sofia-direct-nontty-'));
|
|
79
|
+
store = new SessionStore(tmpDir);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
afterEach(async () => {
|
|
83
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('fails with exit code 1 when session is missing', async () => {
|
|
87
|
+
const io = createNonTtyIO();
|
|
88
|
+
const client = createFakeCopilotClient([]);
|
|
89
|
+
|
|
90
|
+
const result = await runDirectCommand({
|
|
91
|
+
sessionId: undefined as unknown as string,
|
|
92
|
+
phase: 'Discover',
|
|
93
|
+
store,
|
|
94
|
+
client,
|
|
95
|
+
io,
|
|
96
|
+
nonInteractive: true,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(result.exitCode).toBe(1);
|
|
100
|
+
expect(result.error).toContain('session');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('fails with exit code 1 when phase is missing in non-interactive mode', async () => {
|
|
104
|
+
const session = createTestSession();
|
|
105
|
+
await store.save(session);
|
|
106
|
+
const io = createNonTtyIO();
|
|
107
|
+
const client = createFakeCopilotClient([]);
|
|
108
|
+
|
|
109
|
+
const result = await runDirectCommand({
|
|
110
|
+
sessionId: session.sessionId,
|
|
111
|
+
phase: undefined as unknown as PhaseValue,
|
|
112
|
+
store,
|
|
113
|
+
client,
|
|
114
|
+
io,
|
|
115
|
+
nonInteractive: true,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(result.exitCode).toBe(1);
|
|
119
|
+
expect(result.error).toContain('phase');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('outputs JSON-only on stdout with --json', async () => {
|
|
123
|
+
const session = createTestSession({ phase: 'Discover' });
|
|
124
|
+
await store.save(session);
|
|
125
|
+
|
|
126
|
+
const io = createNonTtyIO({ choice: 'exit' });
|
|
127
|
+
const client = createFakeCopilotClient([
|
|
128
|
+
{ role: 'assistant', content: '{"businessContext": {"company": "Test Corp"}}' },
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
await runDirectCommand({
|
|
132
|
+
sessionId: session.sessionId,
|
|
133
|
+
phase: 'Discover',
|
|
134
|
+
store,
|
|
135
|
+
client,
|
|
136
|
+
io,
|
|
137
|
+
json: true,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// All stdout output should be valid JSON lines
|
|
141
|
+
for (const line of io.output.filter(l => l.trim())) {
|
|
142
|
+
expect(() => JSON.parse(line)).not.toThrow();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('sends activity to activityLog, not stdout', async () => {
|
|
147
|
+
const session = createTestSession({ phase: 'Discover' });
|
|
148
|
+
await store.save(session);
|
|
149
|
+
|
|
150
|
+
const io = createNonTtyIO({ choice: 'exit' });
|
|
151
|
+
const client = createFakeCopilotClient([
|
|
152
|
+
{ role: 'assistant', content: 'Analyzing your business context.' },
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
await runDirectCommand({
|
|
156
|
+
sessionId: session.sessionId,
|
|
157
|
+
phase: 'Discover',
|
|
158
|
+
store,
|
|
159
|
+
client,
|
|
160
|
+
io,
|
|
161
|
+
json: true,
|
|
162
|
+
debug: true,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Output should not contain activity markers
|
|
166
|
+
const stdoutText = io.output.join('');
|
|
167
|
+
expect(stdoutText).not.toContain('[activity]');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('returns actionable error as JSON', async () => {
|
|
171
|
+
const io = createNonTtyIO();
|
|
172
|
+
const client = createFakeCopilotClient([]);
|
|
173
|
+
|
|
174
|
+
const result = await runDirectCommand({
|
|
175
|
+
sessionId: 'missing-session',
|
|
176
|
+
phase: 'Discover',
|
|
177
|
+
store,
|
|
178
|
+
client,
|
|
179
|
+
io,
|
|
180
|
+
json: true,
|
|
181
|
+
nonInteractive: true,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(result.exitCode).toBe(1);
|
|
185
|
+
expect(result.error).toBeDefined();
|
|
186
|
+
// When json mode, the IO output should have JSON error
|
|
187
|
+
const jsonErrors = io.output.filter(l => {
|
|
188
|
+
try { const o = JSON.parse(l); return o.error; } catch { return false; }
|
|
189
|
+
});
|
|
190
|
+
expect(jsonErrors.length).toBeGreaterThan(0);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('retries transient failures when --retry specified', async () => {
|
|
194
|
+
const session = createTestSession({ phase: 'Discover' });
|
|
195
|
+
await store.save(session);
|
|
196
|
+
|
|
197
|
+
let callCount = 0;
|
|
198
|
+
const client = createFakeCopilotClient([], {
|
|
199
|
+
onChat: async () => {
|
|
200
|
+
callCount++;
|
|
201
|
+
if (callCount <= 2) {
|
|
202
|
+
throw Object.assign(new Error('Connection refused'), { code: 'ECONNREFUSED' });
|
|
203
|
+
}
|
|
204
|
+
return { role: 'assistant' as const, content: 'Business context captured.' };
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Each retry attempt consumes one input (the error thrown by onChat breaks the
|
|
209
|
+
// loop before readInput is called again). Provide one input per attempt, then
|
|
210
|
+
// null to end the successful run.
|
|
211
|
+
const io = createNonTtyIO({ choice: 'exit' }, [
|
|
212
|
+
'attempt 1', // consumed by attempt 1 (fails)
|
|
213
|
+
'attempt 2', // consumed by attempt 2 (fails)
|
|
214
|
+
'attempt 3', // consumed by attempt 3 (succeeds, then loop reads again)
|
|
215
|
+
null, // ends the successful loop iteration
|
|
216
|
+
]);
|
|
217
|
+
|
|
218
|
+
await runDirectCommand({
|
|
219
|
+
sessionId: session.sessionId,
|
|
220
|
+
phase: 'Discover',
|
|
221
|
+
store,
|
|
222
|
+
client,
|
|
223
|
+
io,
|
|
224
|
+
retry: 3,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Should have retried and eventually succeeded
|
|
228
|
+
expect(callCount).toBeGreaterThan(1);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('fails after exhausting retries', async () => {
|
|
232
|
+
const session = createTestSession({ phase: 'Discover' });
|
|
233
|
+
await store.save(session);
|
|
234
|
+
|
|
235
|
+
const client = createFakeCopilotClient([], {
|
|
236
|
+
onChat: async () => {
|
|
237
|
+
throw Object.assign(new Error('Connection refused'), { code: 'ECONNREFUSED' });
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Each retry attempt consumes one input. retry=2 means 3 total attempts.
|
|
242
|
+
const io = createNonTtyIO({ choice: 'exit' }, [
|
|
243
|
+
'attempt 1', // attempt 1 (fails)
|
|
244
|
+
'attempt 2', // attempt 2/retry 1 (fails)
|
|
245
|
+
'attempt 3', // attempt 3/retry 2 (fails, exhausted)
|
|
246
|
+
]);
|
|
247
|
+
|
|
248
|
+
const result = await runDirectCommand({
|
|
249
|
+
sessionId: session.sessionId,
|
|
250
|
+
phase: 'Discover',
|
|
251
|
+
store,
|
|
252
|
+
client,
|
|
253
|
+
io,
|
|
254
|
+
retry: 2,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(result.exitCode).toBe(1);
|
|
258
|
+
expect(result.error).toContain('Connection refused');
|
|
259
|
+
});
|
|
260
|
+
});
|