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,175 @@
1
+ /**
2
+ * Integration test for Discovery Phase Enrichment Flow (T029).
3
+ *
4
+ * Simulates Step 1 completion in the discover handler and verifies:
5
+ * - The user is offered web search enrichment when available
6
+ * - DiscoveryEnricher.enrichFromWebSearch() is called when web search provided
7
+ * - Session is updated with discovery.enrichment
8
+ * - Enrichment data is included in the Ideate phase system prompt
9
+ */
10
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
11
+ import { createPhaseHandler } from '../../src/phases/phaseHandlers.js';
12
+ // ── Helpers ──────────────────────────────────────────────────────────────────
13
+ function makeSession(overrides) {
14
+ return {
15
+ sessionId: 'test-session',
16
+ schemaVersion: '0.3.0',
17
+ createdAt: new Date().toISOString(),
18
+ updatedAt: new Date().toISOString(),
19
+ phase: 'Discover',
20
+ status: 'Active',
21
+ participants: [],
22
+ artifacts: { generatedFiles: [] },
23
+ ...overrides,
24
+ };
25
+ }
26
+ function makeLoopIO() {
27
+ return {
28
+ write: vi.fn(),
29
+ writeActivity: vi.fn(),
30
+ writeToolSummary: vi.fn(),
31
+ readInput: vi.fn().mockResolvedValue('n'),
32
+ showDecisionGate: vi.fn().mockResolvedValue({ choice: 'continue' }),
33
+ isJsonMode: false,
34
+ isTTY: true,
35
+ };
36
+ }
37
+ function makeMcpManager() {
38
+ return {
39
+ isAvailable: vi.fn().mockReturnValue(false),
40
+ callTool: vi.fn().mockResolvedValue({}),
41
+ markConnected: vi.fn(),
42
+ markDisconnected: vi.fn(),
43
+ disconnectAll: vi.fn().mockResolvedValue(undefined),
44
+ };
45
+ }
46
+ // ── Tests ────────────────────────────────────────────────────────────────────
47
+ describe('Discovery enrichment integration flow (T029)', () => {
48
+ let io;
49
+ let mcpManager;
50
+ let webSearchClient;
51
+ beforeEach(() => {
52
+ io = makeLoopIO();
53
+ mcpManager = makeMcpManager();
54
+ webSearchClient = {
55
+ search: vi.fn().mockResolvedValue({
56
+ results: [
57
+ {
58
+ title: 'Acme launches AI',
59
+ url: 'https://news.com/1',
60
+ snippet: 'Acme Corp announced a new AI product',
61
+ },
62
+ ],
63
+ degraded: false,
64
+ }),
65
+ };
66
+ });
67
+ it('runs enrichment via postExtract when businessContext is available and webSearchClient provided', async () => {
68
+ const handler = createPhaseHandler('Discover', {
69
+ discover: { io, mcpManager, webSearchClient },
70
+ });
71
+ await handler._preload();
72
+ const session = makeSession({
73
+ businessContext: {
74
+ businessDescription: 'Acme Corp manufactures widgets and is exploring AI',
75
+ challenges: ['efficiency', 'quality control'],
76
+ },
77
+ });
78
+ // postExtract should trigger enrichment
79
+ const updates = await handler.postExtract(session);
80
+ expect(updates.discovery).toBeDefined();
81
+ expect(updates.discovery.enrichment).toBeDefined();
82
+ expect(updates.discovery.enrichment.sourcesUsed).toContain('websearch');
83
+ expect(updates.discovery.enrichment.companyNews).toBeDefined();
84
+ expect(updates.discovery.enrichment.companyNews.length).toBeGreaterThan(0);
85
+ expect(webSearchClient.search).toHaveBeenCalled();
86
+ });
87
+ it('does not run enrichment when businessContext is missing', async () => {
88
+ const handler = createPhaseHandler('Discover', {
89
+ discover: { io, mcpManager, webSearchClient },
90
+ });
91
+ await handler._preload();
92
+ const session = makeSession(); // no businessContext
93
+ const updates = await handler.postExtract(session);
94
+ expect(updates.discovery).toBeUndefined();
95
+ expect(webSearchClient.search).not.toHaveBeenCalled();
96
+ });
97
+ it('runs enrichment only once (first extraction)', async () => {
98
+ const handler = createPhaseHandler('Discover', {
99
+ discover: { io, mcpManager, webSearchClient },
100
+ });
101
+ await handler._preload();
102
+ const session = makeSession({
103
+ businessContext: {
104
+ businessDescription: 'Acme Corp',
105
+ challenges: ['challenge'],
106
+ },
107
+ });
108
+ // First call: triggers enrichment
109
+ const updates1 = await handler.postExtract(session);
110
+ expect(updates1.discovery).toBeDefined();
111
+ // Second call: should not re-trigger
112
+ const updates2 = await handler.postExtract(session);
113
+ expect(updates2).toEqual({});
114
+ // search is called once (first successful query) in the first call only, not in second
115
+ expect(webSearchClient.search).toHaveBeenCalledTimes(1);
116
+ });
117
+ it('does not run enrichment when no webSearchClient or WorkIQ available', async () => {
118
+ const handler = createPhaseHandler('Discover', {
119
+ discover: { io, mcpManager },
120
+ });
121
+ await handler._preload();
122
+ const session = makeSession({
123
+ businessContext: {
124
+ businessDescription: 'Acme Corp',
125
+ challenges: ['challenge'],
126
+ },
127
+ });
128
+ const updates = await handler.postExtract(session);
129
+ expect(updates).toEqual({});
130
+ });
131
+ it('enrichment result stored in session.discovery.enrichment is picked up by Ideate prompt', async () => {
132
+ // Create a session with enrichment already stored
133
+ const session = makeSession({
134
+ phase: 'Ideate',
135
+ businessContext: {
136
+ businessDescription: 'Acme Corp',
137
+ challenges: ['efficiency'],
138
+ },
139
+ discovery: {
140
+ enrichment: {
141
+ companyNews: ['Acme raises $10M Series B'],
142
+ competitorInfo: ['Widget Inc growing fast'],
143
+ industryTrends: ['AI in manufacturing trending'],
144
+ enrichedAt: new Date().toISOString(),
145
+ sourcesUsed: ['websearch'],
146
+ },
147
+ },
148
+ });
149
+ const ideateHandler = createPhaseHandler('Ideate');
150
+ await ideateHandler._preload();
151
+ const prompt = ideateHandler.buildSystemPrompt(session);
152
+ // The ideate handler includes businessContext in its prompt
153
+ expect(prompt).toContain('Acme Corp');
154
+ // Note: Enrichment injection into ideation prompt is a downstream task,
155
+ // this test verifies the data is available for injection
156
+ });
157
+ it('enrichment does not interfere with normal extractResult', async () => {
158
+ const handler = createPhaseHandler('Discover', {
159
+ discover: { io, mcpManager, webSearchClient },
160
+ });
161
+ await handler._preload();
162
+ const session = makeSession();
163
+ // Normal extractResult should still work
164
+ const response = `## Business Context
165
+ \`\`\`json
166
+ {
167
+ "businessDescription": "Acme Corp",
168
+ "challenges": ["efficiency"]
169
+ }
170
+ \`\`\``;
171
+ const updates = handler.extractResult(session, response);
172
+ // extractResult should not contain discovery (that's postExtract's job)
173
+ expect(updates.businessContext).toBeDefined();
174
+ });
175
+ });
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Integration test: Export Artifacts (T037)
3
+ *
4
+ * Tests the export pipeline from session to Markdown + summary.json:
5
+ * - Export a complete session with all phases populated
6
+ * - Verify per-phase Markdown files are generated
7
+ * - Verify summary.json matches contract structure
8
+ * - Verify export handles partial sessions (only some phases done)
9
+ * - Verify export paths and directory creation
10
+ */
11
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
12
+ import { mkdtemp, rm, readFile, readdir } from 'node:fs/promises';
13
+ import { join } from 'node:path';
14
+ import { tmpdir } from 'node:os';
15
+ import { exportSession } from '../../src/sessions/exportWriter.js';
16
+ // ── Helpers ──────────────────────────────────────────────────────────────────
17
+ function createFullSession() {
18
+ return {
19
+ sessionId: 'test-export-session',
20
+ schemaVersion: '1.0.0',
21
+ createdAt: '2025-01-01T00:00:00Z',
22
+ updatedAt: '2025-06-15T12:00:00Z',
23
+ phase: 'Complete',
24
+ status: 'Completed',
25
+ participants: [
26
+ { id: 'p1', displayName: 'Alice', role: 'Facilitator' },
27
+ ],
28
+ businessContext: {
29
+ businessDescription: 'Logistics company specializing in last-mile delivery',
30
+ challenges: ['Route optimization is manual', 'High fuel costs'],
31
+ constraints: ['Budget limited to $50k'],
32
+ successMetrics: [{ name: 'Delivery time', value: '30', unit: 'minutes' }],
33
+ },
34
+ workflow: {
35
+ activities: [
36
+ { id: 'a1', name: 'Receive Order', description: 'Customer places order' },
37
+ { id: 'a2', name: 'Plan Route', description: 'Manual route planning' },
38
+ { id: 'a3', name: 'Deliver', description: 'Driver delivers package' },
39
+ ],
40
+ edges: [
41
+ { fromStepId: 'a1', toStepId: 'a2' },
42
+ { fromStepId: 'a2', toStepId: 'a3' },
43
+ ],
44
+ },
45
+ ideas: [
46
+ {
47
+ id: 'i1',
48
+ title: 'AI Route Optimizer',
49
+ description: 'Use ML to find optimal delivery routes',
50
+ workflowStepIds: ['a2'],
51
+ aspirationalScope: 'Cover all urban routes',
52
+ },
53
+ {
54
+ id: 'i2',
55
+ title: 'Predictive Demand',
56
+ description: 'Forecast delivery demand to pre-position drivers',
57
+ workflowStepIds: ['a1', 'a2'],
58
+ },
59
+ ],
60
+ evaluation: {
61
+ method: 'feasibility-value-matrix',
62
+ ideas: [
63
+ { ideaId: 'i1', feasibility: 4, value: 5, risks: ['Data quality'] },
64
+ { ideaId: 'i2', feasibility: 3, value: 4 },
65
+ ],
66
+ },
67
+ selection: {
68
+ ideaId: 'i1',
69
+ selectionRationale: 'Highest combined feasibility + value score',
70
+ confirmedByUser: true,
71
+ confirmedAt: '2025-06-15T11:00:00Z',
72
+ },
73
+ plan: {
74
+ milestones: [
75
+ { id: 'm1', title: 'Data Collection', items: ['Set up telemetry', 'Collect 3 months of delivery data'] },
76
+ { id: 'm2', title: 'Model Training', items: ['Train route model', 'Validate on historical data'] },
77
+ { id: 'm3', title: 'Integration', items: ['API endpoint', 'Driver app integration'] },
78
+ ],
79
+ architectureNotes: 'Microservices with Azure Maps API',
80
+ dependencies: ['Azure subscription', 'Historical delivery data'],
81
+ },
82
+ poc: {
83
+ repoSource: 'local',
84
+ iterations: [
85
+ {
86
+ iteration: 1,
87
+ startedAt: '2025-06-15T11:30:00Z',
88
+ outcome: 'scaffold',
89
+ filesChanged: ['package.json', 'src/index.ts'],
90
+ changesSummary: 'Initial scaffold with simple greedy routing',
91
+ },
92
+ ],
93
+ },
94
+ artifacts: { generatedFiles: [] },
95
+ turns: [
96
+ { phase: 'Discover', sequence: 1, role: 'user', content: 'We are a logistics company', timestamp: '2025-01-01T00:01:00Z' },
97
+ { phase: 'Discover', sequence: 2, role: 'assistant', content: 'Understood, let me document that.', timestamp: '2025-01-01T00:02:00Z' },
98
+ { phase: 'Ideate', sequence: 3, role: 'user', content: 'Generate ideas', timestamp: '2025-01-01T00:03:00Z' },
99
+ ],
100
+ };
101
+ }
102
+ function createPartialSession() {
103
+ return {
104
+ sessionId: 'test-partial-session',
105
+ schemaVersion: '1.0.0',
106
+ createdAt: '2025-01-01T00:00:00Z',
107
+ updatedAt: '2025-01-01T01:00:00Z',
108
+ phase: 'Ideate',
109
+ status: 'Active',
110
+ participants: [],
111
+ businessContext: {
112
+ businessDescription: 'A SaaS platform',
113
+ challenges: ['Customer churn'],
114
+ },
115
+ artifacts: { generatedFiles: [] },
116
+ turns: [],
117
+ };
118
+ }
119
+ // ── Tests ────────────────────────────────────────────────────────────────────
120
+ describe('Export Artifacts Flow', () => {
121
+ let tmpDir;
122
+ beforeEach(async () => {
123
+ tmpDir = await mkdtemp(join(tmpdir(), 'sofia-export-'));
124
+ });
125
+ afterEach(async () => {
126
+ await rm(tmpDir, { recursive: true, force: true });
127
+ });
128
+ it('exports a complete session with all phase Markdown files', async () => {
129
+ const session = createFullSession();
130
+ const exportDir = join(tmpDir, session.sessionId);
131
+ const result = await exportSession(session, exportDir);
132
+ expect(result.files.length).toBeGreaterThanOrEqual(6); // discover + ideate + design + select + plan + develop + summary
133
+ const files = await readdir(exportDir);
134
+ expect(files).toContain('summary.json');
135
+ });
136
+ it('generates valid summary.json matching contract', async () => {
137
+ const session = createFullSession();
138
+ const exportDir = join(tmpDir, session.sessionId);
139
+ await exportSession(session, exportDir);
140
+ const summaryRaw = await readFile(join(exportDir, 'summary.json'), 'utf-8');
141
+ const summary = JSON.parse(summaryRaw);
142
+ expect(summary.sessionId).toBe('test-export-session');
143
+ expect(summary.phase).toBe('Complete');
144
+ expect(summary.status).toBe('Completed');
145
+ expect(summary.exportedAt).toBeDefined();
146
+ expect(Array.isArray(summary.files)).toBe(true);
147
+ expect(summary.files.length).toBeGreaterThan(0);
148
+ });
149
+ it('exports Discover markdown with business context', async () => {
150
+ const session = createFullSession();
151
+ const exportDir = join(tmpDir, session.sessionId);
152
+ await exportSession(session, exportDir);
153
+ const files = await readdir(exportDir);
154
+ const discoverFile = files.find((f) => f.includes('discover'));
155
+ expect(discoverFile).toBeDefined();
156
+ const content = await readFile(join(exportDir, discoverFile), 'utf-8');
157
+ expect(content).toContain('Logistics company');
158
+ expect(content).toContain('Route optimization');
159
+ });
160
+ it('exports Ideate markdown with ideas', async () => {
161
+ const session = createFullSession();
162
+ const exportDir = join(tmpDir, session.sessionId);
163
+ await exportSession(session, exportDir);
164
+ const files = await readdir(exportDir);
165
+ const ideateFile = files.find((f) => f.includes('ideate'));
166
+ expect(ideateFile).toBeDefined();
167
+ const content = await readFile(join(exportDir, ideateFile), 'utf-8');
168
+ expect(content).toContain('AI Route Optimizer');
169
+ });
170
+ it('exports Plan markdown with milestones', async () => {
171
+ const session = createFullSession();
172
+ const exportDir = join(tmpDir, session.sessionId);
173
+ await exportSession(session, exportDir);
174
+ const files = await readdir(exportDir);
175
+ const planFile = files.find((f) => f.includes('plan'));
176
+ expect(planFile).toBeDefined();
177
+ const content = await readFile(join(exportDir, planFile), 'utf-8');
178
+ expect(content).toContain('Data Collection');
179
+ expect(content).toContain('Model Training');
180
+ });
181
+ it('handles partial session with only Discover data', async () => {
182
+ const session = createPartialSession();
183
+ const exportDir = join(tmpDir, session.sessionId);
184
+ const result = await exportSession(session, exportDir);
185
+ // Should still have at least discover markdown + summary.json
186
+ expect(result.files.length).toBeGreaterThanOrEqual(2);
187
+ const files = await readdir(exportDir);
188
+ expect(files).toContain('summary.json');
189
+ const discoverFile = files.find((f) => f.includes('discover'));
190
+ expect(discoverFile).toBeDefined();
191
+ });
192
+ it('summary file list contains relative paths', async () => {
193
+ const session = createFullSession();
194
+ const exportDir = join(tmpDir, session.sessionId);
195
+ await exportSession(session, exportDir);
196
+ const summaryRaw = await readFile(join(exportDir, 'summary.json'), 'utf-8');
197
+ const summary = JSON.parse(summaryRaw);
198
+ for (const file of summary.files) {
199
+ expect(file).not.toContain(tmpDir); // Should be relative, not absolute
200
+ }
201
+ });
202
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Integration test: Export fallback flow.
3
+ *
4
+ * Tests the full export pipeline with null structured data but present
5
+ * conversation turns — verifies that all phase files are generated.
6
+ */
7
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8
+ import { mkdtemp, rm, readFile, readdir } from 'node:fs/promises';
9
+ import { join } from 'node:path';
10
+ import { tmpdir } from 'node:os';
11
+ import { exportSession } from '../../src/sessions/exportWriter.js';
12
+ let tmpDir;
13
+ beforeEach(async () => {
14
+ tmpDir = await mkdtemp(join(tmpdir(), 'sofia-export-fallback-'));
15
+ });
16
+ afterEach(async () => {
17
+ await rm(tmpDir, { recursive: true, force: true });
18
+ });
19
+ describe('export fallback flow', () => {
20
+ it('generates 6 markdown files when all phases have turns but no structured data', async () => {
21
+ const now = new Date().toISOString();
22
+ const phases = ['Discover', 'Ideate', 'Design', 'Select', 'Plan', 'Develop'];
23
+ const turns = phases.flatMap((phase, i) => [
24
+ {
25
+ phase,
26
+ sequence: i * 2 + 1,
27
+ role: 'user',
28
+ content: `Tell me about the ${phase} phase`,
29
+ timestamp: now,
30
+ },
31
+ {
32
+ phase,
33
+ sequence: i * 2 + 2,
34
+ role: 'assistant',
35
+ content: `Here is information about the ${phase} phase.`,
36
+ timestamp: now,
37
+ },
38
+ ]);
39
+ const session = {
40
+ sessionId: 'fallback-test',
41
+ schemaVersion: '1.0.0',
42
+ createdAt: now,
43
+ updatedAt: now,
44
+ phase: 'Complete',
45
+ status: 'Completed',
46
+ participants: [],
47
+ artifacts: { generatedFiles: [] },
48
+ turns,
49
+ // Discover has conversation fallback built-in
50
+ businessContext: {
51
+ businessDescription: 'Test Company',
52
+ challenges: ['Testing'],
53
+ },
54
+ // All other structured fields are null/undefined
55
+ };
56
+ await exportSession(session, tmpDir);
57
+ const files = await readdir(tmpDir);
58
+ expect(files).toContain('discover.md');
59
+ expect(files).toContain('ideate.md');
60
+ expect(files).toContain('design.md');
61
+ expect(files).toContain('select.md');
62
+ expect(files).toContain('plan.md');
63
+ expect(files).toContain('develop.md');
64
+ expect(files).toContain('summary.json');
65
+ // Verify each file has conversation content
66
+ for (const phase of phases) {
67
+ const content = await readFile(join(tmpDir, `${phase.toLowerCase()}.md`), 'utf-8');
68
+ expect(content).toContain('## Conversation');
69
+ }
70
+ // Verify summary.json lists all files
71
+ const summaryRaw = await readFile(join(tmpDir, 'summary.json'), 'utf-8');
72
+ const summary = JSON.parse(summaryRaw);
73
+ const mdFiles = summary.files.filter((f) => f.path.endsWith('.md'));
74
+ expect(mdFiles.length).toBe(6);
75
+ // Verify highlights exist
76
+ expect(summary.highlights).toBeDefined();
77
+ expect(summary.highlights.length).toBeGreaterThan(0);
78
+ });
79
+ it('returns null for phase with neither structured data nor turns', async () => {
80
+ const now = new Date().toISOString();
81
+ const session = {
82
+ sessionId: 'empty-test',
83
+ schemaVersion: '1.0.0',
84
+ createdAt: now,
85
+ updatedAt: now,
86
+ phase: 'Discover',
87
+ status: 'Active',
88
+ participants: [],
89
+ artifacts: { generatedFiles: [] },
90
+ turns: [],
91
+ };
92
+ await exportSession(session, tmpDir);
93
+ const files = await readdir(tmpDir);
94
+ // Only summary.json should be generated — no phase files
95
+ expect(files).toContain('summary.json');
96
+ const mdFiles = files.filter((f) => f.endsWith('.md'));
97
+ expect(mdFiles).toHaveLength(0);
98
+ });
99
+ });
@@ -0,0 +1,190 @@
1
+ /**
2
+ * T042: Integration test for MCP degradation flow.
3
+ *
4
+ * Configures McpManager with servers marked unavailable (or stub transports
5
+ * that throw), runs GitHubMcpAdapter + McpContextEnricher calls, and asserts
6
+ * graceful degradation (no throws; adapters return { available: false, reason }
7
+ * or fallback context) per US1 Acceptance Scenario 4 and FR-013.
8
+ */
9
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
+ import { McpManager } from '../../src/mcp/mcpManager.js';
11
+ import { GitHubMcpAdapter } from '../../src/develop/githubMcpAdapter.js';
12
+ import { McpContextEnricher } from '../../src/develop/mcpContextEnricher.js';
13
+ vi.mock('../../src/mcp/webSearch.js', () => ({
14
+ isWebSearchConfigured: vi.fn(() => false),
15
+ }));
16
+ // ── Helpers ──────────────────────────────────────────────────────────────────
17
+ function makeEmptyConfig() {
18
+ return { servers: {} };
19
+ }
20
+ function makeConfigWithServers() {
21
+ return {
22
+ servers: {
23
+ github: {
24
+ name: 'github',
25
+ type: 'http',
26
+ url: 'https://api.github.com/mcp',
27
+ },
28
+ context7: {
29
+ name: 'context7',
30
+ type: 'stdio',
31
+ command: 'npx',
32
+ args: ['-y', '@upstash/context7-mcp'],
33
+ },
34
+ azure: {
35
+ name: 'azure',
36
+ type: 'stdio',
37
+ command: 'npx',
38
+ args: ['-y', '@azure/mcp'],
39
+ },
40
+ },
41
+ };
42
+ }
43
+ // ── Tests ────────────────────────────────────────────────────────────────────
44
+ describe('MCP Degradation Flow (integration)', () => {
45
+ let originalGithubToken;
46
+ beforeEach(() => {
47
+ originalGithubToken = process.env.GITHUB_TOKEN;
48
+ });
49
+ afterEach(() => {
50
+ if (originalGithubToken !== undefined) {
51
+ process.env.GITHUB_TOKEN = originalGithubToken;
52
+ }
53
+ else {
54
+ delete process.env.GITHUB_TOKEN;
55
+ }
56
+ vi.restoreAllMocks();
57
+ });
58
+ describe('GitHubMcpAdapter graceful degradation', () => {
59
+ it('returns available:false when GitHub MCP is not configured', async () => {
60
+ const manager = new McpManager(makeEmptyConfig());
61
+ const adapter = new GitHubMcpAdapter(manager);
62
+ expect(adapter.isAvailable()).toBe(false);
63
+ const createResult = await adapter.createRepository({ name: 'test-poc' });
64
+ expect(createResult.available).toBe(false);
65
+ if (!createResult.available) {
66
+ expect(createResult.reason).toBeDefined();
67
+ }
68
+ const pushResult = await adapter.pushFiles({
69
+ repoUrl: 'https://github.com/acme/test',
70
+ files: [{ path: 'index.ts', content: 'hello' }],
71
+ commitMessage: 'init',
72
+ });
73
+ expect(pushResult.available).toBe(false);
74
+ if (!pushResult.available) {
75
+ expect(pushResult.reason).toBeDefined();
76
+ }
77
+ });
78
+ it('returns available:false when GitHub is configured but not connected', async () => {
79
+ const manager = new McpManager(makeConfigWithServers());
80
+ // Do NOT call markConnected — server is configured but unavailable
81
+ const adapter = new GitHubMcpAdapter(manager);
82
+ expect(adapter.isAvailable()).toBe(false);
83
+ const createResult = await adapter.createRepository({ name: 'test-poc' });
84
+ expect(createResult.available).toBe(false);
85
+ });
86
+ it('never throws from createRepository or pushFiles', async () => {
87
+ const manager = new McpManager(makeConfigWithServers());
88
+ manager.markConnected('github');
89
+ // Stub callTool to throw
90
+ vi.spyOn(manager, 'callTool').mockRejectedValue(new Error('network crash'));
91
+ const adapter = new GitHubMcpAdapter(manager);
92
+ // Should not throw
93
+ const createResult = await adapter.createRepository({ name: 'test-poc' });
94
+ expect(createResult.available).toBe(false);
95
+ if (!createResult.available) {
96
+ expect(createResult.reason).toBeDefined();
97
+ }
98
+ const pushResult = await adapter.pushFiles({
99
+ repoUrl: 'https://github.com/acme/test',
100
+ files: [{ path: 'index.ts', content: 'hello' }],
101
+ commitMessage: 'init',
102
+ });
103
+ expect(pushResult.available).toBe(false);
104
+ });
105
+ });
106
+ describe('McpContextEnricher graceful degradation', () => {
107
+ it('returns empty context when all services unavailable', async () => {
108
+ const manager = new McpManager(makeEmptyConfig());
109
+ const enricher = new McpContextEnricher(manager);
110
+ const result = await enricher.enrich({
111
+ dependencies: ['express', 'zod'],
112
+ architectureNotes: 'Use Azure Cosmos DB',
113
+ stuckIterations: 3,
114
+ failingTests: ['test fails with TypeError'],
115
+ });
116
+ expect(result.combined).toBe('');
117
+ expect(result.libraryDocs).toBeUndefined();
118
+ expect(result.azureGuidance).toBeUndefined();
119
+ expect(result.webSearchResults).toBeUndefined();
120
+ });
121
+ it('returns empty context when servers configured but not connected', async () => {
122
+ const manager = new McpManager(makeConfigWithServers());
123
+ // Not marking any server as connected
124
+ const enricher = new McpContextEnricher(manager);
125
+ const result = await enricher.enrich({
126
+ dependencies: ['express'],
127
+ architectureNotes: 'Use Azure Functions',
128
+ });
129
+ expect(result.combined).toBe('');
130
+ expect(result.libraryDocs).toBeUndefined();
131
+ expect(result.azureGuidance).toBeUndefined();
132
+ });
133
+ it('returns fallback context when callTool throws for all services', async () => {
134
+ const manager = new McpManager(makeConfigWithServers());
135
+ manager.markConnected('context7');
136
+ manager.markConnected('azure');
137
+ // Stub callTool to throw for all calls
138
+ vi.spyOn(manager, 'callTool').mockRejectedValue(new Error('transport broken'));
139
+ const enricher = new McpContextEnricher(manager);
140
+ const result = await enricher.enrich({
141
+ dependencies: ['express'],
142
+ architectureNotes: 'Use Azure Cosmos DB for data',
143
+ });
144
+ // Context7 should fall back to npm links
145
+ if (result.libraryDocs) {
146
+ expect(result.libraryDocs).toContain('npmjs.com');
147
+ }
148
+ // Azure should fall back to static guidance
149
+ if (result.azureGuidance) {
150
+ expect(result.azureGuidance).toContain('DefaultAzureCredential');
151
+ }
152
+ });
153
+ it('never throws from enrich()', async () => {
154
+ const manager = new McpManager(makeConfigWithServers());
155
+ manager.markConnected('context7');
156
+ manager.markConnected('azure');
157
+ vi.spyOn(manager, 'callTool').mockRejectedValue(new Error('catastrophic'));
158
+ const enricher = new McpContextEnricher(manager);
159
+ // Should not throw
160
+ const result = await enricher.enrich({
161
+ dependencies: ['express', 'zod'],
162
+ architectureNotes: 'Use Azure Cosmos DB',
163
+ stuckIterations: 5,
164
+ failingTests: ['test fails'],
165
+ });
166
+ // Should still return a valid EnrichedContext
167
+ expect(result).toHaveProperty('combined');
168
+ expect(typeof result.combined).toBe('string');
169
+ });
170
+ });
171
+ describe('Combined adapter + enricher degradation', () => {
172
+ it('full workflow with all MCP unavailable returns graceful defaults', async () => {
173
+ const manager = new McpManager(makeEmptyConfig());
174
+ const adapter = new GitHubMcpAdapter(manager);
175
+ const enricher = new McpContextEnricher(manager);
176
+ // Adapter: not available
177
+ expect(adapter.isAvailable()).toBe(false);
178
+ const repoResult = await adapter.createRepository({ name: 'test' });
179
+ expect(repoResult.available).toBe(false);
180
+ // Enricher: empty context
181
+ const contextResult = await enricher.enrich({
182
+ dependencies: ['express'],
183
+ architectureNotes: 'Use Azure',
184
+ stuckIterations: 3,
185
+ failingTests: ['test fails'],
186
+ });
187
+ expect(contextResult.combined).toBe('');
188
+ });
189
+ });
190
+ });