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,592 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConversationLoop tests.
|
|
3
|
+
*
|
|
4
|
+
* Validates the multi-turn conversation orchestration, streaming render,
|
|
5
|
+
* event dispatching, phase handling, and shutdown behavior.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
8
|
+
import { ConversationLoop, } from '../../../src/loop/conversationLoop.js';
|
|
9
|
+
import { createFakeCopilotClient } from '../../../src/shared/copilotClient.js';
|
|
10
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
11
|
+
function makeSession(overrides) {
|
|
12
|
+
return {
|
|
13
|
+
sessionId: 'test-session-1',
|
|
14
|
+
schemaVersion: '1.0.0',
|
|
15
|
+
createdAt: '2025-01-01T00:00:00Z',
|
|
16
|
+
updatedAt: '2025-01-01T00:00:00Z',
|
|
17
|
+
phase: 'Discover',
|
|
18
|
+
status: 'Active',
|
|
19
|
+
participants: [{ id: 'p1', displayName: 'Alice', role: 'Facilitator' }],
|
|
20
|
+
artifacts: { generatedFiles: [] },
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Create a LoopIO that feeds predetermined inputs then returns null. */
|
|
25
|
+
function makeIO(inputs, opts) {
|
|
26
|
+
let inputIndex = 0;
|
|
27
|
+
const written = [];
|
|
28
|
+
const activities = [];
|
|
29
|
+
return {
|
|
30
|
+
write(text) {
|
|
31
|
+
written.push(text);
|
|
32
|
+
},
|
|
33
|
+
writeActivity(text) {
|
|
34
|
+
activities.push(text);
|
|
35
|
+
},
|
|
36
|
+
writeToolSummary(_toolName, _summary) {
|
|
37
|
+
// no-op for tests
|
|
38
|
+
},
|
|
39
|
+
async readInput(_prompt) {
|
|
40
|
+
if (inputIndex >= inputs.length)
|
|
41
|
+
return null;
|
|
42
|
+
return inputs[inputIndex++] ?? null;
|
|
43
|
+
},
|
|
44
|
+
async showDecisionGate(_phase) {
|
|
45
|
+
return { choice: 'continue' };
|
|
46
|
+
},
|
|
47
|
+
isJsonMode: opts?.json ?? false,
|
|
48
|
+
isTTY: opts?.tty ?? true,
|
|
49
|
+
// Expose captured output for assertions
|
|
50
|
+
get _written() {
|
|
51
|
+
return written;
|
|
52
|
+
},
|
|
53
|
+
get _activities() {
|
|
54
|
+
return activities;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function makePhaseHandler(overrides) {
|
|
59
|
+
return {
|
|
60
|
+
phase: 'Discover',
|
|
61
|
+
buildSystemPrompt: () => 'You are a workshop facilitator.',
|
|
62
|
+
extractResult: (_session) => ({}),
|
|
63
|
+
...overrides,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
67
|
+
describe('ConversationLoop', () => {
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
// Remove any leftover SIGINT listeners from previous tests
|
|
70
|
+
process.removeAllListeners('SIGINT');
|
|
71
|
+
});
|
|
72
|
+
describe('basic conversation flow', () => {
|
|
73
|
+
it('sends user input to LLM and accumulates turns', async () => {
|
|
74
|
+
const client = createFakeCopilotClient([
|
|
75
|
+
{ role: 'assistant', content: 'Tell me about your business.' },
|
|
76
|
+
{ role: 'assistant', content: 'Great, let us proceed.' },
|
|
77
|
+
]);
|
|
78
|
+
const io = makeIO(['We sell widgets', 'We have 50 employees']);
|
|
79
|
+
const handler = makePhaseHandler();
|
|
80
|
+
const session = makeSession();
|
|
81
|
+
const loop = new ConversationLoop({
|
|
82
|
+
client,
|
|
83
|
+
io,
|
|
84
|
+
session,
|
|
85
|
+
phaseHandler: handler,
|
|
86
|
+
});
|
|
87
|
+
const result = await loop.run();
|
|
88
|
+
// Should have 4 turns: 2 user + 2 assistant
|
|
89
|
+
expect(result.turns).toBeDefined();
|
|
90
|
+
expect(result.turns.length).toBe(4);
|
|
91
|
+
expect(result.turns[0].role).toBe('user');
|
|
92
|
+
expect(result.turns[0].content).toBe('We sell widgets');
|
|
93
|
+
expect(result.turns[1].role).toBe('assistant');
|
|
94
|
+
expect(result.turns[1].content).toBe('Tell me about your business.');
|
|
95
|
+
expect(result.turns[2].role).toBe('user');
|
|
96
|
+
expect(result.turns[2].content).toBe('We have 50 employees');
|
|
97
|
+
expect(result.turns[3].role).toBe('assistant');
|
|
98
|
+
expect(result.turns[3].content).toBe('Great, let us proceed.');
|
|
99
|
+
});
|
|
100
|
+
it('terminates on null input (EOF/Ctrl+D)', async () => {
|
|
101
|
+
const client = createFakeCopilotClient([]);
|
|
102
|
+
const io = makeIO([null]);
|
|
103
|
+
const handler = makePhaseHandler();
|
|
104
|
+
const loop = new ConversationLoop({
|
|
105
|
+
client,
|
|
106
|
+
io,
|
|
107
|
+
session: makeSession(),
|
|
108
|
+
phaseHandler: handler,
|
|
109
|
+
});
|
|
110
|
+
const result = await loop.run();
|
|
111
|
+
expect(result.turns ?? []).toHaveLength(0);
|
|
112
|
+
});
|
|
113
|
+
it('updates session after each turn via onSessionUpdate callback', async () => {
|
|
114
|
+
const client = createFakeCopilotClient([{ role: 'assistant', content: 'Response one' }]);
|
|
115
|
+
const io = makeIO(['hello']);
|
|
116
|
+
const updates = [];
|
|
117
|
+
const onSessionUpdate = vi.fn(async (s) => {
|
|
118
|
+
updates.push({ ...s });
|
|
119
|
+
});
|
|
120
|
+
const loop = new ConversationLoop({
|
|
121
|
+
client,
|
|
122
|
+
io,
|
|
123
|
+
session: makeSession(),
|
|
124
|
+
phaseHandler: makePhaseHandler(),
|
|
125
|
+
onSessionUpdate,
|
|
126
|
+
});
|
|
127
|
+
await loop.run();
|
|
128
|
+
expect(onSessionUpdate).toHaveBeenCalledTimes(1);
|
|
129
|
+
expect(updates[0].turns).toHaveLength(2);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe('event dispatching', () => {
|
|
133
|
+
it('emits events for TextDelta and Activity', async () => {
|
|
134
|
+
const client = createFakeCopilotClient([{ role: 'assistant', content: 'Hello!' }]);
|
|
135
|
+
const io = makeIO(['hi']);
|
|
136
|
+
const events = [];
|
|
137
|
+
const loop = new ConversationLoop({
|
|
138
|
+
client,
|
|
139
|
+
io,
|
|
140
|
+
session: makeSession(),
|
|
141
|
+
phaseHandler: makePhaseHandler(),
|
|
142
|
+
onEvent: (e) => events.push(e),
|
|
143
|
+
});
|
|
144
|
+
await loop.run();
|
|
145
|
+
// Should have at least: Activity (starting phase) + TextDelta
|
|
146
|
+
const activityEvents = events.filter((e) => e.type === 'Activity');
|
|
147
|
+
const textEvents = events.filter((e) => e.type === 'TextDelta');
|
|
148
|
+
expect(activityEvents.length).toBeGreaterThanOrEqual(1);
|
|
149
|
+
expect(textEvents.length).toBe(1);
|
|
150
|
+
expect(textEvents[0].type === 'TextDelta' && textEvents[0].text).toBe('Hello!');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe('streaming output', () => {
|
|
154
|
+
it('writes streamed text to io.write in TTY mode', async () => {
|
|
155
|
+
const client = createFakeCopilotClient([{ role: 'assistant', content: 'Streaming content' }]);
|
|
156
|
+
const io = makeIO(['go'], { tty: true, json: false });
|
|
157
|
+
const loop = new ConversationLoop({
|
|
158
|
+
client,
|
|
159
|
+
io,
|
|
160
|
+
session: makeSession(),
|
|
161
|
+
phaseHandler: makePhaseHandler(),
|
|
162
|
+
});
|
|
163
|
+
await loop.run();
|
|
164
|
+
const ioAny = io;
|
|
165
|
+
// In TTY mode, text goes through renderMarkdown which may add formatting
|
|
166
|
+
const allOutput = ioAny._written.join('');
|
|
167
|
+
expect(allOutput).toContain('Streaming content');
|
|
168
|
+
});
|
|
169
|
+
it('outputs JSON envelope in JSON mode', async () => {
|
|
170
|
+
const client = createFakeCopilotClient([{ role: 'assistant', content: 'Result text' }]);
|
|
171
|
+
const io = makeIO(['go'], { json: true });
|
|
172
|
+
const loop = new ConversationLoop({
|
|
173
|
+
client,
|
|
174
|
+
io,
|
|
175
|
+
session: makeSession(),
|
|
176
|
+
phaseHandler: makePhaseHandler(),
|
|
177
|
+
});
|
|
178
|
+
await loop.run();
|
|
179
|
+
const ioAny = io;
|
|
180
|
+
const jsonOutputs = ioAny._written.filter((w) => w.startsWith('{'));
|
|
181
|
+
expect(jsonOutputs.length).toBeGreaterThanOrEqual(1);
|
|
182
|
+
const parsed = JSON.parse(jsonOutputs[0]);
|
|
183
|
+
expect(parsed.phase).toBe('Discover');
|
|
184
|
+
expect(parsed.content).toBe('Result text');
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
describe('phase handler integration', () => {
|
|
188
|
+
it('applies extractResult updates to session', async () => {
|
|
189
|
+
const client = createFakeCopilotClient([
|
|
190
|
+
{ role: 'assistant', content: 'We identified your challenges' },
|
|
191
|
+
]);
|
|
192
|
+
const io = makeIO(['Our business sells widgets']);
|
|
193
|
+
const handler = makePhaseHandler({
|
|
194
|
+
extractResult: (_session, _response) => ({
|
|
195
|
+
businessContext: {
|
|
196
|
+
businessDescription: 'Widget seller',
|
|
197
|
+
challenges: ['Growth'],
|
|
198
|
+
},
|
|
199
|
+
}),
|
|
200
|
+
});
|
|
201
|
+
const loop = new ConversationLoop({
|
|
202
|
+
client,
|
|
203
|
+
io,
|
|
204
|
+
session: makeSession(),
|
|
205
|
+
phaseHandler: handler,
|
|
206
|
+
});
|
|
207
|
+
const result = await loop.run();
|
|
208
|
+
expect(result.businessContext).toBeDefined();
|
|
209
|
+
expect(result.businessContext.businessDescription).toBe('Widget seller');
|
|
210
|
+
expect(result.businessContext.challenges).toEqual(['Growth']);
|
|
211
|
+
});
|
|
212
|
+
it('uses system prompt from handler when creating session', async () => {
|
|
213
|
+
const createSessionSpy = vi.fn();
|
|
214
|
+
const client = createFakeCopilotClient([{ role: 'assistant', content: 'ok' }]);
|
|
215
|
+
// Spy on createSession
|
|
216
|
+
const originalCreateSession = client.createSession.bind(client);
|
|
217
|
+
client.createSession = async (opts) => {
|
|
218
|
+
createSessionSpy(opts);
|
|
219
|
+
return originalCreateSession(opts);
|
|
220
|
+
};
|
|
221
|
+
const io = makeIO(['test']);
|
|
222
|
+
const handler = makePhaseHandler({
|
|
223
|
+
buildSystemPrompt: () => 'Custom system prompt for discover',
|
|
224
|
+
});
|
|
225
|
+
const loop = new ConversationLoop({
|
|
226
|
+
client,
|
|
227
|
+
io,
|
|
228
|
+
session: makeSession(),
|
|
229
|
+
phaseHandler: handler,
|
|
230
|
+
});
|
|
231
|
+
await loop.run();
|
|
232
|
+
expect(createSessionSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
233
|
+
systemPrompt: expect.stringContaining('Custom system prompt for discover'),
|
|
234
|
+
}));
|
|
235
|
+
});
|
|
236
|
+
it('includes references from handler in session options', async () => {
|
|
237
|
+
const createSessionSpy = vi.fn();
|
|
238
|
+
const client = createFakeCopilotClient([{ role: 'assistant', content: 'ok' }]);
|
|
239
|
+
const originalCreateSession = client.createSession.bind(client);
|
|
240
|
+
client.createSession = async (opts) => {
|
|
241
|
+
createSessionSpy(opts);
|
|
242
|
+
return originalCreateSession(opts);
|
|
243
|
+
};
|
|
244
|
+
const io = makeIO(['test']);
|
|
245
|
+
const handler = makePhaseHandler({
|
|
246
|
+
getReferences: () => ['doc1.md', 'doc2.md'],
|
|
247
|
+
});
|
|
248
|
+
const loop = new ConversationLoop({
|
|
249
|
+
client,
|
|
250
|
+
io,
|
|
251
|
+
session: makeSession(),
|
|
252
|
+
phaseHandler: handler,
|
|
253
|
+
});
|
|
254
|
+
await loop.run();
|
|
255
|
+
expect(createSessionSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
256
|
+
references: ['doc1.md', 'doc2.md'],
|
|
257
|
+
}));
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
describe('phase completion', () => {
|
|
261
|
+
it('does not break loop on empty input when isComplete returns false', async () => {
|
|
262
|
+
const client = createFakeCopilotClient([
|
|
263
|
+
{ role: 'assistant', content: 'Need more info' },
|
|
264
|
+
{ role: 'assistant', content: 'Thanks' },
|
|
265
|
+
]);
|
|
266
|
+
let callCount = 0;
|
|
267
|
+
const io = makeIO(['', 'more data']);
|
|
268
|
+
const handler = makePhaseHandler({
|
|
269
|
+
isComplete: () => {
|
|
270
|
+
callCount++;
|
|
271
|
+
// First call: not complete; won't be called again because second input is non-empty
|
|
272
|
+
return callCount > 1;
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
const loop = new ConversationLoop({
|
|
276
|
+
client,
|
|
277
|
+
io,
|
|
278
|
+
session: makeSession(),
|
|
279
|
+
phaseHandler: handler,
|
|
280
|
+
});
|
|
281
|
+
const result = await loop.run();
|
|
282
|
+
// Both inputs should produce turns (empty string still gets sent, "more data" also)
|
|
283
|
+
expect(result.turns.length).toBeGreaterThanOrEqual(2);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
describe('session state', () => {
|
|
287
|
+
it('getSession returns a copy of current session', async () => {
|
|
288
|
+
const client = createFakeCopilotClient([]);
|
|
289
|
+
const io = makeIO([null]);
|
|
290
|
+
const session = makeSession();
|
|
291
|
+
const loop = new ConversationLoop({
|
|
292
|
+
client,
|
|
293
|
+
io,
|
|
294
|
+
session,
|
|
295
|
+
phaseHandler: makePhaseHandler(),
|
|
296
|
+
});
|
|
297
|
+
const before = loop.getSession();
|
|
298
|
+
expect(before.sessionId).toBe('test-session-1');
|
|
299
|
+
// Mutate the returned copy
|
|
300
|
+
before.sessionId = 'mutated';
|
|
301
|
+
// Original should be unchanged
|
|
302
|
+
expect(loop.getSession().sessionId).toBe('test-session-1');
|
|
303
|
+
});
|
|
304
|
+
it('updates updatedAt timestamp after each turn', async () => {
|
|
305
|
+
const client = createFakeCopilotClient([{ role: 'assistant', content: 'ok' }]);
|
|
306
|
+
const io = makeIO(['hello']);
|
|
307
|
+
const loop = new ConversationLoop({
|
|
308
|
+
client,
|
|
309
|
+
io,
|
|
310
|
+
session: makeSession({ updatedAt: '2025-01-01T00:00:00Z' }),
|
|
311
|
+
phaseHandler: makePhaseHandler(),
|
|
312
|
+
});
|
|
313
|
+
const result = await loop.run();
|
|
314
|
+
expect(result.updatedAt).not.toBe('2025-01-01T00:00:00Z');
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
describe('edge cases', () => {
|
|
318
|
+
it('handles handler with no getReferences gracefully', async () => {
|
|
319
|
+
const client = createFakeCopilotClient([{ role: 'assistant', content: 'ok' }]);
|
|
320
|
+
const io = makeIO(['test']);
|
|
321
|
+
const handler = makePhaseHandler();
|
|
322
|
+
delete handler.getReferences;
|
|
323
|
+
const loop = new ConversationLoop({
|
|
324
|
+
client,
|
|
325
|
+
io,
|
|
326
|
+
session: makeSession(),
|
|
327
|
+
phaseHandler: handler,
|
|
328
|
+
});
|
|
329
|
+
// Should not throw
|
|
330
|
+
const result = await loop.run();
|
|
331
|
+
expect(result.turns).toHaveLength(2);
|
|
332
|
+
});
|
|
333
|
+
it('handles exhausted fake responses gracefully', async () => {
|
|
334
|
+
// Only 1 response configured but 2 messages sent
|
|
335
|
+
const client = createFakeCopilotClient([{ role: 'assistant', content: 'First' }]);
|
|
336
|
+
const io = makeIO(['msg1', 'msg2']);
|
|
337
|
+
const loop = new ConversationLoop({
|
|
338
|
+
client,
|
|
339
|
+
io,
|
|
340
|
+
session: makeSession(),
|
|
341
|
+
phaseHandler: makePhaseHandler(),
|
|
342
|
+
});
|
|
343
|
+
const result = await loop.run();
|
|
344
|
+
expect(result.turns).toHaveLength(4); // 2 user + 2 assistant
|
|
345
|
+
expect(result.turns[3].content).toContain('No more responses');
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
// ── T073: Auto-start behavior ──────────────────────────────────────────
|
|
349
|
+
describe('auto-start with initialMessage (T073)', () => {
|
|
350
|
+
it('sends initialMessage to LLM before readInput()', async () => {
|
|
351
|
+
const client = createFakeCopilotClient([
|
|
352
|
+
{ role: 'assistant', content: 'Welcome to the Discover phase!' },
|
|
353
|
+
{ role: 'assistant', content: 'Great, thanks for that info.' },
|
|
354
|
+
]);
|
|
355
|
+
const readInputCalls = [];
|
|
356
|
+
const io = makeIO(['user says hello']);
|
|
357
|
+
const origReadInput = io.readInput.bind(io);
|
|
358
|
+
io.readInput = async (prompt) => {
|
|
359
|
+
readInputCalls.push(prompt ?? '');
|
|
360
|
+
return origReadInput(prompt);
|
|
361
|
+
};
|
|
362
|
+
const loop = new ConversationLoop({
|
|
363
|
+
client,
|
|
364
|
+
io,
|
|
365
|
+
session: makeSession(),
|
|
366
|
+
phaseHandler: makePhaseHandler(),
|
|
367
|
+
initialMessage: 'Introduce the Discover phase and ask the first question.',
|
|
368
|
+
});
|
|
369
|
+
const result = await loop.run();
|
|
370
|
+
// Initial message turn + user turn = 4 turns total
|
|
371
|
+
expect(result.turns).toHaveLength(4);
|
|
372
|
+
// First turn pair: system initial message → LLM greeting
|
|
373
|
+
expect(result.turns[0].role).toBe('user');
|
|
374
|
+
expect(result.turns[0].content).toBe('Introduce the Discover phase and ask the first question.');
|
|
375
|
+
expect(result.turns[1].role).toBe('assistant');
|
|
376
|
+
expect(result.turns[1].content).toBe('Welcome to the Discover phase!');
|
|
377
|
+
});
|
|
378
|
+
it('streams the greeting response to output', async () => {
|
|
379
|
+
const client = createFakeCopilotClient([
|
|
380
|
+
{ role: 'assistant', content: 'Hello! Welcome to sofIA.' },
|
|
381
|
+
]);
|
|
382
|
+
const io = makeIO([], { tty: true, json: false });
|
|
383
|
+
const loop = new ConversationLoop({
|
|
384
|
+
client,
|
|
385
|
+
io,
|
|
386
|
+
session: makeSession(),
|
|
387
|
+
phaseHandler: makePhaseHandler(),
|
|
388
|
+
initialMessage: 'Start the phase.',
|
|
389
|
+
});
|
|
390
|
+
await loop.run();
|
|
391
|
+
const ioTyped = io;
|
|
392
|
+
const allOutput = ioTyped._written.join('');
|
|
393
|
+
expect(allOutput).toContain('Hello! Welcome to sofIA.');
|
|
394
|
+
});
|
|
395
|
+
it('records initial exchange in turn history', async () => {
|
|
396
|
+
const client = createFakeCopilotClient([
|
|
397
|
+
{ role: 'assistant', content: 'Phase intro response' },
|
|
398
|
+
]);
|
|
399
|
+
const io = makeIO([]);
|
|
400
|
+
const loop = new ConversationLoop({
|
|
401
|
+
client,
|
|
402
|
+
io,
|
|
403
|
+
session: makeSession(),
|
|
404
|
+
phaseHandler: makePhaseHandler(),
|
|
405
|
+
initialMessage: 'Auto-start message',
|
|
406
|
+
});
|
|
407
|
+
const result = await loop.run();
|
|
408
|
+
expect(result.turns).toHaveLength(2);
|
|
409
|
+
expect(result.turns[0].role).toBe('user');
|
|
410
|
+
expect(result.turns[0].content).toBe('Auto-start message');
|
|
411
|
+
expect(result.turns[1].role).toBe('assistant');
|
|
412
|
+
expect(result.turns[1].content).toBe('Phase intro response');
|
|
413
|
+
});
|
|
414
|
+
it('does NOT auto-start when initialMessage is not provided', async () => {
|
|
415
|
+
const client = createFakeCopilotClient([{ role: 'assistant', content: 'Response' }]);
|
|
416
|
+
const io = makeIO(['user input']);
|
|
417
|
+
const loop = new ConversationLoop({
|
|
418
|
+
client,
|
|
419
|
+
io,
|
|
420
|
+
session: makeSession(),
|
|
421
|
+
phaseHandler: makePhaseHandler(),
|
|
422
|
+
// No initialMessage
|
|
423
|
+
});
|
|
424
|
+
const result = await loop.run();
|
|
425
|
+
// Only user + assistant turns, no initial message turn
|
|
426
|
+
expect(result.turns).toHaveLength(2);
|
|
427
|
+
expect(result.turns[0].role).toBe('user');
|
|
428
|
+
expect(result.turns[0].content).toBe('user input');
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
// ── Session resume: conversation history in system prompt ────────────────
|
|
432
|
+
describe('session resume with prior turns', () => {
|
|
433
|
+
it('injects prior conversation history into the system prompt on resume', async () => {
|
|
434
|
+
const createSessionSpy = vi.fn();
|
|
435
|
+
const client = createFakeCopilotClient([
|
|
436
|
+
{ role: 'assistant', content: 'Welcome back! You told me about widgets.' },
|
|
437
|
+
]);
|
|
438
|
+
const originalCreateSession = client.createSession.bind(client);
|
|
439
|
+
client.createSession = async (opts) => {
|
|
440
|
+
createSessionSpy(opts);
|
|
441
|
+
return originalCreateSession(opts);
|
|
442
|
+
};
|
|
443
|
+
// Session with existing turns from a prior Discover conversation
|
|
444
|
+
const session = makeSession({
|
|
445
|
+
turns: [
|
|
446
|
+
{
|
|
447
|
+
phase: 'Discover',
|
|
448
|
+
sequence: 1,
|
|
449
|
+
role: 'user',
|
|
450
|
+
content: 'We sell widgets worldwide',
|
|
451
|
+
timestamp: '2025-01-01T00:00:00Z',
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
phase: 'Discover',
|
|
455
|
+
sequence: 2,
|
|
456
|
+
role: 'assistant',
|
|
457
|
+
content: 'Great, what are your main challenges?',
|
|
458
|
+
timestamp: '2025-01-01T00:01:00Z',
|
|
459
|
+
},
|
|
460
|
+
],
|
|
461
|
+
});
|
|
462
|
+
const io = makeIO([]);
|
|
463
|
+
const handler = makePhaseHandler({
|
|
464
|
+
buildSystemPrompt: () => 'You are a workshop facilitator.',
|
|
465
|
+
});
|
|
466
|
+
const loop = new ConversationLoop({
|
|
467
|
+
client,
|
|
468
|
+
io,
|
|
469
|
+
session,
|
|
470
|
+
phaseHandler: handler,
|
|
471
|
+
initialMessage: 'We are resuming. Summarize progress.',
|
|
472
|
+
});
|
|
473
|
+
await loop.run();
|
|
474
|
+
// The system prompt should contain the prior conversation history
|
|
475
|
+
const passedOpts = createSessionSpy.mock.calls[0][0];
|
|
476
|
+
expect(passedOpts.systemPrompt).toContain('We sell widgets worldwide');
|
|
477
|
+
expect(passedOpts.systemPrompt).toContain('Great, what are your main challenges?');
|
|
478
|
+
expect(passedOpts.systemPrompt).toContain('Previous conversation');
|
|
479
|
+
});
|
|
480
|
+
it('does NOT inject history when no prior turns exist', async () => {
|
|
481
|
+
const createSessionSpy = vi.fn();
|
|
482
|
+
const client = createFakeCopilotClient([
|
|
483
|
+
{ role: 'assistant', content: 'Welcome to the workshop!' },
|
|
484
|
+
]);
|
|
485
|
+
const originalCreateSession = client.createSession.bind(client);
|
|
486
|
+
client.createSession = async (opts) => {
|
|
487
|
+
createSessionSpy(opts);
|
|
488
|
+
return originalCreateSession(opts);
|
|
489
|
+
};
|
|
490
|
+
const io = makeIO([]);
|
|
491
|
+
const handler = makePhaseHandler({
|
|
492
|
+
buildSystemPrompt: () => 'You are a workshop facilitator.',
|
|
493
|
+
});
|
|
494
|
+
const loop = new ConversationLoop({
|
|
495
|
+
client,
|
|
496
|
+
io,
|
|
497
|
+
session: makeSession(), // No turns
|
|
498
|
+
phaseHandler: handler,
|
|
499
|
+
initialMessage: 'Start the Discover phase.',
|
|
500
|
+
});
|
|
501
|
+
await loop.run();
|
|
502
|
+
const passedOpts = createSessionSpy.mock.calls[0][0];
|
|
503
|
+
// System prompt should contain what the handler returned plus phase boundary
|
|
504
|
+
expect(passedOpts.systemPrompt).toContain('You are a workshop facilitator.');
|
|
505
|
+
});
|
|
506
|
+
it('only includes turns for the current phase in the history', async () => {
|
|
507
|
+
const createSessionSpy = vi.fn();
|
|
508
|
+
const client = createFakeCopilotClient([
|
|
509
|
+
{ role: 'assistant', content: 'Resuming ideation.' },
|
|
510
|
+
]);
|
|
511
|
+
const originalCreateSession = client.createSession.bind(client);
|
|
512
|
+
client.createSession = async (opts) => {
|
|
513
|
+
createSessionSpy(opts);
|
|
514
|
+
return originalCreateSession(opts);
|
|
515
|
+
};
|
|
516
|
+
const session = makeSession({
|
|
517
|
+
phase: 'Ideate',
|
|
518
|
+
turns: [
|
|
519
|
+
{
|
|
520
|
+
phase: 'Discover',
|
|
521
|
+
sequence: 1,
|
|
522
|
+
role: 'user',
|
|
523
|
+
content: 'Discovery message (should NOT appear)',
|
|
524
|
+
timestamp: '2025-01-01T00:00:00Z',
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
phase: 'Ideate',
|
|
528
|
+
sequence: 2,
|
|
529
|
+
role: 'user',
|
|
530
|
+
content: 'Ideation message (should appear)',
|
|
531
|
+
timestamp: '2025-01-01T01:00:00Z',
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
phase: 'Ideate',
|
|
535
|
+
sequence: 3,
|
|
536
|
+
role: 'assistant',
|
|
537
|
+
content: 'Ideation response (should appear)',
|
|
538
|
+
timestamp: '2025-01-01T01:01:00Z',
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
});
|
|
542
|
+
const io = makeIO([]);
|
|
543
|
+
const handler = makePhaseHandler({
|
|
544
|
+
phase: 'Ideate',
|
|
545
|
+
buildSystemPrompt: () => 'Ideation facilitator.',
|
|
546
|
+
});
|
|
547
|
+
const loop = new ConversationLoop({
|
|
548
|
+
client,
|
|
549
|
+
io,
|
|
550
|
+
session,
|
|
551
|
+
phaseHandler: handler,
|
|
552
|
+
initialMessage: 'Resume ideation.',
|
|
553
|
+
});
|
|
554
|
+
await loop.run();
|
|
555
|
+
const passedOpts = createSessionSpy.mock.calls[0][0];
|
|
556
|
+
expect(passedOpts.systemPrompt).toContain('Ideation message (should appear)');
|
|
557
|
+
expect(passedOpts.systemPrompt).toContain('Ideation response (should appear)');
|
|
558
|
+
expect(passedOpts.systemPrompt).not.toContain('Discovery message (should NOT appear)');
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
// ── T055: SessionOptions.onUsage callback ─────────────────────────────────
|
|
562
|
+
describe('SessionOptions.onUsage (T055)', () => {
|
|
563
|
+
it('accepts an onUsage callback on SessionOptions', () => {
|
|
564
|
+
const opts = {
|
|
565
|
+
systemPrompt: 'Test',
|
|
566
|
+
onUsage: vi.fn(),
|
|
567
|
+
};
|
|
568
|
+
expect(opts.onUsage).toBeDefined();
|
|
569
|
+
expect(typeof opts.onUsage).toBe('function');
|
|
570
|
+
});
|
|
571
|
+
it('onUsage callback is forwarded when passed through createSession', async () => {
|
|
572
|
+
const usageCb = vi.fn();
|
|
573
|
+
const createSessionSpy = vi.fn();
|
|
574
|
+
const client = createFakeCopilotClient([{ role: 'assistant', content: 'OK' }]);
|
|
575
|
+
const originalCreateSession = client.createSession.bind(client);
|
|
576
|
+
client.createSession = async (opts) => {
|
|
577
|
+
createSessionSpy(opts);
|
|
578
|
+
return originalCreateSession(opts);
|
|
579
|
+
};
|
|
580
|
+
await client.createSession({
|
|
581
|
+
systemPrompt: 'Test',
|
|
582
|
+
onUsage: usageCb,
|
|
583
|
+
});
|
|
584
|
+
const passedOpts = createSessionSpy.mock.calls[0][0];
|
|
585
|
+
expect(passedOpts.onUsage).toBe(usageCb);
|
|
586
|
+
});
|
|
587
|
+
it('omitting onUsage does not set it on SessionOptions', () => {
|
|
588
|
+
const opts = { systemPrompt: 'Test' };
|
|
589
|
+
expect(opts.onUsage).toBeUndefined();
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
});
|