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,143 @@
1
+ /**
2
+ * Direct command mode (US3).
3
+ *
4
+ * Provides non-interactive and automation-friendly entry points:
5
+ * - `sofia workshop --session <id> --phase <phase>` jumps to a specific phase
6
+ * - Enforces required inputs in non-interactive mode
7
+ * - JSON-only stdout when --json
8
+ * - Retry transient failures when --retry specified
9
+ */
10
+ import { ConversationLoop } from '../loop/conversationLoop.js';
11
+ import { createPhaseHandler, getPhaseOrder } from '../phases/phaseHandlers.js';
12
+ import { classifyError, toUserMessage } from '../shared/errorClassifier.js';
13
+ // ── Validation ───────────────────────────────────────────────────────────────
14
+ const VALID_PHASES = getPhaseOrder();
15
+ function validateInputs(opts) {
16
+ if (!opts.sessionId) {
17
+ const error = 'Required: --session <id>. Provide a session ID to target.';
18
+ emitError(opts, error);
19
+ return { exitCode: 1, error };
20
+ }
21
+ if (opts.nonInteractive && !opts.phase) {
22
+ const error = 'Required: --phase <phase>. In non-interactive mode, a phase must be specified.';
23
+ emitError(opts, error);
24
+ return { exitCode: 1, error };
25
+ }
26
+ if (opts.phase && !VALID_PHASES.includes(opts.phase)) {
27
+ const error = `Invalid phase "${opts.phase}". Valid phases: ${VALID_PHASES.join(', ')}`;
28
+ emitError(opts, error);
29
+ return { exitCode: 1, error };
30
+ }
31
+ return null; // validation passed
32
+ }
33
+ function emitError(opts, message) {
34
+ if (opts.json || opts.io.isJsonMode) {
35
+ // JSON mode: follow the standard `{ error: "..." }` shape used by other commands
36
+ opts.io.write(JSON.stringify({ error: message }) + '\n');
37
+ }
38
+ else {
39
+ // Non-JSON mode: emit a human-readable error message so failures are visible
40
+ opts.io.write(`Error: ${message}\n`);
41
+ }
42
+ }
43
+ // ── Core execution ──────────────────────────────────────────────────────────
44
+ /**
45
+ * Run a direct command: load session, execute phase, persist results.
46
+ *
47
+ * Supports retry for transient failures (connection, timeout, etc.).
48
+ */
49
+ export async function runDirectCommand(opts) {
50
+ // 1. Validate inputs
51
+ const validationError = validateInputs(opts);
52
+ if (validationError)
53
+ return validationError;
54
+ // 2. Load session
55
+ let session;
56
+ try {
57
+ if (!(await opts.store.exists(opts.sessionId))) {
58
+ const error = `Session "${opts.sessionId}" not found.`;
59
+ emitError(opts, error);
60
+ return { exitCode: 1, error };
61
+ }
62
+ session = await opts.store.load(opts.sessionId);
63
+ }
64
+ catch (err) {
65
+ const error = err instanceof Error ? err.message : 'Failed to load session';
66
+ emitError(opts, error);
67
+ return { exitCode: 1, error };
68
+ }
69
+ // 3. Set phase
70
+ if (opts.phase) {
71
+ session.phase = opts.phase;
72
+ session.updatedAt = new Date().toISOString();
73
+ await opts.store.save(session);
74
+ }
75
+ // 4. Run phase with retry
76
+ const maxAttempts = (opts.retry ?? 0) + 1;
77
+ let lastError;
78
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
79
+ try {
80
+ session = await runPhase(session, opts);
81
+ return { exitCode: 0 };
82
+ }
83
+ catch (err) {
84
+ lastError = err instanceof Error ? err : new Error(String(err));
85
+ const classification = classifyError(lastError);
86
+ if (!classification.recoverable || attempt >= maxAttempts) {
87
+ const error = lastError.message;
88
+ emitError(opts, error);
89
+ return { exitCode: 1, error };
90
+ }
91
+ // Log retry attempt
92
+ opts.io.writeActivity(`Attempt ${attempt}/${maxAttempts} failed (${classification.category}): ${toUserMessage(classification)}. Retrying...`);
93
+ // Brief backoff before retry
94
+ await new Promise((resolve) => setTimeout(resolve, 100 * attempt));
95
+ }
96
+ }
97
+ // Should not reach here, but safety net
98
+ const error = lastError?.message ?? 'Unknown error';
99
+ return { exitCode: 1, error };
100
+ }
101
+ // ── Phase runner ────────────────────────────────────────────────────────────
102
+ async function runPhase(session, opts) {
103
+ const phase = session.phase;
104
+ const handler = createPhaseHandler(phase);
105
+ await handler._preload();
106
+ const events = [];
107
+ const loop = new ConversationLoop({
108
+ client: opts.client,
109
+ io: opts.io,
110
+ session,
111
+ phaseHandler: handler,
112
+ onEvent: (e) => {
113
+ events.push(e);
114
+ if (opts.debug && e.type === 'Activity') {
115
+ opts.io.writeActivity(e.message);
116
+ }
117
+ },
118
+ onSessionUpdate: async (updatedSession) => {
119
+ session = updatedSession;
120
+ await opts.store.save(session);
121
+ },
122
+ });
123
+ session = await loop.run();
124
+ session.updatedAt = new Date().toISOString();
125
+ await opts.store.save(session);
126
+ // In JSON mode, emit result summary
127
+ if (opts.json || opts.io.isJsonMode) {
128
+ opts.io.write(JSON.stringify({
129
+ sessionId: session.sessionId,
130
+ phase: session.phase,
131
+ status: session.status,
132
+ updatedAt: session.updatedAt,
133
+ }) + '\n');
134
+ }
135
+ // Decision gate
136
+ const gateResult = await opts.io.showDecisionGate(phase);
137
+ if (gateResult.choice === 'exit') {
138
+ session.status = 'Paused';
139
+ session.updatedAt = new Date().toISOString();
140
+ await opts.store.save(session);
141
+ }
142
+ return session;
143
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Loads a `.env` file into `process.env` without overwriting existing values.
3
+ *
4
+ * Called at CLI startup so that variables written by `infra/deploy.sh`
5
+ * (e.g. `FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL_DEPLOYMENT_NAME`) are
6
+ * available to the application automatically.
7
+ */
8
+ import { config } from 'dotenv';
9
+ /**
10
+ * Load environment variables from the given `.env` file path.
11
+ * Variables already present in `process.env` are **not** overwritten.
12
+ * If the file does not exist the call is a silent no-op.
13
+ */
14
+ export function loadEnvFile(envPath) {
15
+ config({ path: envPath, override: false, quiet: true });
16
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Export command handler.
3
+ *
4
+ * Implements `sofia export` — exports workshop artifacts for a session.
5
+ * Generates Markdown files per phase and a summary.json.
6
+ */
7
+ import { createDefaultStore } from '../sessions/sessionStore.js';
8
+ import { exportSession } from '../sessions/exportWriter.js';
9
+ import { ensureExportDir } from '../sessions/exportPaths.js';
10
+ export async function exportCommand(opts) {
11
+ if (!opts.session) {
12
+ if (opts.json) {
13
+ process.stdout.write(JSON.stringify({ error: 'No session specified. Use --session <id>.' }) + '\n');
14
+ }
15
+ else {
16
+ console.error('Error: No session specified. Use --session <id>.');
17
+ }
18
+ process.exitCode = 1;
19
+ return;
20
+ }
21
+ const store = createDefaultStore();
22
+ if (!(await store.exists(opts.session))) {
23
+ const msg = `Session "${opts.session}" not found.`;
24
+ if (opts.json) {
25
+ process.stdout.write(JSON.stringify({ error: msg }) + '\n');
26
+ }
27
+ else {
28
+ console.error(`Error: ${msg}`);
29
+ }
30
+ process.exitCode = 1;
31
+ return;
32
+ }
33
+ const session = await store.load(opts.session);
34
+ const exportDir = opts.output
35
+ ? opts.output
36
+ : await ensureExportDir(opts.session);
37
+ const result = await exportSession(session, exportDir);
38
+ if (opts.json) {
39
+ process.stdout.write(JSON.stringify({
40
+ sessionId: session.sessionId,
41
+ exported: true,
42
+ exportDir: result.exportDir,
43
+ files: result.files,
44
+ }) + '\n');
45
+ }
46
+ else {
47
+ console.log(`Exported session "${session.sessionId}" to ${result.exportDir}`);
48
+ console.log(`Generated ${result.files.length} file(s):`);
49
+ for (const file of result.files) {
50
+ console.log(` - ${file.path} (${file.type})`);
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * sofIA CLI entrypoint.
4
+ *
5
+ * Provides the main `sofia` command. Running `sofia` with no subcommand
6
+ * starts the workshop flow (default). Explicit subcommands:
7
+ * - workshop: alias for the default workshop flow
8
+ * - status: display session status
9
+ * - export: export session artifacts
10
+ */
11
+ import path from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { Command } from 'commander';
14
+ import { loadEnvFile } from './envLoader.js';
15
+ // ── Load .env from workspace root ──────────────────────────────────────────
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+ loadEnvFile(path.resolve(__dirname, '../../.env'));
18
+ // ── Build CLI ──────────────────────────────────────────────────────────────
19
+ /**
20
+ * Build the Commander program. Accepts optional handler overrides for testing.
21
+ * When handlers are not provided, they dynamically import the real modules.
22
+ */
23
+ export function buildCli(handlers) {
24
+ const program = new Command();
25
+ program.name('sofia').description('sofIA — AI Discovery Workshop CLI').version('0.1.0');
26
+ // ── Global options ──────────────────────────────────────────────────────
27
+ program
28
+ .option('--session <id>', 'Target an existing session')
29
+ .option('--json', 'Emit machine-readable JSON only on stdout')
30
+ .option('--debug', 'Enable debug telemetry')
31
+ .option('--log-file <path>', 'Write structured logs to file')
32
+ .option('--non-interactive', 'Disallow prompts; fail with actionable error if input is missing');
33
+ // ── Workshop options promoted to top level (FR-004) ──────────────────────
34
+ program
35
+ .option('--new-session', 'Start a new session')
36
+ .option('--phase <phase>', 'Jump to a specific phase')
37
+ .option('--retry <count>', 'Retry transient failures N times', parseInt);
38
+ // ── Workshop handler logic ──────────────────────────────────────────────
39
+ async function invokeWorkshopHandler(programOpts, cmdOpts) {
40
+ const merged = { ...programOpts, ...cmdOpts };
41
+ if (handlers?.workshopHandler) {
42
+ await handlers.workshopHandler(merged);
43
+ return;
44
+ }
45
+ // Direct command mode: if --session and --phase are both specified
46
+ if (merged.session && merged.phase) {
47
+ const { runDirectCommand } = await import('./directCommands.js');
48
+ const { createLoopIO } = await import('./ioContext.js');
49
+ const { createDefaultStore } = await import('../sessions/sessionStore.js');
50
+ const { createCopilotClient } = await import('../shared/copilotClient.js');
51
+ const { getLogger } = await import('../logging/logger.js');
52
+ const store = createDefaultStore();
53
+ const io = createLoopIO({
54
+ json: merged.json,
55
+ nonInteractive: merged.nonInteractive,
56
+ });
57
+ let client;
58
+ try {
59
+ client = await createCopilotClient();
60
+ }
61
+ catch (err) {
62
+ const logger = getLogger();
63
+ logger.error({ err }, 'Failed to create Copilot client — cannot run direct command');
64
+ const msg = err instanceof Error ? err.message : 'Unknown error creating Copilot client';
65
+ if (merged.json) {
66
+ process.stdout.write(JSON.stringify({ error: msg }) + '\n');
67
+ }
68
+ else {
69
+ console.error(`Error: ${msg}`);
70
+ }
71
+ process.exitCode = 1;
72
+ return;
73
+ }
74
+ const result = await runDirectCommand({
75
+ sessionId: merged.session,
76
+ phase: merged.phase,
77
+ store,
78
+ client,
79
+ io,
80
+ nonInteractive: merged.nonInteractive,
81
+ json: merged.json,
82
+ debug: merged.debug,
83
+ retry: merged.retry,
84
+ });
85
+ process.exitCode = result.exitCode;
86
+ return;
87
+ }
88
+ const { workshopCommand } = await import('./workshopCommand.js');
89
+ await workshopCommand(merged);
90
+ }
91
+ // ── Workshop command (default + alias) ─────────────────────────────────
92
+ program
93
+ .command('workshop', { isDefault: true })
94
+ .description('Start or resume a governed AI Discovery Workshop session')
95
+ .action(async () => {
96
+ const opts = program.opts();
97
+ await invokeWorkshopHandler(opts);
98
+ });
99
+ // ── Status command ────────────────────────────────────────────────────
100
+ program
101
+ .command('status')
102
+ .description('Display session status and next expected action')
103
+ .action(async () => {
104
+ if (handlers?.statusHandler) {
105
+ await handlers.statusHandler(program.opts());
106
+ return;
107
+ }
108
+ const { statusCommand } = await import('./statusCommand.js');
109
+ const globalOpts = program.opts();
110
+ await statusCommand(globalOpts);
111
+ });
112
+ // ── Export command ────────────────────────────────────────────────────
113
+ program
114
+ .command('export')
115
+ .description('Export workshop artifacts for a session')
116
+ .option('--output <dir>', 'Output directory (default: ./exports/<sessionId>/)')
117
+ .action(async (opts) => {
118
+ if (handlers?.exportHandler) {
119
+ await handlers.exportHandler({ ...program.opts(), ...opts });
120
+ return;
121
+ }
122
+ const { exportCommand } = await import('./exportCommand.js');
123
+ const globalOpts = program.opts();
124
+ await exportCommand({ ...globalOpts, ...opts });
125
+ });
126
+ // ── Dev command ───────────────────────────────────────────────────────
127
+ program
128
+ .command('dev')
129
+ .description('Generate a proof-of-concept repository for a completed workshop session')
130
+ .option('--max-iterations <n>', 'Maximum Ralph loop iterations (default: 20)', parseInt)
131
+ .option('--output <dir>', 'Output directory for the PoC (default: ./poc/<sessionId>/)')
132
+ .option('--force', 'Overwrite existing output directory and start fresh')
133
+ .action(async (opts) => {
134
+ const { developCommand } = await import('./developCommand.js');
135
+ const { createDefaultStore } = await import('../sessions/sessionStore.js');
136
+ const { createLoopIO } = await import('./ioContext.js');
137
+ const { createCopilotClient } = await import('../shared/copilotClient.js');
138
+ const { getLogger } = await import('../logging/logger.js');
139
+ const { loadMcpConfig, McpManager } = await import('../mcp/mcpManager.js');
140
+ const { join: pathJoin } = await import('node:path');
141
+ const globalOpts = program.opts();
142
+ const merged = { ...globalOpts, ...opts };
143
+ const store = createDefaultStore();
144
+ const io = createLoopIO({
145
+ json: merged.json,
146
+ nonInteractive: merged.nonInteractive,
147
+ });
148
+ let client;
149
+ try {
150
+ client = await createCopilotClient();
151
+ }
152
+ catch (err) {
153
+ const logger = getLogger();
154
+ logger.error({ err }, 'Failed to create Copilot client');
155
+ const msg = err instanceof Error ? err.message : 'Unknown error creating Copilot client';
156
+ if (merged.json) {
157
+ process.stdout.write(JSON.stringify({ error: msg }) + '\n');
158
+ }
159
+ else {
160
+ process.stderr.write(`Error: ${msg}\n`);
161
+ }
162
+ process.exitCode = 1;
163
+ return;
164
+ }
165
+ // Load MCP config and wire adapters when MCP servers are configured
166
+ const mcpConfigPath = pathJoin(process.cwd(), '.vscode', 'mcp.json');
167
+ let mcpManager;
168
+ try {
169
+ const mcpConfig = await loadMcpConfig(mcpConfigPath);
170
+ if (Object.keys(mcpConfig.servers).length > 0) {
171
+ mcpManager = new McpManager(mcpConfig);
172
+ // Mark all configured servers as connected (optimistic: actual MCP failures
173
+ // degrade gracefully inside GitHubMcpAdapter / McpContextEnricher)
174
+ for (const name of mcpManager.listServers()) {
175
+ mcpManager.markConnected(name);
176
+ }
177
+ }
178
+ }
179
+ catch {
180
+ // MCP config is optional — proceed without it
181
+ }
182
+ await developCommand(merged, {
183
+ store,
184
+ io,
185
+ client,
186
+ mcpManager,
187
+ });
188
+ });
189
+ return program;
190
+ }
191
+ // ── Auto-parse when run as CLI entrypoint ─────────────────────────────────
192
+ // Only auto-parse when running as the main CLI entrypoint, not when imported for testing
193
+ const isMainModule = process.argv[1] &&
194
+ (process.argv[1].endsWith('/cli/index.js') ||
195
+ process.argv[1].endsWith('/cli/index.ts') ||
196
+ process.argv[1].endsWith('sofia'));
197
+ if (isMainModule) {
198
+ const program = buildCli();
199
+ program.parseAsync(process.argv).catch((err) => {
200
+ console.error(err instanceof Error ? err.message : 'Unknown error');
201
+ process.exit(1);
202
+ });
203
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * IO Context.
3
+ *
4
+ * Detects TTY vs non-TTY, JSON mode, and provides the LoopIO
5
+ * implementation for the ConversationLoop based on CLI options.
6
+ */
7
+ import * as readline from 'node:readline';
8
+ import { renderMarkdown } from '../shared/markdownRenderer.js';
9
+ /**
10
+ * Create a LoopIO implementation based on CLI context.
11
+ */
12
+ export function createLoopIO(options = {}) {
13
+ const input = options.input ?? process.stdin;
14
+ const output = options.output ?? process.stdout;
15
+ const errorOutput = options.errorOutput ?? process.stderr;
16
+ const isTTY = !options.nonInteractive && Boolean(input.isTTY);
17
+ const isJsonMode = options.json ?? false;
18
+ const isDebug = options.debug ?? false;
19
+ let rl = null;
20
+ let rlClosed = false;
21
+ function getReadline() {
22
+ if (!rl || rlClosed) {
23
+ rlClosed = false;
24
+ rl = readline.createInterface({
25
+ input: input,
26
+ output: isTTY ? output : undefined,
27
+ terminal: isTTY,
28
+ });
29
+ rl.on('close', () => { rlClosed = true; });
30
+ }
31
+ return rl;
32
+ }
33
+ return {
34
+ write(text) {
35
+ output.write(text);
36
+ },
37
+ writeActivity(text) {
38
+ if (!isJsonMode) {
39
+ errorOutput.write(`[activity] ${text}\n`);
40
+ }
41
+ },
42
+ writeToolSummary(toolName, summary, details) {
43
+ if (isJsonMode || options.nonInteractive)
44
+ return;
45
+ errorOutput.write(`✓ ${toolName}: ${summary}\n`);
46
+ if (isDebug && details) {
47
+ if (details.args) {
48
+ errorOutput.write(` args: ${JSON.stringify(details.args, null, 2)}\n`);
49
+ }
50
+ if (details.result !== undefined) {
51
+ errorOutput.write(` result: ${JSON.stringify(details.result, null, 2)}\n`);
52
+ }
53
+ }
54
+ },
55
+ async readInput(prompt) {
56
+ if (options.nonInteractive) {
57
+ return null; // Non-interactive mode: no input
58
+ }
59
+ return new Promise((resolve) => {
60
+ const r = getReadline();
61
+ r.question(prompt ?? '> ', (answer) => {
62
+ resolve(answer);
63
+ });
64
+ r.once('close', () => resolve(null));
65
+ });
66
+ },
67
+ async showDecisionGate(phase) {
68
+ if (isJsonMode || options.nonInteractive) {
69
+ return { choice: 'continue' };
70
+ }
71
+ const phaseOrder = [
72
+ 'Discover', 'Ideate', 'Design', 'Select', 'Plan', 'Develop', 'Complete',
73
+ ];
74
+ const currentIdx = phaseOrder.indexOf(phase);
75
+ const nextPhase = currentIdx < phaseOrder.length - 1 ? phaseOrder[currentIdx + 1] : null;
76
+ const rendered = renderMarkdown(`\n---\n\n**Phase "${phase}" complete.**\n\n` +
77
+ `Options:\n` +
78
+ ` 1. Continue to ${nextPhase ?? 'Complete'}\n` +
79
+ ` 2. Refine current phase\n` +
80
+ ` 3. Return to main menu\n` +
81
+ ` 4. Exit\n`, { isTTY });
82
+ output.write(rendered);
83
+ return new Promise((resolve) => {
84
+ const r = getReadline();
85
+ r.question('Choose [1-4]: ', (answer) => {
86
+ const choiceMap = {
87
+ '1': 'continue',
88
+ '2': 'refine',
89
+ '3': 'menu',
90
+ '4': 'exit',
91
+ };
92
+ const choice = choiceMap[answer.trim()] ?? 'continue';
93
+ resolve({
94
+ choice,
95
+ targetPhase: choice === 'continue' ? (nextPhase ?? undefined) : undefined,
96
+ });
97
+ });
98
+ });
99
+ },
100
+ isJsonMode,
101
+ isTTY,
102
+ };
103
+ }
104
+ /**
105
+ * Close any open readline interfaces.
106
+ */
107
+ export function closeIO(_io) {
108
+ // The IO might hold a readline interface — no-op if not applicable
109
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Preflight checks — validates environment readiness before starting a workshop.
3
+ *
4
+ * Each check returns a PreflightCheck result. runPreflightChecks() collects all
5
+ * results, never short-circuits, and returns an aggregate PreflightResult.
6
+ */
7
+ /**
8
+ * Run all preflight checks in parallel and aggregate results.
9
+ *
10
+ * - Checks that throw are caught and reported as fail with `required: true`.
11
+ * - Overall pass requires that no check with `required !== false` has status 'fail'.
12
+ */
13
+ export async function runPreflightChecks(checks) {
14
+ const entries = Object.entries(checks);
15
+ const results = await Promise.all(entries.map(async ([key, fn]) => {
16
+ try {
17
+ return await fn();
18
+ }
19
+ catch (err) {
20
+ const message = err instanceof Error ? err.message : String(err);
21
+ return {
22
+ name: key.replace(/^check/, '').replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, ''),
23
+ status: 'fail',
24
+ message,
25
+ required: true,
26
+ };
27
+ }
28
+ }));
29
+ const passed = results.every((c) => c.status !== 'fail' || c.required === false);
30
+ return { passed, checks: results };
31
+ }
32
+ /**
33
+ * Check for legacy web search env vars (FR-016).
34
+ *
35
+ * If `SOFIA_FOUNDRY_AGENT_ENDPOINT` or `SOFIA_FOUNDRY_AGENT_KEY` are set,
36
+ * returns a fail result with migration instructions.
37
+ */
38
+ export async function checkLegacyWebSearchEnvVars() {
39
+ const legacyEndpoint = process.env.SOFIA_FOUNDRY_AGENT_ENDPOINT;
40
+ const legacyKey = process.env.SOFIA_FOUNDRY_AGENT_KEY;
41
+ if (legacyEndpoint || legacyKey) {
42
+ return {
43
+ name: 'legacy-web-search-env',
44
+ status: 'fail',
45
+ message: 'Legacy web search env vars detected. ' +
46
+ 'Migrate: replace SOFIA_FOUNDRY_AGENT_ENDPOINT with FOUNDRY_PROJECT_ENDPOINT ' +
47
+ 'and remove SOFIA_FOUNDRY_AGENT_KEY (API key auth is no longer used). ' +
48
+ 'See docs/environment.md',
49
+ required: true,
50
+ };
51
+ }
52
+ return {
53
+ name: 'legacy-web-search-env',
54
+ status: 'pass',
55
+ message: 'No legacy web search env vars detected',
56
+ };
57
+ }