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,169 @@
1
+ /**
2
+ * Phase summarizer tests.
3
+ *
4
+ * Tests for the post-phase summarization fallback that extracts
5
+ * structured data from conversation transcripts when the conversation
6
+ * loop's inline extraction fails.
7
+ */
8
+ import { describe, it, expect, vi } from 'vitest';
9
+
10
+ import {
11
+ needsSummarization,
12
+ buildPhaseTranscript,
13
+ phaseSummarize,
14
+ } from '../../../src/loop/phaseSummarizer.js';
15
+ import type { WorkshopSession } from '../../../src/shared/schemas/session.js';
16
+ import type { PhaseHandler } from '../../../src/loop/conversationLoop.js';
17
+
18
+ function emptySession(overrides?: Partial<WorkshopSession>): WorkshopSession {
19
+ return {
20
+ sessionId: 'test-1',
21
+ schemaVersion: '1.0.0',
22
+ createdAt: '2025-01-01T00:00:00Z',
23
+ updatedAt: '2025-01-01T00:00:00Z',
24
+ phase: 'Discover',
25
+ status: 'Active',
26
+ participants: [],
27
+ artifacts: { generatedFiles: [] },
28
+ turns: [],
29
+ ...overrides,
30
+ };
31
+ }
32
+
33
+ describe('needsSummarization', () => {
34
+ it('returns true when Ideate session field is null', () => {
35
+ const session = emptySession();
36
+ expect(needsSummarization('Ideate', session)).toBe(true);
37
+ });
38
+
39
+ it('returns false when Ideate session field is populated', () => {
40
+ const session = emptySession({
41
+ ideas: [{ id: 'idea-1', title: 'Test', description: 'Desc', workflowStepIds: [] }],
42
+ });
43
+ expect(needsSummarization('Ideate', session)).toBe(false);
44
+ });
45
+
46
+ it('returns true when Design session field is null', () => {
47
+ expect(needsSummarization('Design', emptySession())).toBe(true);
48
+ });
49
+
50
+ it('returns false when Design session field is populated', () => {
51
+ const session = emptySession({
52
+ evaluation: { method: 'feasibility-value-matrix', ideas: [] },
53
+ });
54
+ expect(needsSummarization('Design', session)).toBe(false);
55
+ });
56
+
57
+ it('returns false for unknown phase', () => {
58
+ expect(needsSummarization('Complete', emptySession())).toBe(false);
59
+ });
60
+ });
61
+
62
+ describe('buildPhaseTranscript', () => {
63
+ it('returns empty string when no turns for phase', () => {
64
+ const session = emptySession();
65
+ expect(buildPhaseTranscript('Ideate', session)).toBe('');
66
+ });
67
+
68
+ it('concatenates turns for the specified phase', () => {
69
+ const session = emptySession({
70
+ turns: [
71
+ { phase: 'Ideate', sequence: 1, role: 'user', content: 'Hello', timestamp: '2025-01-01T00:00:00Z' },
72
+ { phase: 'Ideate', sequence: 2, role: 'assistant', content: 'Hi there', timestamp: '2025-01-01T00:00:00Z' },
73
+ { phase: 'Design', sequence: 3, role: 'user', content: 'Other phase', timestamp: '2025-01-01T00:00:00Z' },
74
+ ],
75
+ });
76
+ const transcript = buildPhaseTranscript('Ideate', session);
77
+ expect(transcript).toContain('[user]: Hello');
78
+ expect(transcript).toContain('[assistant]: Hi there');
79
+ expect(transcript).not.toContain('Other phase');
80
+ });
81
+ });
82
+
83
+ describe('phaseSummarize', () => {
84
+ function createFakeClient(responseText: string) {
85
+ return {
86
+ createSession: vi.fn().mockResolvedValue({
87
+ send: vi.fn().mockImplementation(async function* () {
88
+ yield { type: 'TextDelta', text: responseText };
89
+ }),
90
+ }),
91
+ };
92
+ }
93
+
94
+ function createHandler(extractReturn: Partial<WorkshopSession> = {}): PhaseHandler {
95
+ return {
96
+ phase: 'Ideate',
97
+ buildSystemPrompt: () => 'test prompt',
98
+ extractResult: vi.fn().mockReturnValue(extractReturn),
99
+ };
100
+ }
101
+
102
+ it('returns empty object when session field already populated (no-op)', async () => {
103
+ const session = emptySession({
104
+ ideas: [{ id: 'idea-1', title: 'Test', description: 'Desc', workflowStepIds: [] }],
105
+ });
106
+ const client = createFakeClient('');
107
+ const handler = createHandler();
108
+ const result = await phaseSummarize(client as never, 'Ideate', session, handler);
109
+ expect(result).toEqual({});
110
+ expect(client.createSession).not.toHaveBeenCalled();
111
+ });
112
+
113
+ it('returns empty object when no transcript turns exist', async () => {
114
+ const session = emptySession();
115
+ const client = createFakeClient('');
116
+ const handler = createHandler();
117
+ const result = await phaseSummarize(client as never, 'Ideate', session, handler);
118
+ expect(result).toEqual({});
119
+ });
120
+
121
+ it('extracts IdeaCard[] from LLM summary response', async () => {
122
+ const ideas = [{ id: 'idea-1', title: 'AI Assistant', description: 'Automate tasks', workflowStepIds: ['s1'] }];
123
+ const responseJson = '```json\n' + JSON.stringify(ideas) + '\n```';
124
+ const client = createFakeClient(responseJson);
125
+ const handler = createHandler({ ideas });
126
+
127
+ const session = emptySession({
128
+ turns: [
129
+ { phase: 'Ideate', sequence: 1, role: 'user', content: 'Give me ideas', timestamp: '2025-01-01T00:00:00Z' },
130
+ { phase: 'Ideate', sequence: 2, role: 'assistant', content: 'Here are ideas', timestamp: '2025-01-01T00:00:00Z' },
131
+ ],
132
+ });
133
+
134
+ const result = await phaseSummarize(client as never, 'Ideate', session, handler);
135
+ expect(result).toEqual({ ideas });
136
+ expect(handler.extractResult).toHaveBeenCalledWith(session, responseJson);
137
+ });
138
+
139
+ it('returns empty object when LLM returns invalid response (no crash)', async () => {
140
+ const client = createFakeClient('This is not JSON at all');
141
+ const handler = createHandler({});
142
+
143
+ const session = emptySession({
144
+ turns: [
145
+ { phase: 'Ideate', sequence: 1, role: 'user', content: 'Give me ideas', timestamp: '2025-01-01T00:00:00Z' },
146
+ { phase: 'Ideate', sequence: 2, role: 'assistant', content: 'Here are ideas', timestamp: '2025-01-01T00:00:00Z' },
147
+ ],
148
+ });
149
+
150
+ const result = await phaseSummarize(client as never, 'Ideate', session, handler);
151
+ expect(result).toEqual({});
152
+ });
153
+
154
+ it('does not throw when client throws', async () => {
155
+ const client = {
156
+ createSession: vi.fn().mockRejectedValue(new Error('Network error')),
157
+ };
158
+ const handler = createHandler();
159
+
160
+ const session = emptySession({
161
+ turns: [
162
+ { phase: 'Ideate', sequence: 1, role: 'user', content: 'Hello', timestamp: '2025-01-01T00:00:00Z' },
163
+ ],
164
+ });
165
+
166
+ const result = await phaseSummarize(client as never, 'Ideate', session, handler);
167
+ expect(result).toEqual({});
168
+ });
169
+ });
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Tests for incremental markdown rendering during streaming (T080).
3
+ *
4
+ * Verifies that TextDelta chunks are rendered through markdownRenderer
5
+ * in TTY mode, raw markdown in non-TTY/JSON mode, and that turn history
6
+ * stores raw markdown (not ANSI).
7
+ */
8
+ import { describe, it, expect, vi } from 'vitest';
9
+
10
+ import {
11
+ ConversationLoop,
12
+ type LoopIO,
13
+ type PhaseHandler,
14
+ } from '../../../src/loop/conversationLoop.js';
15
+ import { createFakeCopilotClient } from '../../../src/shared/copilotClient.js';
16
+ import type { WorkshopSession } from '../../../src/shared/schemas/session.js';
17
+ import * as markdownRenderer from '../../../src/shared/markdownRenderer.js';
18
+ import { createNoOpSpinner } from '../../../src/shared/activitySpinner.js';
19
+
20
+ // ── Helpers ─────────────────────────────────────────────────────────────────
21
+
22
+ function makeSession(overrides?: Partial<WorkshopSession>): WorkshopSession {
23
+ return {
24
+ sessionId: 'test-md-stream',
25
+ schemaVersion: '1.0.0',
26
+ createdAt: '2025-01-01T00:00:00Z',
27
+ updatedAt: '2025-01-01T00:00:00Z',
28
+ phase: 'Discover',
29
+ status: 'Active',
30
+ participants: [],
31
+ artifacts: { generatedFiles: [] },
32
+ ...overrides,
33
+ };
34
+ }
35
+
36
+ function makeIO(inputs: (string | null)[], opts?: { json?: boolean; tty?: boolean }): LoopIO & { _written: string[]; _activities: string[] } {
37
+ let inputIndex = 0;
38
+ const written: string[] = [];
39
+ const activities: string[] = [];
40
+
41
+ return {
42
+ write(text: string) {
43
+ written.push(text);
44
+ },
45
+ writeActivity(text: string) {
46
+ activities.push(text);
47
+ },
48
+ writeToolSummary(_toolName: string, _summary: string) {
49
+ // no-op for these tests
50
+ },
51
+ async readInput(): Promise<string | null> {
52
+ if (inputIndex >= inputs.length) return null;
53
+ return inputs[inputIndex++] ?? null;
54
+ },
55
+ async showDecisionGate() {
56
+ return { choice: 'continue' as const };
57
+ },
58
+ isJsonMode: opts?.json ?? false,
59
+ isTTY: opts?.tty ?? true,
60
+ get _written() {
61
+ return written;
62
+ },
63
+ get _activities() {
64
+ return activities;
65
+ },
66
+ };
67
+ }
68
+
69
+ function makePhaseHandler(overrides?: Partial<PhaseHandler>): PhaseHandler {
70
+ return {
71
+ phase: 'Discover',
72
+ buildSystemPrompt: () => 'System prompt',
73
+ extractResult: () => ({}),
74
+ ...overrides,
75
+ };
76
+ }
77
+
78
+ // ── Tests ────────────────────────────────────────────────────────────────────
79
+
80
+ describe('Incremental streaming markdown rendering (T080)', () => {
81
+ it('renders TextDelta chunks through renderMarkdown in TTY mode', async () => {
82
+ const renderSpy = vi.spyOn(markdownRenderer, 'renderMarkdown');
83
+
84
+ const client = createFakeCopilotClient([
85
+ { role: 'assistant', content: '## Hello World\n\nSome **bold** text.' },
86
+ ]);
87
+
88
+ const io = makeIO(['test input'], { tty: true, json: false });
89
+
90
+ const loop = new ConversationLoop({
91
+ client,
92
+ io,
93
+ session: makeSession(),
94
+ phaseHandler: makePhaseHandler(),
95
+ spinner: createNoOpSpinner(),
96
+ });
97
+
98
+ await loop.run();
99
+
100
+ // renderMarkdown should have been called for the TextDelta chunk
101
+ expect(renderSpy).toHaveBeenCalled();
102
+ const callArgs = renderSpy.mock.calls;
103
+ // At least one call should have the LLM content
104
+ const contentCalls = callArgs.filter(([text]) => text.includes('Hello World'));
105
+ expect(contentCalls.length).toBeGreaterThanOrEqual(1);
106
+
107
+ renderSpy.mockRestore();
108
+ });
109
+
110
+ it('writes raw text in non-TTY mode without markdown rendering', async () => {
111
+ const client = createFakeCopilotClient([
112
+ { role: 'assistant', content: '## Raw heading' },
113
+ ]);
114
+
115
+ const io = makeIO(['test'], { tty: false, json: false });
116
+
117
+ const loop = new ConversationLoop({
118
+ client,
119
+ io,
120
+ session: makeSession(),
121
+ phaseHandler: makePhaseHandler(),
122
+ spinner: createNoOpSpinner(),
123
+ });
124
+
125
+ await loop.run();
126
+
127
+ // In non-TTY mode, raw text should be written
128
+ const allOutput = io._written.join('');
129
+ expect(allOutput).toContain('## Raw heading');
130
+ });
131
+
132
+ it('preserves raw markdown in JSON mode output', async () => {
133
+ const client = createFakeCopilotClient([
134
+ { role: 'assistant', content: '**Bold** text' },
135
+ ]);
136
+
137
+ const io = makeIO(['test'], { tty: false, json: true });
138
+
139
+ const loop = new ConversationLoop({
140
+ client,
141
+ io,
142
+ session: makeSession(),
143
+ phaseHandler: makePhaseHandler(),
144
+ spinner: createNoOpSpinner(),
145
+ });
146
+
147
+ await loop.run();
148
+
149
+ const jsonOutputs = io._written.filter((w: string) => w.startsWith('{'));
150
+ expect(jsonOutputs.length).toBeGreaterThanOrEqual(1);
151
+ const parsed = JSON.parse(jsonOutputs[0]);
152
+ expect(parsed.content).toBe('**Bold** text');
153
+ });
154
+
155
+ it('stores raw markdown in turn history (not ANSI)', async () => {
156
+ const client = createFakeCopilotClient([
157
+ { role: 'assistant', content: '## Phase Output\n\nContent here.' },
158
+ ]);
159
+
160
+ const io = makeIO(['input'], { tty: true, json: false });
161
+
162
+ const loop = new ConversationLoop({
163
+ client,
164
+ io,
165
+ session: makeSession(),
166
+ phaseHandler: makePhaseHandler(),
167
+ spinner: createNoOpSpinner(),
168
+ });
169
+
170
+ const result = await loop.run();
171
+
172
+ // Turn history should contain raw markdown, not ANSI escape codes
173
+ const assistantTurn = result.turns?.find(t => t.role === 'assistant');
174
+ expect(assistantTurn).toBeDefined();
175
+ expect(assistantTurn!.content).toBe('## Phase Output\n\nContent here.');
176
+ // Should NOT contain ANSI escape sequences
177
+ // eslint-disable-next-line no-control-regex
178
+ expect(assistantTurn!.content).not.toMatch(/\u001b\[/);
179
+ });
180
+ });
@@ -0,0 +1,336 @@
1
+ /**
2
+ * MCP Manager tests.
3
+ *
4
+ * The McpManager loads .vscode/mcp.json, manages connections to MCP servers,
5
+ * lists available tools, and classifies errors.
6
+ *
7
+ * T007: Tests for callTool() real dispatch (lazy transport, retry, normalization)
8
+ * T049: Tests for toSdkMcpServers() conversion
9
+ */
10
+ import { describe, it, expect } from 'vitest';
11
+
12
+ import {
13
+ McpManager,
14
+ loadMcpConfig,
15
+ classifyMcpError,
16
+ toSdkMcpServers,
17
+ } from '../../../src/mcp/mcpManager.js';
18
+ import type {
19
+ McpConfig,
20
+ StdioServerConfig,
21
+ HttpServerConfig,
22
+ } from '../../../src/mcp/mcpManager.js';
23
+
24
+ // ── Tests ────────────────────────────────────────────────────────────────────
25
+
26
+ describe('McpManager', () => {
27
+ describe('loadMcpConfig', () => {
28
+ it('loads and parses .vscode/mcp.json from given path', async () => {
29
+ const config = await loadMcpConfig(
30
+ new URL('../../../.vscode/mcp.json', import.meta.url).pathname,
31
+ );
32
+
33
+ expect(config).toBeDefined();
34
+ expect(config.servers).toBeDefined();
35
+ expect(Object.keys(config.servers).length).toBeGreaterThan(0);
36
+ });
37
+
38
+ it('returns empty servers when file does not exist', async () => {
39
+ const config = await loadMcpConfig('/nonexistent/mcp.json');
40
+ expect(config.servers).toEqual({});
41
+ });
42
+
43
+ it('identifies server types correctly (stdio vs http)', async () => {
44
+ const config = await loadMcpConfig(
45
+ new URL('../../../.vscode/mcp.json', import.meta.url).pathname,
46
+ );
47
+
48
+ // workiq has command → stdio type
49
+ const workiq = config.servers['workiq'];
50
+ expect(workiq).toBeDefined();
51
+ expect(workiq.type).toBe('stdio');
52
+
53
+ // github has url → http type
54
+ const github = config.servers['github'];
55
+ expect(github).toBeDefined();
56
+ expect(github.type).toBe('http');
57
+ });
58
+ });
59
+
60
+ describe('McpManager instance', () => {
61
+ it('can be created with a config', () => {
62
+ const config = {
63
+ servers: {
64
+ testServer: {
65
+ name: 'testServer',
66
+ type: 'stdio' as const,
67
+ command: 'echo',
68
+ args: ['hello'],
69
+ },
70
+ },
71
+ };
72
+
73
+ const manager = new McpManager(config);
74
+ expect(manager).toBeDefined();
75
+ });
76
+
77
+ it('listServers returns configured server names', () => {
78
+ const config = {
79
+ servers: {
80
+ s1: { name: 's1', type: 'stdio' as const, command: 'echo', args: [] },
81
+ s2: { name: 's2', type: 'http' as const, url: 'http://example.com' },
82
+ },
83
+ };
84
+
85
+ const manager = new McpManager(config);
86
+ const names = manager.listServers();
87
+ expect(names).toEqual(['s1', 's2']);
88
+ });
89
+
90
+ it('getServerConfig returns config for a known server', () => {
91
+ const config = {
92
+ servers: {
93
+ myServer: {
94
+ name: 'myServer',
95
+ type: 'stdio' as const,
96
+ command: 'npx',
97
+ args: ['-y', 'my-tool'],
98
+ },
99
+ },
100
+ };
101
+
102
+ const manager = new McpManager(config);
103
+ const sc = manager.getServerConfig('myServer');
104
+ expect(sc).toBeDefined();
105
+ expect(sc!.type).toBe('stdio');
106
+ expect((sc as import('../../../src/mcp/mcpManager.js').StdioServerConfig).command).toBe(
107
+ 'npx',
108
+ );
109
+ });
110
+
111
+ it('getServerConfig returns undefined for unknown server', () => {
112
+ const manager = new McpManager({ servers: {} });
113
+ expect(manager.getServerConfig('nope')).toBeUndefined();
114
+ });
115
+
116
+ it('isAvailable returns false when not connected', () => {
117
+ const config = {
118
+ servers: {
119
+ s1: { name: 's1', type: 'stdio' as const, command: 'echo', args: [] },
120
+ },
121
+ };
122
+
123
+ const manager = new McpManager(config);
124
+ expect(manager.isAvailable('s1')).toBe(false);
125
+ });
126
+ });
127
+
128
+ describe('classifyMcpError', () => {
129
+ it('classifies ECONNREFUSED as connection-refused', () => {
130
+ const err = new Error('connect ECONNREFUSED 127.0.0.1:3000');
131
+ (err as Error & { code?: string }).code = 'ECONNREFUSED';
132
+ expect(classifyMcpError(err)).toBe('connection-refused');
133
+ });
134
+
135
+ it('classifies ENOTFOUND as dns-failure', () => {
136
+ const err = new Error('getaddrinfo ENOTFOUND example.com');
137
+ (err as Error & { code?: string }).code = 'ENOTFOUND';
138
+ expect(classifyMcpError(err)).toBe('dns-failure');
139
+ });
140
+
141
+ it('classifies ETIMEDOUT as timeout', () => {
142
+ const err = new Error('connect ETIMEDOUT');
143
+ (err as Error & { code?: string }).code = 'ETIMEDOUT';
144
+ expect(classifyMcpError(err)).toBe('timeout');
145
+ });
146
+
147
+ it('classifies unknown errors as unknown', () => {
148
+ expect(classifyMcpError(new Error('something weird'))).toBe('unknown');
149
+ });
150
+
151
+ it('classifies non-Error values as unknown', () => {
152
+ expect(classifyMcpError('string error')).toBe('unknown');
153
+ });
154
+ });
155
+
156
+ // ── T007: callTool() real dispatch tests ─────────────────────────────────
157
+
158
+ describe('callTool() real dispatch', () => {
159
+ it('throws when server is not in config', async () => {
160
+ const manager = new McpManager({ servers: {} });
161
+ await expect(manager.callTool('nonexistent', 'tool', {})).rejects.toThrow(
162
+ /[Uu]nknown.*nonexistent|not available/,
163
+ );
164
+ });
165
+
166
+ it('returns unwrapped content from ToolCallResponse', async () => {
167
+ // This test will fail until T016 implements real dispatch.
168
+ // Once implemented, McpManager.callTool should return the
169
+ // unwrapped content from the transport's ToolCallResponse.
170
+ const config: McpConfig = {
171
+ servers: {
172
+ testserver: {
173
+ name: 'testserver',
174
+ type: 'http',
175
+ url: 'https://test.example.com/mcp',
176
+ } as HttpServerConfig,
177
+ },
178
+ };
179
+ const manager = new McpManager(config);
180
+ manager.markConnected('testserver');
181
+
182
+ // After T016: This should dispatch to transport and return parsed content.
183
+ // For now, we just verify the method exists and can be called
184
+ // (it currently throws "not yet wired to transport").
185
+ try {
186
+ await manager.callTool('testserver', 'search', { query: 'test' });
187
+ } catch (err) {
188
+ // Expected to throw "not yet wired" until T016
189
+ expect(err).toBeInstanceOf(Error);
190
+ }
191
+ });
192
+
193
+ it('throws when calling unavailable server', async () => {
194
+ const config: McpConfig = {
195
+ servers: {
196
+ myserver: {
197
+ name: 'myserver',
198
+ type: 'stdio',
199
+ command: 'echo',
200
+ args: [],
201
+ } as StdioServerConfig,
202
+ },
203
+ };
204
+ const manager = new McpManager(config);
205
+ // Don't mark connected
206
+
207
+ await expect(manager.callTool('myserver', 'tool', {})).rejects.toThrow(/not available/);
208
+ });
209
+ });
210
+
211
+ // ── T049: toSdkMcpServers() tests ───────────────────────────────────────
212
+
213
+ describe('toSdkMcpServers()', () => {
214
+ it('converts StdioServerConfig to SDK format', () => {
215
+ const config: McpConfig = {
216
+ servers: {
217
+ context7: {
218
+ name: 'context7',
219
+ type: 'stdio',
220
+ command: 'npx',
221
+ args: ['-y', '@upstash/context7-mcp'],
222
+ env: { NODE_ENV: 'production' },
223
+ cwd: '/tmp',
224
+ tools: ['resolve-library-id', 'query-docs'],
225
+ timeout: 15000,
226
+ } as StdioServerConfig,
227
+ },
228
+ };
229
+
230
+ const result = toSdkMcpServers(config);
231
+ expect(result).toEqual({
232
+ context7: {
233
+ type: 'stdio',
234
+ command: 'npx',
235
+ args: ['-y', '@upstash/context7-mcp'],
236
+ tools: ['resolve-library-id', 'query-docs'],
237
+ env: { NODE_ENV: 'production' },
238
+ cwd: '/tmp',
239
+ timeout: 15000,
240
+ },
241
+ });
242
+ });
243
+
244
+ it('converts HttpServerConfig to SDK format', () => {
245
+ const config: McpConfig = {
246
+ servers: {
247
+ github: {
248
+ name: 'github',
249
+ type: 'http',
250
+ url: 'https://api.githubcopilot.com/mcp/',
251
+ headers: { 'X-Custom': 'value' },
252
+ tools: ['create_repository'],
253
+ timeout: 60000,
254
+ } as HttpServerConfig,
255
+ },
256
+ };
257
+
258
+ const result = toSdkMcpServers(config);
259
+ expect(result).toEqual({
260
+ github: {
261
+ type: 'http',
262
+ url: 'https://api.githubcopilot.com/mcp/',
263
+ tools: ['create_repository'],
264
+ headers: { 'X-Custom': 'value' },
265
+ timeout: 60000,
266
+ },
267
+ });
268
+ });
269
+
270
+ it('returns empty object for empty servers', () => {
271
+ const config: McpConfig = { servers: {} };
272
+ const result = toSdkMcpServers(config);
273
+ expect(result).toEqual({});
274
+ });
275
+
276
+ it('defaults tools to ["*"] when not specified', () => {
277
+ const config: McpConfig = {
278
+ servers: {
279
+ simple: {
280
+ name: 'simple',
281
+ type: 'stdio',
282
+ command: 'echo',
283
+ args: [],
284
+ } as StdioServerConfig,
285
+ },
286
+ };
287
+
288
+ const result = toSdkMcpServers(config);
289
+ expect(result.simple.tools).toEqual(['*']);
290
+ });
291
+
292
+ it('omits optional fields when not in source config', () => {
293
+ const config: McpConfig = {
294
+ servers: {
295
+ minimal: {
296
+ name: 'minimal',
297
+ type: 'http',
298
+ url: 'https://example.com/mcp',
299
+ } as HttpServerConfig,
300
+ },
301
+ };
302
+
303
+ const result = toSdkMcpServers(config);
304
+ expect(result.minimal).toEqual({
305
+ type: 'http',
306
+ url: 'https://example.com/mcp',
307
+ tools: ['*'],
308
+ });
309
+ expect(result.minimal).not.toHaveProperty('headers');
310
+ expect(result.minimal).not.toHaveProperty('timeout');
311
+ });
312
+
313
+ it('converts mixed stdio and http configs', () => {
314
+ const config: McpConfig = {
315
+ servers: {
316
+ local: {
317
+ name: 'local',
318
+ type: 'stdio',
319
+ command: 'node',
320
+ args: ['server.js'],
321
+ } as StdioServerConfig,
322
+ remote: {
323
+ name: 'remote',
324
+ type: 'http',
325
+ url: 'https://api.example.com',
326
+ } as HttpServerConfig,
327
+ },
328
+ };
329
+
330
+ const result = toSdkMcpServers(config);
331
+ expect(Object.keys(result)).toEqual(['local', 'remote']);
332
+ expect(result.local.type).toBe('stdio');
333
+ expect(result.remote.type).toBe('http');
334
+ });
335
+ });
336
+ });