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.
Files changed (435) hide show
  1. package/.github/agents/copilot-instructions.md +39 -0
  2. package/.github/agents/speckit.analyze.agent.md +184 -0
  3. package/.github/agents/speckit.checklist.agent.md +294 -0
  4. package/.github/agents/speckit.clarify.agent.md +181 -0
  5. package/.github/agents/speckit.constitution.agent.md +84 -0
  6. package/.github/agents/speckit.implement.agent.md +135 -0
  7. package/.github/agents/speckit.plan.agent.md +90 -0
  8. package/.github/agents/speckit.specify.agent.md +258 -0
  9. package/.github/agents/speckit.tasks.agent.md +137 -0
  10. package/.github/agents/speckit.taskstoissues.agent.md +30 -0
  11. package/.github/copilot-instructions.md +257 -0
  12. package/.github/prompts/speckit.analyze.prompt.md +3 -0
  13. package/.github/prompts/speckit.checklist.prompt.md +3 -0
  14. package/.github/prompts/speckit.clarify.prompt.md +3 -0
  15. package/.github/prompts/speckit.constitution.prompt.md +3 -0
  16. package/.github/prompts/speckit.implement.prompt.md +3 -0
  17. package/.github/prompts/speckit.plan.prompt.md +3 -0
  18. package/.github/prompts/speckit.specify.prompt.md +3 -0
  19. package/.github/prompts/speckit.tasks.prompt.md +3 -0
  20. package/.github/prompts/speckit.taskstoissues.prompt.md +3 -0
  21. package/.github/workflows/ci.yml +38 -0
  22. package/.prettierrc +6 -0
  23. package/.specify/memory/constitution.md +181 -0
  24. package/.specify/scripts/bash/check-prerequisites.sh +166 -0
  25. package/.specify/scripts/bash/common.sh +156 -0
  26. package/.specify/scripts/bash/create-new-feature.sh +297 -0
  27. package/.specify/scripts/bash/setup-plan.sh +61 -0
  28. package/.specify/scripts/bash/update-agent-context.sh +810 -0
  29. package/.specify/templates/agent-file-template.md +28 -0
  30. package/.specify/templates/checklist-template.md +40 -0
  31. package/.specify/templates/constitution-template.md +50 -0
  32. package/.specify/templates/plan-template.md +113 -0
  33. package/.specify/templates/spec-template.md +115 -0
  34. package/.specify/templates/tasks-template.md +251 -0
  35. package/.vscode/mcp.json +42 -0
  36. package/.vscode/settings.json +19 -0
  37. package/CODE_OF_CONDUCT.md +128 -0
  38. package/LICENSE +21 -0
  39. package/README.md +213 -0
  40. package/dist/src/cli/developCommand.js +240 -0
  41. package/dist/src/cli/directCommands.js +143 -0
  42. package/dist/src/cli/envLoader.js +16 -0
  43. package/dist/src/cli/exportCommand.js +53 -0
  44. package/dist/src/cli/index.js +203 -0
  45. package/dist/src/cli/ioContext.js +109 -0
  46. package/dist/src/cli/preflight.js +57 -0
  47. package/dist/src/cli/statusCommand.js +110 -0
  48. package/dist/src/cli/workshopCommand.js +400 -0
  49. package/dist/src/develop/checkpointState.js +86 -0
  50. package/dist/src/develop/codeGenerator.js +319 -0
  51. package/dist/src/develop/dynamicScaffolder.js +226 -0
  52. package/dist/src/develop/githubMcpAdapter.js +122 -0
  53. package/dist/src/develop/index.js +15 -0
  54. package/dist/src/develop/mcpContextEnricher.js +195 -0
  55. package/dist/src/develop/pocScaffolder.js +542 -0
  56. package/dist/src/develop/ralphLoop.js +659 -0
  57. package/dist/src/develop/templateRegistry.js +364 -0
  58. package/dist/src/develop/testRunner.js +202 -0
  59. package/dist/src/logging/logger.js +58 -0
  60. package/dist/src/loop/conversationLoop.js +227 -0
  61. package/dist/src/loop/phaseSummarizer.js +87 -0
  62. package/dist/src/mcp/mcpManager.js +267 -0
  63. package/dist/src/mcp/mcpTransport.js +391 -0
  64. package/dist/src/mcp/retryPolicy.js +47 -0
  65. package/dist/src/mcp/webSearch.js +254 -0
  66. package/dist/src/phases/contextSummarizer.js +101 -0
  67. package/dist/src/phases/discoveryEnricher.js +156 -0
  68. package/dist/src/phases/phaseExtractors.js +222 -0
  69. package/dist/src/phases/phaseHandlers.js +328 -0
  70. package/dist/src/prompts/design.md +51 -0
  71. package/dist/src/prompts/develop-boundary.md +51 -0
  72. package/dist/src/prompts/develop.md +111 -0
  73. package/dist/src/prompts/discover.md +58 -0
  74. package/dist/src/prompts/ideate.md +56 -0
  75. package/dist/src/prompts/plan.md +51 -0
  76. package/dist/src/prompts/promptLoader.js +167 -0
  77. package/dist/src/prompts/promptLoader.ts +198 -0
  78. package/dist/src/prompts/select.md +47 -0
  79. package/dist/src/prompts/summarize/README.md +8 -0
  80. package/dist/src/prompts/summarize/design-summary.md +37 -0
  81. package/dist/src/prompts/summarize/develop-summary.md +25 -0
  82. package/dist/src/prompts/summarize/ideate-summary.md +27 -0
  83. package/dist/src/prompts/summarize/plan-summary.md +27 -0
  84. package/dist/src/prompts/summarize/select-summary.md +21 -0
  85. package/dist/src/prompts/system.md +28 -0
  86. package/dist/src/sessions/exportPaths.js +22 -0
  87. package/dist/src/sessions/exportWriter.js +406 -0
  88. package/dist/src/sessions/sessionManager.js +81 -0
  89. package/dist/src/sessions/sessionStore.js +65 -0
  90. package/dist/src/shared/activitySpinner.js +91 -0
  91. package/dist/src/shared/copilotClient.js +129 -0
  92. package/dist/src/shared/data/cards.json +1249 -0
  93. package/dist/src/shared/data/cardsLoader.js +51 -0
  94. package/dist/src/shared/errorClassifier.js +120 -0
  95. package/dist/src/shared/events.js +28 -0
  96. package/dist/src/shared/markdownRenderer.js +34 -0
  97. package/dist/src/shared/schemas/session.js +265 -0
  98. package/dist/src/shared/tableRenderer.js +20 -0
  99. package/dist/src/vendor/chalk.js +2 -0
  100. package/dist/src/vendor/cli-table3.js +3 -0
  101. package/dist/src/vendor/commander.js +2 -0
  102. package/dist/src/vendor/marked-terminal.js +3 -0
  103. package/dist/src/vendor/marked.js +2 -0
  104. package/dist/src/vendor/ora.js +2 -0
  105. package/dist/src/vendor/pino.js +2 -0
  106. package/dist/src/vendor/zod.js +2 -0
  107. package/dist/tests/e2e/developE2e.spec.js +126 -0
  108. package/dist/tests/e2e/developFailureE2e.spec.js +247 -0
  109. package/dist/tests/e2e/developPty.spec.js +75 -0
  110. package/dist/tests/e2e/discoveryWebSearchRelevance.spec.js +84 -0
  111. package/dist/tests/e2e/harness.spec.js +83 -0
  112. package/dist/tests/e2e/mcpLive.spec.js +120 -0
  113. package/dist/tests/e2e/newSession.e2e.spec.js +177 -0
  114. package/dist/tests/e2e/ralphLoopEnrichmentComparison.spec.js +62 -0
  115. package/dist/tests/e2e/workiqEnrichment.spec.js +56 -0
  116. package/dist/tests/e2e/zavaSimulation.spec.js +452 -0
  117. package/dist/tests/fixtures/test-fixture-project/src/add.js +3 -0
  118. package/dist/tests/fixtures/test-fixture-project/tests/failing.test.js +6 -0
  119. package/dist/tests/fixtures/test-fixture-project/tests/hanging.test.js +8 -0
  120. package/dist/tests/fixtures/test-fixture-project/tests/passing.test.js +10 -0
  121. package/dist/tests/fixtures/test-fixture-project/vitest.config.js +6 -0
  122. package/dist/tests/integration/autoStartConversation.spec.js +138 -0
  123. package/dist/tests/integration/defaultCommand.spec.js +147 -0
  124. package/dist/tests/integration/directCommandNonTty.spec.js +224 -0
  125. package/dist/tests/integration/directCommandTty.spec.js +151 -0
  126. package/dist/tests/integration/discoveryEnrichmentFlow.spec.js +175 -0
  127. package/dist/tests/integration/exportArtifacts.spec.js +202 -0
  128. package/dist/tests/integration/exportFallbackFlow.spec.js +99 -0
  129. package/dist/tests/integration/mcpDegradationFlow.spec.js +190 -0
  130. package/dist/tests/integration/mcpTransportFlow.spec.js +139 -0
  131. package/dist/tests/integration/newSessionFlow.spec.js +343 -0
  132. package/dist/tests/integration/pocGithubMcp.spec.js +186 -0
  133. package/dist/tests/integration/pocLocalFallback.spec.js +171 -0
  134. package/dist/tests/integration/pocScaffold.spec.js +163 -0
  135. package/dist/tests/integration/ralphLoopFlow.spec.js +359 -0
  136. package/dist/tests/integration/ralphLoopPartial.spec.js +368 -0
  137. package/dist/tests/integration/resumeAndBacktrack.spec.js +247 -0
  138. package/dist/tests/integration/spinnerLifecycle.spec.js +220 -0
  139. package/dist/tests/integration/summarizationFlow.spec.js +115 -0
  140. package/dist/tests/integration/testRunnerReal.spec.js +52 -0
  141. package/dist/tests/integration/webSearchAgent.spec.js +128 -0
  142. package/dist/tests/live/copilotSdkLive.spec.js +107 -0
  143. package/dist/tests/live/zavaFullWorkshop.spec.js +392 -0
  144. package/dist/tests/setup/loadEnv.js +3 -0
  145. package/dist/tests/unit/cli/developCommand.spec.js +567 -0
  146. package/dist/tests/unit/cli/directCommands.spec.js +279 -0
  147. package/dist/tests/unit/cli/envLoader.spec.js +58 -0
  148. package/dist/tests/unit/cli/ioContext.spec.js +119 -0
  149. package/dist/tests/unit/cli/preflight.spec.js +108 -0
  150. package/dist/tests/unit/cli/statusCommand.spec.js +111 -0
  151. package/dist/tests/unit/cli/workshopClientFallback.spec.js +80 -0
  152. package/dist/tests/unit/cli/workshopCommand.spec.js +329 -0
  153. package/dist/tests/unit/config/vitestEnvSetup.spec.js +13 -0
  154. package/dist/tests/unit/develop/checkpointState.spec.js +315 -0
  155. package/dist/tests/unit/develop/codeGenerator.spec.js +355 -0
  156. package/dist/tests/unit/develop/githubMcpAdapter.spec.js +231 -0
  157. package/dist/tests/unit/develop/mcpContextEnricher.spec.js +433 -0
  158. package/dist/tests/unit/develop/outputValidator.spec.js +119 -0
  159. package/dist/tests/unit/develop/pocScaffolder.spec.js +353 -0
  160. package/dist/tests/unit/develop/ralphLoop.spec.js +1248 -0
  161. package/dist/tests/unit/develop/templateRegistry.spec.js +85 -0
  162. package/dist/tests/unit/develop/testRunner.spec.js +249 -0
  163. package/dist/tests/unit/infraBicep.spec.js +92 -0
  164. package/dist/tests/unit/infraDeploy.spec.js +82 -0
  165. package/dist/tests/unit/infraTeardown.spec.js +63 -0
  166. package/dist/tests/unit/logging/logger.spec.js +43 -0
  167. package/dist/tests/unit/loop/conversationLoop.spec.js +592 -0
  168. package/dist/tests/unit/loop/phaseSummarizer.spec.js +141 -0
  169. package/dist/tests/unit/loop/streamingMarkdown.spec.js +147 -0
  170. package/dist/tests/unit/mcp/mcpManager.spec.js +279 -0
  171. package/dist/tests/unit/mcp/mcpTransport.spec.js +529 -0
  172. package/dist/tests/unit/mcp/retryPolicy.spec.js +218 -0
  173. package/dist/tests/unit/mcp/timeoutValidation.spec.js +46 -0
  174. package/dist/tests/unit/mcp/webSearch.spec.js +567 -0
  175. package/dist/tests/unit/phases/contextSummarizer.spec.js +140 -0
  176. package/dist/tests/unit/phases/discoveryEnricher.repeatCalls.spec.js +93 -0
  177. package/dist/tests/unit/phases/discoveryEnricher.spec.js +411 -0
  178. package/dist/tests/unit/phases/phaseExtractors.spec.js +352 -0
  179. package/dist/tests/unit/phases/phaseHandlers.spec.js +425 -0
  180. package/dist/tests/unit/prompts/promptLoader.spec.js +118 -0
  181. package/dist/tests/unit/schemas/pocSchemas.spec.js +412 -0
  182. package/dist/tests/unit/schemas/session.spec.js +257 -0
  183. package/dist/tests/unit/sessions/exportPaths.spec.js +31 -0
  184. package/dist/tests/unit/sessions/exportWriter.spec.js +655 -0
  185. package/dist/tests/unit/sessions/sessionManager.spec.js +151 -0
  186. package/dist/tests/unit/sessions/sessionStore.spec.js +116 -0
  187. package/dist/tests/unit/shared/activitySpinner.spec.js +175 -0
  188. package/dist/tests/unit/shared/cardsLoader.spec.js +76 -0
  189. package/dist/tests/unit/shared/copilotClient.spec.js +155 -0
  190. package/dist/tests/unit/shared/errorClassifier.spec.js +131 -0
  191. package/dist/tests/unit/shared/events.spec.js +55 -0
  192. package/dist/tests/unit/shared/markdownRenderer.spec.js +35 -0
  193. package/dist/tests/unit/shared/markdownRendererChunks.spec.js +70 -0
  194. package/dist/tests/unit/shared/tableRenderer.spec.js +34 -0
  195. package/dist/vitest.config.js +14 -0
  196. package/dist/vitest.live.config.js +18 -0
  197. package/docs/README.md +35 -0
  198. package/docs/architecture.md +169 -0
  199. package/docs/cli-usage.md +207 -0
  200. package/docs/environment.md +66 -0
  201. package/docs/export-format.md +146 -0
  202. package/docs/session-model.md +113 -0
  203. package/eslint.config.js +35 -0
  204. package/infra/deploy.sh +193 -0
  205. package/infra/gather-env.sh +211 -0
  206. package/infra/main.bicep +90 -0
  207. package/infra/main.bicepparam +18 -0
  208. package/infra/resources.bicep +134 -0
  209. package/infra/teardown.sh +114 -0
  210. package/package.json +63 -0
  211. package/specs/001-cli-workshop-rebuild/checklists/requirements.md +35 -0
  212. package/specs/001-cli-workshop-rebuild/contracts/cli.md +59 -0
  213. package/specs/001-cli-workshop-rebuild/contracts/export-summary-json.md +23 -0
  214. package/specs/001-cli-workshop-rebuild/contracts/session-json.md +30 -0
  215. package/specs/001-cli-workshop-rebuild/data-model.md +210 -0
  216. package/specs/001-cli-workshop-rebuild/plan.md +361 -0
  217. package/specs/001-cli-workshop-rebuild/quickstart.md +83 -0
  218. package/specs/001-cli-workshop-rebuild/research.md +116 -0
  219. package/specs/001-cli-workshop-rebuild/spec.md +240 -0
  220. package/specs/001-cli-workshop-rebuild/tasks.md +476 -0
  221. package/specs/002-poc-generation/contracts/poc-output.md +172 -0
  222. package/specs/002-poc-generation/contracts/ralph-loop.md +113 -0
  223. package/specs/002-poc-generation/data-model.md +172 -0
  224. package/specs/002-poc-generation/plan.md +109 -0
  225. package/specs/002-poc-generation/quickstart.md +97 -0
  226. package/specs/002-poc-generation/research.md +786 -0
  227. package/specs/002-poc-generation/spec.md +81 -0
  228. package/specs/002-poc-generation/tasks-fix.md +198 -0
  229. package/specs/002-poc-generation/tasks.md +252 -0
  230. package/specs/003-mcp-transport-integration/checklists/requirements.md +37 -0
  231. package/specs/003-mcp-transport-integration/contracts/context-enricher.md +220 -0
  232. package/specs/003-mcp-transport-integration/contracts/discovery-enricher.md +267 -0
  233. package/specs/003-mcp-transport-integration/contracts/github-adapter.md +149 -0
  234. package/specs/003-mcp-transport-integration/contracts/mcp-transport.md +288 -0
  235. package/specs/003-mcp-transport-integration/data-model.md +326 -0
  236. package/specs/003-mcp-transport-integration/plan.md +114 -0
  237. package/specs/003-mcp-transport-integration/quickstart.md +311 -0
  238. package/specs/003-mcp-transport-integration/research.md +395 -0
  239. package/specs/003-mcp-transport-integration/spec.md +234 -0
  240. package/specs/003-mcp-transport-integration/tasks.md +324 -0
  241. package/specs/003-next-spec-gaps.md +150 -0
  242. package/specs/004-dev-resume-hardening/checklists/requirements.md +37 -0
  243. package/specs/004-dev-resume-hardening/contracts/cli.md +160 -0
  244. package/specs/004-dev-resume-hardening/data-model.md +321 -0
  245. package/specs/004-dev-resume-hardening/plan.md +107 -0
  246. package/specs/004-dev-resume-hardening/quickstart.md +115 -0
  247. package/specs/004-dev-resume-hardening/research.md +142 -0
  248. package/specs/004-dev-resume-hardening/spec.md +221 -0
  249. package/specs/004-dev-resume-hardening/tasks.md +333 -0
  250. package/specs/005-ai-search-deploy/checklists/requirements.md +39 -0
  251. package/specs/005-ai-search-deploy/contracts/web-search-tool.md +241 -0
  252. package/specs/005-ai-search-deploy/data-model.md +130 -0
  253. package/specs/005-ai-search-deploy/plan.md +93 -0
  254. package/specs/005-ai-search-deploy/quickstart.md +96 -0
  255. package/specs/005-ai-search-deploy/research.md +187 -0
  256. package/specs/005-ai-search-deploy/spec.md +143 -0
  257. package/specs/005-ai-search-deploy/tasks.md +284 -0
  258. package/specs/006-workshop-extraction-fixes/checklists/requirements.md +61 -0
  259. package/specs/006-workshop-extraction-fixes/contracts/summarization-and-export.md +131 -0
  260. package/specs/006-workshop-extraction-fixes/data-model.md +149 -0
  261. package/specs/006-workshop-extraction-fixes/plan.md +123 -0
  262. package/specs/006-workshop-extraction-fixes/quickstart.md +101 -0
  263. package/specs/006-workshop-extraction-fixes/research.md +143 -0
  264. package/specs/006-workshop-extraction-fixes/spec.md +210 -0
  265. package/specs/006-workshop-extraction-fixes/tasks.md +316 -0
  266. package/src/cli/developCommand.ts +308 -0
  267. package/src/cli/directCommands.ts +195 -0
  268. package/src/cli/envLoader.ts +17 -0
  269. package/src/cli/exportCommand.ts +65 -0
  270. package/src/cli/index.ts +249 -0
  271. package/src/cli/ioContext.ts +139 -0
  272. package/src/cli/preflight.ts +86 -0
  273. package/src/cli/statusCommand.ts +118 -0
  274. package/src/cli/workshopCommand.ts +496 -0
  275. package/src/develop/checkpointState.ts +121 -0
  276. package/src/develop/codeGenerator.ts +402 -0
  277. package/src/develop/dynamicScaffolder.ts +284 -0
  278. package/src/develop/githubMcpAdapter.ts +199 -0
  279. package/src/develop/index.ts +34 -0
  280. package/src/develop/mcpContextEnricher.ts +279 -0
  281. package/src/develop/pocScaffolder.ts +646 -0
  282. package/src/develop/ralphLoop.ts +1044 -0
  283. package/src/develop/templateRegistry.ts +427 -0
  284. package/src/develop/testRunner.ts +276 -0
  285. package/src/logging/logger.ts +73 -0
  286. package/src/loop/conversationLoop.ts +355 -0
  287. package/src/loop/phaseSummarizer.ts +114 -0
  288. package/src/mcp/mcpManager.ts +365 -0
  289. package/src/mcp/mcpTransport.ts +562 -0
  290. package/src/mcp/retryPolicy.ts +87 -0
  291. package/src/mcp/webSearch.ts +388 -0
  292. package/src/originalPrompts/design_thinking.md +178 -0
  293. package/src/originalPrompts/design_thinking_persona.md +76 -0
  294. package/src/originalPrompts/document_generator_example.md +77 -0
  295. package/src/originalPrompts/document_generator_persona.md +47 -0
  296. package/src/originalPrompts/facilitator_persona.md +125 -0
  297. package/src/originalPrompts/guardrails.md +47 -0
  298. package/src/phases/contextSummarizer.ts +154 -0
  299. package/src/phases/discoveryEnricher.ts +223 -0
  300. package/src/phases/phaseExtractors.ts +247 -0
  301. package/src/phases/phaseHandlers.ts +450 -0
  302. package/src/prompts/design.md +51 -0
  303. package/src/prompts/develop-boundary.md +51 -0
  304. package/src/prompts/develop.md +111 -0
  305. package/src/prompts/discover.md +58 -0
  306. package/src/prompts/ideate.md +56 -0
  307. package/src/prompts/plan.md +51 -0
  308. package/src/prompts/promptLoader.ts +198 -0
  309. package/src/prompts/select.md +47 -0
  310. package/src/prompts/summarize/README.md +8 -0
  311. package/src/prompts/summarize/design-summary.md +37 -0
  312. package/src/prompts/summarize/develop-summary.md +25 -0
  313. package/src/prompts/summarize/ideate-summary.md +27 -0
  314. package/src/prompts/summarize/plan-summary.md +27 -0
  315. package/src/prompts/summarize/select-summary.md +21 -0
  316. package/src/prompts/system.md +28 -0
  317. package/src/sessions/exportPaths.ts +28 -0
  318. package/src/sessions/exportWriter.ts +490 -0
  319. package/src/sessions/sessionManager.ts +119 -0
  320. package/src/sessions/sessionStore.ts +69 -0
  321. package/src/shared/activitySpinner.ts +108 -0
  322. package/src/shared/copilotClient.ts +291 -0
  323. package/src/shared/data/cards.json +1249 -0
  324. package/src/shared/data/cardsLoader.ts +70 -0
  325. package/src/shared/errorClassifier.ts +160 -0
  326. package/src/shared/events.ts +103 -0
  327. package/src/shared/markdownRenderer.ts +44 -0
  328. package/src/shared/schemas/session.ts +346 -0
  329. package/src/shared/tableRenderer.ts +28 -0
  330. package/src/types/marked-terminal.d.ts +5 -0
  331. package/src/vendor/chalk.ts +2 -0
  332. package/src/vendor/cli-table3.ts +3 -0
  333. package/src/vendor/commander.ts +2 -0
  334. package/src/vendor/marked-terminal.ts +3 -0
  335. package/src/vendor/marked.ts +2 -0
  336. package/src/vendor/ora.ts +2 -0
  337. package/src/vendor/pino.ts +3 -0
  338. package/src/vendor/zod.ts +3 -0
  339. package/tests/e2e/developE2e.spec.ts +152 -0
  340. package/tests/e2e/developFailureE2e.spec.ts +289 -0
  341. package/tests/e2e/developPty.spec.ts +86 -0
  342. package/tests/e2e/discoveryWebSearchRelevance.spec.ts +103 -0
  343. package/tests/e2e/harness.spec.ts +104 -0
  344. package/tests/e2e/mcpLive.spec.ts +149 -0
  345. package/tests/e2e/newSession.e2e.spec.ts +245 -0
  346. package/tests/e2e/ralphLoopEnrichmentComparison.spec.ts +70 -0
  347. package/tests/e2e/workiqEnrichment.spec.ts +72 -0
  348. package/tests/e2e/zava-assessment/agent-interaction-script.md +258 -0
  349. package/tests/e2e/zava-assessment/company-profile.md +98 -0
  350. package/tests/e2e/zava-assessment/expected-results-checklist.md +454 -0
  351. package/tests/e2e/zavaSimulation.spec.ts +511 -0
  352. package/tests/fixtures/completedSession.json +141 -0
  353. package/tests/fixtures/test-fixture-project/package-lock.json +1585 -0
  354. package/tests/fixtures/test-fixture-project/package.json +12 -0
  355. package/tests/fixtures/test-fixture-project/src/add.ts +3 -0
  356. package/tests/fixtures/test-fixture-project/tests/failing.test.ts +7 -0
  357. package/tests/fixtures/test-fixture-project/tests/hanging.test.ts +9 -0
  358. package/tests/fixtures/test-fixture-project/tests/passing.test.ts +13 -0
  359. package/tests/fixtures/test-fixture-project/vitest.config.ts +7 -0
  360. package/tests/integration/autoStartConversation.spec.ts +168 -0
  361. package/tests/integration/defaultCommand.spec.ts +179 -0
  362. package/tests/integration/directCommandNonTty.spec.ts +260 -0
  363. package/tests/integration/directCommandTty.spec.ts +185 -0
  364. package/tests/integration/discoveryEnrichmentFlow.spec.ts +209 -0
  365. package/tests/integration/exportArtifacts.spec.ts +232 -0
  366. package/tests/integration/exportFallbackFlow.spec.ts +115 -0
  367. package/tests/integration/mcpDegradationFlow.spec.ts +231 -0
  368. package/tests/integration/mcpTransportFlow.spec.ts +178 -0
  369. package/tests/integration/newSessionFlow.spec.ts +406 -0
  370. package/tests/integration/pocGithubMcp.spec.ts +224 -0
  371. package/tests/integration/pocLocalFallback.spec.ts +205 -0
  372. package/tests/integration/pocScaffold.spec.ts +220 -0
  373. package/tests/integration/ralphLoopFlow.spec.ts +430 -0
  374. package/tests/integration/ralphLoopPartial.spec.ts +416 -0
  375. package/tests/integration/resumeAndBacktrack.spec.ts +278 -0
  376. package/tests/integration/spinnerLifecycle.spec.ts +270 -0
  377. package/tests/integration/summarizationFlow.spec.ts +135 -0
  378. package/tests/integration/testRunnerReal.spec.ts +63 -0
  379. package/tests/integration/webSearchAgent.spec.ts +155 -0
  380. package/tests/live/copilotSdkLive.spec.ts +149 -0
  381. package/tests/live/zavaFullWorkshop.spec.ts +515 -0
  382. package/tests/setup/loadEnv.ts +5 -0
  383. package/tests/unit/cli/developCommand.spec.ts +679 -0
  384. package/tests/unit/cli/directCommands.spec.ts +325 -0
  385. package/tests/unit/cli/envLoader.spec.ts +73 -0
  386. package/tests/unit/cli/ioContext.spec.ts +148 -0
  387. package/tests/unit/cli/preflight.spec.ts +125 -0
  388. package/tests/unit/cli/statusCommand.spec.ts +134 -0
  389. package/tests/unit/cli/workshopClientFallback.spec.ts +100 -0
  390. package/tests/unit/cli/workshopCommand.spec.ts +378 -0
  391. package/tests/unit/config/vitestEnvSetup.spec.ts +24 -0
  392. package/tests/unit/develop/checkpointState.spec.ts +378 -0
  393. package/tests/unit/develop/codeGenerator.spec.ts +447 -0
  394. package/tests/unit/develop/githubMcpAdapter.spec.ts +283 -0
  395. package/tests/unit/develop/mcpContextEnricher.spec.ts +564 -0
  396. package/tests/unit/develop/outputValidator.spec.ts +134 -0
  397. package/tests/unit/develop/pocScaffolder.spec.ts +451 -0
  398. package/tests/unit/develop/ralphLoop.spec.ts +1439 -0
  399. package/tests/unit/develop/templateRegistry.spec.ts +106 -0
  400. package/tests/unit/develop/testRunner.spec.ts +294 -0
  401. package/tests/unit/infraBicep.spec.ts +116 -0
  402. package/tests/unit/infraDeploy.spec.ts +102 -0
  403. package/tests/unit/infraTeardown.spec.ts +77 -0
  404. package/tests/unit/logging/logger.spec.ts +50 -0
  405. package/tests/unit/loop/conversationLoop.spec.ts +719 -0
  406. package/tests/unit/loop/phaseSummarizer.spec.ts +169 -0
  407. package/tests/unit/loop/streamingMarkdown.spec.ts +180 -0
  408. package/tests/unit/mcp/mcpManager.spec.ts +336 -0
  409. package/tests/unit/mcp/mcpTransport.spec.ts +689 -0
  410. package/tests/unit/mcp/retryPolicy.spec.ts +278 -0
  411. package/tests/unit/mcp/timeoutValidation.spec.ts +55 -0
  412. package/tests/unit/mcp/webSearch.spec.ts +718 -0
  413. package/tests/unit/phases/contextSummarizer.spec.ts +158 -0
  414. package/tests/unit/phases/discoveryEnricher.repeatCalls.spec.ts +125 -0
  415. package/tests/unit/phases/discoveryEnricher.spec.ts +512 -0
  416. package/tests/unit/phases/phaseExtractors.spec.ts +406 -0
  417. package/tests/unit/phases/phaseHandlers.spec.ts +483 -0
  418. package/tests/unit/prompts/promptLoader.spec.ts +144 -0
  419. package/tests/unit/schemas/pocSchemas.spec.ts +457 -0
  420. package/tests/unit/schemas/session.spec.ts +328 -0
  421. package/tests/unit/sessions/exportPaths.spec.ts +38 -0
  422. package/tests/unit/sessions/exportWriter.spec.ts +737 -0
  423. package/tests/unit/sessions/sessionManager.spec.ts +174 -0
  424. package/tests/unit/sessions/sessionStore.spec.ts +136 -0
  425. package/tests/unit/shared/activitySpinner.spec.ts +211 -0
  426. package/tests/unit/shared/cardsLoader.spec.ts +89 -0
  427. package/tests/unit/shared/copilotClient.spec.ts +185 -0
  428. package/tests/unit/shared/errorClassifier.spec.ts +152 -0
  429. package/tests/unit/shared/events.spec.ts +71 -0
  430. package/tests/unit/shared/markdownRenderer.spec.ts +42 -0
  431. package/tests/unit/shared/markdownRendererChunks.spec.ts +83 -0
  432. package/tests/unit/shared/tableRenderer.spec.ts +38 -0
  433. package/tsconfig.json +20 -0
  434. package/vitest.config.ts +15 -0
  435. 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
+ }