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,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sofia dev` command handler.
|
|
3
|
+
*
|
|
4
|
+
* Runs the Develop phase for a completed workshop session:
|
|
5
|
+
* - Validates the session has selection + plan
|
|
6
|
+
* - Derives checkpoint state for resume decisions
|
|
7
|
+
* - Creates a RalphLoop and runs it
|
|
8
|
+
* - Displays results (repo URL/path, final status)
|
|
9
|
+
*
|
|
10
|
+
* Contract: specs/002-poc-generation/tasks.md (T019, T029, T038)
|
|
11
|
+
* Contract: specs/004-dev-resume-hardening/contracts/cli.md
|
|
12
|
+
*/
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
import type { WorkshopSession } from '../shared/schemas/session.js';
|
|
17
|
+
import type { McpManager } from '../mcp/mcpManager.js';
|
|
18
|
+
import { createNoOpSpinner } from '../shared/activitySpinner.js';
|
|
19
|
+
import { RalphLoop } from '../develop/ralphLoop.js';
|
|
20
|
+
import { McpContextEnricher } from '../develop/mcpContextEnricher.js';
|
|
21
|
+
import { deriveCheckpointState } from '../develop/checkpointState.js';
|
|
22
|
+
import { createDefaultRegistry, selectTemplate } from '../develop/templateRegistry.js';
|
|
23
|
+
import { PocScaffolder } from '../develop/pocScaffolder.js';
|
|
24
|
+
|
|
25
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
export interface DevelopCommandOptions {
|
|
28
|
+
/** Session ID to develop */
|
|
29
|
+
session?: string;
|
|
30
|
+
/** Maximum Ralph loop iterations */
|
|
31
|
+
maxIterations?: number;
|
|
32
|
+
/** Output directory for the PoC */
|
|
33
|
+
output?: string;
|
|
34
|
+
/** Force overwrite of existing output directory */
|
|
35
|
+
force?: boolean;
|
|
36
|
+
/** Emit machine-readable JSON only */
|
|
37
|
+
json?: boolean;
|
|
38
|
+
/** Enable debug output */
|
|
39
|
+
debug?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface DevelopCommandDeps {
|
|
43
|
+
store: {
|
|
44
|
+
load(sessionId: string): Promise<WorkshopSession | null>;
|
|
45
|
+
save(session: WorkshopSession): Promise<void>;
|
|
46
|
+
list(): Promise<string[]>;
|
|
47
|
+
};
|
|
48
|
+
io: import('../loop/conversationLoop.js').LoopIO;
|
|
49
|
+
client: import('../shared/copilotClient.js').CopilotClient;
|
|
50
|
+
/** Optional MCP manager — when provided, wires GitHub MCP and context enrichment */
|
|
51
|
+
mcpManager?: McpManager;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Validation ────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Validate that a session is ready for the Develop phase.
|
|
58
|
+
*
|
|
59
|
+
* Returns null if valid, or an error message if not.
|
|
60
|
+
*/
|
|
61
|
+
export function validateSessionForDevelop(session: WorkshopSession): string | null {
|
|
62
|
+
if (!session.selection) {
|
|
63
|
+
return [
|
|
64
|
+
'Session is missing an idea selection.',
|
|
65
|
+
'Run the Select phase first: sofia workshop --session ' + session.sessionId,
|
|
66
|
+
].join('\n');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!session.plan) {
|
|
70
|
+
return [
|
|
71
|
+
'Session is missing an implementation plan.',
|
|
72
|
+
'Run the Plan phase first: sofia workshop --session ' + session.sessionId,
|
|
73
|
+
].join('\n');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Command handler ───────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Main handler for the `sofia dev` command.
|
|
83
|
+
*/
|
|
84
|
+
export async function developCommand(
|
|
85
|
+
opts: DevelopCommandOptions,
|
|
86
|
+
deps: DevelopCommandDeps,
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
const { store, io, client } = deps;
|
|
89
|
+
const { mcpManager } = deps;
|
|
90
|
+
const json = opts.json ?? false;
|
|
91
|
+
// ── Resolve session ──────────────────────────────────────────────────────
|
|
92
|
+
let sessionId = opts.session;
|
|
93
|
+
|
|
94
|
+
if (!sessionId) {
|
|
95
|
+
// Try to find the most recent session
|
|
96
|
+
const sessions = await store.list();
|
|
97
|
+
if (sessions.length === 0) {
|
|
98
|
+
const msg = 'No sessions found. Start a workshop first: sofia workshop';
|
|
99
|
+
if (json) {
|
|
100
|
+
process.stdout.write(JSON.stringify({ error: msg }) + '\n');
|
|
101
|
+
} else {
|
|
102
|
+
io.writeActivity(msg);
|
|
103
|
+
}
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
sessionId = sessions[sessions.length - 1];
|
|
108
|
+
io.writeActivity(`Using most recent session: ${sessionId}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Load session ─────────────────────────────────────────────────────────
|
|
112
|
+
const session = await store.load(sessionId);
|
|
113
|
+
|
|
114
|
+
if (!session) {
|
|
115
|
+
const msg = `Session not found: ${sessionId}`;
|
|
116
|
+
if (json) {
|
|
117
|
+
process.stdout.write(JSON.stringify({ error: msg }) + '\n');
|
|
118
|
+
} else {
|
|
119
|
+
io.writeActivity(msg);
|
|
120
|
+
}
|
|
121
|
+
process.exitCode = 1;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Validate session ─────────────────────────────────────────────────────
|
|
126
|
+
const validationError = validateSessionForDevelop(session);
|
|
127
|
+
if (validationError) {
|
|
128
|
+
if (json) {
|
|
129
|
+
process.stdout.write(JSON.stringify({ error: validationError }) + '\n');
|
|
130
|
+
} else {
|
|
131
|
+
process.stderr.write(`Error: ${validationError}\n`);
|
|
132
|
+
}
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── Determine output directory ───────────────────────────────────────────
|
|
138
|
+
const outputDir = opts.output
|
|
139
|
+
? join(process.cwd(), opts.output)
|
|
140
|
+
: join(process.cwd(), '..', 'poc', sessionId);
|
|
141
|
+
|
|
142
|
+
// ── Handle --force: reset session.poc AND output directory ───────────────
|
|
143
|
+
if (opts.force) {
|
|
144
|
+
// FR-008/009/010: Clear session state before doing anything else
|
|
145
|
+
session.poc = undefined;
|
|
146
|
+
await store.save(session);
|
|
147
|
+
|
|
148
|
+
const dirExists = existsSync(outputDir);
|
|
149
|
+
if (dirExists) {
|
|
150
|
+
try {
|
|
151
|
+
rmSync(outputDir, { recursive: true, force: true });
|
|
152
|
+
} catch (err: unknown) {
|
|
153
|
+
const msg = `Failed to clear output directory: ${err instanceof Error ? err.message : String(err)}`;
|
|
154
|
+
if (json) {
|
|
155
|
+
process.stdout.write(JSON.stringify({ error: msg }) + '\n');
|
|
156
|
+
} else {
|
|
157
|
+
process.stderr.write(`Error: ${msg}\n`);
|
|
158
|
+
}
|
|
159
|
+
process.exitCode = 1;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!json) {
|
|
164
|
+
io.writeActivity('Cleared existing output directory and session state (--force)');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Derive checkpoint state (resume detection) ───────────────────────────
|
|
169
|
+
const checkpoint = deriveCheckpointState(session, outputDir);
|
|
170
|
+
|
|
171
|
+
// FR-005: If PoC already succeeded, exit with completion message
|
|
172
|
+
if (!opts.force && checkpoint.priorFinalStatus === 'success') {
|
|
173
|
+
const msg = `PoC already complete for session ${sessionId}. Use --force to start fresh.`;
|
|
174
|
+
if (json) {
|
|
175
|
+
process.stdout.write(JSON.stringify({ status: 'already-complete', sessionId }) + '\n');
|
|
176
|
+
} else {
|
|
177
|
+
io.writeActivity(msg);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// FR-006: Default to resume for failed/partial
|
|
183
|
+
if (!opts.force && checkpoint.hasPriorRun) {
|
|
184
|
+
if (!json) {
|
|
185
|
+
io.writeActivity(
|
|
186
|
+
`Resuming session ${sessionId} from iteration ${checkpoint.resumeFromIteration} (${checkpoint.completedIterations} completed iterations found)`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── Template selection ────────────────────────────────────────────────────
|
|
192
|
+
const registry = createDefaultRegistry();
|
|
193
|
+
const template = selectTemplate(
|
|
194
|
+
registry,
|
|
195
|
+
session.plan?.architectureNotes,
|
|
196
|
+
session.plan?.dependencies,
|
|
197
|
+
);
|
|
198
|
+
if (!json) {
|
|
199
|
+
const matchedPattern = template.matchPatterns.find((p) =>
|
|
200
|
+
[session.plan?.architectureNotes ?? '', ...(session.plan?.dependencies ?? [])]
|
|
201
|
+
.join(' ')
|
|
202
|
+
.toLowerCase()
|
|
203
|
+
.includes(p.toLowerCase()),
|
|
204
|
+
);
|
|
205
|
+
io.writeActivity(
|
|
206
|
+
`Selected template: ${template.id}${matchedPattern ? ` (matched '${matchedPattern}' in architecture notes)` : ' (default)'}`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── Create RalphLoop ─────────────────────────────────────────────────────
|
|
211
|
+
const spinner = createNoOpSpinner();
|
|
212
|
+
|
|
213
|
+
const enricher = mcpManager ? new McpContextEnricher(mcpManager) : undefined;
|
|
214
|
+
|
|
215
|
+
const ralph = new RalphLoop({
|
|
216
|
+
client,
|
|
217
|
+
io,
|
|
218
|
+
session,
|
|
219
|
+
spinner,
|
|
220
|
+
maxIterations: opts.maxIterations ?? 10,
|
|
221
|
+
outputDir,
|
|
222
|
+
enricher,
|
|
223
|
+
checkpoint,
|
|
224
|
+
scaffolder: new PocScaffolder(template),
|
|
225
|
+
templateEntry: template,
|
|
226
|
+
onSessionUpdate: async (updated) => {
|
|
227
|
+
await store.save(updated);
|
|
228
|
+
},
|
|
229
|
+
onEvent: (event) => {
|
|
230
|
+
if (opts.debug) {
|
|
231
|
+
io.writeActivity(`[event] ${event.type}: ${JSON.stringify(event)}`);
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// ── Run the loop ─────────────────────────────────────────────────────────
|
|
237
|
+
if (!json) {
|
|
238
|
+
io.writeActivity(
|
|
239
|
+
`Starting PoC generation for session: ${sessionId}\n` +
|
|
240
|
+
`Output: ${outputDir}\n` +
|
|
241
|
+
`Max iterations: ${opts.maxIterations ?? 10}`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let result;
|
|
246
|
+
try {
|
|
247
|
+
result = await ralph.run();
|
|
248
|
+
} catch (err: unknown) {
|
|
249
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
250
|
+
if (json) {
|
|
251
|
+
process.stdout.write(JSON.stringify({ error: msg }) + '\n');
|
|
252
|
+
} else {
|
|
253
|
+
process.stderr.write(`Error: ${msg}\n`);
|
|
254
|
+
}
|
|
255
|
+
process.exitCode = 1;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ── Display results ──────────────────────────────────────────────────────
|
|
260
|
+
if (json) {
|
|
261
|
+
const output = {
|
|
262
|
+
sessionId,
|
|
263
|
+
finalStatus: result.finalStatus,
|
|
264
|
+
terminationReason: result.terminationReason,
|
|
265
|
+
iterationsCompleted: result.iterationsCompleted,
|
|
266
|
+
repoSource: result.session.poc?.repoSource ?? 'local',
|
|
267
|
+
repoUrl: result.session.poc?.repoUrl,
|
|
268
|
+
repoPath: result.session.poc?.repoPath ?? outputDir,
|
|
269
|
+
outputDir: result.outputDir,
|
|
270
|
+
};
|
|
271
|
+
process.stdout.write(JSON.stringify(output) + '\n');
|
|
272
|
+
} else {
|
|
273
|
+
const repoInfo = result.session.poc?.repoUrl
|
|
274
|
+
? `Repository URL: ${result.session.poc.repoUrl}`
|
|
275
|
+
: `Repository Path: ${result.session.poc?.repoPath ?? outputDir}`;
|
|
276
|
+
|
|
277
|
+
io.write(
|
|
278
|
+
[
|
|
279
|
+
'',
|
|
280
|
+
`PoC Generation Complete`,
|
|
281
|
+
`──────────────────────`,
|
|
282
|
+
`Status: ${result.finalStatus}`,
|
|
283
|
+
`Reason: ${result.terminationReason}`,
|
|
284
|
+
`Iterations: ${result.iterationsCompleted}`,
|
|
285
|
+
repoInfo,
|
|
286
|
+
'',
|
|
287
|
+
].join('\n'),
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
if (result.finalStatus !== 'success') {
|
|
291
|
+
io.write(
|
|
292
|
+
[
|
|
293
|
+
'Recovery options:',
|
|
294
|
+
` • Resume: sofia dev --session ${sessionId}`,
|
|
295
|
+
` • More iterations: sofia dev --session ${sessionId} --max-iterations 20`,
|
|
296
|
+
` • More iterations: sofia dev --session ${sessionId} --max-iterations 30`,
|
|
297
|
+
` • Start fresh: sofia dev --session ${sessionId} --force`,
|
|
298
|
+
'',
|
|
299
|
+
].join('\n'),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Exit code based on final status
|
|
305
|
+
if (result.finalStatus === 'failed') {
|
|
306
|
+
process.exitCode = 1;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct command mode (US3).
|
|
3
|
+
*
|
|
4
|
+
* Provides non-interactive and automation-friendly entry points:
|
|
5
|
+
* - `sofia workshop --session <id> --phase <phase>` jumps to a specific phase
|
|
6
|
+
* - Enforces required inputs in non-interactive mode
|
|
7
|
+
* - JSON-only stdout when --json
|
|
8
|
+
* - Retry transient failures when --retry specified
|
|
9
|
+
*/
|
|
10
|
+
import { ConversationLoop } from '../loop/conversationLoop.js';
|
|
11
|
+
import type { LoopIO } from '../loop/conversationLoop.js';
|
|
12
|
+
import type { CopilotClient } from '../shared/copilotClient.js';
|
|
13
|
+
import type { WorkshopSession, PhaseValue } from '../shared/schemas/session.js';
|
|
14
|
+
import { SessionStore } from '../sessions/sessionStore.js';
|
|
15
|
+
import { createPhaseHandler, getPhaseOrder } from '../phases/phaseHandlers.js';
|
|
16
|
+
import type { SofiaEvent } from '../shared/events.js';
|
|
17
|
+
import { classifyError, toUserMessage } from '../shared/errorClassifier.js';
|
|
18
|
+
|
|
19
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export interface DirectCommandOptions {
|
|
22
|
+
sessionId: string;
|
|
23
|
+
phase: PhaseValue;
|
|
24
|
+
store: SessionStore;
|
|
25
|
+
client: CopilotClient;
|
|
26
|
+
io: LoopIO;
|
|
27
|
+
nonInteractive?: boolean;
|
|
28
|
+
json?: boolean;
|
|
29
|
+
debug?: boolean;
|
|
30
|
+
retry?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface DirectCommandResult {
|
|
34
|
+
exitCode: number;
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Validation ───────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
const VALID_PHASES = getPhaseOrder();
|
|
41
|
+
|
|
42
|
+
function validateInputs(opts: DirectCommandOptions): DirectCommandResult | null {
|
|
43
|
+
if (!opts.sessionId) {
|
|
44
|
+
const error = 'Required: --session <id>. Provide a session ID to target.';
|
|
45
|
+
emitError(opts, error);
|
|
46
|
+
return { exitCode: 1, error };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (opts.nonInteractive && !opts.phase) {
|
|
50
|
+
const error = 'Required: --phase <phase>. In non-interactive mode, a phase must be specified.';
|
|
51
|
+
emitError(opts, error);
|
|
52
|
+
return { exitCode: 1, error };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (opts.phase && !VALID_PHASES.includes(opts.phase)) {
|
|
56
|
+
const error = `Invalid phase "${opts.phase}". Valid phases: ${VALID_PHASES.join(', ')}`;
|
|
57
|
+
emitError(opts, error);
|
|
58
|
+
return { exitCode: 1, error };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null; // validation passed
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function emitError(opts: DirectCommandOptions, message: string): void {
|
|
65
|
+
if (opts.json || opts.io.isJsonMode) {
|
|
66
|
+
// JSON mode: follow the standard `{ error: "..." }` shape used by other commands
|
|
67
|
+
opts.io.write(JSON.stringify({ error: message }) + '\n');
|
|
68
|
+
} else {
|
|
69
|
+
// Non-JSON mode: emit a human-readable error message so failures are visible
|
|
70
|
+
opts.io.write(`Error: ${message}\n`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Core execution ──────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Run a direct command: load session, execute phase, persist results.
|
|
78
|
+
*
|
|
79
|
+
* Supports retry for transient failures (connection, timeout, etc.).
|
|
80
|
+
*/
|
|
81
|
+
export async function runDirectCommand(opts: DirectCommandOptions): Promise<DirectCommandResult> {
|
|
82
|
+
// 1. Validate inputs
|
|
83
|
+
const validationError = validateInputs(opts);
|
|
84
|
+
if (validationError) return validationError;
|
|
85
|
+
|
|
86
|
+
// 2. Load session
|
|
87
|
+
let session: WorkshopSession;
|
|
88
|
+
try {
|
|
89
|
+
if (!(await opts.store.exists(opts.sessionId))) {
|
|
90
|
+
const error = `Session "${opts.sessionId}" not found.`;
|
|
91
|
+
emitError(opts, error);
|
|
92
|
+
return { exitCode: 1, error };
|
|
93
|
+
}
|
|
94
|
+
session = await opts.store.load(opts.sessionId);
|
|
95
|
+
} catch (err: unknown) {
|
|
96
|
+
const error = err instanceof Error ? err.message : 'Failed to load session';
|
|
97
|
+
emitError(opts, error);
|
|
98
|
+
return { exitCode: 1, error };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 3. Set phase
|
|
102
|
+
if (opts.phase) {
|
|
103
|
+
session.phase = opts.phase;
|
|
104
|
+
session.updatedAt = new Date().toISOString();
|
|
105
|
+
await opts.store.save(session);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 4. Run phase with retry
|
|
109
|
+
const maxAttempts = (opts.retry ?? 0) + 1;
|
|
110
|
+
let lastError: Error | undefined;
|
|
111
|
+
|
|
112
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
113
|
+
try {
|
|
114
|
+
session = await runPhase(session, opts);
|
|
115
|
+
return { exitCode: 0 };
|
|
116
|
+
} catch (err: unknown) {
|
|
117
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
118
|
+
const classification = classifyError(lastError);
|
|
119
|
+
|
|
120
|
+
if (!classification.recoverable || attempt >= maxAttempts) {
|
|
121
|
+
const error = lastError.message;
|
|
122
|
+
emitError(opts, error);
|
|
123
|
+
return { exitCode: 1, error };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Log retry attempt
|
|
127
|
+
opts.io.writeActivity(
|
|
128
|
+
`Attempt ${attempt}/${maxAttempts} failed (${classification.category}): ${toUserMessage(classification)}. Retrying...`,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Brief backoff before retry
|
|
132
|
+
await new Promise((resolve) => setTimeout(resolve, 100 * attempt));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Should not reach here, but safety net
|
|
137
|
+
const error = lastError?.message ?? 'Unknown error';
|
|
138
|
+
return { exitCode: 1, error };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── Phase runner ────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
async function runPhase(
|
|
144
|
+
session: WorkshopSession,
|
|
145
|
+
opts: DirectCommandOptions,
|
|
146
|
+
): Promise<WorkshopSession> {
|
|
147
|
+
const phase = session.phase;
|
|
148
|
+
const handler = createPhaseHandler(phase);
|
|
149
|
+
await handler._preload();
|
|
150
|
+
|
|
151
|
+
const events: SofiaEvent[] = [];
|
|
152
|
+
|
|
153
|
+
const loop = new ConversationLoop({
|
|
154
|
+
client: opts.client,
|
|
155
|
+
io: opts.io,
|
|
156
|
+
session,
|
|
157
|
+
phaseHandler: handler,
|
|
158
|
+
onEvent: (e) => {
|
|
159
|
+
events.push(e);
|
|
160
|
+
if (opts.debug && e.type === 'Activity') {
|
|
161
|
+
opts.io.writeActivity(e.message);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
onSessionUpdate: async (updatedSession) => {
|
|
165
|
+
session = updatedSession;
|
|
166
|
+
await opts.store.save(session);
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
session = await loop.run();
|
|
171
|
+
session.updatedAt = new Date().toISOString();
|
|
172
|
+
await opts.store.save(session);
|
|
173
|
+
|
|
174
|
+
// In JSON mode, emit result summary
|
|
175
|
+
if (opts.json || opts.io.isJsonMode) {
|
|
176
|
+
opts.io.write(
|
|
177
|
+
JSON.stringify({
|
|
178
|
+
sessionId: session.sessionId,
|
|
179
|
+
phase: session.phase,
|
|
180
|
+
status: session.status,
|
|
181
|
+
updatedAt: session.updatedAt,
|
|
182
|
+
}) + '\n',
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Decision gate
|
|
187
|
+
const gateResult = await opts.io.showDecisionGate(phase);
|
|
188
|
+
if (gateResult.choice === 'exit') {
|
|
189
|
+
session.status = 'Paused';
|
|
190
|
+
session.updatedAt = new Date().toISOString();
|
|
191
|
+
await opts.store.save(session);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return session;
|
|
195
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads a `.env` file into `process.env` without overwriting existing values.
|
|
3
|
+
*
|
|
4
|
+
* Called at CLI startup so that variables written by `infra/deploy.sh`
|
|
5
|
+
* (e.g. `FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL_DEPLOYMENT_NAME`) are
|
|
6
|
+
* available to the application automatically.
|
|
7
|
+
*/
|
|
8
|
+
import { config } from 'dotenv';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load environment variables from the given `.env` file path.
|
|
12
|
+
* Variables already present in `process.env` are **not** overwritten.
|
|
13
|
+
* If the file does not exist the call is a silent no-op.
|
|
14
|
+
*/
|
|
15
|
+
export function loadEnvFile(envPath: string): void {
|
|
16
|
+
config({ path: envPath, override: false, quiet: true });
|
|
17
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export command handler.
|
|
3
|
+
*
|
|
4
|
+
* Implements `sofia export` — exports workshop artifacts for a session.
|
|
5
|
+
* Generates Markdown files per phase and a summary.json.
|
|
6
|
+
*/
|
|
7
|
+
import { createDefaultStore } from '../sessions/sessionStore.js';
|
|
8
|
+
import { exportSession } from '../sessions/exportWriter.js';
|
|
9
|
+
import { ensureExportDir } from '../sessions/exportPaths.js';
|
|
10
|
+
|
|
11
|
+
export interface ExportCommandOptions {
|
|
12
|
+
session?: string;
|
|
13
|
+
json?: boolean;
|
|
14
|
+
debug?: boolean;
|
|
15
|
+
logFile?: string;
|
|
16
|
+
nonInteractive?: boolean;
|
|
17
|
+
output?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function exportCommand(opts: ExportCommandOptions): Promise<void> {
|
|
21
|
+
if (!opts.session) {
|
|
22
|
+
if (opts.json) {
|
|
23
|
+
process.stdout.write(JSON.stringify({ error: 'No session specified. Use --session <id>.' }) + '\n');
|
|
24
|
+
} else {
|
|
25
|
+
console.error('Error: No session specified. Use --session <id>.');
|
|
26
|
+
}
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const store = createDefaultStore();
|
|
32
|
+
|
|
33
|
+
if (!(await store.exists(opts.session))) {
|
|
34
|
+
const msg = `Session "${opts.session}" not found.`;
|
|
35
|
+
if (opts.json) {
|
|
36
|
+
process.stdout.write(JSON.stringify({ error: msg }) + '\n');
|
|
37
|
+
} else {
|
|
38
|
+
console.error(`Error: ${msg}`);
|
|
39
|
+
}
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const session = await store.load(opts.session);
|
|
45
|
+
const exportDir = opts.output
|
|
46
|
+
? opts.output
|
|
47
|
+
: await ensureExportDir(opts.session);
|
|
48
|
+
|
|
49
|
+
const result = await exportSession(session, exportDir);
|
|
50
|
+
|
|
51
|
+
if (opts.json) {
|
|
52
|
+
process.stdout.write(JSON.stringify({
|
|
53
|
+
sessionId: session.sessionId,
|
|
54
|
+
exported: true,
|
|
55
|
+
exportDir: result.exportDir,
|
|
56
|
+
files: result.files,
|
|
57
|
+
}) + '\n');
|
|
58
|
+
} else {
|
|
59
|
+
console.log(`Exported session "${session.sessionId}" to ${result.exportDir}`);
|
|
60
|
+
console.log(`Generated ${result.files.length} file(s):`);
|
|
61
|
+
for (const file of result.files) {
|
|
62
|
+
console.log(` - ${file.path} (${file.type})`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|