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,279 @@
1
+ /**
2
+ * Unit tests for directCommands module (T052).
3
+ *
4
+ * Covers:
5
+ * - Input validation (missing session, missing phase in non-interactive)
6
+ * - Phase validation
7
+ * - JSON error output format
8
+ * - ioContext TTY/non-TTY detection
9
+ * - JSON output separation (stdout vs stderr)
10
+ * - Retry behavior and backoff
11
+ */
12
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
13
+ import { mkdtemp, rm } from 'node:fs/promises';
14
+ import { join } from 'node:path';
15
+ import { tmpdir } from 'node:os';
16
+ import { Readable, Writable } from 'node:stream';
17
+ import { createFakeCopilotClient } from '../../../src/shared/copilotClient.js';
18
+ import { SessionStore } from '../../../src/sessions/sessionStore.js';
19
+ import { runDirectCommand } from '../../../src/cli/directCommands.js';
20
+ import { createLoopIO } from '../../../src/cli/ioContext.js';
21
+ // ── Helpers ──────────────────────────────────────────────────────────────────
22
+ function createTestSession(overrides) {
23
+ const now = new Date().toISOString();
24
+ return {
25
+ sessionId: 'unit-direct-test',
26
+ schemaVersion: '1.0.0',
27
+ createdAt: now,
28
+ updatedAt: now,
29
+ phase: 'Discover',
30
+ status: 'Active',
31
+ participants: [],
32
+ artifacts: { generatedFiles: [] },
33
+ turns: [],
34
+ ...overrides,
35
+ };
36
+ }
37
+ function createTestIO(opts = {}) {
38
+ let inputIdx = 0;
39
+ const output = [];
40
+ const activityLog = [];
41
+ const inputs = opts.inputs ?? [null];
42
+ const gateChoice = opts.gateChoice ?? { choice: 'exit' };
43
+ return {
44
+ write(text) { output.push(text); },
45
+ writeActivity(text) { activityLog.push(text); },
46
+ writeToolSummary(_toolName, _summary) { },
47
+ async readInput(_prompt) {
48
+ if (inputIdx >= inputs.length)
49
+ return null;
50
+ return inputs[inputIdx++];
51
+ },
52
+ async showDecisionGate(_phase) {
53
+ return gateChoice;
54
+ },
55
+ isJsonMode: opts.isJsonMode ?? false,
56
+ isTTY: opts.isTTY ?? false,
57
+ output,
58
+ activityLog,
59
+ };
60
+ }
61
+ // ── Tests ────────────────────────────────────────────────────────────────────
62
+ describe('directCommands validation', () => {
63
+ let tmpDir;
64
+ let store;
65
+ beforeEach(async () => {
66
+ tmpDir = await mkdtemp(join(tmpdir(), 'sofia-unit-dc-'));
67
+ store = new SessionStore(tmpDir);
68
+ });
69
+ afterEach(async () => {
70
+ await rm(tmpDir, { recursive: true, force: true });
71
+ });
72
+ it('rejects undefined sessionId', async () => {
73
+ const io = createTestIO();
74
+ const client = createFakeCopilotClient([]);
75
+ const result = await runDirectCommand({
76
+ sessionId: undefined,
77
+ phase: 'Discover',
78
+ store, client, io,
79
+ });
80
+ expect(result.exitCode).toBe(1);
81
+ expect(result.error).toContain('session');
82
+ });
83
+ it('rejects empty sessionId', async () => {
84
+ const io = createTestIO();
85
+ const client = createFakeCopilotClient([]);
86
+ const result = await runDirectCommand({
87
+ sessionId: '',
88
+ phase: 'Discover',
89
+ store, client, io,
90
+ });
91
+ expect(result.exitCode).toBe(1);
92
+ expect(result.error).toContain('session');
93
+ });
94
+ it('rejects missing phase in non-interactive mode', async () => {
95
+ const session = createTestSession();
96
+ await store.save(session);
97
+ const io = createTestIO();
98
+ const client = createFakeCopilotClient([]);
99
+ const result = await runDirectCommand({
100
+ sessionId: session.sessionId,
101
+ phase: undefined,
102
+ store, client, io,
103
+ nonInteractive: true,
104
+ });
105
+ expect(result.exitCode).toBe(1);
106
+ expect(result.error).toContain('phase');
107
+ });
108
+ it('rejects invalid phase name', async () => {
109
+ const session = createTestSession();
110
+ await store.save(session);
111
+ const io = createTestIO();
112
+ const client = createFakeCopilotClient([]);
113
+ const result = await runDirectCommand({
114
+ sessionId: session.sessionId,
115
+ phase: 'Brainstorm',
116
+ store, client, io,
117
+ });
118
+ expect(result.exitCode).toBe(1);
119
+ expect(result.error).toContain('Invalid phase');
120
+ expect(result.error).toContain('Brainstorm');
121
+ });
122
+ it('rejects nonexistent session', async () => {
123
+ const io = createTestIO();
124
+ const client = createFakeCopilotClient([]);
125
+ const result = await runDirectCommand({
126
+ sessionId: 'does-not-exist',
127
+ phase: 'Discover',
128
+ store, client, io,
129
+ });
130
+ expect(result.exitCode).toBe(1);
131
+ expect(result.error).toContain('not found');
132
+ });
133
+ });
134
+ describe('directCommands JSON output', () => {
135
+ let tmpDir;
136
+ let store;
137
+ beforeEach(async () => {
138
+ tmpDir = await mkdtemp(join(tmpdir(), 'sofia-unit-dc-json-'));
139
+ store = new SessionStore(tmpDir);
140
+ });
141
+ afterEach(async () => {
142
+ await rm(tmpDir, { recursive: true, force: true });
143
+ });
144
+ it('emits error as JSON when json=true', async () => {
145
+ const io = createTestIO({ isJsonMode: true });
146
+ const client = createFakeCopilotClient([]);
147
+ await runDirectCommand({
148
+ sessionId: 'missing',
149
+ phase: 'Discover',
150
+ store, client, io,
151
+ json: true,
152
+ });
153
+ const jsonLines = io.output.filter(l => {
154
+ try {
155
+ JSON.parse(l);
156
+ return true;
157
+ }
158
+ catch {
159
+ return false;
160
+ }
161
+ });
162
+ expect(jsonLines.length).toBeGreaterThan(0);
163
+ const parsed = JSON.parse(jsonLines[0]);
164
+ expect(parsed.error).toBeDefined();
165
+ expect(typeof parsed.error).toBe('string');
166
+ expect(parsed.error).toContain('not found');
167
+ });
168
+ it('emits result summary as JSON on success', async () => {
169
+ const session = createTestSession();
170
+ await store.save(session);
171
+ const io = createTestIO({ isJsonMode: true, inputs: ['hello', null] });
172
+ const client = createFakeCopilotClient([
173
+ { role: 'assistant', content: 'I understand.' },
174
+ ]);
175
+ await runDirectCommand({
176
+ sessionId: session.sessionId,
177
+ phase: 'Discover',
178
+ store, client, io,
179
+ json: true,
180
+ });
181
+ // Should contain at least the result summary
182
+ const jsonLines = io.output
183
+ .filter(l => { try {
184
+ const o = JSON.parse(l);
185
+ return o.sessionId;
186
+ }
187
+ catch {
188
+ return false;
189
+ } });
190
+ expect(jsonLines.length).toBeGreaterThan(0);
191
+ const result = JSON.parse(jsonLines[0]);
192
+ expect(result.sessionId).toBe(session.sessionId);
193
+ expect(result.phase).toBe('Discover');
194
+ });
195
+ });
196
+ describe('ioContext', () => {
197
+ it('creates TTY IO when input stream has isTTY', () => {
198
+ const input = new Readable({ read() { } });
199
+ input.isTTY = true;
200
+ const output = new Writable({ write(_c, _e, cb) { cb(); } });
201
+ const io = createLoopIO({ input, output });
202
+ expect(io.isTTY).toBe(true);
203
+ expect(io.isJsonMode).toBe(false);
204
+ });
205
+ it('creates non-TTY IO when nonInteractive is true', () => {
206
+ const input = new Readable({ read() { } });
207
+ input.isTTY = true;
208
+ const output = new Writable({ write(_c, _e, cb) { cb(); } });
209
+ const io = createLoopIO({ input, output, nonInteractive: true });
210
+ expect(io.isTTY).toBe(false);
211
+ });
212
+ it('sets isJsonMode when json option is true', () => {
213
+ const io = createLoopIO({ json: true });
214
+ expect(io.isJsonMode).toBe(true);
215
+ });
216
+ it('non-interactive readInput returns null', async () => {
217
+ const io = createLoopIO({ nonInteractive: true });
218
+ const result = await io.readInput('prompt: ');
219
+ expect(result).toBeNull();
220
+ });
221
+ });
222
+ describe('directCommands retry', () => {
223
+ let tmpDir;
224
+ let store;
225
+ beforeEach(async () => {
226
+ tmpDir = await mkdtemp(join(tmpdir(), 'sofia-unit-dc-retry-'));
227
+ store = new SessionStore(tmpDir);
228
+ });
229
+ afterEach(async () => {
230
+ await rm(tmpDir, { recursive: true, force: true });
231
+ });
232
+ it('does not retry non-recoverable errors', async () => {
233
+ const session = createTestSession();
234
+ await store.save(session);
235
+ let callCount = 0;
236
+ const client = createFakeCopilotClient([], {
237
+ onChat: async () => {
238
+ callCount++;
239
+ // Auth errors are not recoverable
240
+ const err = new Error('Unauthorized');
241
+ err.statusCode = 401;
242
+ throw err;
243
+ },
244
+ });
245
+ const io = createTestIO({ inputs: ['test', 'test', 'test'] });
246
+ await runDirectCommand({
247
+ sessionId: session.sessionId,
248
+ phase: 'Discover',
249
+ store, client, io,
250
+ retry: 3,
251
+ });
252
+ // Should only attempt once — auth errors are not retried
253
+ expect(callCount).toBe(1);
254
+ });
255
+ it('logs retry activity messages', async () => {
256
+ const session = createTestSession();
257
+ await store.save(session);
258
+ let callCount = 0;
259
+ const client = createFakeCopilotClient([], {
260
+ onChat: async () => {
261
+ callCount++;
262
+ if (callCount <= 1) {
263
+ throw Object.assign(new Error('Connection refused'), { code: 'ECONNREFUSED' });
264
+ }
265
+ return { role: 'assistant', content: 'OK' };
266
+ },
267
+ });
268
+ const io = createTestIO({ inputs: ['test', 'test', null] });
269
+ await runDirectCommand({
270
+ sessionId: session.sessionId,
271
+ phase: 'Discover',
272
+ store, client, io,
273
+ retry: 2,
274
+ });
275
+ // Should have logged retry activity
276
+ const retryLogs = io.activityLog.filter(l => l.includes('Retrying'));
277
+ expect(retryLogs.length).toBeGreaterThan(0);
278
+ });
279
+ });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Environment loader tests.
3
+ *
4
+ * Validates that dotenv is loaded at startup to populate process.env
5
+ * from a .env file in the project root.
6
+ */
7
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import os from 'node:os';
11
+ describe('loadEnvFile', () => {
12
+ let tmpDir;
13
+ const savedEnv = {};
14
+ beforeEach(() => {
15
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sofia-env-'));
16
+ // Save and clear any existing values
17
+ savedEnv['FOUNDRY_PROJECT_ENDPOINT'] = process.env.FOUNDRY_PROJECT_ENDPOINT;
18
+ savedEnv['FOUNDRY_MODEL_DEPLOYMENT_NAME'] = process.env.FOUNDRY_MODEL_DEPLOYMENT_NAME;
19
+ delete process.env.FOUNDRY_PROJECT_ENDPOINT;
20
+ delete process.env.FOUNDRY_MODEL_DEPLOYMENT_NAME;
21
+ });
22
+ afterEach(() => {
23
+ // Restore original env
24
+ for (const [key, val] of Object.entries(savedEnv)) {
25
+ if (val === undefined)
26
+ delete process.env[key];
27
+ else
28
+ process.env[key] = val;
29
+ }
30
+ fs.rmSync(tmpDir, { recursive: true, force: true });
31
+ });
32
+ it('loads variables from .env file into process.env', async () => {
33
+ const envPath = path.join(tmpDir, '.env');
34
+ fs.writeFileSync(envPath, [
35
+ 'FOUNDRY_PROJECT_ENDPOINT="https://my-endpoint.cognitiveservices.azure.com"',
36
+ 'FOUNDRY_MODEL_DEPLOYMENT_NAME="gpt-4.1-mini"',
37
+ ].join('\n'));
38
+ // Dynamic import to avoid caching
39
+ const { loadEnvFile } = await import('../../../src/cli/envLoader.js');
40
+ loadEnvFile(envPath);
41
+ expect(process.env.FOUNDRY_PROJECT_ENDPOINT).toBe('https://my-endpoint.cognitiveservices.azure.com');
42
+ expect(process.env.FOUNDRY_MODEL_DEPLOYMENT_NAME).toBe('gpt-4.1-mini');
43
+ });
44
+ it('does not overwrite existing env vars', async () => {
45
+ process.env.FOUNDRY_PROJECT_ENDPOINT = 'already-set';
46
+ const envPath = path.join(tmpDir, '.env');
47
+ fs.writeFileSync(envPath, 'FOUNDRY_PROJECT_ENDPOINT="from-file"');
48
+ const { loadEnvFile } = await import('../../../src/cli/envLoader.js');
49
+ loadEnvFile(envPath);
50
+ expect(process.env.FOUNDRY_PROJECT_ENDPOINT).toBe('already-set');
51
+ });
52
+ it('does nothing when .env file does not exist', async () => {
53
+ const envPath = path.join(tmpDir, '.env-nonexistent');
54
+ const { loadEnvFile } = await import('../../../src/cli/envLoader.js');
55
+ // Should not throw
56
+ expect(() => loadEnvFile(envPath)).not.toThrow();
57
+ });
58
+ });
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Tests for writeToolSummary in LoopIO / ioContext (T086).
3
+ *
4
+ * Verifies that tool summaries are correctly formatted in default mode,
5
+ * expanded in --debug mode, and suppressed in JSON/non-TTY mode.
6
+ */
7
+ import { describe, it, expect, beforeEach } from 'vitest';
8
+ import { Writable } from 'node:stream';
9
+ import { Readable } from 'node:stream';
10
+ import { createLoopIO } from '../../../src/cli/ioContext.js';
11
+ // ── Helpers ─────────────────────────────────────────────────────────────────
12
+ function createCaptureStream() {
13
+ const chunks = [];
14
+ const stream = new Writable({
15
+ write(chunk, _encoding, callback) {
16
+ chunks.push(chunk.toString());
17
+ callback();
18
+ },
19
+ });
20
+ stream.getOutput = () => chunks.join('');
21
+ return stream;
22
+ }
23
+ function createMockInput() {
24
+ const readable = new Readable({ read() { } });
25
+ // Mark as non-TTY by default
26
+ return readable;
27
+ }
28
+ // ── Tests ────────────────────────────────────────────────────────────────────
29
+ describe('writeToolSummary in LoopIO (T086)', () => {
30
+ describe('default mode (TTY, no debug, no JSON)', () => {
31
+ let errorStream;
32
+ let outputStream;
33
+ beforeEach(() => {
34
+ errorStream = createCaptureStream();
35
+ outputStream = createCaptureStream();
36
+ });
37
+ it('prints "✓ <toolName>: <summary>" to stderr', () => {
38
+ const input = createMockInput();
39
+ input.isTTY = true;
40
+ const io = createLoopIO({
41
+ input,
42
+ output: outputStream,
43
+ errorOutput: errorStream,
44
+ });
45
+ io.writeToolSummary('WorkIQ', 'Found 12 relevant processes');
46
+ const errOutput = errorStream.getOutput();
47
+ expect(errOutput).toContain('✓ WorkIQ: Found 12 relevant processes');
48
+ });
49
+ it('does not write tool summary to stdout', () => {
50
+ const input = createMockInput();
51
+ input.isTTY = true;
52
+ const io = createLoopIO({
53
+ input,
54
+ output: outputStream,
55
+ errorOutput: errorStream,
56
+ });
57
+ io.writeToolSummary('Context7', '8 docs found');
58
+ const stdOutput = outputStream.getOutput();
59
+ expect(stdOutput).toBe('');
60
+ });
61
+ });
62
+ describe('debug mode', () => {
63
+ let errorStream;
64
+ let outputStream;
65
+ beforeEach(() => {
66
+ errorStream = createCaptureStream();
67
+ outputStream = createCaptureStream();
68
+ });
69
+ it('shows tool args and result details in addition to summary', () => {
70
+ const input = createMockInput();
71
+ input.isTTY = true;
72
+ const io = createLoopIO({
73
+ input,
74
+ output: outputStream,
75
+ errorOutput: errorStream,
76
+ debug: true,
77
+ });
78
+ io.writeToolSummary('WorkIQ', 'Found 5 processes', {
79
+ args: { query: 'logistics', limit: 10 },
80
+ result: { count: 5, processes: ['p1', 'p2'] },
81
+ });
82
+ const errOutput = errorStream.getOutput();
83
+ expect(errOutput).toContain('✓ WorkIQ: Found 5 processes');
84
+ expect(errOutput).toContain('query');
85
+ expect(errOutput).toContain('logistics');
86
+ expect(errOutput).toContain('count');
87
+ });
88
+ });
89
+ describe('JSON mode', () => {
90
+ it('omits tool summaries from stdout', () => {
91
+ const errorStream = createCaptureStream();
92
+ const outputStream = createCaptureStream();
93
+ const input = createMockInput();
94
+ const io = createLoopIO({
95
+ json: true,
96
+ input,
97
+ output: outputStream,
98
+ errorOutput: errorStream,
99
+ });
100
+ io.writeToolSummary('WorkIQ', 'Found stuff');
101
+ expect(outputStream.getOutput()).toBe('');
102
+ });
103
+ });
104
+ describe('non-interactive mode', () => {
105
+ it('omits tool summaries from stdout', () => {
106
+ const errorStream = createCaptureStream();
107
+ const outputStream = createCaptureStream();
108
+ const input = createMockInput();
109
+ const io = createLoopIO({
110
+ nonInteractive: true,
111
+ input,
112
+ output: outputStream,
113
+ errorOutput: errorStream,
114
+ });
115
+ io.writeToolSummary('GitHub', 'Repo cloned');
116
+ expect(outputStream.getOutput()).toBe('');
117
+ });
118
+ });
119
+ });
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Preflight checks tests (T056, T023).
3
+ *
4
+ * Validates startup checks for:
5
+ * - Copilot SDK availability
6
+ * - MCP server configuration presence
7
+ * - Environment variable requirements
8
+ * - Graceful degradation when services unavailable
9
+ * - Legacy env var detection (FR-016)
10
+ */
11
+ import { describe, it, expect, afterEach } from 'vitest';
12
+ import { runPreflightChecks, checkLegacyWebSearchEnvVars, } from '../../../src/cli/preflight.js';
13
+ describe('preflight checks', () => {
14
+ it('returns all-pass when all checks succeed', async () => {
15
+ const result = await runPreflightChecks({
16
+ checkCopilotSdk: async () => ({ name: 'copilot-sdk', status: 'pass', message: 'SDK available' }),
17
+ checkMcpConfig: async () => ({ name: 'mcp-config', status: 'pass', message: 'Config found' }),
18
+ });
19
+ expect(result.passed).toBe(true);
20
+ expect(result.checks.every((c) => c.status === 'pass')).toBe(true);
21
+ });
22
+ it('returns failed when a required check fails', async () => {
23
+ const result = await runPreflightChecks({
24
+ checkCopilotSdk: async () => ({ name: 'copilot-sdk', status: 'fail', message: 'SDK not found', required: true }),
25
+ checkMcpConfig: async () => ({ name: 'mcp-config', status: 'pass', message: 'Config found' }),
26
+ });
27
+ expect(result.passed).toBe(false);
28
+ expect(result.checks.find((c) => c.name === 'copilot-sdk')?.status).toBe('fail');
29
+ });
30
+ it('returns pass with warnings for optional failures', async () => {
31
+ const result = await runPreflightChecks({
32
+ checkCopilotSdk: async () => ({ name: 'copilot-sdk', status: 'pass', message: 'SDK available' }),
33
+ checkMcpConfig: async () => ({ name: 'mcp-config', status: 'warn', message: 'Config not found, will use defaults' }),
34
+ });
35
+ expect(result.passed).toBe(true);
36
+ expect(result.checks.find((c) => c.name === 'mcp-config')?.status).toBe('warn');
37
+ });
38
+ it('handles check functions that throw', async () => {
39
+ const result = await runPreflightChecks({
40
+ checkCopilotSdk: async () => { throw new Error('Unexpected failure'); },
41
+ checkMcpConfig: async () => ({ name: 'mcp-config', status: 'pass', message: 'OK' }),
42
+ });
43
+ expect(result.passed).toBe(false);
44
+ const failedCheck = result.checks.find((c) => c.status === 'fail');
45
+ expect(failedCheck).toBeDefined();
46
+ expect(failedCheck.message).toContain('Unexpected failure');
47
+ });
48
+ it('collects all check results even when some fail', async () => {
49
+ const result = await runPreflightChecks({
50
+ checkCopilotSdk: async () => ({ name: 'copilot-sdk', status: 'fail', message: 'Missing', required: true }),
51
+ checkMcpConfig: async () => ({ name: 'mcp-config', status: 'fail', message: 'Missing', required: true }),
52
+ });
53
+ expect(result.checks).toHaveLength(2);
54
+ expect(result.checks.filter((c) => c.status === 'fail')).toHaveLength(2);
55
+ });
56
+ it('includes check names and messages in results', async () => {
57
+ const result = await runPreflightChecks({
58
+ checkCopilotSdk: async () => ({ name: 'copilot-sdk', status: 'pass', message: 'v1.0.0 available' }),
59
+ });
60
+ expect(result.checks[0].name).toBe('copilot-sdk');
61
+ expect(result.checks[0].message).toBe('v1.0.0 available');
62
+ });
63
+ });
64
+ describe('checkLegacyWebSearchEnvVars (T023)', () => {
65
+ const originalEnv = { ...process.env };
66
+ afterEach(() => {
67
+ process.env = { ...originalEnv };
68
+ });
69
+ it('passes when no legacy env vars are set', async () => {
70
+ delete process.env.SOFIA_FOUNDRY_AGENT_ENDPOINT;
71
+ delete process.env.SOFIA_FOUNDRY_AGENT_KEY;
72
+ const result = await checkLegacyWebSearchEnvVars();
73
+ expect(result.status).toBe('pass');
74
+ expect(result.name).toBe('legacy-web-search-env');
75
+ });
76
+ it('fails when SOFIA_FOUNDRY_AGENT_ENDPOINT is set', async () => {
77
+ process.env.SOFIA_FOUNDRY_AGENT_ENDPOINT = 'https://old-endpoint.example.com';
78
+ delete process.env.SOFIA_FOUNDRY_AGENT_KEY;
79
+ const result = await checkLegacyWebSearchEnvVars();
80
+ expect(result.status).toBe('fail');
81
+ expect(result.required).toBe(true);
82
+ expect(result.message).toContain('Legacy web search env vars detected');
83
+ expect(result.message).toContain('FOUNDRY_PROJECT_ENDPOINT');
84
+ });
85
+ it('fails when SOFIA_FOUNDRY_AGENT_KEY is set', async () => {
86
+ delete process.env.SOFIA_FOUNDRY_AGENT_ENDPOINT;
87
+ process.env.SOFIA_FOUNDRY_AGENT_KEY = 'old-api-key';
88
+ const result = await checkLegacyWebSearchEnvVars();
89
+ expect(result.status).toBe('fail');
90
+ expect(result.required).toBe(true);
91
+ expect(result.message).toContain('API key auth is no longer used');
92
+ });
93
+ it('fails when both legacy vars are set', async () => {
94
+ process.env.SOFIA_FOUNDRY_AGENT_ENDPOINT = 'https://old-endpoint.example.com';
95
+ process.env.SOFIA_FOUNDRY_AGENT_KEY = 'old-api-key';
96
+ const result = await checkLegacyWebSearchEnvVars();
97
+ expect(result.status).toBe('fail');
98
+ expect(result.required).toBe(true);
99
+ });
100
+ it('fails preflight when integrated with runPreflightChecks', async () => {
101
+ process.env.SOFIA_FOUNDRY_AGENT_ENDPOINT = 'https://old-endpoint.example.com';
102
+ const result = await runPreflightChecks({
103
+ checkLegacyWebSearch: checkLegacyWebSearchEnvVars,
104
+ });
105
+ expect(result.passed).toBe(false);
106
+ expect(result.checks[0].name).toBe('legacy-web-search-env');
107
+ });
108
+ });
@@ -0,0 +1,111 @@
1
+ /**
2
+ * T064: Unit tests for statusCommand — session name display.
3
+ *
4
+ * Verifies that statusCommand displays the session name in both
5
+ * TTY table and JSON output formats.
6
+ */
7
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
8
+ function makeSession(overrides) {
9
+ return {
10
+ sessionId: 'test-session-001',
11
+ schemaVersion: '1.0.0',
12
+ createdAt: '2026-01-01T00:00:00Z',
13
+ updatedAt: '2026-01-01T01:00:00Z',
14
+ phase: 'Discover',
15
+ status: 'Active',
16
+ participants: [],
17
+ artifacts: { generatedFiles: [] },
18
+ turns: [],
19
+ ...overrides,
20
+ };
21
+ }
22
+ // Mock sessionStore
23
+ const mockStore = {
24
+ load: vi.fn(),
25
+ save: vi.fn(),
26
+ exists: vi.fn(),
27
+ list: vi.fn(),
28
+ };
29
+ vi.mock('../../../src/sessions/sessionStore.js', () => ({
30
+ createDefaultStore: () => mockStore,
31
+ SessionStore: vi.fn(),
32
+ }));
33
+ describe('statusCommand', () => {
34
+ let stdoutChunks;
35
+ let stderrChunks;
36
+ const originalWrite = process.stdout.write;
37
+ const originalConsoleLog = console.log;
38
+ const originalConsoleError = console.error;
39
+ beforeEach(() => {
40
+ stdoutChunks = [];
41
+ stderrChunks = [];
42
+ process.stdout.write = vi.fn((chunk) => {
43
+ stdoutChunks.push(chunk.toString());
44
+ return true;
45
+ });
46
+ console.log = vi.fn((...args) => {
47
+ stdoutChunks.push(args.map(String).join(' '));
48
+ });
49
+ console.error = vi.fn((...args) => {
50
+ stderrChunks.push(args.map(String).join(' '));
51
+ });
52
+ process.exitCode = undefined;
53
+ vi.resetAllMocks();
54
+ });
55
+ afterEach(() => {
56
+ process.stdout.write = originalWrite;
57
+ console.log = originalConsoleLog;
58
+ console.error = originalConsoleError;
59
+ process.exitCode = undefined;
60
+ });
61
+ describe('session name display (T064)', () => {
62
+ it('displays session name in JSON output for single session', async () => {
63
+ const session = makeSession({ name: 'Logistics AI Workshop' });
64
+ mockStore.exists.mockResolvedValue(true);
65
+ mockStore.load.mockResolvedValue(session);
66
+ const { statusCommand } = await import('../../../src/cli/statusCommand.js');
67
+ await statusCommand({ session: 'test-session-001', json: true });
68
+ const output = stdoutChunks.join('');
69
+ const parsed = JSON.parse(output);
70
+ expect(parsed.name).toBe('Logistics AI Workshop');
71
+ });
72
+ it('omits name from JSON output when session has no name', async () => {
73
+ const session = makeSession();
74
+ mockStore.exists.mockResolvedValue(true);
75
+ mockStore.load.mockResolvedValue(session);
76
+ const { statusCommand } = await import('../../../src/cli/statusCommand.js');
77
+ await statusCommand({ session: 'test-session-001', json: true });
78
+ const output = stdoutChunks.join('');
79
+ const parsed = JSON.parse(output);
80
+ expect(parsed.name).toBeUndefined();
81
+ });
82
+ it('displays session name in TTY output for single session', async () => {
83
+ const session = makeSession({ name: 'Retail AI Insights' });
84
+ mockStore.exists.mockResolvedValue(true);
85
+ mockStore.load.mockResolvedValue(session);
86
+ const { statusCommand } = await import('../../../src/cli/statusCommand.js');
87
+ await statusCommand({ session: 'test-session-001', json: false });
88
+ const output = stdoutChunks.join(' ');
89
+ expect(output).toContain('Retail AI Insights');
90
+ });
91
+ it('displays session name in session list JSON output', async () => {
92
+ const session = makeSession({ name: 'Supply Chain AI' });
93
+ mockStore.list.mockResolvedValue(['test-session-001']);
94
+ mockStore.load.mockResolvedValue(session);
95
+ const { statusCommand } = await import('../../../src/cli/statusCommand.js');
96
+ await statusCommand({ json: true });
97
+ const output = stdoutChunks.join('');
98
+ const parsed = JSON.parse(output);
99
+ expect(parsed.sessions[0].name).toBe('Supply Chain AI');
100
+ });
101
+ it('displays session name in session list TTY table', async () => {
102
+ const session = makeSession({ name: 'HR Automation' });
103
+ mockStore.list.mockResolvedValue(['test-session-001']);
104
+ mockStore.load.mockResolvedValue(session);
105
+ const { statusCommand } = await import('../../../src/cli/statusCommand.js');
106
+ await statusCommand({ json: false });
107
+ const output = stdoutChunks.join(' ');
108
+ expect(output).toContain('HR Automation');
109
+ });
110
+ });
111
+ });