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,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status command handler.
|
|
3
|
+
*
|
|
4
|
+
* Implements `sofia status` — displays session status and next expected action.
|
|
5
|
+
* Supports both TTY (human-readable) and JSON output modes.
|
|
6
|
+
*/
|
|
7
|
+
import { createDefaultStore } from '../sessions/sessionStore.js';
|
|
8
|
+
import { renderTable } from '../shared/tableRenderer.js';
|
|
9
|
+
import { getNextPhase } from '../phases/phaseHandlers.js';
|
|
10
|
+
export async function statusCommand(opts) {
|
|
11
|
+
if (!opts.session) {
|
|
12
|
+
// Try to list all sessions
|
|
13
|
+
const store = createDefaultStore();
|
|
14
|
+
const sessions = await store.list();
|
|
15
|
+
if (sessions.length === 0) {
|
|
16
|
+
if (opts.json) {
|
|
17
|
+
process.stdout.write(JSON.stringify({ error: 'No sessions found. Start one with: sofia workshop --new-session' }) + '\n');
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.error('No sessions found. Start one with: sofia workshop --new-session');
|
|
21
|
+
}
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Show all sessions
|
|
26
|
+
if (opts.json) {
|
|
27
|
+
const statuses = [];
|
|
28
|
+
for (const id of sessions) {
|
|
29
|
+
try {
|
|
30
|
+
const s = await store.load(id);
|
|
31
|
+
statuses.push({
|
|
32
|
+
sessionId: s.sessionId,
|
|
33
|
+
name: s.name,
|
|
34
|
+
phase: s.phase,
|
|
35
|
+
status: s.status,
|
|
36
|
+
updatedAt: s.updatedAt,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
statuses.push({ sessionId: id, error: 'Failed to load' });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
process.stdout.write(JSON.stringify({ sessions: statuses }) + '\n');
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const rows = [];
|
|
47
|
+
for (const id of sessions) {
|
|
48
|
+
try {
|
|
49
|
+
const s = await store.load(id);
|
|
50
|
+
rows.push([s.sessionId.slice(0, 8), s.name ?? '', s.phase, s.status, s.updatedAt]);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
rows.push([id.slice(0, 8), '', '?', 'Error', '?']);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
console.log(renderTable({
|
|
57
|
+
head: ['Session', 'Name', 'Phase', 'Status', 'Updated'],
|
|
58
|
+
rows,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const store = createDefaultStore();
|
|
64
|
+
if (!(await store.exists(opts.session))) {
|
|
65
|
+
if (opts.json) {
|
|
66
|
+
process.stdout.write(JSON.stringify({ error: `Session "${opts.session}" not found.` }) + '\n');
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.error(`Error: Session "${opts.session}" not found.`);
|
|
70
|
+
}
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const session = await store.load(opts.session);
|
|
75
|
+
if (opts.json) {
|
|
76
|
+
const status = {
|
|
77
|
+
sessionId: session.sessionId,
|
|
78
|
+
phase: session.phase,
|
|
79
|
+
status: session.status,
|
|
80
|
+
nextPhase: getNextPhase(session.phase),
|
|
81
|
+
turns: session.turns?.length ?? 0,
|
|
82
|
+
participants: session.participants.length,
|
|
83
|
+
createdAt: session.createdAt,
|
|
84
|
+
updatedAt: session.updatedAt,
|
|
85
|
+
hasBusinessContext: !!session.businessContext,
|
|
86
|
+
hasWorkflow: !!session.workflow,
|
|
87
|
+
ideaCount: session.ideas?.length ?? 0,
|
|
88
|
+
hasSelection: !!session.selection,
|
|
89
|
+
hasPlan: !!session.plan,
|
|
90
|
+
};
|
|
91
|
+
if (session.name)
|
|
92
|
+
status.name = session.name;
|
|
93
|
+
process.stdout.write(JSON.stringify(status) + '\n');
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const next = getNextPhase(session.phase);
|
|
97
|
+
console.log(`\nSession: ${session.sessionId}`);
|
|
98
|
+
if (session.name) {
|
|
99
|
+
console.log(`Name: ${session.name}`);
|
|
100
|
+
}
|
|
101
|
+
console.log(`Phase: ${session.phase}`);
|
|
102
|
+
console.log(`Status: ${session.status}`);
|
|
103
|
+
console.log(`Turns: ${session.turns?.length ?? 0}`);
|
|
104
|
+
console.log(`Updated: ${session.updatedAt}`);
|
|
105
|
+
if (next) {
|
|
106
|
+
console.log(`Next: ${next}`);
|
|
107
|
+
}
|
|
108
|
+
console.log('');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workshop command handler.
|
|
3
|
+
*
|
|
4
|
+
* Implements `sofia workshop` — the main interactive workshop command
|
|
5
|
+
* with New Session, Resume Session, Status, and Export menu options.
|
|
6
|
+
* Drives phase-by-phase conversation with decision gates.
|
|
7
|
+
*/
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { ConversationLoop } from '../loop/conversationLoop.js';
|
|
10
|
+
import { createCopilotClient } from '../shared/copilotClient.js';
|
|
11
|
+
import { ActivitySpinner } from '../shared/activitySpinner.js';
|
|
12
|
+
import { getLogger } from '../logging/logger.js';
|
|
13
|
+
import { createDefaultStore } from '../sessions/sessionStore.js';
|
|
14
|
+
import { createLoopIO } from './ioContext.js';
|
|
15
|
+
import { createPhaseHandler, getPhaseOrder, getNextPhase } from '../phases/phaseHandlers.js';
|
|
16
|
+
import { renderMarkdown } from '../shared/markdownRenderer.js';
|
|
17
|
+
import { destroyWebSearchSession, isWebSearchConfigured, createWebSearchTool } from '../mcp/webSearch.js';
|
|
18
|
+
import { loadEnvFile } from './envLoader.js';
|
|
19
|
+
import { loadMcpConfig, McpManager } from '../mcp/mcpManager.js';
|
|
20
|
+
import { RalphLoop } from '../develop/ralphLoop.js';
|
|
21
|
+
import { deriveCheckpointState } from '../develop/checkpointState.js';
|
|
22
|
+
import { createDefaultRegistry, selectTemplate } from '../develop/templateRegistry.js';
|
|
23
|
+
/**
|
|
24
|
+
* Generate a timestamp-based session ID for human-friendly filenames.
|
|
25
|
+
* Format: YYYY-MM-DD_HHmmss (e.g. "2026-02-27_143052")
|
|
26
|
+
*/
|
|
27
|
+
function generateSessionId() {
|
|
28
|
+
const d = new Date();
|
|
29
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
30
|
+
return [
|
|
31
|
+
d.getFullYear(),
|
|
32
|
+
'-',
|
|
33
|
+
pad(d.getMonth() + 1),
|
|
34
|
+
'-',
|
|
35
|
+
pad(d.getDate()),
|
|
36
|
+
'_',
|
|
37
|
+
pad(d.getHours()),
|
|
38
|
+
pad(d.getMinutes()),
|
|
39
|
+
pad(d.getSeconds()),
|
|
40
|
+
].join('');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Format a session identifier for display.
|
|
44
|
+
* Shows "name (id)" when a name exists, otherwise just the id.
|
|
45
|
+
*/
|
|
46
|
+
function sessionDisplayName(session) {
|
|
47
|
+
return session.name ? `${session.name} (${session.sessionId})` : session.sessionId;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create a new empty workshop session.
|
|
51
|
+
*/
|
|
52
|
+
function createNewSession() {
|
|
53
|
+
const now = new Date().toISOString();
|
|
54
|
+
return {
|
|
55
|
+
sessionId: generateSessionId(),
|
|
56
|
+
schemaVersion: '1.0.0',
|
|
57
|
+
createdAt: now,
|
|
58
|
+
updatedAt: now,
|
|
59
|
+
phase: 'Discover',
|
|
60
|
+
status: 'Active',
|
|
61
|
+
participants: [],
|
|
62
|
+
artifacts: { generatedFiles: [] },
|
|
63
|
+
turns: [],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Show the main workshop menu and return user choice.
|
|
68
|
+
*/
|
|
69
|
+
async function showMainMenu(io, existingSessions) {
|
|
70
|
+
const hasExisting = existingSessions.length > 0;
|
|
71
|
+
const menuText = [
|
|
72
|
+
'\n# sofIA — AI Discovery Workshop\n',
|
|
73
|
+
'Choose an option:\n',
|
|
74
|
+
' 1. Start a new workshop session',
|
|
75
|
+
hasExisting ? ' 2. Resume an existing session' : '',
|
|
76
|
+
' 3. Exit',
|
|
77
|
+
'',
|
|
78
|
+
]
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.join('\n');
|
|
81
|
+
io.write(renderMarkdown(menuText, { isTTY: io.isTTY }));
|
|
82
|
+
const answer = await io.readInput('Choose [1-3]: ');
|
|
83
|
+
if (answer === null || answer.trim() === '3')
|
|
84
|
+
return 'exit';
|
|
85
|
+
if (answer.trim() === '2' && hasExisting)
|
|
86
|
+
return 'resume';
|
|
87
|
+
return 'new';
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Run the workshop flow for a session through its phases.
|
|
91
|
+
*/
|
|
92
|
+
async function runWorkshop(session, client, io, store, options, handlerConfig) {
|
|
93
|
+
const logger = getLogger();
|
|
94
|
+
const phaseOrder = getPhaseOrder();
|
|
95
|
+
let currentPhaseIdx = phaseOrder.indexOf(session.phase);
|
|
96
|
+
if (currentPhaseIdx === -1)
|
|
97
|
+
currentPhaseIdx = 0;
|
|
98
|
+
// If a specific phase was requested, jump to it
|
|
99
|
+
if (options.phase) {
|
|
100
|
+
const requestedIdx = phaseOrder.indexOf(options.phase);
|
|
101
|
+
if (requestedIdx !== -1) {
|
|
102
|
+
currentPhaseIdx = requestedIdx;
|
|
103
|
+
session.phase = phaseOrder[currentPhaseIdx];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
while (currentPhaseIdx < phaseOrder.length) {
|
|
107
|
+
const phase = phaseOrder[currentPhaseIdx];
|
|
108
|
+
session.phase = phase;
|
|
109
|
+
session.updatedAt = new Date().toISOString();
|
|
110
|
+
await store.save(session);
|
|
111
|
+
io.write(renderMarkdown(`\n## Phase: ${phase}\n`, { isTTY: io.isTTY }));
|
|
112
|
+
// Create and preload the phase handler
|
|
113
|
+
const handler = createPhaseHandler(phase, handlerConfig);
|
|
114
|
+
await handler._preload();
|
|
115
|
+
// Generate initial message for auto-start
|
|
116
|
+
const initialMessage = handler.getInitialMessage?.(session);
|
|
117
|
+
const events = [];
|
|
118
|
+
// Create activity spinner for visual feedback
|
|
119
|
+
const spinner = new ActivitySpinner({
|
|
120
|
+
isTTY: io.isTTY,
|
|
121
|
+
isJsonMode: io.isJsonMode,
|
|
122
|
+
debugMode: options.debug,
|
|
123
|
+
});
|
|
124
|
+
const loop = new ConversationLoop({
|
|
125
|
+
client,
|
|
126
|
+
io,
|
|
127
|
+
session,
|
|
128
|
+
phaseHandler: handler,
|
|
129
|
+
initialMessage,
|
|
130
|
+
spinner,
|
|
131
|
+
onEvent: (e) => {
|
|
132
|
+
events.push(e);
|
|
133
|
+
if (options.debug && e.type === 'Activity') {
|
|
134
|
+
io.writeActivity(e.message);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
onSessionUpdate: async (updatedSession) => {
|
|
138
|
+
session = updatedSession;
|
|
139
|
+
await store.save(session);
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
// Run the conversation loop for this phase
|
|
143
|
+
session = await loop.run();
|
|
144
|
+
session.updatedAt = new Date().toISOString();
|
|
145
|
+
await store.save(session);
|
|
146
|
+
// Decision gate
|
|
147
|
+
const gateResult = await io.showDecisionGate(phase);
|
|
148
|
+
switch (gateResult.choice) {
|
|
149
|
+
case 'continue': {
|
|
150
|
+
const next = getNextPhase(phase);
|
|
151
|
+
if (next) {
|
|
152
|
+
// FR-020: Show transition guidance when Plan → Develop
|
|
153
|
+
if (next === 'Develop') {
|
|
154
|
+
io.write(renderMarkdown(`\n### Starting PoC Generation\n\n` +
|
|
155
|
+
`The Plan phase is complete. Now generating proof-of-concept code...\n`, { isTTY: io.isTTY }));
|
|
156
|
+
// FR-021: Always run PoC generation after Plan phase
|
|
157
|
+
try {
|
|
158
|
+
const outputDir = join(process.cwd(), '..', 'poc', session.sessionId);
|
|
159
|
+
const checkpoint = deriveCheckpointState(session, outputDir);
|
|
160
|
+
const templateEntry = selectTemplate(createDefaultRegistry(), session.plan?.architectureNotes, session.plan?.dependencies);
|
|
161
|
+
const ralphLoop = new RalphLoop({
|
|
162
|
+
client,
|
|
163
|
+
io,
|
|
164
|
+
session,
|
|
165
|
+
spinner,
|
|
166
|
+
maxIterations: 20,
|
|
167
|
+
outputDir,
|
|
168
|
+
onSessionUpdate: async (updated) => {
|
|
169
|
+
session = updated;
|
|
170
|
+
await store.save(session);
|
|
171
|
+
},
|
|
172
|
+
onEvent: (event) => {
|
|
173
|
+
// Log PoC generation events if debug is enabled
|
|
174
|
+
if (options.debug) {
|
|
175
|
+
logger.debug('RalphLoop event', { event });
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
checkpoint,
|
|
179
|
+
templateEntry,
|
|
180
|
+
});
|
|
181
|
+
const result = await ralphLoop.run();
|
|
182
|
+
session = result.session;
|
|
183
|
+
if (result.finalStatus === 'success') {
|
|
184
|
+
io.write(renderMarkdown(`\n✅ PoC generated successfully in ${result.outputDir}\n\n` +
|
|
185
|
+
`Next steps:\n` +
|
|
186
|
+
`- Review the generated code\n` +
|
|
187
|
+
`- Run tests: \`cd ${result.outputDir} && npm test\`\n` +
|
|
188
|
+
`- Continue developing: \`sofia dev --session ${session.sessionId}\`\n`, { isTTY: io.isTTY }));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
io.write(renderMarkdown(`\n⚠️ PoC generation incomplete (${result.terminationReason})\n\n` +
|
|
192
|
+
`The workshop can continue. To retry or resume PoC development:\n` +
|
|
193
|
+
`\`\`\`\n` +
|
|
194
|
+
`sofia dev --session ${session.sessionId}\n` +
|
|
195
|
+
`\`\`\`\n`, { isTTY: io.isTTY }));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
200
|
+
io.write(renderMarkdown(`\n⚠️ PoC generation failed: ${msg}\n\n` +
|
|
201
|
+
`The workshop can continue. To retry:\n` +
|
|
202
|
+
`\`\`\`\n` +
|
|
203
|
+
`sofia dev --session ${session.sessionId}\n` +
|
|
204
|
+
`\`\`\`\n`, { isTTY: io.isTTY }));
|
|
205
|
+
}
|
|
206
|
+
// Continue to next phase regardless of PoC outcome
|
|
207
|
+
}
|
|
208
|
+
currentPhaseIdx = phaseOrder.indexOf(next);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
// Mark session as completed
|
|
212
|
+
session.phase = 'Complete';
|
|
213
|
+
session.status = 'Completed';
|
|
214
|
+
session.updatedAt = new Date().toISOString();
|
|
215
|
+
await store.save(session);
|
|
216
|
+
io.write(renderMarkdown('\n## Workshop Complete!\n\nUse `sofia export` to generate artifacts.\n', {
|
|
217
|
+
isTTY: io.isTTY,
|
|
218
|
+
}));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case 'refine':
|
|
224
|
+
// Stay on the same phase
|
|
225
|
+
break;
|
|
226
|
+
case 'menu':
|
|
227
|
+
return; // Return to main menu
|
|
228
|
+
case 'exit':
|
|
229
|
+
session.status = 'Paused';
|
|
230
|
+
session.updatedAt = new Date().toISOString();
|
|
231
|
+
await store.save(session);
|
|
232
|
+
io.write(renderMarkdown(`\nSession **${sessionDisplayName(session)}** paused. Resume later with \`sofia workshop --session ${session.sessionId}\`.\n`, { isTTY: io.isTTY }));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
export async function workshopCommand(opts) {
|
|
238
|
+
try {
|
|
239
|
+
await workshopCommandInner(opts);
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
// Clean up ephemeral web search agent on workshop exit (FR-015)
|
|
243
|
+
await destroyWebSearchSession();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async function workshopCommandInner(opts) {
|
|
247
|
+
// FR-010: Load .env before any env var checks (e.g., isWebSearchConfigured)
|
|
248
|
+
loadEnvFile(join(process.cwd(), '.env'));
|
|
249
|
+
const store = createDefaultStore();
|
|
250
|
+
const io = createLoopIO({
|
|
251
|
+
json: opts.json,
|
|
252
|
+
nonInteractive: opts.nonInteractive,
|
|
253
|
+
debug: opts.debug,
|
|
254
|
+
});
|
|
255
|
+
// Get Copilot client
|
|
256
|
+
let client;
|
|
257
|
+
try {
|
|
258
|
+
client = await createCopilotClient();
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
const logger = getLogger();
|
|
262
|
+
logger.error({ err }, 'Failed to create Copilot client — cannot start workshop');
|
|
263
|
+
const msg = err instanceof Error ? err.message : 'Unknown error creating Copilot client';
|
|
264
|
+
if (opts.json) {
|
|
265
|
+
process.stdout.write(JSON.stringify({ error: msg }) + '\n');
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
console.error(`Error: ${msg}`);
|
|
269
|
+
}
|
|
270
|
+
process.exitCode = 1;
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
// FR-011: Create McpManager from .vscode/mcp.json
|
|
274
|
+
let mcpManager;
|
|
275
|
+
try {
|
|
276
|
+
const mcpConfigPath = join(process.cwd(), '.vscode', 'mcp.json');
|
|
277
|
+
const mcpConfig = await loadMcpConfig(mcpConfigPath);
|
|
278
|
+
mcpManager = new McpManager(mcpConfig);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// MCP not configured — proceed without it
|
|
282
|
+
}
|
|
283
|
+
// FR-012: Create WebSearchClient when configured
|
|
284
|
+
let webSearchClient;
|
|
285
|
+
if (isWebSearchConfigured()) {
|
|
286
|
+
const config = {
|
|
287
|
+
projectEndpoint: process.env.FOUNDRY_PROJECT_ENDPOINT,
|
|
288
|
+
modelDeploymentName: process.env.FOUNDRY_MODEL_DEPLOYMENT_NAME,
|
|
289
|
+
};
|
|
290
|
+
const searchFn = createWebSearchTool(config);
|
|
291
|
+
webSearchClient = { search: searchFn };
|
|
292
|
+
}
|
|
293
|
+
// Build handler config with MCP + web search
|
|
294
|
+
const handlerConfig = {
|
|
295
|
+
discover: {
|
|
296
|
+
io,
|
|
297
|
+
mcpManager,
|
|
298
|
+
webSearchClient,
|
|
299
|
+
},
|
|
300
|
+
mcpManager,
|
|
301
|
+
webSearchClient,
|
|
302
|
+
};
|
|
303
|
+
// Direct session resumption
|
|
304
|
+
if (opts.session) {
|
|
305
|
+
if (await store.exists(opts.session)) {
|
|
306
|
+
const session = await store.load(opts.session);
|
|
307
|
+
io.write(renderMarkdown(`\nResuming session: **${sessionDisplayName(session)}**\n`, {
|
|
308
|
+
isTTY: io.isTTY,
|
|
309
|
+
}));
|
|
310
|
+
await runWorkshop(session, client, io, store, opts, handlerConfig);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
const msg = `Session "${opts.session}" not found.`;
|
|
314
|
+
if (opts.json) {
|
|
315
|
+
process.stdout.write(JSON.stringify({ error: msg }) + '\n');
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
console.error(`Error: ${msg}`);
|
|
319
|
+
}
|
|
320
|
+
process.exitCode = 1;
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
// New session flag
|
|
325
|
+
if (opts.newSession) {
|
|
326
|
+
const session = createNewSession();
|
|
327
|
+
await store.save(session);
|
|
328
|
+
if (opts.json) {
|
|
329
|
+
process.stdout.write(JSON.stringify({ sessionId: session.sessionId, phase: session.phase }) + '\n');
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
io.write(renderMarkdown(`\nNew session created: **${session.sessionId}**\n`, { isTTY: io.isTTY }));
|
|
333
|
+
}
|
|
334
|
+
await runWorkshop(session, client, io, store, opts, handlerConfig);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// Interactive menu
|
|
338
|
+
if (opts.nonInteractive) {
|
|
339
|
+
if (opts.json) {
|
|
340
|
+
process.stdout.write(JSON.stringify({ error: 'Non-interactive mode requires --session or --new-session.' }) +
|
|
341
|
+
'\n');
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
console.error('Error: Non-interactive mode requires --session or --new-session.');
|
|
345
|
+
}
|
|
346
|
+
process.exitCode = 1;
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const existingSessions = await store.list();
|
|
350
|
+
const choice = await showMainMenu(io, existingSessions);
|
|
351
|
+
switch (choice) {
|
|
352
|
+
case 'new': {
|
|
353
|
+
const session = createNewSession();
|
|
354
|
+
await store.save(session);
|
|
355
|
+
io.write(renderMarkdown(`\nNew session: **${session.sessionId}**\n`, { isTTY: io.isTTY }));
|
|
356
|
+
await runWorkshop(session, client, io, store, opts, handlerConfig);
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
case 'resume': {
|
|
360
|
+
const sessions = await store.list();
|
|
361
|
+
if (sessions.length === 0) {
|
|
362
|
+
io.write('No existing sessions found.\n');
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
const sessionEntries = (await Promise.all(sessions.map(async (id) => {
|
|
366
|
+
try {
|
|
367
|
+
const session = await store.load(id);
|
|
368
|
+
return { id, display: sessionDisplayName(session), session };
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return { id, display: id, session: undefined };
|
|
372
|
+
}
|
|
373
|
+
}))).filter((entry) => entry.display !== entry.id);
|
|
374
|
+
// Show session list
|
|
375
|
+
io.write('\nAvailable sessions:\n');
|
|
376
|
+
sessionEntries.forEach((entry, idx) => {
|
|
377
|
+
io.write(` ${idx + 1}. ${entry.display}\n`);
|
|
378
|
+
});
|
|
379
|
+
const answer = await io.readInput('Choose session number: ');
|
|
380
|
+
if (answer === null)
|
|
381
|
+
break;
|
|
382
|
+
const idx = parseInt(answer.trim(), 10) - 1;
|
|
383
|
+
if (idx >= 0 && idx < sessionEntries.length) {
|
|
384
|
+
const entry = sessionEntries[idx];
|
|
385
|
+
const session = entry.session ?? (await store.load(entry.id));
|
|
386
|
+
io.write(renderMarkdown(`\nResuming session: **${sessionDisplayName(session)}**\n`, {
|
|
387
|
+
isTTY: io.isTTY,
|
|
388
|
+
}));
|
|
389
|
+
await runWorkshop(session, client, io, store, opts, handlerConfig);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
io.write('Invalid selection.\n');
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
case 'exit':
|
|
397
|
+
io.write('Goodbye!\n');
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkpoint State Derivation.
|
|
3
|
+
*
|
|
4
|
+
* Derives resume behavior from existing session state. This is a runtime-only
|
|
5
|
+
* convenience type — it is NOT persisted to the session.
|
|
6
|
+
*
|
|
7
|
+
* Contract: specs/004-dev-resume-hardening/contracts/cli.md
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
// ── Derivation ───────────────────────────────────────────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* Derive the checkpoint state from an existing session and output directory.
|
|
14
|
+
*
|
|
15
|
+
* Used by `developCommand` and `RalphLoop` to decide resume behavior.
|
|
16
|
+
*/
|
|
17
|
+
export function deriveCheckpointState(session, outputDir) {
|
|
18
|
+
const poc = session.poc;
|
|
19
|
+
// No prior run
|
|
20
|
+
if (!poc || !Array.isArray(poc.iterations) || poc.iterations.length === 0) {
|
|
21
|
+
return {
|
|
22
|
+
hasPriorRun: false,
|
|
23
|
+
completedIterations: 0,
|
|
24
|
+
lastIterationIncomplete: false,
|
|
25
|
+
resumeFromIteration: 1,
|
|
26
|
+
canSkipScaffold: false,
|
|
27
|
+
priorFinalStatus: undefined,
|
|
28
|
+
priorIterations: [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Validate iteration entries — if any are corrupt, fall back to fresh run
|
|
32
|
+
if (!validateIterations(poc.iterations)) {
|
|
33
|
+
return {
|
|
34
|
+
hasPriorRun: false,
|
|
35
|
+
completedIterations: 0,
|
|
36
|
+
lastIterationIncomplete: false,
|
|
37
|
+
resumeFromIteration: 1,
|
|
38
|
+
canSkipScaffold: false,
|
|
39
|
+
priorFinalStatus: undefined,
|
|
40
|
+
priorIterations: [],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const lastIter = poc.iterations[poc.iterations.length - 1];
|
|
44
|
+
const lastIncomplete = !lastIter.testResults && lastIter.outcome !== 'scaffold';
|
|
45
|
+
const completedIters = lastIncomplete ? poc.iterations.slice(0, -1) : poc.iterations;
|
|
46
|
+
const metadataPath = join(outputDir, '.sofia-metadata.json');
|
|
47
|
+
const metadataExists = existsSync(metadataPath);
|
|
48
|
+
// Validate metadata integrity: sessionId must match
|
|
49
|
+
let canSkipScaffold = metadataExists;
|
|
50
|
+
if (metadataExists) {
|
|
51
|
+
try {
|
|
52
|
+
const raw = readFileSync(metadataPath, 'utf-8');
|
|
53
|
+
const metadata = JSON.parse(raw);
|
|
54
|
+
if (metadata.sessionId !== session.sessionId) {
|
|
55
|
+
canSkipScaffold = false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
canSkipScaffold = false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
hasPriorRun: true,
|
|
64
|
+
completedIterations: completedIters.length,
|
|
65
|
+
lastIterationIncomplete: lastIncomplete,
|
|
66
|
+
resumeFromIteration: completedIters.length + 1,
|
|
67
|
+
canSkipScaffold,
|
|
68
|
+
priorFinalStatus: poc.finalStatus,
|
|
69
|
+
priorIterations: completedIters,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// ── Validation ───────────────────────────────────────────────────────────────
|
|
73
|
+
/**
|
|
74
|
+
* Validate iteration entries have required fields and valid shapes.
|
|
75
|
+
* Returns false if any iteration is corrupt.
|
|
76
|
+
*/
|
|
77
|
+
function validateIterations(iterations) {
|
|
78
|
+
for (const iter of iterations) {
|
|
79
|
+
if (typeof iter.iteration !== 'number' ||
|
|
80
|
+
typeof iter.startedAt !== 'string' ||
|
|
81
|
+
!iter.startedAt) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|