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,451 @@
1
+ /**
2
+ * T011: Unit tests for PocScaffolder.
3
+ *
4
+ * Verifies scaffold creates all required files, skip-if-exists behavior,
5
+ * and ScaffoldContext population from session.
6
+ */
7
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8
+ import { mkdtemp, rm, readFile, readdir, stat } from 'node:fs/promises';
9
+ import { join } from 'node:path';
10
+ import { tmpdir } from 'node:os';
11
+
12
+ import { PocScaffolder, toKebabCase, validatePocOutput } from '../../../src/develop/pocScaffolder.js';
13
+ import type { WorkshopSession } from '../../../src/shared/schemas/session.js';
14
+
15
+ // ── Helpers ──────────────────────────────────────────────────────────────────
16
+
17
+ function makeSession(overrides?: Partial<WorkshopSession>): WorkshopSession {
18
+ const now = new Date().toISOString();
19
+ return {
20
+ sessionId: 'scaffold-test-001',
21
+ schemaVersion: '1.0.0',
22
+ createdAt: now,
23
+ updatedAt: now,
24
+ phase: 'Develop',
25
+ status: 'Active',
26
+ participants: [],
27
+ artifacts: { generatedFiles: [] },
28
+ ideas: [
29
+ {
30
+ id: 'idea-1',
31
+ title: 'AI Route Optimizer',
32
+ description: 'Optimize delivery routes using AI.',
33
+ workflowStepIds: ['a1'],
34
+ },
35
+ ],
36
+ selection: {
37
+ ideaId: 'idea-1',
38
+ selectionRationale: 'Best idea.',
39
+ confirmedByUser: true,
40
+ },
41
+ plan: {
42
+ milestones: [{ id: 'm1', title: 'Setup', items: ['Init project'] }],
43
+ architectureNotes: 'Node.js 20 + TypeScript + Express. Use vitest for tests.',
44
+ dependencies: ['express', 'typescript', 'vitest'],
45
+ },
46
+ ...overrides,
47
+ };
48
+ }
49
+
50
+ describe('toKebabCase', () => {
51
+ it('converts a title to kebab case', () => {
52
+ expect(toKebabCase('AI Route Optimizer')).toBe('ai-route-optimizer');
53
+ });
54
+
55
+ it('removes special characters', () => {
56
+ expect(toKebabCase('AI-Powered Delivery (Beta)')).toBe('ai-powered-delivery-beta');
57
+ });
58
+
59
+ it('handles leading/trailing spaces', () => {
60
+ expect(toKebabCase(' My Project ')).toBe('my-project');
61
+ });
62
+
63
+ it('truncates to 64 characters', () => {
64
+ const long = 'a'.repeat(100);
65
+ expect(toKebabCase(long)).toHaveLength(64);
66
+ });
67
+ });
68
+
69
+ describe('PocScaffolder', () => {
70
+ let tmpDir: string;
71
+
72
+ beforeEach(async () => {
73
+ tmpDir = await mkdtemp(join(tmpdir(), 'sofia-scaffold-test-'));
74
+ });
75
+
76
+ afterEach(async () => {
77
+ await rm(tmpDir, { recursive: true, force: true });
78
+ });
79
+
80
+ describe('buildContext', () => {
81
+ it('builds a context from a session', () => {
82
+ const session = makeSession();
83
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
84
+
85
+ expect(ctx.sessionId).toBe('scaffold-test-001');
86
+ expect(ctx.projectName).toBe('ai-route-optimizer');
87
+ expect(ctx.ideaTitle).toBe('AI Route Optimizer');
88
+ expect(ctx.ideaDescription).toBe('Optimize delivery routes using AI.');
89
+ expect(ctx.outputDir).toBe(tmpDir);
90
+ });
91
+
92
+ it('infers Express framework from architecture notes', () => {
93
+ const session = makeSession();
94
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
95
+ expect(ctx.techStack.framework).toBe('Express');
96
+ });
97
+
98
+ it('uses idea title as project name', () => {
99
+ const session = makeSession({
100
+ ideas: [{ id: 'idea-1', title: 'My Cool AI App', description: 'desc', workflowStepIds: [] }],
101
+ });
102
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
103
+ expect(ctx.projectName).toBe('my-cool-ai-app');
104
+ });
105
+
106
+ it('falls back gracefully when no ideas or selection', () => {
107
+ const session = makeSession({ ideas: [], selection: undefined });
108
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
109
+ expect(ctx.ideaTitle).toBe('AI PoC');
110
+ expect(ctx.projectName).toBe('ai-poc');
111
+ });
112
+ });
113
+
114
+ describe('scaffold', () => {
115
+ it('creates all required files', async () => {
116
+ const session = makeSession();
117
+ const scaffolder = new PocScaffolder();
118
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
119
+
120
+ const result = await scaffolder.scaffold(ctx);
121
+
122
+ // Check all required files were created
123
+ const requiredFiles = [
124
+ '.gitignore',
125
+ 'package.json',
126
+ 'tsconfig.json',
127
+ 'README.md',
128
+ 'src/index.ts',
129
+ 'tests/index.test.ts',
130
+ '.sofia-metadata.json',
131
+ ];
132
+
133
+ for (const file of requiredFiles) {
134
+ const fullPath = join(tmpDir, file);
135
+ const exists = await stat(fullPath).then(() => true).catch(() => false);
136
+ expect(exists, `Expected ${file} to exist`).toBe(true);
137
+ }
138
+
139
+ expect(result.createdFiles).toContain('package.json');
140
+ expect(result.createdFiles).toContain('README.md');
141
+ expect(result.createdFiles).toContain('.gitignore');
142
+ expect(result.createdFiles).toContain('.sofia-metadata.json');
143
+ });
144
+
145
+ it('package.json has required structure', async () => {
146
+ const session = makeSession();
147
+ const scaffolder = new PocScaffolder();
148
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
149
+ await scaffolder.scaffold(ctx);
150
+
151
+ const content = await readFile(join(tmpDir, 'package.json'), 'utf-8');
152
+ const pkg = JSON.parse(content) as Record<string, unknown>;
153
+
154
+ expect(pkg.type).toBe('module');
155
+ expect((pkg.scripts as Record<string, string>).test).toBe('vitest run');
156
+ expect((pkg.scripts as Record<string, string>).build).toBeDefined();
157
+ expect(pkg.version).toBe('0.1.0');
158
+ expect(pkg.name).toBe('ai-route-optimizer');
159
+ });
160
+
161
+ it('tsconfig.json has strict TypeScript config', async () => {
162
+ const session = makeSession();
163
+ const scaffolder = new PocScaffolder();
164
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
165
+ await scaffolder.scaffold(ctx);
166
+
167
+ const content = await readFile(join(tmpDir, 'tsconfig.json'), 'utf-8');
168
+ const tsconfig = JSON.parse(content) as {
169
+ compilerOptions: { strict: boolean; target: string };
170
+ };
171
+
172
+ expect(tsconfig.compilerOptions.strict).toBe(true);
173
+ expect(tsconfig.compilerOptions.target).toBe('ES2022');
174
+ });
175
+
176
+ it('.gitignore excludes node_modules, dist, coverage', async () => {
177
+ const session = makeSession();
178
+ const scaffolder = new PocScaffolder();
179
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
180
+ await scaffolder.scaffold(ctx);
181
+
182
+ const content = await readFile(join(tmpDir, '.gitignore'), 'utf-8');
183
+ expect(content).toContain('node_modules/');
184
+ expect(content).toContain('dist/');
185
+ expect(content).toContain('coverage/');
186
+ });
187
+
188
+ it('.sofia-metadata.json links to session', async () => {
189
+ const session = makeSession();
190
+ const scaffolder = new PocScaffolder();
191
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
192
+ await scaffolder.scaffold(ctx);
193
+
194
+ const content = await readFile(join(tmpDir, '.sofia-metadata.json'), 'utf-8');
195
+ const meta = JSON.parse(content) as Record<string, unknown>;
196
+
197
+ expect(meta.sessionId).toBe('scaffold-test-001');
198
+ expect(meta.featureSpec).toBe('002-poc-generation');
199
+ expect(meta.ideaTitle).toBe('AI Route Optimizer');
200
+ expect(meta.generatedAt).toBeDefined();
201
+ });
202
+
203
+ it('README.md contains idea title and session ID', async () => {
204
+ const session = makeSession();
205
+ const scaffolder = new PocScaffolder();
206
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
207
+ await scaffolder.scaffold(ctx);
208
+
209
+ const content = await readFile(join(tmpDir, 'README.md'), 'utf-8');
210
+ expect(content).toContain('AI Route Optimizer');
211
+ expect(content).toContain('scaffold-test-001');
212
+ });
213
+
214
+ it('creates src/ and tests/ directories', async () => {
215
+ const session = makeSession();
216
+ const scaffolder = new PocScaffolder();
217
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
218
+ await scaffolder.scaffold(ctx);
219
+
220
+ const srcEntries = await readdir(join(tmpDir, 'src'));
221
+ const testEntries = await readdir(join(tmpDir, 'tests'));
222
+
223
+ expect(srcEntries.some((f) => f.endsWith('.ts'))).toBe(true);
224
+ expect(testEntries.some((f) => f.endsWith('.test.ts'))).toBe(true);
225
+ });
226
+
227
+ it('returns list of created files', async () => {
228
+ const session = makeSession();
229
+ const scaffolder = new PocScaffolder();
230
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
231
+ const result = await scaffolder.scaffold(ctx);
232
+
233
+ expect(result.createdFiles.length).toBeGreaterThan(0);
234
+ expect(result.skippedFiles).toEqual([]);
235
+ });
236
+ });
237
+
238
+ describe('skip-if-exists behavior', () => {
239
+ it('skips package.json if it already exists (skipIfExists: true)', async () => {
240
+ const session = makeSession();
241
+ const scaffolder = new PocScaffolder();
242
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
243
+
244
+ // Create a custom package.json first
245
+ const { writeFile, mkdir } = await import('node:fs/promises');
246
+ await mkdir(tmpDir, { recursive: true });
247
+ await writeFile(
248
+ join(tmpDir, 'package.json'),
249
+ JSON.stringify({ name: 'custom', scripts: { test: 'echo custom' } }),
250
+ 'utf-8',
251
+ );
252
+
253
+ // Scaffold should skip existing package.json
254
+ const result = await scaffolder.scaffold(ctx);
255
+ expect(result.skippedFiles).toContain('package.json');
256
+
257
+ // Custom content should be preserved
258
+ const content = await readFile(join(tmpDir, 'package.json'), 'utf-8');
259
+ const pkg = JSON.parse(content) as { name: string };
260
+ expect(pkg.name).toBe('custom');
261
+ });
262
+
263
+ it('always overwrites .gitignore (skipIfExists: false)', async () => {
264
+ const session = makeSession();
265
+ const scaffolder = new PocScaffolder();
266
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
267
+
268
+ // Create a custom .gitignore first
269
+ const { writeFile } = await import('node:fs/promises');
270
+ await writeFile(join(tmpDir, '.gitignore'), '# custom\n', 'utf-8');
271
+
272
+ // Scaffold should overwrite .gitignore
273
+ await scaffolder.scaffold(ctx);
274
+
275
+ const content = await readFile(join(tmpDir, '.gitignore'), 'utf-8');
276
+ expect(content).toContain('node_modules/'); // Generated content
277
+ });
278
+ });
279
+
280
+ describe('getTemplateFiles', () => {
281
+ it('returns list of template file paths', () => {
282
+ const scaffolder = new PocScaffolder();
283
+ const files = scaffolder.getTemplateFiles();
284
+ expect(files).toContain('package.json');
285
+ expect(files).toContain('README.md');
286
+ expect(files).toContain('.gitignore');
287
+ expect(files).toContain('src/index.ts');
288
+ expect(files).toContain('tests/index.test.ts');
289
+ });
290
+ });
291
+ });
292
+
293
+ describe('validatePocOutput', () => {
294
+ let tmpDir: string;
295
+
296
+ beforeEach(async () => {
297
+ tmpDir = await mkdtemp(join(tmpdir(), 'sofia-validate-test-'));
298
+ });
299
+
300
+ afterEach(async () => {
301
+ await rm(tmpDir, { recursive: true, force: true });
302
+ });
303
+
304
+ it('returns valid=true for a complete scaffold', async () => {
305
+ const session = makeSession();
306
+ const scaffolder = new PocScaffolder();
307
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
308
+ await scaffolder.scaffold(ctx);
309
+
310
+ const result = await validatePocOutput(tmpDir);
311
+ expect(result.valid).toBe(true);
312
+ expect(result.missingFiles).toEqual([]);
313
+ expect(result.errors).toEqual([]);
314
+ });
315
+
316
+ it('reports missing required files', async () => {
317
+ const result = await validatePocOutput(tmpDir);
318
+ expect(result.valid).toBe(false);
319
+ expect(result.missingFiles).toContain('package.json');
320
+ expect(result.missingFiles).toContain('README.md');
321
+ });
322
+
323
+ it('reports error when package.json lacks test script', async () => {
324
+ const { writeFile, mkdir } = await import('node:fs/promises');
325
+ await mkdir(join(tmpDir, 'src'), { recursive: true });
326
+ await mkdir(join(tmpDir, 'tests'), { recursive: true });
327
+ await writeFile(join(tmpDir, 'package.json'), JSON.stringify({ name: 'test', scripts: {} }), 'utf-8');
328
+ await writeFile(join(tmpDir, 'README.md'), '# Test', 'utf-8');
329
+ await writeFile(join(tmpDir, 'tsconfig.json'), JSON.stringify({}), 'utf-8');
330
+ await writeFile(join(tmpDir, '.gitignore'), 'node_modules/', 'utf-8');
331
+ await writeFile(join(tmpDir, '.sofia-metadata.json'), JSON.stringify({ sessionId: 'x' }), 'utf-8');
332
+ await writeFile(join(tmpDir, 'src/index.ts'), 'export function main() {}', 'utf-8');
333
+ await writeFile(join(tmpDir, 'tests/index.test.ts'), 'import { test } from "vitest"', 'utf-8');
334
+
335
+ const result = await validatePocOutput(tmpDir);
336
+ expect(result.errors).toContain('package.json is missing "test" script');
337
+ });
338
+ });
339
+
340
+ // ── Template entry construction (T036) ────────────────────────────────────
341
+
342
+ describe('PocScaffolder with TemplateEntry', () => {
343
+ let tmpDir: string;
344
+
345
+ beforeEach(async () => {
346
+ tmpDir = await mkdtemp(join(tmpdir(), 'scaffolder-template-'));
347
+ });
348
+
349
+ afterEach(async () => {
350
+ await rm(tmpDir, { recursive: true, force: true });
351
+ });
352
+
353
+ it('uses TemplateEntry.files when constructed with a template entry (T036)', async () => {
354
+ const { PYTHON_PYTEST_TEMPLATE } = await import('../../../src/develop/templateRegistry.js');
355
+ const scaffolder = new PocScaffolder(PYTHON_PYTEST_TEMPLATE);
356
+
357
+ // The template should contain Python-specific file paths
358
+ const filePaths = scaffolder.getTemplateFiles();
359
+ expect(filePaths).toContain('requirements.txt');
360
+ expect(filePaths).toContain('src/main.py');
361
+ expect(filePaths).toContain('tests/test_main.py');
362
+ // Should NOT contain TypeScript files
363
+ expect(filePaths).not.toContain('tsconfig.json');
364
+ expect(filePaths).not.toContain('src/index.ts');
365
+ });
366
+
367
+ it('uses TemplateEntry.techStack in buildContext (T010)', async () => {
368
+ const { PYTHON_PYTEST_TEMPLATE } = await import('../../../src/develop/templateRegistry.js');
369
+ const session = makeSession();
370
+ const ctx = PocScaffolder.buildContext(session, tmpDir, PYTHON_PYTEST_TEMPLATE);
371
+ expect(ctx.techStack.language).toBe('Python');
372
+ expect(ctx.techStack.runtime).toBe('Python 3.11');
373
+ });
374
+
375
+ it('falls back to default TypeScript techStack when no template entry provided', () => {
376
+ const session = makeSession();
377
+ const ctx = PocScaffolder.buildContext(session, tmpDir);
378
+ expect(ctx.techStack.language).toBe('TypeScript');
379
+ expect(ctx.techStack.runtime).toBe('Node.js 20');
380
+ });
381
+ });
382
+
383
+ // ── T072: TODO marker scanning records totalInitial, remaining, markers ───
384
+
385
+ describe('PocScaffolder.scanAndRecordTodos (T072)', () => {
386
+ let tmpDir: string;
387
+
388
+ beforeEach(async () => {
389
+ tmpDir = await mkdtemp(join(tmpdir(), 'todo-scan-'));
390
+ });
391
+
392
+ afterEach(async () => {
393
+ await rm(tmpDir, { recursive: true, force: true });
394
+ });
395
+
396
+ it('scans scaffold files for TODO markers and records counts in .sofia-metadata.json', async () => {
397
+ const { writeFile, mkdir } = await import('node:fs/promises');
398
+
399
+ // Create a minimal project with TODO markers
400
+ await mkdir(join(tmpDir, 'src'), { recursive: true });
401
+ await writeFile(
402
+ join(tmpDir, 'src/index.ts'),
403
+ '// TODO: Implement the core functionality\nexport function main() {}\n// TODO: Add error handling\n',
404
+ );
405
+ await writeFile(
406
+ join(tmpDir, 'src/utils.ts'),
407
+ 'export function helper() { return 42; }\n',
408
+ );
409
+ await writeFile(
410
+ join(tmpDir, '.sofia-metadata.json'),
411
+ JSON.stringify({ sessionId: 'test-001', scaffoldedAt: new Date().toISOString() }),
412
+ );
413
+
414
+ const result = await PocScaffolder.scanAndRecordTodos(tmpDir);
415
+
416
+ // Verify return value
417
+ expect(result.totalInitial).toBe(2);
418
+ expect(result.remaining).toBe(2);
419
+ expect(result.markers).toHaveLength(2);
420
+ expect(result.markers[0]).toContain('src/index.ts:1');
421
+ expect(result.markers[0]).toContain('TODO:');
422
+ expect(result.markers[1]).toContain('src/index.ts:3');
423
+
424
+ // Verify .sofia-metadata.json was updated
425
+ const metaRaw = await readFile(join(tmpDir, '.sofia-metadata.json'), 'utf-8');
426
+ const metadata = JSON.parse(metaRaw);
427
+ expect(metadata.todos).toBeDefined();
428
+ expect(metadata.todos.totalInitial).toBe(2);
429
+ expect(metadata.todos.remaining).toBe(2);
430
+ expect(metadata.todos.markers).toHaveLength(2);
431
+ });
432
+
433
+ it('records zero TODOs when no markers exist', async () => {
434
+ const { writeFile } = await import('node:fs/promises');
435
+
436
+ await writeFile(
437
+ join(tmpDir, 'index.ts'),
438
+ 'export function main() { return "clean"; }\n',
439
+ );
440
+ await writeFile(
441
+ join(tmpDir, '.sofia-metadata.json'),
442
+ JSON.stringify({ sessionId: 'test-002' }),
443
+ );
444
+
445
+ const result = await PocScaffolder.scanAndRecordTodos(tmpDir);
446
+
447
+ expect(result.totalInitial).toBe(0);
448
+ expect(result.remaining).toBe(0);
449
+ expect(result.markers).toHaveLength(0);
450
+ });
451
+ });