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,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export writer.
|
|
3
|
+
*
|
|
4
|
+
* Generates Markdown artifacts per phase and summary.json
|
|
5
|
+
* under the export directory for a session.
|
|
6
|
+
*
|
|
7
|
+
* Contract: specs/001-cli-workshop-rebuild/contracts/export-summary-json.md
|
|
8
|
+
*/
|
|
9
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
import type { WorkshopSession } from '../shared/schemas/session.js';
|
|
13
|
+
|
|
14
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export interface ExportFile {
|
|
17
|
+
path: string; // Relative to export directory
|
|
18
|
+
type: string; // 'markdown' | 'json'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ExportResult {
|
|
22
|
+
exportDir: string;
|
|
23
|
+
files: ExportFile[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ExportSummary {
|
|
27
|
+
sessionId: string;
|
|
28
|
+
exportedAt: string;
|
|
29
|
+
phase: string;
|
|
30
|
+
status: string;
|
|
31
|
+
files: ExportFile[];
|
|
32
|
+
highlights?: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Phase Markdown Generators ────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
function generateDiscoverMarkdown(session: WorkshopSession): string | null {
|
|
38
|
+
const lines: string[] = ['# Discover Phase\n'];
|
|
39
|
+
|
|
40
|
+
if (session.businessContext) {
|
|
41
|
+
lines.push('## Business Context\n');
|
|
42
|
+
lines.push(`**Business**: ${session.businessContext.businessDescription}\n`);
|
|
43
|
+
lines.push('### Challenges\n');
|
|
44
|
+
for (const c of session.businessContext.challenges) {
|
|
45
|
+
lines.push(`- ${c}`);
|
|
46
|
+
}
|
|
47
|
+
lines.push('');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (session.workflow) {
|
|
51
|
+
lines.push('## Workflow\n');
|
|
52
|
+
lines.push('### Activities\n');
|
|
53
|
+
for (const act of session.workflow.activities) {
|
|
54
|
+
lines.push(`- **${act.name}** (${act.id})`);
|
|
55
|
+
}
|
|
56
|
+
lines.push('');
|
|
57
|
+
if (session.workflow.edges.length > 0) {
|
|
58
|
+
lines.push('### Connections\n');
|
|
59
|
+
for (const edge of session.workflow.edges) {
|
|
60
|
+
lines.push(`- ${edge.fromStepId} → ${edge.toStepId}`);
|
|
61
|
+
}
|
|
62
|
+
lines.push('');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Include conversation turns for this phase
|
|
67
|
+
const discoverTurns = session.turns?.filter((t) => t.phase === 'Discover') ?? [];
|
|
68
|
+
if (discoverTurns.length > 0) {
|
|
69
|
+
lines.push('## Conversation\n');
|
|
70
|
+
for (const turn of discoverTurns) {
|
|
71
|
+
lines.push(`**${turn.role}**: ${turn.content}\n`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return lines.length > 1 ? lines.join('\n') : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function generateIdeateMarkdown(session: WorkshopSession): string | null {
|
|
79
|
+
const lines: string[] = ['# Ideate Phase\n'];
|
|
80
|
+
|
|
81
|
+
// FR-020: Render structured data if available
|
|
82
|
+
if (session.ideas?.length) {
|
|
83
|
+
lines.push('## Ideas\n');
|
|
84
|
+
for (const idea of session.ideas) {
|
|
85
|
+
lines.push(`### ${idea.title}\n`);
|
|
86
|
+
lines.push(`${idea.description}\n`);
|
|
87
|
+
if (idea.workflowStepIds.length > 0) {
|
|
88
|
+
lines.push(`**Workflow steps**: ${idea.workflowStepIds.join(', ')}\n`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// FR-022: Always include conversation turns if they exist
|
|
94
|
+
const ideateTurns = session.turns?.filter((t) => t.phase === 'Ideate') ?? [];
|
|
95
|
+
if (ideateTurns.length > 0) {
|
|
96
|
+
lines.push('## Conversation\n');
|
|
97
|
+
for (const turn of ideateTurns) {
|
|
98
|
+
lines.push(`**${turn.role}**: ${turn.content}\n`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return lines.length > 1 ? lines.join('\n') : null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function generateDesignMarkdown(session: WorkshopSession): string | null {
|
|
106
|
+
const lines: string[] = ['# Design Phase\n'];
|
|
107
|
+
|
|
108
|
+
// FR-020: Render structured data if available
|
|
109
|
+
if (session.evaluation) {
|
|
110
|
+
lines.push(`## Evaluation\n`);
|
|
111
|
+
lines.push(`**Method**: ${session.evaluation.method}\n`);
|
|
112
|
+
lines.push('### Evaluated Ideas\n');
|
|
113
|
+
for (const eval_ of session.evaluation.ideas) {
|
|
114
|
+
lines.push(
|
|
115
|
+
`- **${eval_.ideaId}**: Feasibility=${eval_.feasibility ?? 'N/A'}, Value=${eval_.value ?? 'N/A'}`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
lines.push('');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// FR-022: Always include conversation turns if they exist
|
|
122
|
+
const designTurns = session.turns?.filter((t) => t.phase === 'Design') ?? [];
|
|
123
|
+
if (designTurns.length > 0) {
|
|
124
|
+
lines.push('## Conversation\n');
|
|
125
|
+
for (const turn of designTurns) {
|
|
126
|
+
lines.push(`**${turn.role}**: ${turn.content}\n`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return lines.length > 1 ? lines.join('\n') : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function generateSelectMarkdown(session: WorkshopSession): string | null {
|
|
134
|
+
const lines: string[] = ['# Select Phase\n'];
|
|
135
|
+
|
|
136
|
+
// FR-020: Render structured data if available
|
|
137
|
+
if (session.selection) {
|
|
138
|
+
lines.push(`## Selection\n`);
|
|
139
|
+
lines.push(`**Selected Idea**: ${session.selection.ideaId}\n`);
|
|
140
|
+
lines.push(`**Rationale**: ${session.selection.selectionRationale}\n`);
|
|
141
|
+
lines.push(`**Confirmed**: ${session.selection.confirmedByUser ? 'Yes' : 'No'}\n`);
|
|
142
|
+
if (session.selection.confirmedAt) {
|
|
143
|
+
lines.push(`**Confirmed At**: ${session.selection.confirmedAt}\n`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// FR-022: Always include conversation turns if they exist
|
|
148
|
+
const selectTurns = session.turns?.filter((t) => t.phase === 'Select') ?? [];
|
|
149
|
+
if (selectTurns.length > 0) {
|
|
150
|
+
lines.push('## Conversation\n');
|
|
151
|
+
for (const turn of selectTurns) {
|
|
152
|
+
lines.push(`**${turn.role}**: ${turn.content}\n`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return lines.length > 1 ? lines.join('\n') : null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function generatePlanMarkdown(session: WorkshopSession): string | null {
|
|
160
|
+
const lines: string[] = ['# Plan Phase\n'];
|
|
161
|
+
|
|
162
|
+
// FR-020: Render structured data if available
|
|
163
|
+
if (session.plan?.milestones?.length) {
|
|
164
|
+
lines.push('## Milestones\n');
|
|
165
|
+
for (const m of session.plan.milestones) {
|
|
166
|
+
lines.push(`### ${m.title}\n`);
|
|
167
|
+
for (const item of m.items) {
|
|
168
|
+
lines.push(`- ${item}`);
|
|
169
|
+
}
|
|
170
|
+
lines.push('');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// FR-022: Always include conversation turns if they exist
|
|
175
|
+
const planTurns = session.turns?.filter((t) => t.phase === 'Plan') ?? [];
|
|
176
|
+
if (planTurns.length > 0) {
|
|
177
|
+
lines.push('## Conversation\n');
|
|
178
|
+
for (const turn of planTurns) {
|
|
179
|
+
lines.push(`**${turn.role}**: ${turn.content}\n`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return lines.length > 1 ? lines.join('\n') : null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function generateDevelopMarkdown(session: WorkshopSession): string | null {
|
|
187
|
+
const lines: string[] = ['# Develop Phase\n'];
|
|
188
|
+
|
|
189
|
+
// FR-020: Render structured data if available
|
|
190
|
+
if (session.poc) {
|
|
191
|
+
const poc = session.poc;
|
|
192
|
+
|
|
193
|
+
// Repository location
|
|
194
|
+
lines.push('## PoC Repository\n');
|
|
195
|
+
if (poc.repoUrl) {
|
|
196
|
+
lines.push(`**Repository URL**: ${poc.repoUrl}\n`);
|
|
197
|
+
} else if (poc.repoPath) {
|
|
198
|
+
lines.push(`**Repository Path**: ${poc.repoPath}\n`);
|
|
199
|
+
}
|
|
200
|
+
if (poc.repoSource) {
|
|
201
|
+
lines.push(`**Source**: ${poc.repoSource}\n`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Technology stack
|
|
205
|
+
if (poc.techStack) {
|
|
206
|
+
lines.push('## Technology Stack\n');
|
|
207
|
+
lines.push(`- **Language**: ${poc.techStack.language}`);
|
|
208
|
+
lines.push(`- **Runtime**: ${poc.techStack.runtime}`);
|
|
209
|
+
lines.push(`- **Test Runner**: ${poc.techStack.testRunner}`);
|
|
210
|
+
if (poc.techStack.framework) {
|
|
211
|
+
lines.push(`- **Framework**: ${poc.techStack.framework}`);
|
|
212
|
+
}
|
|
213
|
+
if (poc.techStack.buildCommand) {
|
|
214
|
+
lines.push(`- **Build Command**: ${poc.techStack.buildCommand}`);
|
|
215
|
+
}
|
|
216
|
+
lines.push('');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Final status and termination
|
|
220
|
+
if (poc.finalStatus) {
|
|
221
|
+
lines.push('## Result\n');
|
|
222
|
+
lines.push(`**Status**: ${poc.finalStatus}\n`);
|
|
223
|
+
if (poc.terminationReason) {
|
|
224
|
+
lines.push(`**Termination Reason**: ${poc.terminationReason}\n`);
|
|
225
|
+
}
|
|
226
|
+
if (poc.totalDurationMs !== undefined) {
|
|
227
|
+
lines.push(`**Total Duration**: ${(poc.totalDurationMs / 1000).toFixed(1)}s\n`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Final test results
|
|
232
|
+
if (poc.finalTestResults) {
|
|
233
|
+
const tr = poc.finalTestResults;
|
|
234
|
+
lines.push('## Final Test Results\n');
|
|
235
|
+
lines.push(`- **Passed**: ${tr.passed}`);
|
|
236
|
+
lines.push(`- **Failed**: ${tr.failed}`);
|
|
237
|
+
lines.push(`- **Skipped**: ${tr.skipped}`);
|
|
238
|
+
lines.push(`- **Total**: ${tr.total}`);
|
|
239
|
+
lines.push(`- **Duration**: ${tr.durationMs}ms`);
|
|
240
|
+
if (tr.failures.length > 0) {
|
|
241
|
+
lines.push('\n### Failures\n');
|
|
242
|
+
for (const f of tr.failures) {
|
|
243
|
+
lines.push(`#### ${f.testName}`);
|
|
244
|
+
lines.push(`\`\`\`\n${f.message}\n\`\`\`\n`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
lines.push('');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Iteration timeline
|
|
251
|
+
if (poc.iterations.length > 0) {
|
|
252
|
+
lines.push('## Iteration Timeline\n');
|
|
253
|
+
for (const iter of poc.iterations) {
|
|
254
|
+
const duration = iter.endedAt
|
|
255
|
+
? `${((new Date(iter.endedAt).getTime() - new Date(iter.startedAt).getTime()) / 1000).toFixed(1)}s`
|
|
256
|
+
: 'in progress';
|
|
257
|
+
lines.push(`### Iteration ${iter.iteration} — ${iter.outcome} (${duration})\n`);
|
|
258
|
+
if (iter.changesSummary) {
|
|
259
|
+
lines.push(`${iter.changesSummary}\n`);
|
|
260
|
+
}
|
|
261
|
+
if (iter.filesChanged.length > 0) {
|
|
262
|
+
lines.push(`**Files changed**: ${iter.filesChanged.join(', ')}\n`);
|
|
263
|
+
}
|
|
264
|
+
if (iter.testResults) {
|
|
265
|
+
const tr = iter.testResults;
|
|
266
|
+
lines.push(
|
|
267
|
+
`**Tests**: ${tr.passed} passed, ${tr.failed} failed, ${tr.skipped} skipped (${tr.durationMs}ms)\n`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
if (iter.errorMessage) {
|
|
271
|
+
lines.push(`**Error**: ${iter.errorMessage}\n`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// FR-022: Always include conversation turns if they exist
|
|
278
|
+
const developTurns = session.turns?.filter((t) => t.phase === 'Develop') ?? [];
|
|
279
|
+
if (developTurns.length > 0) {
|
|
280
|
+
lines.push('## Conversation\n');
|
|
281
|
+
for (const turn of developTurns) {
|
|
282
|
+
lines.push(`**${turn.role}**: ${turn.content}\n`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return lines.length > 1 ? lines.join('\n') : null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── Phase generator mapping ──────────────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
const PHASE_GENERATORS: Record<string, (session: WorkshopSession) => string | null> = {
|
|
292
|
+
Discover: generateDiscoverMarkdown,
|
|
293
|
+
Ideate: generateIdeateMarkdown,
|
|
294
|
+
Design: generateDesignMarkdown,
|
|
295
|
+
Select: generateSelectMarkdown,
|
|
296
|
+
Plan: generatePlanMarkdown,
|
|
297
|
+
Develop: generateDevelopMarkdown,
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// ── Export ────────────────────────────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
/** Result of exporting workshop docs into a PoC repo. */
|
|
303
|
+
export interface WorkshopDocsResult {
|
|
304
|
+
/** Files created, relative to repoDir */
|
|
305
|
+
createdFiles: string[];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Generators for the pre-Develop phases only (the workshop decision history).
|
|
310
|
+
* Develop is excluded because the repo itself IS the PoC output.
|
|
311
|
+
*/
|
|
312
|
+
const WORKSHOP_DOC_GENERATORS: Record<string, (session: WorkshopSession) => string | null> = {
|
|
313
|
+
Discover: generateDiscoverMarkdown,
|
|
314
|
+
Ideate: generateIdeateMarkdown,
|
|
315
|
+
Design: generateDesignMarkdown,
|
|
316
|
+
Select: generateSelectMarkdown,
|
|
317
|
+
Plan: generatePlanMarkdown,
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Export workshop documentation into a PoC repository.
|
|
322
|
+
*
|
|
323
|
+
* Creates `docs/workshop/<phase>.md` files for Discover → Plan and a
|
|
324
|
+
* `WORKSHOP.md` index at the repo root. This preserves the full decision
|
|
325
|
+
* history (why this PoC was selected, how it was designed) alongside the code.
|
|
326
|
+
*
|
|
327
|
+
* The Develop phase is intentionally excluded — the repo itself is the PoC.
|
|
328
|
+
*
|
|
329
|
+
* @param session The workshop session to export docs from
|
|
330
|
+
* @param repoDir Root directory of the PoC repository
|
|
331
|
+
* @returns List of created files relative to repoDir
|
|
332
|
+
*/
|
|
333
|
+
export async function exportWorkshopDocs(
|
|
334
|
+
session: WorkshopSession,
|
|
335
|
+
repoDir: string,
|
|
336
|
+
): Promise<WorkshopDocsResult> {
|
|
337
|
+
const workshopDir = join(repoDir, 'docs', 'workshop');
|
|
338
|
+
await mkdir(workshopDir, { recursive: true });
|
|
339
|
+
|
|
340
|
+
const createdFiles: string[] = [];
|
|
341
|
+
const phaseFiles: Array<{ phase: string; fileName: string }> = [];
|
|
342
|
+
|
|
343
|
+
// Generate per-phase markdown files
|
|
344
|
+
for (const [phaseName, generator] of Object.entries(WORKSHOP_DOC_GENERATORS)) {
|
|
345
|
+
const content = generator(session);
|
|
346
|
+
if (content) {
|
|
347
|
+
const fileName = `${phaseName.toLowerCase()}.md`;
|
|
348
|
+
await writeFile(join(workshopDir, fileName), content, 'utf-8');
|
|
349
|
+
createdFiles.push(`docs/workshop/${fileName}`);
|
|
350
|
+
phaseFiles.push({ phase: phaseName, fileName });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Generate WORKSHOP.md index at the repo root
|
|
355
|
+
const indexLines: string[] = [
|
|
356
|
+
'# Workshop Summary\n',
|
|
357
|
+
`This project was generated by [sofIA](https://github.com/jmservera/sofIA-cli) — an AI Discovery Workshop CLI.\n`,
|
|
358
|
+
`**Session**: \`${session.sessionId}\` `,
|
|
359
|
+
`**Generated**: ${new Date().toISOString()}\n`,
|
|
360
|
+
];
|
|
361
|
+
|
|
362
|
+
if (session.businessContext) {
|
|
363
|
+
indexLines.push(`## Business Context\n`);
|
|
364
|
+
indexLines.push(`${session.businessContext.businessDescription}\n`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (session.selection) {
|
|
368
|
+
const idea = session.ideas?.find((i) => i.id === session.selection?.ideaId);
|
|
369
|
+
indexLines.push(`## Selected Idea\n`);
|
|
370
|
+
if (idea) {
|
|
371
|
+
indexLines.push(`**${idea.title}**: ${idea.description}\n`);
|
|
372
|
+
}
|
|
373
|
+
indexLines.push(`**Rationale**: ${session.selection.selectionRationale}\n`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (phaseFiles.length > 0) {
|
|
377
|
+
indexLines.push('## Workshop Documentation\n');
|
|
378
|
+
indexLines.push('Detailed documentation for each workshop phase:\n');
|
|
379
|
+
for (const { phase, fileName } of phaseFiles) {
|
|
380
|
+
indexLines.push(`- [${phase} Phase](docs/workshop/${fileName})`);
|
|
381
|
+
}
|
|
382
|
+
indexLines.push('');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const indexContent = indexLines.join('\n');
|
|
386
|
+
await writeFile(join(repoDir, 'WORKSHOP.md'), indexContent, 'utf-8');
|
|
387
|
+
createdFiles.push('WORKSHOP.md');
|
|
388
|
+
|
|
389
|
+
return { createdFiles };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Export a workshop session to the specified directory.
|
|
394
|
+
* Generates Markdown files for each phase with data and a summary.json.
|
|
395
|
+
*/
|
|
396
|
+
export async function exportSession(
|
|
397
|
+
session: WorkshopSession,
|
|
398
|
+
exportDir: string,
|
|
399
|
+
): Promise<ExportResult> {
|
|
400
|
+
await mkdir(exportDir, { recursive: true });
|
|
401
|
+
|
|
402
|
+
const files: ExportFile[] = [];
|
|
403
|
+
|
|
404
|
+
// Generate phase Markdown files
|
|
405
|
+
for (const [phaseName, generator] of Object.entries(PHASE_GENERATORS)) {
|
|
406
|
+
const content = generator(session);
|
|
407
|
+
if (content) {
|
|
408
|
+
const fileName = `${phaseName.toLowerCase()}.md`;
|
|
409
|
+
await writeFile(join(exportDir, fileName), content, 'utf-8');
|
|
410
|
+
files.push({ path: fileName, type: 'markdown' });
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Generate highlights — include one entry per phase with data or turns (FR-024)
|
|
415
|
+
const highlights: string[] = [];
|
|
416
|
+
if (session.businessContext) {
|
|
417
|
+
highlights.push(`Business: ${session.businessContext.businessDescription}`);
|
|
418
|
+
} else {
|
|
419
|
+
const discoverTurns = session.turns?.filter((t) => t.phase === 'Discover' && t.role === 'assistant') ?? [];
|
|
420
|
+
if (discoverTurns.length > 0) {
|
|
421
|
+
highlights.push(`Discover: ${discoverTurns[0].content.slice(0, 100)}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (session.ideas?.length) {
|
|
425
|
+
highlights.push(`Ideas: ${session.ideas.length} ideas generated`);
|
|
426
|
+
} else {
|
|
427
|
+
const ideateTurns = session.turns?.filter((t) => t.phase === 'Ideate' && t.role === 'assistant') ?? [];
|
|
428
|
+
if (ideateTurns.length > 0) {
|
|
429
|
+
highlights.push(`Ideate: ${ideateTurns[0].content.slice(0, 100)}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (session.evaluation) {
|
|
433
|
+
highlights.push(`Evaluation: ${session.evaluation.ideas.length} ideas evaluated`);
|
|
434
|
+
} else {
|
|
435
|
+
const designTurns = session.turns?.filter((t) => t.phase === 'Design' && t.role === 'assistant') ?? [];
|
|
436
|
+
if (designTurns.length > 0) {
|
|
437
|
+
highlights.push(`Design: ${designTurns[0].content.slice(0, 100)}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (session.selection) {
|
|
441
|
+
highlights.push(`Selected idea: ${session.selection.ideaId}`);
|
|
442
|
+
} else {
|
|
443
|
+
const selectTurns = session.turns?.filter((t) => t.phase === 'Select' && t.role === 'assistant') ?? [];
|
|
444
|
+
if (selectTurns.length > 0) {
|
|
445
|
+
highlights.push(`Select: ${selectTurns[0].content.slice(0, 100)}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (session.plan?.milestones?.length) {
|
|
449
|
+
highlights.push(`${session.plan.milestones.length} milestones planned`);
|
|
450
|
+
} else {
|
|
451
|
+
const planTurns = session.turns?.filter((t) => t.phase === 'Plan' && t.role === 'assistant') ?? [];
|
|
452
|
+
if (planTurns.length > 0) {
|
|
453
|
+
highlights.push(`Plan: ${planTurns[0].content.slice(0, 100)}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// PoC highlights
|
|
457
|
+
if (session.poc) {
|
|
458
|
+
const poc = session.poc;
|
|
459
|
+
if (poc.finalStatus) {
|
|
460
|
+
highlights.push(`PoC status: ${poc.finalStatus}`);
|
|
461
|
+
}
|
|
462
|
+
if (poc.iterations?.length) {
|
|
463
|
+
highlights.push(`PoC iterations: ${poc.iterations.length}`);
|
|
464
|
+
}
|
|
465
|
+
if (poc.terminationReason) {
|
|
466
|
+
highlights.push(`PoC termination: ${poc.terminationReason}`);
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
const developTurns = session.turns?.filter((t) => t.phase === 'Develop' && t.role === 'assistant') ?? [];
|
|
470
|
+
if (developTurns.length > 0) {
|
|
471
|
+
highlights.push(`Develop: ${developTurns[0].content.slice(0, 100)}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Generate summary.json
|
|
476
|
+
const summary: ExportSummary = {
|
|
477
|
+
sessionId: session.sessionId,
|
|
478
|
+
exportedAt: new Date().toISOString(),
|
|
479
|
+
phase: session.phase,
|
|
480
|
+
status: session.status,
|
|
481
|
+
files,
|
|
482
|
+
highlights: highlights.length > 0 ? highlights : undefined,
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const summaryFileName = 'summary.json';
|
|
486
|
+
await writeFile(join(exportDir, summaryFileName), JSON.stringify(summary, null, 2), 'utf-8');
|
|
487
|
+
files.push({ path: summaryFileName, type: 'json' });
|
|
488
|
+
|
|
489
|
+
return { exportDir, files };
|
|
490
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session manager.
|
|
3
|
+
*
|
|
4
|
+
* Provides session lifecycle operations including backtracking
|
|
5
|
+
* (moving to an earlier phase with deterministic invalidation
|
|
6
|
+
* of downstream artifacts).
|
|
7
|
+
*/
|
|
8
|
+
import type { PhaseValue, WorkshopSession } from '../shared/schemas/session.js';
|
|
9
|
+
import { getPhaseOrder } from '../phases/phaseHandlers.js';
|
|
10
|
+
|
|
11
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export interface BacktrackResult {
|
|
14
|
+
success: boolean;
|
|
15
|
+
session: WorkshopSession;
|
|
16
|
+
invalidatedPhases: PhaseValue[];
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ── Phase-to-fields mapping ──────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Maps each phase to the session fields it produces.
|
|
24
|
+
* When invalidating a phase, these fields are cleared.
|
|
25
|
+
*/
|
|
26
|
+
const PHASE_FIELDS: Record<string, (keyof WorkshopSession)[]> = {
|
|
27
|
+
Discover: ['businessContext', 'workflow'],
|
|
28
|
+
Ideate: ['ideas'],
|
|
29
|
+
Design: ['evaluation'],
|
|
30
|
+
Select: ['selection'],
|
|
31
|
+
Plan: ['plan'],
|
|
32
|
+
Develop: ['poc'],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ── Backtrack ────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Backtrack a session to an earlier phase.
|
|
39
|
+
*
|
|
40
|
+
* This invalidates all downstream phase data (fields and turns) to ensure
|
|
41
|
+
* deterministic re-computation when the phase is re-run.
|
|
42
|
+
*
|
|
43
|
+
* - Backtracking to the same phase is a no-op (preserves all data).
|
|
44
|
+
* - Backtracking forward (to a later phase) is rejected.
|
|
45
|
+
* - The target phase's fields are also cleared (since it will be re-run).
|
|
46
|
+
*/
|
|
47
|
+
export function backtrackSession(
|
|
48
|
+
session: WorkshopSession,
|
|
49
|
+
targetPhase: PhaseValue,
|
|
50
|
+
): BacktrackResult {
|
|
51
|
+
const phaseOrder = getPhaseOrder();
|
|
52
|
+
const currentIdx = phaseOrder.indexOf(session.phase as PhaseValue);
|
|
53
|
+
const targetIdx = phaseOrder.indexOf(targetPhase);
|
|
54
|
+
|
|
55
|
+
// Handle phases not in the order (e.g., Complete)
|
|
56
|
+
const effectiveCurrentIdx = currentIdx === -1 ? phaseOrder.length : currentIdx;
|
|
57
|
+
|
|
58
|
+
if (targetIdx === -1) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
session,
|
|
62
|
+
invalidatedPhases: [],
|
|
63
|
+
error: `Unknown phase: ${targetPhase}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (targetIdx > effectiveCurrentIdx) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
session,
|
|
71
|
+
invalidatedPhases: [],
|
|
72
|
+
error: `Cannot backtrack forward from "${session.phase}" to "${targetPhase}".`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Same phase → no-op
|
|
77
|
+
if (targetIdx === effectiveCurrentIdx) {
|
|
78
|
+
return {
|
|
79
|
+
success: true,
|
|
80
|
+
session: { ...session },
|
|
81
|
+
invalidatedPhases: [],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Collect phases to invalidate: from targetPhase to current (inclusive)
|
|
86
|
+
const invalidatedPhases: PhaseValue[] = [];
|
|
87
|
+
const updatedSession = { ...session };
|
|
88
|
+
|
|
89
|
+
for (let i = targetIdx; i <= effectiveCurrentIdx && i < phaseOrder.length; i++) {
|
|
90
|
+
const phase = phaseOrder[i];
|
|
91
|
+
invalidatedPhases.push(phase);
|
|
92
|
+
|
|
93
|
+
// Clear fields produced by this phase
|
|
94
|
+
const fields = PHASE_FIELDS[phase] ?? [];
|
|
95
|
+
for (const field of fields) {
|
|
96
|
+
(updatedSession as Record<string, unknown>)[field] = undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Remove turns from invalidated phases
|
|
101
|
+
if (updatedSession.turns) {
|
|
102
|
+
const validPhases = new Set(
|
|
103
|
+
phaseOrder.slice(0, targetIdx),
|
|
104
|
+
);
|
|
105
|
+
updatedSession.turns = updatedSession.turns.filter(
|
|
106
|
+
t => validPhases.has(t.phase as PhaseValue),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
updatedSession.phase = targetPhase;
|
|
111
|
+
updatedSession.status = 'Active';
|
|
112
|
+
updatedSession.updatedAt = new Date().toISOString();
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
success: true,
|
|
116
|
+
session: updatedSession,
|
|
117
|
+
invalidatedPhases,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session persistence adapter.
|
|
3
|
+
*
|
|
4
|
+
* Reads/writes WorkshopSession JSON files to .sofia/sessions/<sessionId>.json.
|
|
5
|
+
* Uses write-then-rename for atomic writes.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, writeFile, mkdir, readdir, unlink, rename, access } from 'node:fs/promises';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { randomBytes } from 'node:crypto';
|
|
10
|
+
|
|
11
|
+
import { workshopSessionSchema, type WorkshopSession } from '../shared/schemas/session.js';
|
|
12
|
+
|
|
13
|
+
export class SessionStore {
|
|
14
|
+
constructor(private readonly baseDir: string) {}
|
|
15
|
+
|
|
16
|
+
/** Persist a session atomically (write to temp, rename). */
|
|
17
|
+
async save(session: WorkshopSession): Promise<void> {
|
|
18
|
+
await mkdir(this.baseDir, { recursive: true });
|
|
19
|
+
const filePath = this.filePath(session.sessionId);
|
|
20
|
+
const tmpPath = filePath + '.tmp.' + randomBytes(4).toString('hex');
|
|
21
|
+
const json = JSON.stringify(session, null, 2);
|
|
22
|
+
await writeFile(tmpPath, json, 'utf-8');
|
|
23
|
+
await rename(tmpPath, filePath);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Load and validate a session by ID. Throws if not found or invalid. */
|
|
27
|
+
async load(sessionId: string): Promise<WorkshopSession> {
|
|
28
|
+
const filePath = this.filePath(sessionId);
|
|
29
|
+
const raw = await readFile(filePath, 'utf-8');
|
|
30
|
+
const parsed: unknown = JSON.parse(raw);
|
|
31
|
+
return workshopSessionSchema.parse(parsed);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** List all session IDs in the store. */
|
|
35
|
+
async list(): Promise<string[]> {
|
|
36
|
+
try {
|
|
37
|
+
const files = await readdir(this.baseDir);
|
|
38
|
+
return files
|
|
39
|
+
.filter((f) => f.endsWith('.json'))
|
|
40
|
+
.map((f) => f.replace(/\.json$/, ''));
|
|
41
|
+
} catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Check if a session exists. */
|
|
47
|
+
async exists(sessionId: string): Promise<boolean> {
|
|
48
|
+
try {
|
|
49
|
+
await access(this.filePath(sessionId));
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Delete a session file. */
|
|
57
|
+
async delete(sessionId: string): Promise<void> {
|
|
58
|
+
await unlink(this.filePath(sessionId));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private filePath(sessionId: string): string {
|
|
62
|
+
return join(this.baseDir, `${sessionId}.json`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Default store location relative to CWD: .sofia/sessions/ */
|
|
67
|
+
export function createDefaultStore(): SessionStore {
|
|
68
|
+
return new SessionStore(join(process.cwd(), '.sofia', 'sessions'));
|
|
69
|
+
}
|