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,227 @@
1
+ import { createActivityEvent } from '../shared/events.js';
2
+ import { renderMarkdown } from '../shared/markdownRenderer.js';
3
+ import { createNoOpSpinner } from '../shared/activitySpinner.js';
4
+ import { phaseSummarize } from './phaseSummarizer.js';
5
+ // ── ConversationLoop ─────────────────────────────────────────────────────────
6
+ export class ConversationLoop {
7
+ aborted = false;
8
+ session;
9
+ client;
10
+ io;
11
+ handler;
12
+ onEvent;
13
+ onSessionUpdate;
14
+ initialMessage;
15
+ spinner;
16
+ constructor(options) {
17
+ this.client = options.client;
18
+ this.io = options.io;
19
+ this.session = { ...options.session };
20
+ this.handler = options.phaseHandler;
21
+ this.onEvent = options.onEvent ?? (() => { });
22
+ this.onSessionUpdate = options.onSessionUpdate ?? (async () => { });
23
+ this.initialMessage = options.initialMessage;
24
+ this.spinner = options.spinner ?? createNoOpSpinner();
25
+ }
26
+ /** Run the conversation loop for the current phase. */
27
+ async run() {
28
+ this.setupSignalHandler();
29
+ let systemPrompt = this.handler.buildSystemPrompt(this.session);
30
+ // FR-007b, FR-007c: Inject phase boundary instruction to prevent LLM drift
31
+ systemPrompt += `\n\nYou are in the ${this.handler.phase} phase. Do NOT introduce or begin the next phase. The user will be offered a decision gate when this phase is complete.`;
32
+ // Inject prior conversation history into the system prompt when resuming
33
+ // so the LLM has context from previous turns in this phase.
34
+ const priorTurns = (this.session.turns ?? []).filter((t) => t.phase === this.handler.phase);
35
+ if (priorTurns.length > 0) {
36
+ const historyBlock = priorTurns.map((t) => `[${t.role}]: ${t.content}`).join('\n\n');
37
+ systemPrompt += `\n\n## Previous conversation history\n\n${historyBlock}`;
38
+ }
39
+ const references = this.handler.getReferences?.(this.session) ?? [];
40
+ const sessionOpts = { systemPrompt, references };
41
+ const conversationSession = await this.client.createSession(sessionOpts);
42
+ this.emitEvent(createActivityEvent(`Starting ${this.handler.phase} phase`));
43
+ // Auto-start: send initial message to LLM before waiting for user input
44
+ if (this.initialMessage) {
45
+ const response = await this.streamResponse(conversationSession, {
46
+ role: 'user',
47
+ content: this.initialMessage,
48
+ });
49
+ const now = new Date().toISOString();
50
+ const turns = this.session.turns ?? [];
51
+ turns.push({
52
+ phase: this.handler.phase,
53
+ sequence: turns.length + 1,
54
+ role: 'user',
55
+ content: this.initialMessage,
56
+ timestamp: now,
57
+ }, {
58
+ phase: this.handler.phase,
59
+ sequence: turns.length + 2,
60
+ role: 'assistant',
61
+ content: response,
62
+ timestamp: now,
63
+ });
64
+ const updates = this.handler.extractResult(this.session, response);
65
+ this.session = {
66
+ ...this.session,
67
+ ...updates,
68
+ turns,
69
+ updatedAt: now,
70
+ };
71
+ // Run async post-extraction hook if present (e.g., discovery enrichment)
72
+ if (this.handler.postExtract) {
73
+ const postUpdates = await this.handler.postExtract(this.session);
74
+ this.session = { ...this.session, ...postUpdates, updatedAt: new Date().toISOString() };
75
+ }
76
+ await this.onSessionUpdate(this.session);
77
+ }
78
+ // Main conversation loop
79
+ while (!this.aborted) {
80
+ const userInput = await this.io.readInput(`[${this.handler.phase}] > `);
81
+ if (userInput === null) {
82
+ // EOF / Ctrl+D — treat as "done" signal
83
+ break;
84
+ }
85
+ const trimmed = userInput.trim();
86
+ if (trimmed.toLowerCase() === 'done' || trimmed === '') {
87
+ // Check if handler considers the phase complete
88
+ if (this.handler.isComplete?.(this.session, '') !== false) {
89
+ break;
90
+ }
91
+ // Otherwise continue the conversation
92
+ }
93
+ // Send user message and stream response
94
+ const response = await this.streamResponse(conversationSession, {
95
+ role: 'user',
96
+ content: trimmed,
97
+ });
98
+ // Accumulate turn history
99
+ const now = new Date().toISOString();
100
+ const turns = this.session.turns ?? [];
101
+ turns.push({
102
+ phase: this.handler.phase,
103
+ sequence: turns.length + 1,
104
+ role: 'user',
105
+ content: trimmed,
106
+ timestamp: now,
107
+ }, {
108
+ phase: this.handler.phase,
109
+ sequence: turns.length + 2,
110
+ role: 'assistant',
111
+ content: response,
112
+ timestamp: now,
113
+ });
114
+ // Extract structured data from the response
115
+ const updates = this.handler.extractResult(this.session, response);
116
+ this.session = {
117
+ ...this.session,
118
+ ...updates,
119
+ turns,
120
+ updatedAt: now,
121
+ };
122
+ // Run async post-extraction hook if present (e.g., discovery enrichment)
123
+ if (this.handler.postExtract) {
124
+ const postUpdates = await this.handler.postExtract(this.session);
125
+ this.session = { ...this.session, ...postUpdates, updatedAt: new Date().toISOString() };
126
+ }
127
+ // Persist after every turn (FR-039a)
128
+ await this.onSessionUpdate(this.session);
129
+ }
130
+ // FR-006: Post-phase summarization fallback
131
+ // If structured data wasn't extracted during conversation, try a dedicated summarization call
132
+ try {
133
+ const summarizationUpdates = await phaseSummarize(this.client, this.handler.phase, this.session, this.handler);
134
+ if (Object.keys(summarizationUpdates).length > 0) {
135
+ this.session = {
136
+ ...this.session,
137
+ ...summarizationUpdates,
138
+ updatedAt: new Date().toISOString(),
139
+ };
140
+ await this.onSessionUpdate(this.session);
141
+ }
142
+ }
143
+ catch {
144
+ // Summarization failure is non-fatal
145
+ }
146
+ return this.session;
147
+ }
148
+ /** Stream response from the LLM and render incrementally. */
149
+ async streamResponse(session, message) {
150
+ const chunks = [];
151
+ let firstTextDelta = true;
152
+ // Start "Thinking..." spinner before sending
153
+ this.spinner.startThinking();
154
+ try {
155
+ for await (const event of session.send(message)) {
156
+ // Check if user pressed Ctrl+C during streaming
157
+ if (this.aborted)
158
+ break;
159
+ this.emitEvent(event);
160
+ if (event.type === 'TextDelta') {
161
+ // Stop spinner on first text output
162
+ if (firstTextDelta) {
163
+ this.spinner.stop();
164
+ firstTextDelta = false;
165
+ }
166
+ chunks.push(event.text);
167
+ if (!this.io.isJsonMode) {
168
+ // Render markdown for TTY, raw for non-TTY
169
+ if (this.io.isTTY) {
170
+ this.io.write(renderMarkdown(event.text, { isTTY: true }));
171
+ }
172
+ else {
173
+ this.io.write(event.text);
174
+ }
175
+ }
176
+ }
177
+ else if (event.type === 'Activity') {
178
+ this.io.writeActivity(event.message);
179
+ }
180
+ else if (event.type === 'ToolCall') {
181
+ this.spinner.startToolCall(event.toolName);
182
+ }
183
+ else if (event.type === 'ToolResult') {
184
+ const summary = typeof event.result === 'string'
185
+ ? event.result
186
+ : JSON.stringify(event.result).slice(0, 120);
187
+ this.spinner.completeToolCall(event.toolName, summary);
188
+ this.io.writeToolSummary(event.toolName, summary);
189
+ // Resume thinking spinner in case more processing follows
190
+ this.spinner.startThinking();
191
+ }
192
+ }
193
+ }
194
+ finally {
195
+ // Always stop spinner — prevents cursor-hidden state on errors
196
+ this.spinner.stop();
197
+ }
198
+ const fullResponse = chunks.join('');
199
+ // In JSON mode, output the full rendered response
200
+ if (this.io.isJsonMode) {
201
+ this.io.write(JSON.stringify({ phase: this.handler.phase, content: fullResponse }));
202
+ }
203
+ else {
204
+ this.io.write('\n');
205
+ }
206
+ return fullResponse;
207
+ }
208
+ emitEvent(event) {
209
+ this.onEvent(event);
210
+ }
211
+ setupSignalHandler() {
212
+ const handler = () => {
213
+ this.aborted = true;
214
+ this.emitEvent(createActivityEvent('Ctrl+C received — finishing current turn'));
215
+ };
216
+ // Avoid MaxListenersExceededWarning when many loops are created in tests
217
+ const current = process.listenerCount('SIGINT');
218
+ if (current >= 10) {
219
+ process.setMaxListeners(current + 1);
220
+ }
221
+ process.once('SIGINT', handler);
222
+ }
223
+ /** Get the current session state. */
224
+ getSession() {
225
+ return { ...this.session };
226
+ }
227
+ }
@@ -0,0 +1,87 @@
1
+ import { loadSummarizationPrompt } from '../prompts/promptLoader.js';
2
+ // ── Phase → session field mapping ────────────────────────────────────────────
3
+ const PHASE_SESSION_FIELD = {
4
+ Discover: 'businessContext',
5
+ Ideate: 'ideas',
6
+ Design: 'evaluation',
7
+ Select: 'selection',
8
+ Plan: 'plan',
9
+ Develop: 'poc',
10
+ };
11
+ /**
12
+ * Check whether a phase needs post-phase summarization.
13
+ * Returns true if the session field for this phase is null/undefined.
14
+ */
15
+ export function needsSummarization(phase, session) {
16
+ const fieldName = PHASE_SESSION_FIELD[phase];
17
+ if (!fieldName)
18
+ return false;
19
+ const value = session[fieldName];
20
+ return value === undefined || value === null;
21
+ }
22
+ /**
23
+ * Build a concatenated transcript from conversation turns for a specific phase.
24
+ */
25
+ export function buildPhaseTranscript(phase, session) {
26
+ const turns = (session.turns ?? []).filter((t) => t.phase === phase);
27
+ if (turns.length === 0)
28
+ return '';
29
+ return turns.map((t) => `[${t.role}]: ${t.content}`).join('\n\n');
30
+ }
31
+ /**
32
+ * Post-phase summarization call.
33
+ *
34
+ * If the expected structured field for `phase` is still null after the
35
+ * conversation loop, makes a one-shot LLM call to extract it from the
36
+ * full transcript.
37
+ *
38
+ * Returns partial session updates (may be empty if extraction still fails).
39
+ * Never throws — summarization is a best-effort fallback.
40
+ */
41
+ export async function phaseSummarize(client, phase, session, handler) {
42
+ // Skip if field is already populated
43
+ if (!needsSummarization(phase, session)) {
44
+ return {};
45
+ }
46
+ // Build the transcript
47
+ const transcript = buildPhaseTranscript(phase, session);
48
+ if (!transcript) {
49
+ return {};
50
+ }
51
+ try {
52
+ // Load the phase-specific summarization prompt
53
+ const systemPrompt = await loadSummarizationPrompt(phase);
54
+ // Create a new session for the summarization call (avoids polluting context)
55
+ const summarizationSession = await client.createSession({ systemPrompt });
56
+ // Send the transcript as a single user message and collect the full response
57
+ const chunks = [];
58
+ for await (const event of summarizationSession.send({
59
+ role: 'user',
60
+ content: transcript,
61
+ })) {
62
+ if (event.type === 'TextDelta') {
63
+ chunks.push(event.text);
64
+ }
65
+ }
66
+ const response = chunks.join('');
67
+ // Extract structured data using the phase handler's extractResult
68
+ const updates = handler.extractResult(session, response);
69
+ // For Design phase, also extract Mermaid diagram (FR-007a)
70
+ // Store in plan.architectureNotes if available (session schema doesn't have
71
+ // a dedicated architectureDiagram field)
72
+ if (phase === 'Design') {
73
+ const mermaidMatch = response.match(/```mermaid\s*\n([\s\S]*?)\n```/);
74
+ if (mermaidMatch) {
75
+ const diagram = mermaidMatch[1].trim();
76
+ if (!updates.plan) {
77
+ updates.plan = { milestones: [], architectureNotes: diagram };
78
+ }
79
+ }
80
+ }
81
+ return updates;
82
+ }
83
+ catch {
84
+ // Summarization failure is non-fatal — log and return empty
85
+ return {};
86
+ }
87
+ }
@@ -0,0 +1,267 @@
1
+ /**
2
+ * MCP Manager.
3
+ *
4
+ * Loads .vscode/mcp.json configuration, manages MCP server connections,
5
+ * lists available tools, classifies errors, and dispatches real MCP tool calls
6
+ * via the transport layer.
7
+ */
8
+ import { readFile } from 'node:fs/promises';
9
+ import { createTransport, StdioMcpTransport } from './mcpTransport.js';
10
+ import { withRetry } from './retryPolicy.js';
11
+ const ERROR_CODE_MAP = {
12
+ ECONNREFUSED: 'connection-refused',
13
+ ENOTFOUND: 'dns-failure',
14
+ ETIMEDOUT: 'timeout',
15
+ ECONNRESET: 'connection-refused',
16
+ ERR_TLS_CERT_ALTNAME_INVALID: 'auth-failure',
17
+ };
18
+ export function classifyMcpError(err) {
19
+ if (err instanceof Error) {
20
+ const code = err.code;
21
+ if (code && code in ERROR_CODE_MAP) {
22
+ return ERROR_CODE_MAP[code];
23
+ }
24
+ // AbortError from AbortController / fetch timeouts
25
+ if (err instanceof DOMException && err.name === 'AbortError') {
26
+ return 'timeout';
27
+ }
28
+ // Message-based timeout detection
29
+ if (/timed?\s*out/i.test(err.message)) {
30
+ return 'timeout';
31
+ }
32
+ if (err.message.includes('401') || err.message.includes('403')) {
33
+ return 'auth-failure';
34
+ }
35
+ }
36
+ return 'unknown';
37
+ }
38
+ // ── Config loader ────────────────────────────────────────────────────────────
39
+ /**
40
+ * Load and normalize MCP configuration from a JSON file.
41
+ * Returns empty servers if the file doesn't exist.
42
+ */
43
+ export async function loadMcpConfig(configPath) {
44
+ try {
45
+ const raw = await readFile(configPath, 'utf-8');
46
+ // Strip JSONC comments — only full-line // comments and block comments
47
+ // Avoid stripping // inside string values (e.g. URLs like https://)
48
+ const stripped = raw
49
+ .replace(/^\s*\/\/.*$/gm, '') // full-line // comments
50
+ .replace(/\/\*[\s\S]*?\*\//g, '') // block comments
51
+ .replace(/,(\s*[}\]])/g, '$1'); // trailing commas
52
+ const parsed = JSON.parse(stripped);
53
+ const servers = {};
54
+ if (parsed.servers && typeof parsed.servers === 'object') {
55
+ for (const [name, config] of Object.entries(parsed.servers)) {
56
+ const cfg = config;
57
+ if (cfg.url && typeof cfg.url === 'string') {
58
+ servers[name] = {
59
+ name,
60
+ type: 'http',
61
+ url: cfg.url,
62
+ ...(cfg.headers && typeof cfg.headers === 'object'
63
+ ? { headers: cfg.headers }
64
+ : {}),
65
+ ...(Array.isArray(cfg.tools) ? { tools: cfg.tools } : {}),
66
+ ...(typeof cfg.timeout === 'number' ? { timeout: cfg.timeout } : {}),
67
+ };
68
+ }
69
+ else if (cfg.command && typeof cfg.command === 'string') {
70
+ servers[name] = {
71
+ name,
72
+ type: 'stdio',
73
+ command: cfg.command,
74
+ args: Array.isArray(cfg.args) ? cfg.args : [],
75
+ ...(cfg.env && typeof cfg.env === 'object'
76
+ ? { env: cfg.env }
77
+ : {}),
78
+ ...(typeof cfg.cwd === 'string' ? { cwd: cfg.cwd } : {}),
79
+ ...(Array.isArray(cfg.tools) ? { tools: cfg.tools } : {}),
80
+ ...(typeof cfg.timeout === 'number' ? { timeout: cfg.timeout } : {}),
81
+ };
82
+ }
83
+ }
84
+ }
85
+ return { servers };
86
+ }
87
+ catch (err) {
88
+ if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
89
+ return { servers: {} };
90
+ }
91
+ // Re-throw parse or other errors
92
+ throw err;
93
+ }
94
+ }
95
+ /**
96
+ * Convert sofIA's `McpConfig` to the shape expected by the Copilot SDK's
97
+ * `SessionConfig.mcpServers` (`Record<string, MCPServerConfig>`).
98
+ *
99
+ * This bridges `.vscode/mcp.json` → SDK `createSession({ mcpServers })`.
100
+ * The SDK types are `MCPLocalServerConfig` and `MCPRemoteServerConfig`.
101
+ */
102
+ export function toSdkMcpServers(config) {
103
+ const result = {};
104
+ for (const [name, server] of Object.entries(config.servers)) {
105
+ if (server.type === 'stdio') {
106
+ result[name] = {
107
+ type: 'stdio',
108
+ command: server.command,
109
+ args: server.args,
110
+ tools: server.tools ?? ['*'],
111
+ ...(server.env ? { env: server.env } : {}),
112
+ ...(server.cwd ? { cwd: server.cwd } : {}),
113
+ ...(server.timeout ? { timeout: server.timeout } : {}),
114
+ };
115
+ }
116
+ else if (server.type === 'http') {
117
+ result[name] = {
118
+ type: 'http',
119
+ url: server.url,
120
+ tools: server.tools ?? ['*'],
121
+ ...(server.headers ? { headers: server.headers } : {}),
122
+ ...(server.timeout ? { timeout: server.timeout } : {}),
123
+ };
124
+ }
125
+ }
126
+ return result;
127
+ }
128
+ // ── Default Timeouts ─────────────────────────────────────────────────────────
129
+ const DEFAULT_TIMEOUTS = {
130
+ github: 60_000,
131
+ context7: 30_000,
132
+ azure: 30_000,
133
+ workiq: 30_000,
134
+ 'microsoftdocs/mcp': 30_000,
135
+ };
136
+ const FALLBACK_TIMEOUT = 30_000;
137
+ // ── McpManager ───────────────────────────────────────────────────────────────
138
+ export class McpManager {
139
+ config;
140
+ connectedServers = new Set();
141
+ transports = new Map();
142
+ logger;
143
+ constructor(config, logger) {
144
+ this.config = config;
145
+ this.logger = logger;
146
+ }
147
+ /** List all configured server names. */
148
+ listServers() {
149
+ return Object.keys(this.config.servers);
150
+ }
151
+ /** Get configuration for a specific server. */
152
+ getServerConfig(name) {
153
+ return this.config.servers[name];
154
+ }
155
+ /** Check if a server is currently connected (available). */
156
+ isAvailable(name) {
157
+ return this.connectedServers.has(name);
158
+ }
159
+ /** Mark a server as connected. Used by the SDK integration layer. */
160
+ markConnected(name) {
161
+ if (this.config.servers[name]) {
162
+ this.connectedServers.add(name);
163
+ }
164
+ }
165
+ /** Mark a server as disconnected. */
166
+ markDisconnected(name) {
167
+ this.connectedServers.delete(name);
168
+ }
169
+ /** Get all server configs as an array. */
170
+ getAllConfigs() {
171
+ return Object.values(this.config.servers);
172
+ }
173
+ /**
174
+ * Call a tool on a named MCP server.
175
+ *
176
+ * Dispatches to the correct transport (stdio or HTTP), applies retry policy
177
+ * for transient errors, and normalizes the response.
178
+ *
179
+ * @param serverName The MCP server name (e.g., 'github', 'context7', 'azure')
180
+ * @param toolName The tool to call on that server
181
+ * @param args Arguments for the tool
182
+ * @param options Optional timeout and retry configuration
183
+ * @returns The tool response as a parsed object
184
+ */
185
+ async callTool(serverName, toolName, args, options) {
186
+ if (!this.isAvailable(serverName)) {
187
+ throw new Error(`MCP server '${serverName}' is not available`);
188
+ }
189
+ const serverConfig = this.config.servers[serverName];
190
+ if (!serverConfig) {
191
+ throw new Error(`Unknown MCP server: ${serverName}`);
192
+ }
193
+ // Get or create transport
194
+ const transport = this.getOrCreateTransport(serverName, serverConfig);
195
+ // Connect stdio transports on first use
196
+ if (transport instanceof StdioMcpTransport && !transport.isConnected()) {
197
+ await transport.connect();
198
+ }
199
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUTS[serverName] ?? FALLBACK_TIMEOUT;
200
+ try {
201
+ let response;
202
+ if (options?.retryOnTransient !== false) {
203
+ response = await withRetry(() => transport.callTool(toolName, args, timeoutMs), {
204
+ serverName,
205
+ toolName,
206
+ logger: this.logger,
207
+ });
208
+ }
209
+ else {
210
+ response = await transport.callTool(toolName, args, timeoutMs);
211
+ }
212
+ // Normalize content to Record<string, unknown>
213
+ const content = response.content;
214
+ if (typeof content === 'string') {
215
+ // Try to parse JSON string; if not JSON, wrap as { text: content }
216
+ try {
217
+ const parsed = JSON.parse(content);
218
+ if (typeof parsed === 'object' && parsed !== null) {
219
+ return parsed;
220
+ }
221
+ return { text: content };
222
+ }
223
+ catch {
224
+ return { text: content };
225
+ }
226
+ }
227
+ return content;
228
+ }
229
+ catch (err) {
230
+ this.markDisconnected(serverName);
231
+ throw err;
232
+ }
233
+ }
234
+ /**
235
+ * Disconnect all cached transports and clear the registry.
236
+ */
237
+ async disconnectAll() {
238
+ const disconnections = [];
239
+ for (const transport of this.transports.values()) {
240
+ disconnections.push(transport.disconnect());
241
+ }
242
+ await Promise.allSettled(disconnections);
243
+ this.transports.clear();
244
+ }
245
+ /**
246
+ * Get or lazily create a transport for the given server.
247
+ */
248
+ getOrCreateTransport(serverName, config) {
249
+ let transport = this.transports.get(serverName);
250
+ if (!transport) {
251
+ const logger = this.logger ??
252
+ {
253
+ info: () => { },
254
+ warn: () => { },
255
+ error: () => { },
256
+ debug: () => { },
257
+ trace: () => { },
258
+ fatal: () => { },
259
+ child: () => logger,
260
+ level: 'silent',
261
+ };
262
+ transport = createTransport(config, logger);
263
+ this.transports.set(serverName, transport);
264
+ }
265
+ return transport;
266
+ }
267
+ }