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,416 @@
1
+ /**
2
+ * T024: Integration test for partial/failed outcomes.
3
+ *
4
+ * Tests:
5
+ * - max-iterations with some tests passing (partial status)
6
+ * - max-iterations with no tests passing (failed status)
7
+ * - LLM error mid-loop (error outcome on iteration, loop continues)
8
+ */
9
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
+ import { mkdtemp, rm } from 'node:fs/promises';
11
+ import { join } from 'node:path';
12
+ import { tmpdir } from 'node:os';
13
+ import { createRequire } from 'node:module';
14
+
15
+ import { RalphLoop } from '../../src/develop/ralphLoop.js';
16
+ import { PocScaffolder } from '../../src/develop/pocScaffolder.js';
17
+ import { TestRunner } from '../../src/develop/testRunner.js';
18
+ import type { WorkshopSession } from '../../src/shared/schemas/session.js';
19
+ import type { LoopIO } from '../../src/loop/conversationLoop.js';
20
+ import type { CopilotClient } from '../../src/shared/copilotClient.js';
21
+ import type { TestResults } from '../../src/shared/schemas/session.js';
22
+
23
+ // Mock npm install
24
+ vi.mock('node:child_process', async (importOriginal) => {
25
+ const actual = await importOriginal<typeof import('node:child_process')>();
26
+ return {
27
+ ...actual,
28
+ spawn: vi.fn((cmd: string, args: string[]) => {
29
+ if (cmd === 'npm' && args.includes('install')) {
30
+ return {
31
+ stdout: { on: vi.fn() },
32
+ stderr: { on: vi.fn() },
33
+ on: vi.fn((event: string, cb: (code: number) => void) => {
34
+ if (event === 'close') cb(0);
35
+ }),
36
+ kill: vi.fn(),
37
+ killed: false,
38
+ };
39
+ }
40
+ return actual.spawn(cmd, args);
41
+ }),
42
+ };
43
+ });
44
+
45
+ const require = createRequire(import.meta.url);
46
+ const fixtureSession: WorkshopSession = require('../fixtures/completedSession.json') as WorkshopSession;
47
+
48
+ function makeIo(): LoopIO {
49
+ return {
50
+ write: vi.fn(),
51
+ writeActivity: vi.fn(),
52
+ writeToolSummary: vi.fn(),
53
+ readInput: vi.fn().mockResolvedValue(null),
54
+ showDecisionGate: vi.fn(),
55
+ isJsonMode: false,
56
+ isTTY: false,
57
+ };
58
+ }
59
+
60
+ function makeFakeScaffolder(outputDir: string): PocScaffolder {
61
+ return {
62
+ scaffold: vi.fn().mockImplementation(async () => {
63
+ const { writeFile, mkdir } = await import('node:fs/promises');
64
+ await mkdir(join(outputDir, 'src'), { recursive: true });
65
+ await writeFile(join(outputDir, 'package.json'), JSON.stringify({
66
+ name: 'test-poc',
67
+ scripts: { test: 'vitest run' },
68
+ dependencies: {},
69
+ devDependencies: {},
70
+ }), 'utf-8');
71
+ await writeFile(join(outputDir, 'src', 'index.ts'), 'export function main() {}', 'utf-8');
72
+ return {
73
+ createdFiles: ['package.json', 'src/index.ts'],
74
+ skippedFiles: [],
75
+ context: {
76
+ projectName: 'test-poc',
77
+ ideaTitle: 'Test',
78
+ ideaDescription: 'Test',
79
+ techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
80
+ planSummary: 'Test',
81
+ sessionId: fixtureSession.sessionId,
82
+ outputDir,
83
+ },
84
+ };
85
+ }),
86
+ getTemplateFiles: () => [],
87
+ } as unknown as PocScaffolder;
88
+ }
89
+
90
+ function makeClient(): CopilotClient {
91
+ return {
92
+ createSession: vi.fn().mockResolvedValue({
93
+ send: vi.fn().mockReturnValue({
94
+ async *[Symbol.asyncIterator]() {
95
+ yield {
96
+ type: 'TextDelta',
97
+ text: '```typescript file=src/index.ts\nexport function main() { return 1; }\n```\n',
98
+ timestamp: '',
99
+ };
100
+ },
101
+ }),
102
+ getHistory: () => [],
103
+ }),
104
+ };
105
+ }
106
+
107
+ describe('RalphLoop integration — partial/failed outcomes', () => {
108
+ let tmpDir: string;
109
+
110
+ beforeEach(async () => {
111
+ tmpDir = await mkdtemp(join(tmpdir(), 'sofia-ralph-partial-'));
112
+ });
113
+
114
+ afterEach(async () => {
115
+ await rm(tmpDir, { recursive: true, force: true });
116
+ vi.clearAllMocks();
117
+ });
118
+
119
+ it('sets finalStatus=partial when some tests pass at max-iterations', async () => {
120
+ const io = makeIo();
121
+ const client = makeClient();
122
+ const scaffolder = makeFakeScaffolder(tmpDir);
123
+
124
+ const testRunner: TestRunner = {
125
+ run: vi.fn().mockResolvedValue({
126
+ passed: 1,
127
+ failed: 1,
128
+ skipped: 0,
129
+ total: 2,
130
+ durationMs: 400,
131
+ failures: [{ testName: 'suite > test B', message: 'fails always' }],
132
+ rawOutput: '',
133
+ } satisfies TestResults),
134
+ } as unknown as TestRunner;
135
+
136
+ const ralph = new RalphLoop({
137
+ client,
138
+ io,
139
+ session: fixtureSession,
140
+ outputDir: tmpDir,
141
+ maxIterations: 2,
142
+ testRunner,
143
+ scaffolder,
144
+ });
145
+
146
+ const result = await ralph.run();
147
+
148
+ expect(result.terminationReason).toBe('max-iterations');
149
+ expect(result.finalStatus).toBe('partial');
150
+ expect(result.session.poc?.terminationReason).toBe('max-iterations');
151
+ expect(result.session.poc?.finalStatus).toBe('partial');
152
+ });
153
+
154
+ it('sets finalStatus=failed when no tests pass at max-iterations', async () => {
155
+ const io = makeIo();
156
+ const client = makeClient();
157
+ const scaffolder = makeFakeScaffolder(tmpDir);
158
+
159
+ const testRunner: TestRunner = {
160
+ run: vi.fn().mockResolvedValue({
161
+ passed: 0,
162
+ failed: 2,
163
+ skipped: 0,
164
+ total: 2,
165
+ durationMs: 400,
166
+ failures: [
167
+ { testName: 'test A', message: 'always fails' },
168
+ { testName: 'test B', message: 'always fails too' },
169
+ ],
170
+ rawOutput: '',
171
+ } satisfies TestResults),
172
+ } as unknown as TestRunner;
173
+
174
+ const ralph = new RalphLoop({
175
+ client,
176
+ io,
177
+ session: fixtureSession,
178
+ outputDir: tmpDir,
179
+ maxIterations: 2,
180
+ testRunner,
181
+ scaffolder,
182
+ });
183
+
184
+ const result = await ralph.run();
185
+
186
+ expect(result.terminationReason).toBe('max-iterations');
187
+ expect(result.finalStatus).toBe('failed');
188
+ });
189
+
190
+ it('records error iteration when LLM returns empty response, continues loop', async () => {
191
+ const io = makeIo();
192
+ const scaffolder = makeFakeScaffolder(tmpDir);
193
+
194
+ let testCallCount = 0;
195
+ const testRunner: TestRunner = {
196
+ run: vi.fn().mockImplementation(async (): Promise<TestResults> => {
197
+ testCallCount++;
198
+ if (testCallCount >= 2) {
199
+ // After error iteration, tests pass
200
+ return {
201
+ passed: 1,
202
+ failed: 0,
203
+ skipped: 0,
204
+ total: 1,
205
+ durationMs: 300,
206
+ failures: [],
207
+ rawOutput: '',
208
+ };
209
+ }
210
+ return {
211
+ passed: 0,
212
+ failed: 1,
213
+ skipped: 0,
214
+ total: 1,
215
+ durationMs: 400,
216
+ failures: [{ testName: 'test A', message: 'fails' }],
217
+ rawOutput: '',
218
+ };
219
+ }),
220
+ } as unknown as TestRunner;
221
+
222
+ let llmCallCount = 0;
223
+ const client: CopilotClient = {
224
+ createSession: vi.fn().mockResolvedValue({
225
+ send: vi.fn().mockImplementation(() => {
226
+ llmCallCount++;
227
+ if (llmCallCount === 1) {
228
+ // First LLM call: returns empty response (simulating error)
229
+ return {
230
+ async *[Symbol.asyncIterator]() {
231
+ // Empty - no TextDelta events
232
+ },
233
+ };
234
+ }
235
+ // Subsequent calls: return a fix
236
+ return {
237
+ async *[Symbol.asyncIterator]() {
238
+ yield {
239
+ type: 'TextDelta',
240
+ text: '```typescript file=src/index.ts\nexport function main() { return 1; }\n```\n',
241
+ timestamp: '',
242
+ };
243
+ },
244
+ };
245
+ }),
246
+ getHistory: () => [],
247
+ }),
248
+ };
249
+
250
+ const ralph = new RalphLoop({
251
+ client,
252
+ io,
253
+ session: fixtureSession,
254
+ outputDir: tmpDir,
255
+ maxIterations: 5,
256
+ testRunner,
257
+ scaffolder,
258
+ });
259
+
260
+ const result = await ralph.run();
261
+
262
+ // Loop should continue after LLM error and eventually succeed or hit max
263
+ expect(['success', 'failed', 'partial', 'max-iterations']).toContain(result.finalStatus);
264
+
265
+ // Check that error iterations are recorded
266
+ const poc = result.session.poc!;
267
+ const hasErrorIter = poc.iterations.some((i) => i.outcome === 'error');
268
+ // With empty LLM response, we should have an error iteration
269
+ expect(hasErrorIter).toBe(true);
270
+ });
271
+
272
+ it('records terminationReason in session poc state', async () => {
273
+ const io = makeIo();
274
+ const client = makeClient();
275
+ const scaffolder = makeFakeScaffolder(tmpDir);
276
+
277
+ const testRunner: TestRunner = {
278
+ run: vi.fn().mockResolvedValue({
279
+ passed: 0,
280
+ failed: 1,
281
+ skipped: 0,
282
+ total: 1,
283
+ durationMs: 400,
284
+ failures: [{ testName: 'test', message: 'fails' }],
285
+ rawOutput: '',
286
+ } satisfies TestResults),
287
+ } as unknown as TestRunner;
288
+
289
+ const ralph = new RalphLoop({
290
+ client,
291
+ io,
292
+ session: fixtureSession,
293
+ outputDir: tmpDir,
294
+ maxIterations: 2,
295
+ testRunner,
296
+ scaffolder,
297
+ });
298
+
299
+ const result = await ralph.run();
300
+
301
+ expect(result.session.poc?.terminationReason).toBeDefined();
302
+ expect(result.session.poc?.finalStatus).toBeDefined();
303
+ expect(result.session.poc?.iterations.length).toBeGreaterThan(0);
304
+ });
305
+ });
306
+
307
+ // ── Resume from interrupted session (T019) ────────────────────────────────
308
+
309
+ describe('resume from interrupted session', () => {
310
+ let tmpDir: string;
311
+
312
+ beforeEach(async () => {
313
+ tmpDir = await mkdtemp(join(tmpdir(), 'ralph-resume-'));
314
+ });
315
+
316
+ afterEach(async () => {
317
+ await rm(tmpDir, { recursive: true, force: true });
318
+ });
319
+
320
+ it('resumes from session with 2 completed iterations and starts at iteration 3 (T019)', async () => {
321
+ const io = makeIo();
322
+
323
+ // Create a session with 2 prior iterations
324
+ const session: WorkshopSession = {
325
+ ...fixtureSession,
326
+ poc: {
327
+ repoSource: 'local',
328
+ repoPath: tmpDir,
329
+ iterations: [
330
+ {
331
+ iteration: 1,
332
+ startedAt: new Date().toISOString(),
333
+ endedAt: new Date().toISOString(),
334
+ outcome: 'scaffold',
335
+ filesChanged: ['package.json', 'src/index.ts'],
336
+ },
337
+ {
338
+ iteration: 2,
339
+ startedAt: new Date().toISOString(),
340
+ endedAt: new Date().toISOString(),
341
+ outcome: 'tests-failing',
342
+ filesChanged: ['src/index.ts'],
343
+ testResults: {
344
+ passed: 1,
345
+ failed: 1,
346
+ skipped: 0,
347
+ total: 2,
348
+ durationMs: 200,
349
+ failures: [{ testName: 'test1', message: 'Expected x' }],
350
+ },
351
+ },
352
+ ],
353
+ },
354
+ };
355
+
356
+ // Mock a client that returns passing code on first call
357
+ const passingClient: CopilotClient = {
358
+ createSession: vi.fn().mockResolvedValue({
359
+ send: vi.fn().mockReturnValue({
360
+ async *[Symbol.asyncIterator]() {
361
+ yield {
362
+ type: 'TextDelta',
363
+ text: '```typescript file=src/index.ts\nexport function main() { return "ok"; }\n```',
364
+ timestamp: '',
365
+ };
366
+ },
367
+ }),
368
+ getHistory: () => [],
369
+ }),
370
+ };
371
+
372
+ // TestRunner that passes on first call
373
+ const testRunner = {
374
+ run: vi.fn().mockResolvedValue({
375
+ passed: 2,
376
+ failed: 0,
377
+ skipped: 0,
378
+ total: 2,
379
+ durationMs: 100,
380
+ failures: [],
381
+ }),
382
+ } as unknown as TestRunner;
383
+
384
+ const scaffolder = makeFakeScaffolder(tmpDir);
385
+
386
+ const ralph = new RalphLoop({
387
+ client: passingClient,
388
+ io,
389
+ session,
390
+ outputDir: tmpDir,
391
+ maxIterations: 10,
392
+ testRunner,
393
+ scaffolder,
394
+ checkpoint: {
395
+ hasPriorRun: true,
396
+ completedIterations: 2,
397
+ lastIterationIncomplete: false,
398
+ resumeFromIteration: 3,
399
+ canSkipScaffold: false,
400
+ priorFinalStatus: undefined,
401
+ priorIterations: session.poc!.iterations,
402
+ },
403
+ });
404
+
405
+ const result = await ralph.run();
406
+
407
+ // Should have seeded from prior iterations and continued
408
+ // Note: finalStatus may be 'partial' since makeFakeScaffolder doesn't create all
409
+ // files required by validatePocOutput (README.md, tsconfig.json, etc.)
410
+ expect(['success', 'partial']).toContain(result.finalStatus);
411
+ // Total iterations should include the 2 prior + scaffold + tests-passing
412
+ expect(result.iterationsCompleted).toBeGreaterThanOrEqual(3);
413
+ // Session should have iterations from resume
414
+ expect(result.session.poc?.iterations.length).toBeGreaterThanOrEqual(3);
415
+ });
416
+ });
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Integration test: Resume and Backtrack flow (T036)
3
+ *
4
+ * Tests the resume, backtrack, and artifact invalidation flows:
5
+ * - Resume an existing session and continue from the current phase
6
+ * - Backtrack to an earlier phase with deterministic invalidation
7
+ * - Verify downstream data is cleared after backtrack
8
+ * - Verify handler detects cleaned state as incomplete
9
+ * - Re-run a phase and produce fresh results after backtrack
10
+ */
11
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
12
+ import { mkdtemp, rm } from 'node:fs/promises';
13
+ import { join } from 'node:path';
14
+ import { tmpdir } from 'node:os';
15
+
16
+ import { ConversationLoop } from '../../src/loop/conversationLoop.js';
17
+ import type { LoopIO, DecisionGateResult } from '../../src/loop/conversationLoop.js';
18
+ import { createFakeCopilotClient } from '../../src/shared/copilotClient.js';
19
+ import type { WorkshopSession, PhaseValue } from '../../src/shared/schemas/session.js';
20
+ import { SessionStore } from '../../src/sessions/sessionStore.js';
21
+ import { createPhaseHandler } from '../../src/phases/phaseHandlers.js';
22
+ import { backtrackSession } from '../../src/sessions/sessionManager.js';
23
+
24
+ // ── Helpers ──────────────────────────────────────────────────────────────────
25
+
26
+ function createTestSession(overrides?: Partial<WorkshopSession>): WorkshopSession {
27
+ const now = new Date().toISOString();
28
+ return {
29
+ sessionId: 'test-resume-session',
30
+ schemaVersion: '1.0.0',
31
+ createdAt: now,
32
+ updatedAt: now,
33
+ phase: 'Discover',
34
+ status: 'Active',
35
+ participants: [],
36
+ artifacts: { generatedFiles: [] },
37
+ turns: [],
38
+ ...overrides,
39
+ };
40
+ }
41
+
42
+ function createScriptedIO(
43
+ inputs: (string | null)[],
44
+ decisionGateChoice: DecisionGateResult = { choice: 'continue' },
45
+ ): LoopIO & { output: string[]; activityLog: string[] } {
46
+ let inputIdx = 0;
47
+ const output: string[] = [];
48
+ const activityLog: string[] = [];
49
+
50
+ return {
51
+ write(text: string) {
52
+ output.push(text);
53
+ },
54
+ writeActivity(text: string) {
55
+ activityLog.push(text);
56
+ },
57
+ writeToolSummary(_toolName: string, _summary: string) {
58
+ // no-op
59
+ },
60
+ async readInput(_prompt?: string): Promise<string | null> {
61
+ if (inputIdx >= inputs.length) return null;
62
+ return inputs[inputIdx++];
63
+ },
64
+ async showDecisionGate(_phase: PhaseValue): Promise<DecisionGateResult> {
65
+ return decisionGateChoice;
66
+ },
67
+ isJsonMode: false,
68
+ isTTY: false,
69
+ output,
70
+ activityLog,
71
+ };
72
+ }
73
+
74
+ // ── Tests ────────────────────────────────────────────────────────────────────
75
+
76
+ describe('Resume and Backtrack Flow', () => {
77
+ let tmpDir: string;
78
+ let store: SessionStore;
79
+
80
+ beforeEach(async () => {
81
+ tmpDir = await mkdtemp(join(tmpdir(), 'sofia-resume-'));
82
+ store = new SessionStore(tmpDir);
83
+ });
84
+
85
+ afterEach(async () => {
86
+ await rm(tmpDir, { recursive: true, force: true });
87
+ });
88
+
89
+ it('resumes an existing session from disk', async () => {
90
+ // Save a session mid-Ideate
91
+ const session = createTestSession({
92
+ phase: 'Ideate',
93
+ businessContext: {
94
+ businessDescription: 'Logistics company',
95
+ challenges: ['Slow deliveries'],
96
+ },
97
+ workflow: {
98
+ activities: [{ id: 'a1', name: 'Route Planning' }],
99
+ edges: [],
100
+ },
101
+ });
102
+ await store.save(session);
103
+
104
+ // Load it back (resume)
105
+ const loaded = await store.load(session.sessionId);
106
+ expect(loaded.phase).toBe('Ideate');
107
+ expect(loaded.businessContext!.businessDescription).toBe('Logistics company');
108
+ });
109
+
110
+ it('resumes and continues Ideate phase with ConversationLoop', async () => {
111
+ const session = createTestSession({
112
+ phase: 'Ideate',
113
+ businessContext: {
114
+ businessDescription: 'Logistics company',
115
+ challenges: ['Slow deliveries'],
116
+ },
117
+ workflow: {
118
+ activities: [{ id: 'a1', name: 'Route Planning' }],
119
+ edges: [],
120
+ },
121
+ });
122
+ await store.save(session);
123
+
124
+ const loaded = await store.load(session.sessionId);
125
+ const client = createFakeCopilotClient([
126
+ { role: 'assistant', content: 'Let me help you brainstorm ideas for optimizing routes.' },
127
+ ]);
128
+ const io = createScriptedIO(['Can we use AI for routing?', null]);
129
+ const handler = createPhaseHandler('Ideate');
130
+ await handler._preload();
131
+
132
+ const loop = new ConversationLoop({
133
+ client,
134
+ io,
135
+ session: loaded,
136
+ phaseHandler: handler,
137
+ onEvent: () => {},
138
+ onSessionUpdate: async (s) => { await store.save(s); },
139
+ });
140
+
141
+ const result = await loop.run();
142
+ expect(result.turns!.length).toBeGreaterThan(0);
143
+ });
144
+
145
+ it('backtracks from Design to Ideate and clears downstream data', async () => {
146
+ const session = createTestSession({
147
+ phase: 'Design',
148
+ businessContext: {
149
+ businessDescription: 'ACME Corp',
150
+ challenges: ['Cost reduction'],
151
+ },
152
+ workflow: {
153
+ activities: [{ id: 'a1', name: 'Procurement' }],
154
+ edges: [],
155
+ },
156
+ ideas: [
157
+ { id: 'i1', title: 'AI Procurement', description: 'Automated purchasing', workflowStepIds: ['a1'] },
158
+ ],
159
+ evaluation: {
160
+ method: 'feasibility-value-matrix',
161
+ ideas: [{ ideaId: 'i1', feasibility: 4, value: 5 }],
162
+ },
163
+ turns: [
164
+ { phase: 'Discover', sequence: 1, role: 'user', content: 'hello', timestamp: new Date().toISOString() },
165
+ { phase: 'Ideate', sequence: 2, role: 'user', content: 'ideas', timestamp: new Date().toISOString() },
166
+ { phase: 'Design', sequence: 3, role: 'user', content: 'evaluate', timestamp: new Date().toISOString() },
167
+ ],
168
+ });
169
+ await store.save(session);
170
+
171
+ const loaded = await store.load(session.sessionId);
172
+ const result = backtrackSession(loaded, 'Ideate');
173
+
174
+ expect(result.success).toBe(true);
175
+ expect(result.session.phase).toBe('Ideate');
176
+ expect(result.session.ideas).toBeUndefined();
177
+ expect(result.session.evaluation).toBeUndefined();
178
+ // Discover data preserved
179
+ expect(result.session.businessContext).toBeDefined();
180
+ // Only Discover turns remain
181
+ expect(result.session.turns?.length).toBe(1);
182
+ expect(result.session.turns![0].phase).toBe('Discover');
183
+
184
+ // Save backtracked session
185
+ await store.save(result.session);
186
+ const reloaded = await store.load(session.sessionId);
187
+ expect(reloaded.phase).toBe('Ideate');
188
+ });
189
+
190
+ it('re-runs Ideate after backtrack and produces fresh ideas', async () => {
191
+ const session = createTestSession({
192
+ phase: 'Ideate',
193
+ status: 'Active',
194
+ businessContext: {
195
+ businessDescription: 'ACME Corp',
196
+ challenges: ['Cost reduction'],
197
+ },
198
+ });
199
+
200
+ // LLM returns fresh ideas in JSON
201
+ const client = createFakeCopilotClient([
202
+ {
203
+ role: 'assistant',
204
+ content: '```json\n[{"id": "new-1", "title": "Smart Scheduling", "description": "AI scheduling", "workflowStepIds": ["a1"]}]\n```',
205
+ },
206
+ ]);
207
+ const io = createScriptedIO(['Generate new ideas', null]);
208
+ const handler = createPhaseHandler('Ideate');
209
+ await handler._preload();
210
+
211
+ const loop = new ConversationLoop({
212
+ client,
213
+ io,
214
+ session,
215
+ phaseHandler: handler,
216
+ onEvent: () => {},
217
+ onSessionUpdate: async (s) => { await store.save(s); },
218
+ });
219
+
220
+ const result = await loop.run();
221
+ // The extractResult should have captured the ideas
222
+ expect(result.ideas).toBeDefined();
223
+ expect(result.ideas!.length).toBeGreaterThan(0);
224
+ expect(result.ideas![0].id).toBe('new-1');
225
+ });
226
+
227
+ it('backtrack to Discover clears all downstream phase data', () => {
228
+ const session = createTestSession({
229
+ phase: 'Plan',
230
+ businessContext: {
231
+ businessDescription: 'Tech Co',
232
+ challenges: ['Scaling'],
233
+ },
234
+ workflow: {
235
+ activities: [{ id: 'a1', name: 'Deploy' }],
236
+ edges: [],
237
+ },
238
+ ideas: [
239
+ { id: 'i1', title: 'Auto-scale', description: 'Auto scaling', workflowStepIds: ['a1'] },
240
+ ],
241
+ evaluation: {
242
+ method: 'feasibility-value-matrix',
243
+ ideas: [{ ideaId: 'i1', feasibility: 5, value: 5 }],
244
+ },
245
+ selection: {
246
+ ideaId: 'i1',
247
+ selectionRationale: 'Only option',
248
+ confirmedByUser: true,
249
+ },
250
+ plan: {
251
+ milestones: [{ id: 'm1', title: 'Phase 1', items: ['Setup'] }],
252
+ },
253
+ });
254
+
255
+ const result = backtrackSession(session, 'Discover');
256
+ expect(result.success).toBe(true);
257
+ expect(result.invalidatedPhases).toContain('Discover');
258
+ expect(result.invalidatedPhases).toContain('Ideate');
259
+ expect(result.invalidatedPhases).toContain('Design');
260
+ expect(result.invalidatedPhases).toContain('Select');
261
+ expect(result.invalidatedPhases).toContain('Plan');
262
+
263
+ const s = result.session;
264
+ expect(s.businessContext).toBeUndefined();
265
+ expect(s.workflow).toBeUndefined();
266
+ expect(s.ideas).toBeUndefined();
267
+ expect(s.evaluation).toBeUndefined();
268
+ expect(s.selection).toBeUndefined();
269
+ expect(s.plan).toBeUndefined();
270
+ });
271
+
272
+ it('forward backtrack is rejected', () => {
273
+ const session = createTestSession({ phase: 'Ideate' });
274
+ const result = backtrackSession(session, 'Plan');
275
+ expect(result.success).toBe(false);
276
+ expect(result.error).toContain('forward');
277
+ });
278
+ });