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,512 @@
1
+ /**
2
+ * Tests for DiscoveryEnricher — US3 web search enrichment
3
+ * and US4 WorkIQ enrichment.
4
+ *
5
+ * T026: enrichFromWebSearch() basic functionality
6
+ * T027: User consent prompt integration
7
+ * T028: Session schema integration
8
+ */
9
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
10
+
11
+ import { DiscoveryEnricher } from '../../../src/phases/discoveryEnricher.js';
12
+ import type { WebSearchClient } from '../../../src/phases/discoveryEnricher.js';
13
+ import type { LoopIO } from '../../../src/loop/conversationLoop.js';
14
+ import type { McpManager } from '../../../src/mcp/mcpManager.js';
15
+ import {
16
+ DiscoveryEnrichmentSchema,
17
+ workshopSessionSchema,
18
+ } from '../../../src/shared/schemas/session.js';
19
+
20
+ // ── Helpers ──────────────────────────────────────────────────────────────────
21
+
22
+ function makeWebSearchClient(
23
+ results: Array<{ title: string; url: string; snippet: string }> = [],
24
+ opts?: { degraded?: boolean; throws?: boolean },
25
+ ): WebSearchClient {
26
+ return {
27
+ search: opts?.throws
28
+ ? vi.fn().mockRejectedValue(new Error('search failed'))
29
+ : vi.fn().mockResolvedValue({
30
+ results,
31
+ degraded: opts?.degraded ?? false,
32
+ }),
33
+ };
34
+ }
35
+
36
+ function makeLoopIO(overrides?: Partial<LoopIO>): LoopIO {
37
+ return {
38
+ write: vi.fn(),
39
+ writeActivity: vi.fn(),
40
+ writeToolSummary: vi.fn(),
41
+ readInput: vi.fn().mockResolvedValue('n'),
42
+ showDecisionGate: vi.fn().mockResolvedValue({ choice: 'continue' }),
43
+ isJsonMode: false,
44
+ isTTY: true,
45
+ ...overrides,
46
+ };
47
+ }
48
+
49
+ function makeMcpManager(overrides?: Partial<McpManager>): McpManager {
50
+ return {
51
+ isAvailable: vi.fn().mockReturnValue(false),
52
+ callTool: vi.fn().mockResolvedValue({}),
53
+ markConnected: vi.fn(),
54
+ markDisconnected: vi.fn(),
55
+ disconnectAll: vi.fn().mockResolvedValue(undefined),
56
+ ...overrides,
57
+ } as unknown as McpManager;
58
+ }
59
+
60
+ // ── T026: enrichFromWebSearch() ──────────────────────────────────────────────
61
+
62
+ describe('DiscoveryEnricher', () => {
63
+ let enricher: DiscoveryEnricher;
64
+
65
+ beforeEach(() => {
66
+ enricher = new DiscoveryEnricher();
67
+ });
68
+
69
+ describe('enrichFromWebSearch() (T026)', () => {
70
+ it('calls webSearchClient.search with company-news, competitor, and industry-trends queries', async () => {
71
+ const searchFn = vi.fn().mockResolvedValue({ results: [], degraded: false });
72
+ const client: WebSearchClient = { search: searchFn };
73
+
74
+ await enricher.enrichFromWebSearch('Acme Corp makes widgets', client);
75
+
76
+ // Should make 3 search calls (company news, competitors, industry trends)
77
+ expect(searchFn).toHaveBeenCalledTimes(3);
78
+ // First query should mention company/news
79
+ expect(searchFn.mock.calls[0][0]).toMatch(/acme/i);
80
+ expect(searchFn.mock.calls[0][0]).toMatch(/news/i);
81
+ // Second should mention competitors/market
82
+ expect(searchFn.mock.calls[1][0]).toMatch(/competitor|market/i);
83
+ // Third should mention trends
84
+ expect(searchFn.mock.calls[2][0]).toMatch(/trend/i);
85
+ });
86
+
87
+ it('populates companyNews, competitorInfo, industryTrends from results', async () => {
88
+ const newsResults = [
89
+ { title: 'Acme raises $10M', url: 'https://a.com', snippet: 'Acme Corp raised $10M' },
90
+ { title: 'Acme launches product', url: 'https://b.com', snippet: 'New product launched' },
91
+ ];
92
+ const competitorResults = [
93
+ { title: 'Widget Inc grows', url: 'https://c.com', snippet: 'Widget Inc sees 20% growth' },
94
+ ];
95
+ const trendResults = [
96
+ {
97
+ title: 'AI in manufacturing',
98
+ url: 'https://d.com',
99
+ snippet: 'AI transforms manufacturing',
100
+ },
101
+ ];
102
+
103
+ const searchFn = vi
104
+ .fn()
105
+ .mockResolvedValueOnce({ results: newsResults, degraded: false })
106
+ .mockResolvedValueOnce({ results: competitorResults, degraded: false })
107
+ .mockResolvedValueOnce({ results: trendResults, degraded: false });
108
+
109
+ const result = await enricher.enrichFromWebSearch('Acme Corp makes widgets', {
110
+ search: searchFn,
111
+ });
112
+
113
+ expect(result.companyNews).toHaveLength(2);
114
+ expect(result.companyNews![0]).toContain('Acme raises $10M');
115
+ expect(result.competitorInfo ?? []).toHaveLength(0);
116
+ expect(result.industryTrends ?? []).toHaveLength(0);
117
+ });
118
+
119
+ it('stops after first successful query to reduce web search calls', async () => {
120
+ const searchFn = vi
121
+ .fn()
122
+ .mockResolvedValueOnce({
123
+ results: [{ title: 'Acme news', url: 'https://a.com', snippet: 'Acme in manufacturing' }],
124
+ degraded: false,
125
+ })
126
+ .mockResolvedValue({
127
+ results: [{ title: 'Should not be used', url: 'https://b.com', snippet: 'unused' }],
128
+ degraded: false,
129
+ });
130
+
131
+ const result = await enricher.enrichFromWebSearch('Acme Corp makes widgets', {
132
+ search: searchFn,
133
+ });
134
+
135
+ expect(searchFn).toHaveBeenCalledTimes(1);
136
+ expect(result.companyNews).toHaveLength(1);
137
+ expect(result.competitorInfo ?? []).toHaveLength(0);
138
+ expect(result.industryTrends ?? []).toHaveLength(0);
139
+ });
140
+
141
+ it('returns gracefully with empty arrays when search throws', async () => {
142
+ const client = makeWebSearchClient([], { throws: true });
143
+
144
+ const result = await enricher.enrichFromWebSearch('Acme Corp', client);
145
+
146
+ // Should not throw, should return empty-ish object
147
+ expect(result.companyNews ?? []).toHaveLength(0);
148
+ expect(result.competitorInfo ?? []).toHaveLength(0);
149
+ expect(result.industryTrends ?? []).toHaveLength(0);
150
+ });
151
+
152
+ it('returns empty when search result is degraded', async () => {
153
+ const searchFn = vi.fn().mockResolvedValue({ results: [], degraded: true });
154
+ const client: WebSearchClient = { search: searchFn };
155
+
156
+ const result = await enricher.enrichFromWebSearch('Acme Corp', client);
157
+
158
+ expect(result.companyNews ?? []).toHaveLength(0);
159
+ expect(result.competitorInfo ?? []).toHaveLength(0);
160
+ });
161
+
162
+ it('sets sourcesUsed to ["websearch"] when search succeeds', async () => {
163
+ const newsResults = [{ title: 'News', url: 'https://a.com', snippet: 'snippet' }];
164
+ const searchFn = vi.fn().mockResolvedValue({ results: newsResults, degraded: false });
165
+ const client: WebSearchClient = { search: searchFn };
166
+
167
+ const result = await enricher.enrichFromWebSearch('Acme Corp', client);
168
+
169
+ expect(result.sourcesUsed).toContain('websearch');
170
+ });
171
+
172
+ it('sets enrichedAt to an ISO 8601 timestamp', async () => {
173
+ const newsResults = [{ title: 'News', url: 'https://a.com', snippet: 'snippet' }];
174
+ const searchFn = vi.fn().mockResolvedValue({ results: newsResults, degraded: false });
175
+ const client: WebSearchClient = { search: searchFn };
176
+
177
+ const result = await enricher.enrichFromWebSearch('Acme Corp', client);
178
+
179
+ expect(result.enrichedAt).toBeDefined();
180
+ // Validate it's a parseable date
181
+ expect(new Date(result.enrichedAt!).toISOString()).toBe(result.enrichedAt);
182
+ });
183
+
184
+ it('caps array fields at 10 items', async () => {
185
+ // Return 15 results per query
186
+ const bigResults = Array.from({ length: 15 }, (_, i) => ({
187
+ title: `Title ${i}`,
188
+ url: `https://example.com/${i}`,
189
+ snippet: `Snippet ${i}`,
190
+ }));
191
+ const searchFn = vi.fn().mockResolvedValue({ results: bigResults, degraded: false });
192
+ const client: WebSearchClient = { search: searchFn };
193
+
194
+ const result = await enricher.enrichFromWebSearch('Acme Corp', client);
195
+
196
+ expect((result.companyNews ?? []).length).toBeLessThanOrEqual(10);
197
+ expect((result.competitorInfo ?? []).length).toBeLessThanOrEqual(10);
198
+ expect((result.industryTrends ?? []).length).toBeLessThanOrEqual(10);
199
+ });
200
+
201
+ it('populates webSearchResults with combined snippets', async () => {
202
+ const newsResults = [
203
+ { title: 'News', url: 'https://a.com', snippet: 'Company news snippet' },
204
+ ];
205
+ const searchFn = vi.fn().mockResolvedValue({ results: newsResults, degraded: false });
206
+ const client: WebSearchClient = { search: searchFn };
207
+
208
+ const result = await enricher.enrichFromWebSearch('Acme Corp', client);
209
+
210
+ expect(result.webSearchResults).toBeDefined();
211
+ expect(result.webSearchResults).toContain('Company news snippet');
212
+ });
213
+ });
214
+
215
+ // ── T027: enrich() user consent / prompt flow ────────────────────────────
216
+
217
+ describe('enrich() consent flow (T027)', () => {
218
+ it('does not prompt for web search when webSearchClient is not provided', async () => {
219
+ const io = makeLoopIO();
220
+ const mcpManager = makeMcpManager();
221
+
222
+ await enricher.enrich({
223
+ companySummary: 'Acme Corp',
224
+ mcpManager,
225
+ io,
226
+ });
227
+
228
+ // readInput should not be called for web search consent
229
+ // (web search consent is implicit when webSearchClient is provided)
230
+ expect(io.readInput).not.toHaveBeenCalled();
231
+ });
232
+
233
+ it('runs web search when webSearchClient is provided', async () => {
234
+ const searchFn = vi.fn().mockResolvedValue({
235
+ results: [{ title: 'News', url: 'https://a.com', snippet: 'snip' }],
236
+ degraded: false,
237
+ });
238
+ const io = makeLoopIO();
239
+ const mcpManager = makeMcpManager();
240
+
241
+ const result = await enricher.enrich({
242
+ companySummary: 'Acme Corp',
243
+ mcpManager,
244
+ io,
245
+ webSearchClient: { search: searchFn },
246
+ });
247
+
248
+ expect(searchFn).toHaveBeenCalled();
249
+ expect(result.sourcesUsed).toContain('websearch');
250
+ });
251
+
252
+ it('returns valid DiscoveryEnrichment when all sources unavailable', async () => {
253
+ const io = makeLoopIO();
254
+ const mcpManager = makeMcpManager();
255
+
256
+ const result = await enricher.enrich({
257
+ companySummary: 'Acme Corp',
258
+ mcpManager,
259
+ io,
260
+ });
261
+
262
+ // Should return a valid (possibly empty) enrichment — no crash
263
+ const parsed = DiscoveryEnrichmentSchema.safeParse(result);
264
+ expect(parsed.success).toBe(true);
265
+ });
266
+ });
267
+
268
+ // ── T028: Session schema integration ─────────────────────────────────────
269
+
270
+ describe('session schema integration (T028)', () => {
271
+ it('DiscoveryEnrichmentSchema parses valid enrichment', () => {
272
+ const valid = {
273
+ webSearchResults: 'Some results',
274
+ companyNews: ['News 1', 'News 2'],
275
+ competitorInfo: ['Competitor A'],
276
+ industryTrends: ['Trend 1'],
277
+ enrichedAt: new Date().toISOString(),
278
+ sourcesUsed: ['websearch'],
279
+ };
280
+
281
+ const parsed = DiscoveryEnrichmentSchema.safeParse(valid);
282
+ expect(parsed.success).toBe(true);
283
+ });
284
+
285
+ it('validates enrichedAt is ISO 8601 when present', () => {
286
+ const invalidDate = {
287
+ enrichedAt: 'not-a-date',
288
+ sourcesUsed: ['websearch'],
289
+ };
290
+
291
+ const parsed = DiscoveryEnrichmentSchema.safeParse(invalidDate);
292
+ expect(parsed.success).toBe(false);
293
+ });
294
+
295
+ it('validates sourcesUsed entries are lowercase strings', () => {
296
+ const valid = {
297
+ sourcesUsed: ['websearch', 'workiq'],
298
+ };
299
+
300
+ const parsed = DiscoveryEnrichmentSchema.safeParse(valid);
301
+ expect(parsed.success).toBe(true);
302
+ });
303
+
304
+ it('session with discovery.enrichment round-trips through workshopSessionSchema.parse()', () => {
305
+ const session = {
306
+ sessionId: 'test-123',
307
+ schemaVersion: '0.3.0',
308
+ createdAt: new Date().toISOString(),
309
+ updatedAt: new Date().toISOString(),
310
+ phase: 'Discover' as const,
311
+ status: 'Active' as const,
312
+ participants: [],
313
+ artifacts: { generatedFiles: [] },
314
+ discovery: {
315
+ enrichment: {
316
+ companyNews: ['News headline'],
317
+ competitorInfo: ['Competitor X'],
318
+ industryTrends: ['AI trend'],
319
+ enrichedAt: new Date().toISOString(),
320
+ sourcesUsed: ['websearch'],
321
+ },
322
+ },
323
+ };
324
+
325
+ const parsed = workshopSessionSchema.safeParse(session);
326
+ expect(parsed.success).toBe(true);
327
+ if (parsed.success) {
328
+ expect(parsed.data.discovery?.enrichment?.companyNews).toEqual(['News headline']);
329
+ }
330
+ });
331
+
332
+ it('rejects array fields exceeding max 10 items', () => {
333
+ const tooMany = {
334
+ companyNews: Array.from({ length: 11 }, (_, i) => `News ${i}`),
335
+ };
336
+
337
+ const parsed = DiscoveryEnrichmentSchema.safeParse(tooMany);
338
+ expect(parsed.success).toBe(false);
339
+ });
340
+
341
+ it('accepts completely empty enrichment', () => {
342
+ const parsed = DiscoveryEnrichmentSchema.safeParse({});
343
+ expect(parsed.success).toBe(true);
344
+ });
345
+ });
346
+
347
+ // ── T034: enrichFromWorkIQ() ──────────────────────────────────────────────
348
+
349
+ describe('enrichFromWorkIQ() (T034)', () => {
350
+ it('prompts user for consent via io.readInput before any callTool call', async () => {
351
+ const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('n') });
352
+ const mcpManager = makeMcpManager({
353
+ isAvailable: vi.fn().mockReturnValue(true),
354
+ });
355
+
356
+ await enricher.enrichFromWorkIQ('Acme Corp', mcpManager, io);
357
+
358
+ expect(io.readInput).toHaveBeenCalled();
359
+ // Should have asked before calling callTool
360
+ expect(mcpManager.callTool).not.toHaveBeenCalled();
361
+ });
362
+
363
+ it('calls mcpManager.callTool when user consents with "y"', async () => {
364
+ const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('y') });
365
+ const mcpManager = makeMcpManager({
366
+ isAvailable: vi.fn().mockReturnValue(true),
367
+ callTool: vi.fn().mockResolvedValue({
368
+ teamExpertise: ['TypeScript', 'React'],
369
+ collaborationPatterns: ['Daily standups'],
370
+ documentationGaps: ['API docs missing'],
371
+ }),
372
+ });
373
+
374
+ const result = await enricher.enrichFromWorkIQ('Acme Corp', mcpManager, io);
375
+
376
+ expect(mcpManager.callTool).toHaveBeenCalledWith(
377
+ 'workiq',
378
+ 'analyze_team',
379
+ expect.objectContaining({ summary: 'Acme Corp' }),
380
+ { timeoutMs: 30_000 },
381
+ );
382
+ expect(result.workiqInsights).toBeDefined();
383
+ expect(result.workiqInsights!.teamExpertise).toContain('TypeScript');
384
+ expect(result.workiqInsights!.collaborationPatterns).toContain('Daily standups');
385
+ expect(result.workiqInsights!.documentationGaps).toContain('API docs missing');
386
+ expect(result.sourcesUsed).toContain('workiq');
387
+ });
388
+
389
+ it('returns empty workiqInsights when user declines', async () => {
390
+ const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('n') });
391
+ const mcpManager = makeMcpManager({
392
+ isAvailable: vi.fn().mockReturnValue(true),
393
+ });
394
+
395
+ const result = await enricher.enrichFromWorkIQ('Acme Corp', mcpManager, io);
396
+
397
+ expect(result.workiqInsights).toBeUndefined();
398
+ expect(mcpManager.callTool).not.toHaveBeenCalled();
399
+ });
400
+
401
+ it('returns empty workiqInsights when user presses Enter (default No)', async () => {
402
+ const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('') });
403
+ const mcpManager = makeMcpManager({
404
+ isAvailable: vi.fn().mockReturnValue(true),
405
+ });
406
+
407
+ const result = await enricher.enrichFromWorkIQ('Acme Corp', mcpManager, io);
408
+
409
+ expect(result.workiqInsights).toBeUndefined();
410
+ expect(mcpManager.callTool).not.toHaveBeenCalled();
411
+ });
412
+
413
+ it('returns empty workiqInsights gracefully when callTool throws', async () => {
414
+ const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('y') });
415
+ const mcpManager = makeMcpManager({
416
+ isAvailable: vi.fn().mockReturnValue(true),
417
+ callTool: vi.fn().mockRejectedValue(new Error('WorkIQ timeout')),
418
+ });
419
+
420
+ const result = await enricher.enrichFromWorkIQ('Acme Corp', mcpManager, io);
421
+
422
+ // Should not throw
423
+ expect(result.workiqInsights).toBeUndefined();
424
+ });
425
+
426
+ it('extracts insights from response.insights fallback (split by newline)', async () => {
427
+ const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('y') });
428
+ const mcpManager = makeMcpManager({
429
+ isAvailable: vi.fn().mockReturnValue(true),
430
+ callTool: vi.fn().mockResolvedValue({
431
+ insights: 'Expert in TypeScript\nDaily standups\nMissing API docs',
432
+ }),
433
+ });
434
+
435
+ const result = await enricher.enrichFromWorkIQ('Acme Corp', mcpManager, io);
436
+
437
+ expect(result.workiqInsights).toBeDefined();
438
+ // When response has only 'insights' string, it gets split
439
+ expect(result.workiqInsights!.teamExpertise).toBeDefined();
440
+ });
441
+ });
442
+
443
+ // ── T035: enrich() orchestrator with WorkIQ ──────────────────────────────
444
+
445
+ describe('enrich() orchestrator with WorkIQ (T035)', () => {
446
+ it('calls enrichFromWorkIQ when WorkIQ is available', async () => {
447
+ const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('y') });
448
+ const mcpManager = makeMcpManager({
449
+ isAvailable: vi.fn().mockReturnValue(true),
450
+ callTool: vi.fn().mockResolvedValue({
451
+ teamExpertise: ['Node.js'],
452
+ collaborationPatterns: ['Async reviews'],
453
+ documentationGaps: ['Onboarding docs'],
454
+ }),
455
+ });
456
+
457
+ const result = await enricher.enrich({
458
+ companySummary: 'Acme Corp makes widgets',
459
+ mcpManager,
460
+ io,
461
+ });
462
+
463
+ expect(result.workiqInsights).toBeDefined();
464
+ expect(result.sourcesUsed).toContain('workiq');
465
+ });
466
+
467
+ it('merges WorkIQ results with web search results', async () => {
468
+ const searchFn = vi.fn().mockResolvedValue({
469
+ results: [{ title: 'News', url: 'https://a.com', snippet: 'snip' }],
470
+ degraded: false,
471
+ });
472
+ const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('y') });
473
+ const mcpManager = makeMcpManager({
474
+ isAvailable: vi.fn().mockReturnValue(true),
475
+ callTool: vi.fn().mockResolvedValue({
476
+ teamExpertise: ['Python'],
477
+ collaborationPatterns: [],
478
+ documentationGaps: [],
479
+ }),
480
+ });
481
+
482
+ const result = await enricher.enrich({
483
+ companySummary: 'Acme Corp',
484
+ mcpManager,
485
+ io,
486
+ webSearchClient: { search: searchFn },
487
+ });
488
+
489
+ expect(result.sourcesUsed).toContain('websearch');
490
+ expect(result.sourcesUsed).toContain('workiq');
491
+ expect(result.companyNews).toBeDefined();
492
+ expect(result.workiqInsights).toBeDefined();
493
+ });
494
+
495
+ it('returns valid DiscoveryEnrichment with all empty fields when WorkIQ fails', async () => {
496
+ const io = makeLoopIO({ readInput: vi.fn().mockResolvedValue('y') });
497
+ const mcpManager = makeMcpManager({
498
+ isAvailable: vi.fn().mockReturnValue(true),
499
+ callTool: vi.fn().mockRejectedValue(new Error('WorkIQ down')),
500
+ });
501
+
502
+ const result = await enricher.enrich({
503
+ companySummary: 'Acme Corp',
504
+ mcpManager,
505
+ io,
506
+ });
507
+
508
+ const parsed = DiscoveryEnrichmentSchema.safeParse(result);
509
+ expect(parsed.success).toBe(true);
510
+ });
511
+ });
512
+ });