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,73 @@
1
+ /**
2
+ * Logger setup with pino.
3
+ *
4
+ * Features:
5
+ * - File logging when --log-file is provided
6
+ * - Stderr logging by default
7
+ * - Automatic redaction of secrets/PII (password, token, secret, key, auth)
8
+ */
9
+ import pino from 'pino';
10
+ import type { Logger } from 'pino';
11
+ import { mkdirSync } from 'node:fs';
12
+ import { dirname } from 'node:path';
13
+
14
+ export interface LoggerOptions {
15
+ logFile?: string;
16
+ level?: string;
17
+ name?: string;
18
+ }
19
+
20
+ const REDACT_PATHS = [
21
+ 'password',
22
+ 'token',
23
+ 'secret',
24
+ 'apiKey',
25
+ 'api_key',
26
+ 'authorization',
27
+ 'auth',
28
+ 'credential',
29
+ 'credentials',
30
+ ];
31
+
32
+ /**
33
+ * Create a configured pino logger.
34
+ *
35
+ * If logFile is specified, logs go to the file.
36
+ * Otherwise, logs go to stderr (to keep stdout clean for CLI output).
37
+ */
38
+ export function createLogger(options: LoggerOptions = {}): Logger {
39
+ const { logFile, level = 'info', name = 'sofia' } = options;
40
+
41
+ const pinoOpts: pino.LoggerOptions = {
42
+ name,
43
+ level,
44
+ redact: {
45
+ paths: REDACT_PATHS,
46
+ censor: '[REDACTED]',
47
+ },
48
+ };
49
+
50
+ if (logFile) {
51
+ mkdirSync(dirname(logFile), { recursive: true });
52
+ const dest = pino.destination({ dest: logFile, sync: false });
53
+ return pino(pinoOpts, dest);
54
+ }
55
+
56
+ // Default: write to stderr to keep stdout clean
57
+ return pino(pinoOpts, pino.destination({ dest: 2, sync: false }));
58
+ }
59
+
60
+ /** Global logger instance; call initGlobalLogger() to configure it. */
61
+ let globalLogger: Logger | undefined;
62
+
63
+ export function initGlobalLogger(options: LoggerOptions = {}): Logger {
64
+ globalLogger = createLogger(options);
65
+ return globalLogger;
66
+ }
67
+
68
+ export function getLogger(): Logger {
69
+ if (!globalLogger) {
70
+ globalLogger = createLogger();
71
+ }
72
+ return globalLogger;
73
+ }
@@ -0,0 +1,355 @@
1
+ /**
2
+ * ConversationLoop abstraction.
3
+ *
4
+ * Orchestrates multi-turn conversations with the LLM in a phase-based workflow.
5
+ * Handles:
6
+ * - Streaming renderer (incremental text output)
7
+ * - Event dispatching (TextDelta, Activity, ToolCall, etc.)
8
+ * - Ctrl+C handling (graceful shutdown)
9
+ * - Decision gates between phases
10
+ *
11
+ * Single entry point for all phase-based conversations to avoid
12
+ * duplicate inline multi-turn loops (FR-015).
13
+ */
14
+ import type {
15
+ ConversationSession,
16
+ CopilotClient,
17
+ CopilotMessage,
18
+ SessionOptions,
19
+ } from '../shared/copilotClient.js';
20
+ import type { SofiaEvent } from '../shared/events.js';
21
+ import { createActivityEvent } from '../shared/events.js';
22
+ import { renderMarkdown } from '../shared/markdownRenderer.js';
23
+ import type { ActivitySpinner } from '../shared/activitySpinner.js';
24
+ import { createNoOpSpinner } from '../shared/activitySpinner.js';
25
+ import type { PhaseValue } from '../shared/schemas/session.js';
26
+ import type { WorkshopSession } from '../shared/schemas/session.js';
27
+ import { phaseSummarize } from './phaseSummarizer.js';
28
+
29
+ // ── Types ────────────────────────────────────────────────────────────────────
30
+
31
+ export type DecisionGateChoice = 'continue' | 'refine' | 'choose-phase' | 'menu' | 'exit';
32
+
33
+ export interface DecisionGateResult {
34
+ choice: DecisionGateChoice;
35
+ targetPhase?: PhaseValue;
36
+ }
37
+
38
+ export interface LoopIO {
39
+ /** Write text to the user (stdout or equivalent). */
40
+ write(text: string): void;
41
+ /** Write activity/telemetry to stderr. */
42
+ writeActivity(text: string): void;
43
+ /** Write a tool completion summary to stderr. */
44
+ writeToolSummary(
45
+ toolName: string,
46
+ summary: string,
47
+ details?: { args?: Record<string, unknown>; result?: unknown },
48
+ ): void;
49
+ /** Read user input. Returns the input string or null on EOF/Ctrl+D. */
50
+ readInput(prompt?: string): Promise<string | null>;
51
+ /** Show the decision gate and get user choice. */
52
+ showDecisionGate(phase: PhaseValue): Promise<DecisionGateResult>;
53
+ /** Check if running in JSON mode. */
54
+ isJsonMode: boolean;
55
+ /** Check if TTY. */
56
+ isTTY: boolean;
57
+ }
58
+
59
+ export interface PhaseHandler {
60
+ /** The phase this handler manages. */
61
+ phase: PhaseValue;
62
+ /** Build the system prompt for this phase. */
63
+ buildSystemPrompt(session: WorkshopSession): string;
64
+ /** Get reference documents to include. */
65
+ getReferences?(session: WorkshopSession): string[];
66
+ /** Post-process the phase result (extract structured data, update session). */
67
+ extractResult(session: WorkshopSession, response: string): Partial<WorkshopSession>;
68
+ /** Optional async hook called after extractResult to perform async enrichment. */
69
+ postExtract?(session: WorkshopSession): Promise<Partial<WorkshopSession>>;
70
+ /** Optional: determine if the phase is complete based on conversation. */
71
+ isComplete?(session: WorkshopSession, response: string): boolean;
72
+ /** Generate the initial message to send to the LLM at phase start. */
73
+ getInitialMessage?(session: WorkshopSession): string;
74
+ }
75
+
76
+ export interface ConversationLoopOptions {
77
+ client: CopilotClient;
78
+ io: LoopIO;
79
+ session: WorkshopSession;
80
+ phaseHandler: PhaseHandler;
81
+ onEvent?: (event: SofiaEvent) => void;
82
+ onSessionUpdate?: (session: WorkshopSession) => Promise<void>;
83
+ /** If provided, send this message to the LLM before waiting for user input (auto-start). */
84
+ initialMessage?: string;
85
+ /** Activity spinner for visual feedback (no-op spinner used if omitted). */
86
+ spinner?: ActivitySpinner;
87
+ }
88
+
89
+ // ── ConversationLoop ─────────────────────────────────────────────────────────
90
+
91
+ export class ConversationLoop {
92
+ private aborted = false;
93
+ private session: WorkshopSession;
94
+ private readonly client: CopilotClient;
95
+ private readonly io: LoopIO;
96
+ private readonly handler: PhaseHandler;
97
+ private readonly onEvent: (event: SofiaEvent) => void;
98
+ private readonly onSessionUpdate: (session: WorkshopSession) => Promise<void>;
99
+ private readonly initialMessage?: string;
100
+ private readonly spinner: ActivitySpinner;
101
+
102
+ constructor(options: ConversationLoopOptions) {
103
+ this.client = options.client;
104
+ this.io = options.io;
105
+ this.session = { ...options.session };
106
+ this.handler = options.phaseHandler;
107
+ this.onEvent = options.onEvent ?? (() => {});
108
+ this.onSessionUpdate = options.onSessionUpdate ?? (async () => {});
109
+ this.initialMessage = options.initialMessage;
110
+ this.spinner = options.spinner ?? createNoOpSpinner();
111
+ }
112
+
113
+ /** Run the conversation loop for the current phase. */
114
+ async run(): Promise<WorkshopSession> {
115
+ this.setupSignalHandler();
116
+
117
+ let systemPrompt = this.handler.buildSystemPrompt(this.session);
118
+
119
+ // FR-007b, FR-007c: Inject phase boundary instruction to prevent LLM drift
120
+ systemPrompt += `\n\nYou are in the ${this.handler.phase} phase. Do NOT introduce or begin the next phase. The user will be offered a decision gate when this phase is complete.`;
121
+
122
+ // Inject prior conversation history into the system prompt when resuming
123
+ // so the LLM has context from previous turns in this phase.
124
+ const priorTurns = (this.session.turns ?? []).filter((t) => t.phase === this.handler.phase);
125
+ if (priorTurns.length > 0) {
126
+ const historyBlock = priorTurns.map((t) => `[${t.role}]: ${t.content}`).join('\n\n');
127
+ systemPrompt += `\n\n## Previous conversation history\n\n${historyBlock}`;
128
+ }
129
+
130
+ const references = this.handler.getReferences?.(this.session) ?? [];
131
+ const sessionOpts: SessionOptions = { systemPrompt, references };
132
+
133
+ const conversationSession = await this.client.createSession(sessionOpts);
134
+
135
+ this.emitEvent(createActivityEvent(`Starting ${this.handler.phase} phase`));
136
+
137
+ // Auto-start: send initial message to LLM before waiting for user input
138
+ if (this.initialMessage) {
139
+ const response = await this.streamResponse(conversationSession, {
140
+ role: 'user',
141
+ content: this.initialMessage,
142
+ });
143
+
144
+ const now = new Date().toISOString();
145
+ const turns = this.session.turns ?? [];
146
+ turns.push(
147
+ {
148
+ phase: this.handler.phase,
149
+ sequence: turns.length + 1,
150
+ role: 'user',
151
+ content: this.initialMessage,
152
+ timestamp: now,
153
+ },
154
+ {
155
+ phase: this.handler.phase,
156
+ sequence: turns.length + 2,
157
+ role: 'assistant',
158
+ content: response,
159
+ timestamp: now,
160
+ },
161
+ );
162
+
163
+ const updates = this.handler.extractResult(this.session, response);
164
+ this.session = {
165
+ ...this.session,
166
+ ...updates,
167
+ turns,
168
+ updatedAt: now,
169
+ };
170
+
171
+ // Run async post-extraction hook if present (e.g., discovery enrichment)
172
+ if (this.handler.postExtract) {
173
+ const postUpdates = await this.handler.postExtract(this.session);
174
+ this.session = { ...this.session, ...postUpdates, updatedAt: new Date().toISOString() };
175
+ }
176
+
177
+ await this.onSessionUpdate(this.session);
178
+ }
179
+
180
+ // Main conversation loop
181
+ while (!this.aborted) {
182
+ const userInput = await this.io.readInput(`[${this.handler.phase}] > `);
183
+
184
+ if (userInput === null) {
185
+ // EOF / Ctrl+D — treat as "done" signal
186
+ break;
187
+ }
188
+
189
+ const trimmed = userInput.trim();
190
+ if (trimmed.toLowerCase() === 'done' || trimmed === '') {
191
+ // Check if handler considers the phase complete
192
+ if (this.handler.isComplete?.(this.session, '') !== false) {
193
+ break;
194
+ }
195
+ // Otherwise continue the conversation
196
+ }
197
+
198
+ // Send user message and stream response
199
+ const response = await this.streamResponse(conversationSession, {
200
+ role: 'user',
201
+ content: trimmed,
202
+ });
203
+
204
+ // Accumulate turn history
205
+ const now = new Date().toISOString();
206
+ const turns = this.session.turns ?? [];
207
+ turns.push(
208
+ {
209
+ phase: this.handler.phase,
210
+ sequence: turns.length + 1,
211
+ role: 'user',
212
+ content: trimmed,
213
+ timestamp: now,
214
+ },
215
+ {
216
+ phase: this.handler.phase,
217
+ sequence: turns.length + 2,
218
+ role: 'assistant',
219
+ content: response,
220
+ timestamp: now,
221
+ },
222
+ );
223
+
224
+ // Extract structured data from the response
225
+ const updates = this.handler.extractResult(this.session, response);
226
+
227
+ this.session = {
228
+ ...this.session,
229
+ ...updates,
230
+ turns,
231
+ updatedAt: now,
232
+ };
233
+
234
+ // Run async post-extraction hook if present (e.g., discovery enrichment)
235
+ if (this.handler.postExtract) {
236
+ const postUpdates = await this.handler.postExtract(this.session);
237
+ this.session = { ...this.session, ...postUpdates, updatedAt: new Date().toISOString() };
238
+ }
239
+
240
+ // Persist after every turn (FR-039a)
241
+ await this.onSessionUpdate(this.session);
242
+ }
243
+
244
+ // FR-006: Post-phase summarization fallback
245
+ // If structured data wasn't extracted during conversation, try a dedicated summarization call
246
+ try {
247
+ const summarizationUpdates = await phaseSummarize(
248
+ this.client,
249
+ this.handler.phase,
250
+ this.session,
251
+ this.handler,
252
+ );
253
+ if (Object.keys(summarizationUpdates).length > 0) {
254
+ this.session = {
255
+ ...this.session,
256
+ ...summarizationUpdates,
257
+ updatedAt: new Date().toISOString(),
258
+ };
259
+ await this.onSessionUpdate(this.session);
260
+ }
261
+ } catch {
262
+ // Summarization failure is non-fatal
263
+ }
264
+
265
+ return this.session;
266
+ }
267
+
268
+ /** Stream response from the LLM and render incrementally. */
269
+ private async streamResponse(
270
+ session: ConversationSession,
271
+ message: CopilotMessage,
272
+ ): Promise<string> {
273
+ const chunks: string[] = [];
274
+ let firstTextDelta = true;
275
+
276
+ // Start "Thinking..." spinner before sending
277
+ this.spinner.startThinking();
278
+
279
+ try {
280
+ for await (const event of session.send(message)) {
281
+ // Check if user pressed Ctrl+C during streaming
282
+ if (this.aborted) break;
283
+
284
+ this.emitEvent(event);
285
+
286
+ if (event.type === 'TextDelta') {
287
+ // Stop spinner on first text output
288
+ if (firstTextDelta) {
289
+ this.spinner.stop();
290
+ firstTextDelta = false;
291
+ }
292
+
293
+ chunks.push(event.text);
294
+ if (!this.io.isJsonMode) {
295
+ // Render markdown for TTY, raw for non-TTY
296
+ if (this.io.isTTY) {
297
+ this.io.write(renderMarkdown(event.text, { isTTY: true }));
298
+ } else {
299
+ this.io.write(event.text);
300
+ }
301
+ }
302
+ } else if (event.type === 'Activity') {
303
+ this.io.writeActivity(event.message);
304
+ } else if (event.type === 'ToolCall') {
305
+ this.spinner.startToolCall(event.toolName);
306
+ } else if (event.type === 'ToolResult') {
307
+ const summary =
308
+ typeof event.result === 'string'
309
+ ? event.result
310
+ : JSON.stringify(event.result).slice(0, 120);
311
+ this.spinner.completeToolCall(event.toolName, summary);
312
+ this.io.writeToolSummary(event.toolName, summary);
313
+ // Resume thinking spinner in case more processing follows
314
+ this.spinner.startThinking();
315
+ }
316
+ }
317
+ } finally {
318
+ // Always stop spinner — prevents cursor-hidden state on errors
319
+ this.spinner.stop();
320
+ }
321
+
322
+ const fullResponse = chunks.join('');
323
+
324
+ // In JSON mode, output the full rendered response
325
+ if (this.io.isJsonMode) {
326
+ this.io.write(JSON.stringify({ phase: this.handler.phase, content: fullResponse }));
327
+ } else {
328
+ this.io.write('\n');
329
+ }
330
+
331
+ return fullResponse;
332
+ }
333
+
334
+ private emitEvent(event: SofiaEvent): void {
335
+ this.onEvent(event);
336
+ }
337
+
338
+ private setupSignalHandler(): void {
339
+ const handler = () => {
340
+ this.aborted = true;
341
+ this.emitEvent(createActivityEvent('Ctrl+C received — finishing current turn'));
342
+ };
343
+ // Avoid MaxListenersExceededWarning when many loops are created in tests
344
+ const current = process.listenerCount('SIGINT');
345
+ if (current >= 10) {
346
+ process.setMaxListeners(current + 1);
347
+ }
348
+ process.once('SIGINT', handler);
349
+ }
350
+
351
+ /** Get the current session state. */
352
+ getSession(): WorkshopSession {
353
+ return { ...this.session };
354
+ }
355
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Post-phase summarization utility.
3
+ *
4
+ * When the conversation loop finishes a phase and the expected structured
5
+ * session field is still null, this module makes a one-shot LLM call to
6
+ * extract structured data from the full conversation transcript.
7
+ *
8
+ * Contract: specs/006-workshop-extraction-fixes/contracts/summarization-and-export.md
9
+ * FRs: FR-001 through FR-007
10
+ */
11
+ import type { CopilotClient } from '../shared/copilotClient.js';
12
+ import type { PhaseValue, WorkshopSession } from '../shared/schemas/session.js';
13
+ import type { PhaseHandler } from './conversationLoop.js';
14
+ import { loadSummarizationPrompt } from '../prompts/promptLoader.js';
15
+
16
+ // ── Phase → session field mapping ────────────────────────────────────────────
17
+
18
+ const PHASE_SESSION_FIELD: Record<string, keyof WorkshopSession> = {
19
+ Discover: 'businessContext',
20
+ Ideate: 'ideas',
21
+ Design: 'evaluation',
22
+ Select: 'selection',
23
+ Plan: 'plan',
24
+ Develop: 'poc',
25
+ };
26
+
27
+ /**
28
+ * Check whether a phase needs post-phase summarization.
29
+ * Returns true if the session field for this phase is null/undefined.
30
+ */
31
+ export function needsSummarization(phase: PhaseValue, session: WorkshopSession): boolean {
32
+ const fieldName = PHASE_SESSION_FIELD[phase];
33
+ if (!fieldName) return false;
34
+ const value = session[fieldName];
35
+ return value === undefined || value === null;
36
+ }
37
+
38
+ /**
39
+ * Build a concatenated transcript from conversation turns for a specific phase.
40
+ */
41
+ export function buildPhaseTranscript(phase: PhaseValue, session: WorkshopSession): string {
42
+ const turns = (session.turns ?? []).filter((t) => t.phase === phase);
43
+ if (turns.length === 0) return '';
44
+ return turns.map((t) => `[${t.role}]: ${t.content}`).join('\n\n');
45
+ }
46
+
47
+ /**
48
+ * Post-phase summarization call.
49
+ *
50
+ * If the expected structured field for `phase` is still null after the
51
+ * conversation loop, makes a one-shot LLM call to extract it from the
52
+ * full transcript.
53
+ *
54
+ * Returns partial session updates (may be empty if extraction still fails).
55
+ * Never throws — summarization is a best-effort fallback.
56
+ */
57
+ export async function phaseSummarize(
58
+ client: CopilotClient,
59
+ phase: PhaseValue,
60
+ session: WorkshopSession,
61
+ handler: PhaseHandler,
62
+ ): Promise<Partial<WorkshopSession>> {
63
+ // Skip if field is already populated
64
+ if (!needsSummarization(phase, session)) {
65
+ return {};
66
+ }
67
+
68
+ // Build the transcript
69
+ const transcript = buildPhaseTranscript(phase, session);
70
+ if (!transcript) {
71
+ return {};
72
+ }
73
+
74
+ try {
75
+ // Load the phase-specific summarization prompt
76
+ const systemPrompt = await loadSummarizationPrompt(phase);
77
+
78
+ // Create a new session for the summarization call (avoids polluting context)
79
+ const summarizationSession = await client.createSession({ systemPrompt });
80
+
81
+ // Send the transcript as a single user message and collect the full response
82
+ const chunks: string[] = [];
83
+ for await (const event of summarizationSession.send({
84
+ role: 'user',
85
+ content: transcript,
86
+ })) {
87
+ if (event.type === 'TextDelta') {
88
+ chunks.push(event.text);
89
+ }
90
+ }
91
+ const response = chunks.join('');
92
+
93
+ // Extract structured data using the phase handler's extractResult
94
+ const updates = handler.extractResult(session, response);
95
+
96
+ // For Design phase, also extract Mermaid diagram (FR-007a)
97
+ // Store in plan.architectureNotes if available (session schema doesn't have
98
+ // a dedicated architectureDiagram field)
99
+ if (phase === 'Design') {
100
+ const mermaidMatch = response.match(/```mermaid\s*\n([\s\S]*?)\n```/);
101
+ if (mermaidMatch) {
102
+ const diagram = mermaidMatch[1].trim();
103
+ if (!updates.plan) {
104
+ updates.plan = { milestones: [], architectureNotes: diagram };
105
+ }
106
+ }
107
+ }
108
+
109
+ return updates;
110
+ } catch {
111
+ // Summarization failure is non-fatal — log and return empty
112
+ return {};
113
+ }
114
+ }