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,427 @@
1
+ /**
2
+ * Template Registry.
3
+ *
4
+ * Defines scaffold templates for different tech stacks and provides
5
+ * selection logic based on plan architecture notes.
6
+ *
7
+ * Contract: specs/004-dev-resume-hardening/contracts/cli.md
8
+ */
9
+ import type { TemplateFile, ScaffoldContext } from './pocScaffolder.js';
10
+ import type { TechStack } from '../shared/schemas/session.js';
11
+
12
+ // ── Types ────────────────────────────────────────────────────────────────────
13
+
14
+ export interface TemplateEntry {
15
+ /** Unique template identifier */
16
+ id: string;
17
+ /** Human-readable name for logging */
18
+ displayName: string;
19
+ /** Scaffold file definitions */
20
+ files: TemplateFile[];
21
+ /** Technology stack for session state */
22
+ techStack: TechStack;
23
+ /** Command to install dependencies (e.g., 'npm install') */
24
+ installCommand: string;
25
+ /** Command to run tests with JSON output */
26
+ testCommand: string;
27
+ /** Keywords to match from plan's architectureNotes/dependencies */
28
+ matchPatterns: string[];
29
+ }
30
+
31
+ export type TemplateRegistry = Map<string, TemplateEntry>;
32
+
33
+ // ── Template Definitions ─────────────────────────────────────────────────────
34
+
35
+ export const NODE_TS_VITEST_TEMPLATE: TemplateEntry = {
36
+ id: 'node-ts-vitest',
37
+ displayName: 'TypeScript + Node.js + Vitest',
38
+ techStack: {
39
+ language: 'TypeScript',
40
+ runtime: 'Node.js 20',
41
+ testRunner: 'npm test',
42
+ buildCommand: 'npm run build',
43
+ },
44
+ installCommand: 'npm install',
45
+ testCommand: 'npm test -- --reporter=json',
46
+ matchPatterns: ['typescript', 'node', 'vitest', 'ts'],
47
+ files: [
48
+ {
49
+ path: '.gitignore',
50
+ skipIfExists: false,
51
+ content: `node_modules/
52
+ dist/
53
+ coverage/
54
+ *.tsbuildinfo
55
+ .env
56
+ `,
57
+ },
58
+ {
59
+ path: 'package.json',
60
+ skipIfExists: true,
61
+ content: (ctx: ScaffoldContext) =>
62
+ JSON.stringify(
63
+ {
64
+ name: ctx.projectName,
65
+ version: '0.1.0',
66
+ private: true,
67
+ type: 'module',
68
+ scripts: {
69
+ build: 'tsc',
70
+ start: 'node dist/index.js',
71
+ test: 'vitest run',
72
+ },
73
+ dependencies: {},
74
+ devDependencies: {
75
+ typescript: '^5.0.0',
76
+ vitest: '^3.0.0',
77
+ '@types/node': '^20.0.0',
78
+ },
79
+ },
80
+ null,
81
+ 2,
82
+ ) + '\n',
83
+ },
84
+ {
85
+ path: 'tsconfig.json',
86
+ skipIfExists: true,
87
+ content: JSON.stringify(
88
+ {
89
+ compilerOptions: {
90
+ target: 'ES2022',
91
+ module: 'Node16',
92
+ moduleResolution: 'Node16',
93
+ strict: true,
94
+ outDir: 'dist',
95
+ rootDir: 'src',
96
+ declaration: true,
97
+ esModuleInterop: true,
98
+ skipLibCheck: true,
99
+ },
100
+ include: ['src'],
101
+ },
102
+ null,
103
+ 2,
104
+ ) + '\n',
105
+ },
106
+ {
107
+ path: 'README.md',
108
+ skipIfExists: true,
109
+ content: (ctx: ScaffoldContext) => `# ${ctx.ideaTitle}
110
+
111
+ ${ctx.ideaDescription}
112
+
113
+ ## Generated by
114
+
115
+ sofIA — AI Discovery Workshop CLI
116
+ Session: \`${ctx.sessionId}\`
117
+ Generated: ${new Date().toISOString()}
118
+
119
+ ## Prerequisites
120
+
121
+ - Node.js 20+
122
+ - npm 9+
123
+
124
+ ## Quick Start
125
+
126
+ \`\`\`bash
127
+ npm install
128
+ npm test
129
+ npm start
130
+ \`\`\`
131
+
132
+ ## How It Works
133
+
134
+ ${ctx.planSummary}
135
+
136
+ ## Technology Stack
137
+
138
+ - **Language**: ${ctx.techStack.language}
139
+ - **Runtime**: ${ctx.techStack.runtime}
140
+ - **Test Runner**: ${ctx.techStack.testRunner}
141
+ ${ctx.techStack.framework ? `- **Framework**: ${ctx.techStack.framework}\n` : ''}
142
+ `,
143
+ },
144
+ {
145
+ path: 'src/index.ts',
146
+ skipIfExists: true,
147
+ content: (ctx: ScaffoldContext) => `/**
148
+ * ${ctx.ideaTitle}
149
+ *
150
+ * ${ctx.ideaDescription}
151
+ *
152
+ * Generated by sofIA (session: ${ctx.sessionId})
153
+ */
154
+
155
+ /**
156
+ * Main entry point for the ${ctx.projectName} PoC.
157
+ *
158
+ * TODO: Implement the core functionality described in the plan.
159
+ * The test file (tests/index.test.ts) describes the expected behavior.
160
+ */
161
+ export function main(): string {
162
+ return 'Hello from ${ctx.projectName}!';
163
+ }
164
+
165
+ // Run if called directly
166
+ const isMain = process.argv[1]?.endsWith('index.js') || process.argv[1]?.endsWith('index.ts');
167
+ if (isMain) {
168
+ console.log(main());
169
+ }
170
+ `,
171
+ },
172
+ {
173
+ path: 'tests/index.test.ts',
174
+ skipIfExists: true,
175
+ content: (ctx: ScaffoldContext) => `/**
176
+ * Tests for ${ctx.ideaTitle} PoC.
177
+ *
178
+ * These tests describe the expected behavior of the implementation.
179
+ * They are intentionally failing initially — the Ralph loop will
180
+ * iteratively fix the implementation until they pass.
181
+ *
182
+ * Generated by sofIA (session: ${ctx.sessionId})
183
+ */
184
+ import { describe, it, expect } from 'vitest';
185
+ import { main } from '../src/index.js';
186
+
187
+ describe('${ctx.projectName}', () => {
188
+ it('should initialize successfully', () => {
189
+ const result = main();
190
+ expect(result).toBeDefined();
191
+ expect(typeof result).toBe('string');
192
+ });
193
+
194
+ it('should implement core functionality', () => {
195
+ // TODO: sofIA Ralph loop will refine this test to match the actual implementation
196
+ // Plan: ${ctx.planSummary.substring(0, 200)}
197
+ const result = main();
198
+ expect(result).toContain('${ctx.projectName}');
199
+ });
200
+ });
201
+ `,
202
+ },
203
+ {
204
+ path: '.sofia-metadata.json',
205
+ skipIfExists: false,
206
+ content: (ctx: ScaffoldContext) =>
207
+ JSON.stringify(
208
+ {
209
+ sessionId: ctx.sessionId,
210
+ featureSpec: '002-poc-generation',
211
+ generatedAt: new Date().toISOString(),
212
+ ideaTitle: ctx.ideaTitle,
213
+ totalIterations: 0,
214
+ finalStatus: null,
215
+ terminationReason: null,
216
+ techStack: {
217
+ language: ctx.techStack.language.toLowerCase(),
218
+ runtime: ctx.techStack.runtime.toLowerCase(),
219
+ testRunner: ctx.techStack.testRunner,
220
+ },
221
+ },
222
+ null,
223
+ 2,
224
+ ) + '\n',
225
+ },
226
+ ],
227
+ };
228
+
229
+ export const PYTHON_PYTEST_TEMPLATE: TemplateEntry = {
230
+ id: 'python-pytest',
231
+ displayName: 'Python + pytest',
232
+ techStack: {
233
+ language: 'Python',
234
+ runtime: 'Python 3.11',
235
+ testRunner: 'pytest',
236
+ },
237
+ installCommand: 'pip install -r requirements.txt',
238
+ testCommand: 'pytest --tb=short -q --json-report --json-report-file=-',
239
+ matchPatterns: ['python', 'fastapi', 'flask', 'django', 'pytest'],
240
+ files: [
241
+ {
242
+ path: '.gitignore',
243
+ skipIfExists: false,
244
+ content: `__pycache__/
245
+ *.pyc
246
+ .venv/
247
+ venv/
248
+ dist/
249
+ *.egg-info/
250
+ .env
251
+ .pytest_cache/
252
+ `,
253
+ },
254
+ {
255
+ path: 'requirements.txt',
256
+ skipIfExists: true,
257
+ content: (ctx: ScaffoldContext) => `# ${ctx.projectName} dependencies
258
+ pytest>=7.0.0
259
+ pytest-json-report>=1.5.0
260
+ `,
261
+ },
262
+ {
263
+ path: 'pytest.ini',
264
+ skipIfExists: true,
265
+ content: `[pytest]
266
+ testpaths = tests
267
+ python_files = test_*.py
268
+ python_classes = Test*
269
+ python_functions = test_*
270
+ `,
271
+ },
272
+ {
273
+ path: 'README.md',
274
+ skipIfExists: true,
275
+ content: (ctx: ScaffoldContext) => `# ${ctx.ideaTitle}
276
+
277
+ ${ctx.ideaDescription}
278
+
279
+ ## Generated by
280
+
281
+ sofIA — AI Discovery Workshop CLI
282
+ Session: \`${ctx.sessionId}\`
283
+ Generated: ${new Date().toISOString()}
284
+
285
+ ## Prerequisites
286
+
287
+ - Python 3.11+
288
+ - pip
289
+
290
+ ## Quick Start
291
+
292
+ \`\`\`bash
293
+ pip install -r requirements.txt
294
+ pytest
295
+ python src/main.py
296
+ \`\`\`
297
+
298
+ ## How It Works
299
+
300
+ ${ctx.planSummary}
301
+
302
+ ## Technology Stack
303
+
304
+ - **Language**: ${ctx.techStack.language}
305
+ - **Runtime**: ${ctx.techStack.runtime}
306
+ - **Test Runner**: ${ctx.techStack.testRunner}
307
+ ${ctx.techStack.framework ? `- **Framework**: ${ctx.techStack.framework}\n` : ''}
308
+ `,
309
+ },
310
+ {
311
+ path: 'src/__init__.py',
312
+ skipIfExists: true,
313
+ content: '',
314
+ },
315
+ {
316
+ path: 'src/main.py',
317
+ skipIfExists: true,
318
+ content: (ctx: ScaffoldContext) => `"""
319
+ ${ctx.ideaTitle}
320
+
321
+ ${ctx.ideaDescription}
322
+
323
+ Generated by sofIA (session: ${ctx.sessionId})
324
+ """
325
+
326
+
327
+ def main() -> str:
328
+ """Main entry point for the ${ctx.projectName} PoC.
329
+
330
+ TODO: Implement the core functionality described in the plan.
331
+ The test file (tests/test_main.py) describes the expected behavior.
332
+ """
333
+ return "Hello from ${ctx.projectName}!"
334
+
335
+
336
+ if __name__ == "__main__":
337
+ print(main())
338
+ `,
339
+ },
340
+ {
341
+ path: 'tests/test_main.py',
342
+ skipIfExists: true,
343
+ content: (ctx: ScaffoldContext) => `"""
344
+ Tests for ${ctx.ideaTitle} PoC.
345
+
346
+ These tests describe the expected behavior of the implementation.
347
+ They are intentionally failing initially — the Ralph loop will
348
+ iteratively fix the implementation until they pass.
349
+
350
+ Generated by sofIA (session: ${ctx.sessionId})
351
+ """
352
+ from src.main import main
353
+
354
+
355
+ def test_main_returns_string():
356
+ result = main()
357
+ assert isinstance(result, str)
358
+
359
+
360
+ def test_main_contains_project_name():
361
+ result = main()
362
+ assert "${ctx.projectName}" in result
363
+ `,
364
+ },
365
+ {
366
+ path: '.sofia-metadata.json',
367
+ skipIfExists: false,
368
+ content: (ctx: ScaffoldContext) =>
369
+ JSON.stringify(
370
+ {
371
+ sessionId: ctx.sessionId,
372
+ featureSpec: '002-poc-generation',
373
+ generatedAt: new Date().toISOString(),
374
+ ideaTitle: ctx.ideaTitle,
375
+ totalIterations: 0,
376
+ finalStatus: null,
377
+ terminationReason: null,
378
+ techStack: {
379
+ language: ctx.techStack.language.toLowerCase(),
380
+ runtime: ctx.techStack.runtime.toLowerCase(),
381
+ testRunner: ctx.techStack.testRunner,
382
+ },
383
+ },
384
+ null,
385
+ 2,
386
+ ) + '\n',
387
+ },
388
+ ],
389
+ };
390
+
391
+ // ── Default Registry ─────────────────────────────────────────────────────────
392
+
393
+ /**
394
+ * Create the default template registry with built-in templates.
395
+ */
396
+ export function createDefaultRegistry(): TemplateRegistry {
397
+ const registry: TemplateRegistry = new Map();
398
+ registry.set(NODE_TS_VITEST_TEMPLATE.id, NODE_TS_VITEST_TEMPLATE);
399
+ registry.set(PYTHON_PYTEST_TEMPLATE.id, PYTHON_PYTEST_TEMPLATE);
400
+ return registry;
401
+ }
402
+
403
+ // ── Selection ────────────────────────────────────────────────────────────────
404
+
405
+ /**
406
+ * Select a template from the registry based on plan architecture notes and dependencies.
407
+ *
408
+ * Uses first-match-wins logic: concatenates architectureNotes + dependencies into a
409
+ * search string, then checks each template's matchPatterns (case-insensitive).
410
+ * Falls back to `node-ts-vitest` if no match is found.
411
+ */
412
+ export function selectTemplate(
413
+ registry: TemplateRegistry,
414
+ architectureNotes?: string,
415
+ dependencies?: string[],
416
+ ): TemplateEntry {
417
+ const searchText = [architectureNotes ?? '', ...(dependencies ?? [])].join(' ').toLowerCase();
418
+
419
+ for (const entry of registry.values()) {
420
+ if (entry.matchPatterns.some((p) => searchText.includes(p.toLowerCase()))) {
421
+ return entry;
422
+ }
423
+ }
424
+
425
+ // Default fallback
426
+ return registry.get('node-ts-vitest')!;
427
+ }
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Test Runner.
3
+ *
4
+ * Spawns the PoC's test suite via `npm test -- --reporter=json` in the
5
+ * output directory, parses Vitest JSON reporter output into a structured
6
+ * TestResults object, and enforces a 60-second timeout.
7
+ *
8
+ * Contract: specs/002-poc-generation/contracts/ralph-loop.md
9
+ */
10
+ import { spawn } from 'node:child_process';
11
+
12
+ import type { TestFailure, TestResults } from '../shared/schemas/session.js';
13
+
14
+ // ── Constants ────────────────────────────────────────────────────────────────
15
+
16
+ const DEFAULT_TIMEOUT_MS = 60_000;
17
+ const MAX_RAW_OUTPUT_CHARS = 2_000;
18
+
19
+ // ── Vitest JSON output types ─────────────────────────────────────────────────
20
+
21
+ interface VitestAssertionResult {
22
+ title: string;
23
+ fullName: string;
24
+ status: 'passed' | 'failed' | 'skipped' | 'todo' | 'pending';
25
+ duration?: number;
26
+ failureMessages?: string[];
27
+ ancestorTitles?: string[];
28
+ }
29
+
30
+ interface VitestTestResult {
31
+ testFilePath: string;
32
+ status: 'passed' | 'failed';
33
+ assertionResults: VitestAssertionResult[];
34
+ startTime?: number;
35
+ endTime?: number;
36
+ message?: string;
37
+ }
38
+
39
+ interface VitestJsonReport {
40
+ numPassedTests?: number;
41
+ numFailedTests?: number;
42
+ numPendingTests?: number;
43
+ numTotalTests?: number;
44
+ success?: boolean;
45
+ testResults?: VitestTestResult[];
46
+ startTime?: number;
47
+ endTime?: number;
48
+ }
49
+
50
+ // ── TestRunner ────────────────────────────────────────────────────────────────
51
+
52
+ export interface TestRunnerOptions {
53
+ /** Timeout in milliseconds (default: 60000) */
54
+ timeoutMs?: number;
55
+ /** Custom test command (default: 'npm test -- --reporter=json') */
56
+ testCommand?: string;
57
+ }
58
+
59
+ export class TestRunner {
60
+ private readonly timeoutMs: number;
61
+ private readonly testCommand: string;
62
+
63
+ constructor(options: TestRunnerOptions = {}) {
64
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
65
+ this.testCommand = options.testCommand ?? 'npm test -- --reporter=json';
66
+ }
67
+
68
+ /**
69
+ * Run the test suite in the given output directory.
70
+ *
71
+ * Spawns `npm test -- --reporter=json` and parses the JSON output.
72
+ * On timeout, returns an error result.
73
+ */
74
+ async run(outputDir: string): Promise<TestResults> {
75
+ const startTime = Date.now();
76
+
77
+ let rawOutput = '';
78
+ let timedOut = false;
79
+
80
+ try {
81
+ rawOutput = await this.spawnTests(outputDir, (timed) => {
82
+ timedOut = timed;
83
+ });
84
+ } catch (err: unknown) {
85
+ const msg = err instanceof Error ? err.message : String(err);
86
+ return this.buildErrorResult(startTime, `Test runner error: ${msg}`);
87
+ }
88
+
89
+ if (timedOut) {
90
+ return this.buildErrorResult(startTime, `Test runner timed out after ${this.timeoutMs}ms`);
91
+ }
92
+
93
+ // Truncate rawOutput to max chars from the end
94
+ const truncatedOutput =
95
+ rawOutput.length > MAX_RAW_OUTPUT_CHARS
96
+ ? rawOutput.slice(-MAX_RAW_OUTPUT_CHARS)
97
+ : rawOutput;
98
+
99
+ return this.parseOutput(truncatedOutput, startTime, rawOutput);
100
+ }
101
+
102
+ /**
103
+ * Spawn test process and collect output.
104
+ */
105
+ private spawnTests(
106
+ outputDir: string,
107
+ onTimeout: (timed: boolean) => void,
108
+ ): Promise<string> {
109
+ return new Promise((resolve, reject) => {
110
+ const stdoutChunks: Buffer[] = [];
111
+ const stderrChunks: Buffer[] = [];
112
+
113
+ const parts = this.testCommand.split(/\s+/);
114
+ const cmd = parts[0];
115
+ const args = parts.slice(1);
116
+
117
+ const child = spawn(cmd, args, {
118
+ cwd: outputDir,
119
+ shell: false,
120
+ env: {
121
+ ...process.env,
122
+ // Disable color output for reliable JSON parsing
123
+ NO_COLOR: '1',
124
+ FORCE_COLOR: '0',
125
+ },
126
+ });
127
+
128
+ child.stdout.on('data', (chunk: Buffer) => {
129
+ stdoutChunks.push(chunk);
130
+ });
131
+
132
+ child.stderr.on('data', (chunk: Buffer) => {
133
+ stderrChunks.push(chunk);
134
+ });
135
+
136
+ const timer = setTimeout(() => {
137
+ onTimeout(true);
138
+ child.kill('SIGTERM');
139
+ // Force kill after 5 more seconds
140
+ setTimeout(() => {
141
+ if (!child.killed) child.kill('SIGKILL');
142
+ }, 5_000);
143
+ }, this.timeoutMs);
144
+
145
+ child.on('close', () => {
146
+ clearTimeout(timer);
147
+ // Combine stdout and stderr for parsing (Vitest may output to stderr)
148
+ const combined = [
149
+ Buffer.concat(stdoutChunks).toString('utf-8'),
150
+ Buffer.concat(stderrChunks).toString('utf-8'),
151
+ ]
152
+ .filter(Boolean)
153
+ .join('\n');
154
+ resolve(combined);
155
+ });
156
+
157
+ child.on('error', (err) => {
158
+ clearTimeout(timer);
159
+ reject(err);
160
+ });
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Parse Vitest JSON reporter output into TestResults.
166
+ * Exposed as protected for unit testing purposes.
167
+ */
168
+ protected parseOutput(output: string, startTime: number, rawOutput: string): TestResults {
169
+ const durationMs = Date.now() - startTime;
170
+
171
+ // Try to extract JSON from the output (Vitest may mix JSON with other output)
172
+ const json = this.extractJson(output);
173
+
174
+ if (!json) {
175
+ // Could not parse JSON — return raw output as error info
176
+ return {
177
+ passed: 0,
178
+ failed: 0,
179
+ skipped: 0,
180
+ total: 0,
181
+ durationMs,
182
+ failures: [],
183
+ rawOutput: rawOutput.slice(-MAX_RAW_OUTPUT_CHARS),
184
+ };
185
+ }
186
+
187
+ const passed = json.numPassedTests ?? 0;
188
+ const failed = json.numFailedTests ?? 0;
189
+ const skipped = json.numPendingTests ?? 0;
190
+ const total = json.numTotalTests ?? passed + failed + skipped;
191
+
192
+ const failures: TestFailure[] = this.extractFailures(json);
193
+
194
+ return {
195
+ passed,
196
+ failed,
197
+ skipped,
198
+ total,
199
+ durationMs,
200
+ failures: failures.slice(0, 10), // max 10 failures
201
+ rawOutput: rawOutput.slice(-MAX_RAW_OUTPUT_CHARS),
202
+ };
203
+ }
204
+
205
+ /**
206
+ * Extract JSON object from potentially mixed output.
207
+ * Protected for subclass testing.
208
+ */
209
+ protected extractJson(output: string): VitestJsonReport | null {
210
+ // Try to find a JSON object/array in the output
211
+ const lines = output.split('\n');
212
+
213
+ // Try each line (Vitest may output JSON on a single line)
214
+ for (const line of lines) {
215
+ const trimmed = line.trim();
216
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
217
+ try {
218
+ return JSON.parse(trimmed) as VitestJsonReport;
219
+ } catch {
220
+ // Not valid JSON on this line
221
+ }
222
+ }
223
+ }
224
+
225
+ // Try the entire output
226
+ const start = output.indexOf('{');
227
+ const end = output.lastIndexOf('}');
228
+ if (start !== -1 && end !== -1 && end > start) {
229
+ try {
230
+ return JSON.parse(output.slice(start, end + 1)) as VitestJsonReport;
231
+ } catch {
232
+ // Not valid JSON
233
+ }
234
+ }
235
+
236
+ return null;
237
+ }
238
+
239
+ /**
240
+ * Extract TestFailure objects from Vitest JSON output.
241
+ */
242
+ private extractFailures(json: VitestJsonReport): TestFailure[] {
243
+ const failures: TestFailure[] = [];
244
+
245
+ for (const testResult of json.testResults ?? []) {
246
+ for (const assertion of testResult.assertionResults ?? []) {
247
+ if (assertion.status === 'failed') {
248
+ const message = assertion.failureMessages?.join('\n') ?? 'Test failed';
249
+ failures.push({
250
+ testName: assertion.fullName || assertion.title,
251
+ message: message.substring(0, 500), // Truncate individual messages
252
+ file: testResult.testFilePath,
253
+ });
254
+ }
255
+ }
256
+ }
257
+
258
+ return failures;
259
+ }
260
+
261
+ /**
262
+ * Build an error result (timeout or spawn failure).
263
+ * Protected for subclass testing.
264
+ */
265
+ protected buildErrorResult(startTime: number, errorMessage: string): TestResults {
266
+ return {
267
+ passed: 0,
268
+ failed: 0,
269
+ skipped: 0,
270
+ total: 0,
271
+ durationMs: Date.now() - startTime,
272
+ failures: [],
273
+ rawOutput: errorMessage,
274
+ };
275
+ }
276
+ }