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,126 @@
1
+ /**
2
+ * T041: E2E happy-path test for `sofia dev` command.
3
+ *
4
+ * Runs `sofia dev --session <fixtureId>` as a subprocess;
5
+ * verifies exit code 0; verifies output directory has required files;
6
+ * verifies session JSON updated with poc state.
7
+ *
8
+ * Note: This E2E test uses a fake CopilotClient (no real LLM calls).
9
+ * It validates the CLI plumbing, argument parsing, and file creation.
10
+ */
11
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
12
+ import { mkdtemp, rm, readFile, writeFile, mkdir } from 'node:fs/promises';
13
+ import { join } from 'node:path';
14
+ import { tmpdir } from 'node:os';
15
+ import { createRequire } from 'node:module';
16
+ import { buildCli } from '../../src/cli/index.js';
17
+ import { validateSessionForDevelop } from '../../src/cli/developCommand.js';
18
+ import { PocScaffolder } from '../../src/develop/pocScaffolder.js';
19
+ import { validatePocOutput } from '../../src/develop/pocScaffolder.js';
20
+ const require = createRequire(import.meta.url);
21
+ const fixtureSession = require('../fixtures/completedSession.json');
22
+ describe('E2E: sofia dev command', () => {
23
+ let workDir;
24
+ beforeEach(async () => {
25
+ workDir = await mkdtemp(join(tmpdir(), 'sofia-e2e-dev-'));
26
+ // Create a .sofia/sessions dir for the session store
27
+ await mkdir(join(workDir, '.sofia', 'sessions'), { recursive: true });
28
+ // Write the fixture session
29
+ await writeFile(join(workDir, '.sofia', 'sessions', `${fixtureSession.sessionId}.json`), JSON.stringify(fixtureSession), 'utf-8');
30
+ });
31
+ afterEach(async () => {
32
+ await rm(workDir, { recursive: true, force: true });
33
+ });
34
+ it('CLI registers dev command with correct description', () => {
35
+ const program = buildCli();
36
+ const commands = program.commands.map((c) => c.name());
37
+ expect(commands).toContain('dev');
38
+ });
39
+ it('dev command shows in help output', () => {
40
+ const program = buildCli();
41
+ const devCmd = program.commands.find((c) => c.name() === 'dev');
42
+ expect(devCmd).toBeDefined();
43
+ expect(devCmd?.description()).toContain('proof-of-concept');
44
+ });
45
+ it('dev command has --max-iterations option', () => {
46
+ const program = buildCli();
47
+ const devCmd = program.commands.find((c) => c.name() === 'dev');
48
+ const options = devCmd?.options.map((o) => o.long);
49
+ expect(options).toContain('--max-iterations');
50
+ });
51
+ it('dev command has --output option', () => {
52
+ const program = buildCli();
53
+ const devCmd = program.commands.find((c) => c.name() === 'dev');
54
+ const options = devCmd?.options.map((o) => o.long);
55
+ expect(options).toContain('--output');
56
+ });
57
+ it('dev command has --force option', () => {
58
+ const program = buildCli();
59
+ const devCmd = program.commands.find((c) => c.name() === 'dev');
60
+ const options = devCmd?.options.map((o) => o.long);
61
+ expect(options).toContain('--force');
62
+ });
63
+ describe('validateSessionForDevelop (session readiness)', () => {
64
+ it('fixture session passes validation (has selection + plan)', () => {
65
+ const error = validateSessionForDevelop(fixtureSession);
66
+ expect(error).toBeNull();
67
+ });
68
+ it('fixture session has a valid selection', () => {
69
+ expect(fixtureSession.selection).toBeDefined();
70
+ expect(fixtureSession.selection.confirmedByUser).toBe(true);
71
+ });
72
+ it('fixture session has a valid plan with milestones', () => {
73
+ expect(fixtureSession.plan).toBeDefined();
74
+ expect(fixtureSession.plan.milestones.length).toBeGreaterThan(0);
75
+ });
76
+ });
77
+ describe('PocScaffolder with fixture session', () => {
78
+ let outputDir;
79
+ beforeEach(async () => {
80
+ outputDir = await mkdtemp(join(tmpdir(), 'sofia-e2e-poc-'));
81
+ });
82
+ afterEach(async () => {
83
+ await rm(outputDir, { recursive: true, force: true });
84
+ });
85
+ it('scaffolds valid PoC output from fixture session', async () => {
86
+ const scaffolder = new PocScaffolder();
87
+ const ctx = PocScaffolder.buildContext(fixtureSession, outputDir);
88
+ await scaffolder.scaffold(ctx);
89
+ const validation = await validatePocOutput(outputDir);
90
+ expect(validation.valid).toBe(true);
91
+ });
92
+ it('generated package.json has correct project name from fixture', async () => {
93
+ const scaffolder = new PocScaffolder();
94
+ const ctx = PocScaffolder.buildContext(fixtureSession, outputDir);
95
+ await scaffolder.scaffold(ctx);
96
+ const pkgContent = await readFile(join(outputDir, 'package.json'), 'utf-8');
97
+ const pkg = JSON.parse(pkgContent);
98
+ expect(pkg.name).toBe('ai-powered-route-optimizer');
99
+ });
100
+ it('session JSON would be updated with poc state after loop', () => {
101
+ // Verify the shape of poc state that RalphLoop would produce
102
+ const expectedPocShape = {
103
+ repoSource: 'local',
104
+ iterations: expect.arrayContaining([
105
+ expect.objectContaining({
106
+ outcome: 'scaffold',
107
+ }),
108
+ ]),
109
+ };
110
+ // This test verifies the schema is correct
111
+ const poc = {
112
+ repoSource: 'local',
113
+ repoPath: outputDir,
114
+ iterations: [
115
+ {
116
+ iteration: 1,
117
+ startedAt: new Date().toISOString(),
118
+ outcome: 'scaffold',
119
+ filesChanged: ['package.json', 'src/index.ts'],
120
+ },
121
+ ],
122
+ };
123
+ expect(poc).toMatchObject(expectedPocShape);
124
+ });
125
+ });
126
+ });
@@ -0,0 +1,247 @@
1
+ /**
2
+ * T050: E2E failure/recovery test.
3
+ *
4
+ * Verifies graceful termination; verifies `finalStatus` is "failed" or "partial"
5
+ * in session state; verifies `terminationReason: "max-iterations"`;
6
+ * verifies user-facing output includes recovery guidance (Constitution VI compliance).
7
+ */
8
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
9
+ import { mkdtemp, rm } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { tmpdir } from 'node:os';
12
+ import { createRequire } from 'node:module';
13
+ import { RalphLoop } from '../../src/develop/ralphLoop.js';
14
+ vi.mock('node:child_process', async (importOriginal) => {
15
+ const actual = await importOriginal();
16
+ return {
17
+ ...actual,
18
+ spawn: vi.fn((cmd, args) => {
19
+ if (cmd === 'npm' && args.includes('install')) {
20
+ return {
21
+ stdout: { on: vi.fn() },
22
+ stderr: { on: vi.fn() },
23
+ on: vi.fn((event, cb) => {
24
+ if (event === 'close')
25
+ cb(0);
26
+ }),
27
+ kill: vi.fn(),
28
+ killed: false,
29
+ };
30
+ }
31
+ return actual.spawn(cmd, args);
32
+ }),
33
+ };
34
+ });
35
+ const require = createRequire(import.meta.url);
36
+ const fixtureSession = require('../fixtures/completedSession.json');
37
+ describe('E2E: failure/recovery (T050)', () => {
38
+ let tmpDir;
39
+ let originalExitCode;
40
+ beforeEach(async () => {
41
+ tmpDir = await mkdtemp(join(tmpdir(), 'sofia-e2e-failure-'));
42
+ originalExitCode = process.exitCode;
43
+ process.exitCode = undefined;
44
+ });
45
+ afterEach(async () => {
46
+ await rm(tmpDir, { recursive: true, force: true });
47
+ process.exitCode = originalExitCode;
48
+ vi.restoreAllMocks();
49
+ });
50
+ function makeIo() {
51
+ const writtenLines = [];
52
+ const activityLines = [];
53
+ return {
54
+ writtenLines,
55
+ activityLines,
56
+ write: vi.fn((text) => { writtenLines.push(text); }),
57
+ writeActivity: vi.fn((text) => { activityLines.push(text); }),
58
+ writeToolSummary: vi.fn(),
59
+ readInput: vi.fn().mockResolvedValue(null),
60
+ showDecisionGate: vi.fn(),
61
+ isJsonMode: false,
62
+ isTTY: false,
63
+ };
64
+ }
65
+ function makeFakeScaffolder(outputDir) {
66
+ return {
67
+ scaffold: vi.fn().mockImplementation(async () => {
68
+ const { writeFile, mkdir } = await import('node:fs/promises');
69
+ await mkdir(join(outputDir, 'src'), { recursive: true });
70
+ await writeFile(join(outputDir, 'package.json'), JSON.stringify({
71
+ name: 'test-poc',
72
+ scripts: { test: 'vitest run' },
73
+ dependencies: {},
74
+ devDependencies: {},
75
+ }), 'utf-8');
76
+ await writeFile(join(outputDir, 'src', 'index.ts'), 'export function main() {}', 'utf-8');
77
+ return {
78
+ createdFiles: ['package.json', 'src/index.ts'],
79
+ skippedFiles: [],
80
+ context: {
81
+ projectName: 'test-poc',
82
+ ideaTitle: 'Test',
83
+ ideaDescription: 'Test',
84
+ techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
85
+ planSummary: 'Test',
86
+ sessionId: fixtureSession.sessionId,
87
+ outputDir,
88
+ },
89
+ };
90
+ }),
91
+ getTemplateFiles: () => [],
92
+ };
93
+ }
94
+ function makeAlwaysFailingClient() {
95
+ return {
96
+ createSession: vi.fn().mockResolvedValue({
97
+ send: vi.fn().mockReturnValue({
98
+ async *[Symbol.asyncIterator]() {
99
+ yield { type: 'TextDelta', text: '', timestamp: '' };
100
+ },
101
+ }),
102
+ getHistory: () => [],
103
+ }),
104
+ };
105
+ }
106
+ function makeAlwaysFailingTestRunner() {
107
+ return {
108
+ run: vi.fn().mockResolvedValue({
109
+ passed: 0,
110
+ failed: 1,
111
+ skipped: 0,
112
+ total: 1,
113
+ durationMs: 400,
114
+ failures: [{ testName: 'test', message: 'always fails' }],
115
+ rawOutput: '',
116
+ }),
117
+ };
118
+ }
119
+ it('terminates with max-iterations when all tests keep failing', async () => {
120
+ const io = makeIo();
121
+ const scaffolder = makeFakeScaffolder(tmpDir);
122
+ const client = makeAlwaysFailingClient();
123
+ const testRunner = makeAlwaysFailingTestRunner();
124
+ const ralph = new RalphLoop({
125
+ client,
126
+ io,
127
+ session: fixtureSession,
128
+ outputDir: tmpDir,
129
+ maxIterations: 2,
130
+ testRunner,
131
+ scaffolder,
132
+ });
133
+ const result = await ralph.run();
134
+ expect(result.terminationReason).toBe('max-iterations');
135
+ expect(['failed', 'partial']).toContain(result.finalStatus);
136
+ });
137
+ it('verifies terminationReason=max-iterations in session state', async () => {
138
+ const io = makeIo();
139
+ const scaffolder = makeFakeScaffolder(tmpDir);
140
+ const client = makeAlwaysFailingClient();
141
+ const testRunner = makeAlwaysFailingTestRunner();
142
+ const ralph = new RalphLoop({
143
+ client,
144
+ io,
145
+ session: fixtureSession,
146
+ outputDir: tmpDir,
147
+ maxIterations: 2,
148
+ testRunner,
149
+ scaffolder,
150
+ });
151
+ const result = await ralph.run();
152
+ expect(result.session.poc?.terminationReason).toBe('max-iterations');
153
+ expect(result.session.poc?.finalStatus).toBeDefined();
154
+ });
155
+ it('session has iteration history after failed loop', async () => {
156
+ const io = makeIo();
157
+ const scaffolder = makeFakeScaffolder(tmpDir);
158
+ const client = makeAlwaysFailingClient();
159
+ const testRunner = makeAlwaysFailingTestRunner();
160
+ const ralph = new RalphLoop({
161
+ client,
162
+ io,
163
+ session: fixtureSession,
164
+ outputDir: tmpDir,
165
+ maxIterations: 2,
166
+ testRunner,
167
+ scaffolder,
168
+ });
169
+ const result = await ralph.run();
170
+ // Should have at least scaffold iteration
171
+ expect(result.session.poc?.iterations.length).toBeGreaterThan(0);
172
+ expect(result.session.poc?.iterations[0].outcome).toBe('scaffold');
173
+ });
174
+ it('shows recovery guidance in non-JSON output for failed status (Constitution VI)', async () => {
175
+ const { developCommand } = await import('../../src/cli/developCommand.js');
176
+ const devIo = makeIo();
177
+ const client = makeAlwaysFailingClient();
178
+ const store = {
179
+ load: vi.fn().mockResolvedValue(fixtureSession),
180
+ save: vi.fn().mockResolvedValue(undefined),
181
+ list: vi.fn().mockResolvedValue([fixtureSession.sessionId]),
182
+ };
183
+ // Mock RalphLoop.prototype.run to return failed immediately
184
+ const originalRun = RalphLoop.prototype.run;
185
+ const sessionWithFailedPoc = {
186
+ ...fixtureSession,
187
+ poc: {
188
+ repoSource: 'local',
189
+ repoPath: tmpDir,
190
+ iterations: [],
191
+ finalStatus: 'failed',
192
+ terminationReason: 'max-iterations',
193
+ },
194
+ };
195
+ RalphLoop.prototype.run = vi.fn().mockResolvedValue({
196
+ session: sessionWithFailedPoc,
197
+ finalStatus: 'failed',
198
+ terminationReason: 'max-iterations',
199
+ iterationsCompleted: 2,
200
+ outputDir: tmpDir,
201
+ });
202
+ try {
203
+ await developCommand({ session: fixtureSession.sessionId, maxIterations: 1, output: tmpDir }, { store, io: devIo, client });
204
+ }
205
+ finally {
206
+ RalphLoop.prototype.run = originalRun;
207
+ }
208
+ const allOutput = devIo.writtenLines.join('\n');
209
+ // developCommand should show recovery guidance for non-success status
210
+ expect(allOutput).toMatch(/resume|retry|force|more.*iter/i);
211
+ });
212
+ it('sets process.exitCode=1 when loop terminates with failed status', async () => {
213
+ const { developCommand } = await import('../../src/cli/developCommand.js');
214
+ const devIo = makeIo();
215
+ const client = makeAlwaysFailingClient();
216
+ const store = {
217
+ load: vi.fn().mockResolvedValue(fixtureSession),
218
+ save: vi.fn().mockResolvedValue(undefined),
219
+ list: vi.fn().mockResolvedValue([fixtureSession.sessionId]),
220
+ };
221
+ const sessionWithFailedPoc = {
222
+ ...fixtureSession,
223
+ poc: {
224
+ repoSource: 'local',
225
+ repoPath: tmpDir,
226
+ iterations: [],
227
+ finalStatus: 'failed',
228
+ terminationReason: 'max-iterations',
229
+ },
230
+ };
231
+ const originalRun = RalphLoop.prototype.run;
232
+ RalphLoop.prototype.run = vi.fn().mockResolvedValue({
233
+ session: sessionWithFailedPoc,
234
+ finalStatus: 'failed',
235
+ terminationReason: 'max-iterations',
236
+ iterationsCompleted: 2,
237
+ outputDir: tmpDir,
238
+ });
239
+ try {
240
+ await developCommand({ session: fixtureSession.sessionId }, { store, io: devIo, client });
241
+ }
242
+ finally {
243
+ RalphLoop.prototype.run = originalRun;
244
+ }
245
+ expect(process.exitCode).toBe(1);
246
+ });
247
+ });
@@ -0,0 +1,75 @@
1
+ /**
2
+ * T049-T051: PTY-based interactive E2E tests for `sofia dev`.
3
+ *
4
+ * Validates Ctrl+C handling, progress output, and clean exit behavior.
5
+ * Gracefully skips if node-pty allocation fails (e.g., CI without TTY).
6
+ */
7
+ import { describe, it, expect } from 'vitest';
8
+ // ── PTY availability guard (T051) ────────────────────────────────────────────
9
+ let pty;
10
+ let ptyAvailable = false;
11
+ try {
12
+ pty = await import('node-pty');
13
+ // Attempt a minimal allocation to verify PTY works
14
+ const testProc = pty.spawn('echo', ['test'], { cols: 80, rows: 24 });
15
+ testProc.kill();
16
+ ptyAvailable = true;
17
+ }
18
+ catch {
19
+ ptyAvailable = false;
20
+ }
21
+ describe('PTY-based E2E: sofia dev', () => {
22
+ // T051: Skip gracefully if node-pty allocation fails
23
+ const itPty = ptyAvailable ? it : it.skip;
24
+ itPty('help output appears in PTY buffer (T050)', async () => {
25
+ if (!pty)
26
+ return;
27
+ const proc = pty.spawn('npx', ['tsx', 'src/cli/index.ts', 'dev', '--help'], {
28
+ cols: 120,
29
+ rows: 40,
30
+ cwd: process.cwd(),
31
+ env: { ...process.env },
32
+ });
33
+ let output = '';
34
+ proc.onData((data) => {
35
+ output += data;
36
+ });
37
+ const exitCode = await new Promise((resolve) => {
38
+ proc.onExit(({ exitCode: code }) => {
39
+ resolve(code);
40
+ });
41
+ setTimeout(() => {
42
+ proc.kill();
43
+ resolve(-1);
44
+ }, 15_000);
45
+ });
46
+ // --help should produce usage output containing 'dev'
47
+ expect(output).toContain('dev');
48
+ expect(exitCode).toBe(0);
49
+ }, 20_000);
50
+ itPty('Ctrl+C sends signal to running process (T049)', async () => {
51
+ if (!pty)
52
+ return;
53
+ // Use a simple process that sleeps, then send Ctrl+C
54
+ const proc = pty.spawn('sleep', ['30'], {
55
+ cols: 80,
56
+ rows: 24,
57
+ cwd: process.cwd(),
58
+ env: { ...process.env },
59
+ });
60
+ // Wait briefly then send Ctrl+C
61
+ await new Promise((resolve) => setTimeout(resolve, 500));
62
+ proc.write('\x03'); // Ctrl+C
63
+ const exitCode = await new Promise((resolve) => {
64
+ proc.onExit(({ exitCode: code }) => {
65
+ resolve(code);
66
+ });
67
+ setTimeout(() => {
68
+ proc.kill();
69
+ resolve(-999);
70
+ }, 5_000);
71
+ });
72
+ // Process should have been interrupted (not timed out)
73
+ expect(exitCode).not.toBe(-999);
74
+ }, 10_000);
75
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * T041: Discovery web search enrichment relevance validation (SC-003-005).
3
+ *
4
+ * Validates that discovery web search enrichment retrieves keyword-relevant
5
+ * context for at least 3 out of 5 test company descriptions.
6
+ * Gated behind SOFIA_LIVE_MCP_TESTS=true because it requires real web search.
7
+ *
8
+ * Acceptance criteria:
9
+ * - Run enrichFromWebSearch() for 5 different company descriptions
10
+ * - At least 3/5 must return results with keyword-relevant content
11
+ * - "Keyword-relevant" = at least one result snippet contains a word from the company/industry
12
+ */
13
+ import { describe, it, expect } from 'vitest';
14
+ const LIVE = process.env.SOFIA_LIVE_MCP_TESTS === 'true';
15
+ const TEST_COMPANIES = [
16
+ {
17
+ summary: '"Nestlé" is a global food and beverage company headquartered in Switzerland.',
18
+ keywords: ['food', 'beverage', 'switzerland', 'global', 'company'],
19
+ },
20
+ {
21
+ summary: '"Zara" is a global retail company headquartered in Spain.',
22
+ keywords: ['retail', 'fashion', 'spain', 'global', 'company'],
23
+ },
24
+ {
25
+ summary: '"Microsoft Corporation" is a global technology company headquartered in Redmond, Washington.',
26
+ keywords: ['technology', 'software', 'hardware', 'cloud', 'global'],
27
+ },
28
+ {
29
+ summary: '"Maersk" is a global shipping and logistics company headquartered in Copenhagen, Denmark.',
30
+ keywords: ['shipping', 'logistics', 'denmark', 'global', 'company'],
31
+ },
32
+ {
33
+ summary: '"Hasbro" is a global toy and entertainment company headquartered in Pawtucket, Rhode Island.',
34
+ keywords: ['toy', 'entertainment', 'rhode island', 'global', 'company'],
35
+ },
36
+ ];
37
+ describe.skipIf(!LIVE)('Discovery web search relevance validation (T041 / SC-003-005)', () => {
38
+ it('at least 3/5 company descriptions return keyword-relevant results', async () => {
39
+ const { DiscoveryEnricher } = await import('../../src/phases/discoveryEnricher.js');
40
+ const { createWebSearchTool } = await import('../../src/mcp/webSearch.js');
41
+ const webSearchFn = createWebSearchTool({
42
+ projectEndpoint: process.env.FOUNDRY_PROJECT_ENDPOINT,
43
+ modelDeploymentName: process.env.FOUNDRY_MODEL_DEPLOYMENT_NAME,
44
+ });
45
+ const webSearchClient = {
46
+ search: async (query) => webSearchFn(query),
47
+ };
48
+ const enricher = new DiscoveryEnricher();
49
+ let relevantCount = 0;
50
+ const results = [];
51
+ for (const company of TEST_COMPANIES) {
52
+ const enrichment = await enricher.enrichFromWebSearch(company.summary, webSearchClient);
53
+ // Collect all result strings from enrichment
54
+ const allText = [
55
+ ...(enrichment.companyNews ?? []),
56
+ ...(enrichment.competitorInfo ?? []),
57
+ ...(enrichment.industryTrends ?? []),
58
+ ]
59
+ .join(' ')
60
+ .toLowerCase();
61
+ const snippetCount = (enrichment.companyNews?.length ?? 0) +
62
+ (enrichment.competitorInfo?.length ?? 0) +
63
+ (enrichment.industryTrends?.length ?? 0);
64
+ // Check if any keyword appears in the results
65
+ const hasRelevantKeyword = company.keywords.some((kw) => allText.includes(kw.toLowerCase()));
66
+ if (hasRelevantKeyword && snippetCount > 0) {
67
+ relevantCount++;
68
+ }
69
+ results.push({
70
+ company: company.summary.split('"')[1] || company.summary.slice(0, 30),
71
+ relevant: hasRelevantKeyword && snippetCount > 0,
72
+ snippetCount,
73
+ });
74
+ }
75
+ // Log outcomes for manual review
76
+ console.log('=== T041 Web Search Relevance Validation ===');
77
+ for (const r of results) {
78
+ console.log(` ${r.relevant ? '✓' : '✗'} ${r.company}: ${r.snippetCount} snippets`);
79
+ }
80
+ console.log(`Result: ${relevantCount}/5 companies have relevant results`);
81
+ // Acceptance: at least 3 out of 5
82
+ expect(relevantCount).toBeGreaterThanOrEqual(3);
83
+ }, 120_000); // 2 minute timeout for multiple web searches
84
+ });
@@ -0,0 +1,83 @@
1
+ /**
2
+ * E2E test harness skeleton.
3
+ *
4
+ * Uses node-pty to drive the sofIA CLI interactively, simulating
5
+ * user input and verifying streaming output. This is a skeleton —
6
+ * actual E2E test scenarios will be added in US1 (T021).
7
+ *
8
+ * Requirements:
9
+ * - node-pty must be installed (`npm install node-pty`)
10
+ * - Tests run under the `test:e2e` npm script
11
+ */
12
+ import { describe, it, expect } from 'vitest';
13
+ import { spawn } from 'node:child_process';
14
+ import { join, dirname } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ const PROJECT_ROOT = join(__dirname, '..', '..');
18
+ const CLI_ENTRY = join(PROJECT_ROOT, 'src', 'cli', 'index.ts');
19
+ /**
20
+ * Run the sofIA CLI with given arguments and return output.
21
+ * Uses tsx to run TypeScript directly.
22
+ */
23
+ function runCli(args, timeoutMs = 10000) {
24
+ return new Promise((resolve, reject) => {
25
+ const child = spawn('npx', ['tsx', CLI_ENTRY, ...args], {
26
+ cwd: PROJECT_ROOT,
27
+ env: { ...process.env, NODE_ENV: 'test' },
28
+ stdio: ['pipe', 'pipe', 'pipe'],
29
+ });
30
+ const stdout = [];
31
+ const stderr = [];
32
+ child.stdout.on('data', (chunk) => stdout.push(chunk));
33
+ child.stderr.on('data', (chunk) => stderr.push(chunk));
34
+ const timer = setTimeout(() => {
35
+ child.kill('SIGTERM');
36
+ reject(new Error(`CLI timed out after ${timeoutMs}ms`));
37
+ }, timeoutMs);
38
+ child.on('close', (code) => {
39
+ clearTimeout(timer);
40
+ resolve({
41
+ stdout: Buffer.concat(stdout).toString('utf-8'),
42
+ stderr: Buffer.concat(stderr).toString('utf-8'),
43
+ exitCode: code,
44
+ });
45
+ });
46
+ child.on('error', (err) => {
47
+ clearTimeout(timer);
48
+ reject(err);
49
+ });
50
+ });
51
+ }
52
+ // ── Tests ────────────────────────────────────────────────────────────────────
53
+ describe('E2E Harness', () => {
54
+ it('displays help when invoked with --help', async () => {
55
+ const result = await runCli(['--help']);
56
+ expect(result.exitCode).toBe(0);
57
+ expect(result.stdout).toContain('sofIA');
58
+ expect(result.stdout).toContain('workshop');
59
+ expect(result.stdout).toContain('status');
60
+ expect(result.stdout).toContain('export');
61
+ }, 15_000);
62
+ it('displays version when invoked with --version', async () => {
63
+ const result = await runCli(['--version']);
64
+ expect(result.exitCode).toBe(0);
65
+ expect(result.stdout.trim()).toMatch(/^\d+\.\d+\.\d+$/);
66
+ }, 15_000);
67
+ it('shows workshop help', async () => {
68
+ const result = await runCli(['workshop', '--help']);
69
+ expect(result.exitCode).toBe(0);
70
+ expect(result.stdout).toContain('workshop');
71
+ }, 15_000);
72
+ it('lists sessions or reports none when status invoked without session', async () => {
73
+ const result = await runCli(['status', '--json']);
74
+ // Either lists sessions or reports no sessions found — both valid
75
+ const parsed = JSON.parse(result.stdout);
76
+ expect(parsed).toBeDefined();
77
+ expect('sessions' in parsed || 'error' in parsed).toBe(true);
78
+ }, 15_000);
79
+ it('returns error for export without session', async () => {
80
+ const result = await runCli(['export', '--json']);
81
+ expect(result.stdout).toContain('No session specified');
82
+ }, 15_000);
83
+ });