@codeyam/codeyam-cli 0.1.8 → 0.1.10

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 (343) hide show
  1. package/analyzer-template/.build-info.json +8 -8
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +4 -4
  4. package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +0 -33
  5. package/analyzer-template/packages/analyze/src/lib/ProjectAnalyzer.ts +13 -7
  6. package/analyzer-template/packages/analyze/src/lib/asts/index.ts +7 -2
  7. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.ts +0 -98
  8. package/analyzer-template/packages/aws/package.json +1 -1
  9. package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +76 -0
  10. package/analyzer-template/packages/database/src/lib/loadEntities.ts +0 -6
  11. package/analyzer-template/packages/database/src/lib/updateCommitMetadata.ts +0 -65
  12. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +7 -0
  13. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts.map +1 -1
  14. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js +76 -0
  15. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  16. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts.map +1 -1
  17. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js +0 -6
  18. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js.map +1 -1
  19. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts.map +1 -1
  20. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js +0 -25
  21. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js.map +1 -1
  22. package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.d.ts +2 -0
  23. package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.d.ts.map +1 -1
  24. package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.js +2 -0
  25. package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.js.map +1 -1
  26. package/analyzer-template/packages/types/src/enums/ProjectFramework.ts +2 -0
  27. package/analyzer-template/packages/ui-components/package.json +1 -1
  28. package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.d.ts +2 -0
  29. package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.d.ts.map +1 -1
  30. package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.js +2 -0
  31. package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.js.map +1 -1
  32. package/codeyam-cli/src/__tests__/memory-scripts/filter-session.test.js +196 -0
  33. package/codeyam-cli/src/__tests__/memory-scripts/filter-session.test.js.map +1 -0
  34. package/codeyam-cli/src/__tests__/memory-scripts/read-json-field.test.js +114 -0
  35. package/codeyam-cli/src/__tests__/memory-scripts/read-json-field.test.js.map +1 -0
  36. package/codeyam-cli/src/__tests__/memory-scripts/ripgrep-fallback.test.js +149 -0
  37. package/codeyam-cli/src/__tests__/memory-scripts/ripgrep-fallback.test.js.map +1 -0
  38. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +45 -0
  39. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -0
  40. package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +101 -47
  41. package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
  42. package/codeyam-cli/src/commands/default.js +3 -46
  43. package/codeyam-cli/src/commands/default.js.map +1 -1
  44. package/codeyam-cli/src/commands/editor.js +1793 -257
  45. package/codeyam-cli/src/commands/editor.js.map +1 -1
  46. package/codeyam-cli/src/commands/init.js +67 -34
  47. package/codeyam-cli/src/commands/init.js.map +1 -1
  48. package/codeyam-cli/src/data/techStacks.js +77 -0
  49. package/codeyam-cli/src/data/techStacks.js.map +1 -0
  50. package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js +173 -0
  51. package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js.map +1 -0
  52. package/codeyam-cli/src/utils/__tests__/backgroundServer.test.js +46 -0
  53. package/codeyam-cli/src/utils/__tests__/backgroundServer.test.js.map +1 -0
  54. package/codeyam-cli/src/utils/__tests__/devServerState.test.js +134 -0
  55. package/codeyam-cli/src/utils/__tests__/devServerState.test.js.map +1 -0
  56. package/codeyam-cli/src/utils/__tests__/editorApi.test.js +137 -0
  57. package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -0
  58. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +742 -1
  59. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  60. package/codeyam-cli/src/utils/__tests__/editorCapture.test.js +93 -0
  61. package/codeyam-cli/src/utils/__tests__/editorCapture.test.js.map +1 -0
  62. package/codeyam-cli/src/utils/__tests__/editorDevServer.test.js +181 -3
  63. package/codeyam-cli/src/utils/__tests__/editorDevServer.test.js.map +1 -1
  64. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +121 -0
  65. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -0
  66. package/codeyam-cli/src/utils/__tests__/editorImageVerifier.test.js +294 -0
  67. package/codeyam-cli/src/utils/__tests__/editorImageVerifier.test.js.map +1 -0
  68. package/codeyam-cli/src/utils/__tests__/editorJournal.test.js +249 -2
  69. package/codeyam-cli/src/utils/__tests__/editorJournal.test.js.map +1 -1
  70. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +520 -0
  71. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -0
  72. package/codeyam-cli/src/utils/__tests__/editorPreloadHelpers.test.js +118 -1
  73. package/codeyam-cli/src/utils/__tests__/editorPreloadHelpers.test.js.map +1 -1
  74. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +209 -3
  75. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
  76. package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +153 -0
  77. package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -0
  78. package/codeyam-cli/src/utils/__tests__/editorScenarioLookup.test.js +139 -0
  79. package/codeyam-cli/src/utils/__tests__/editorScenarioLookup.test.js.map +1 -0
  80. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +221 -0
  81. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -0
  82. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +985 -2
  83. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  84. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +213 -0
  85. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -0
  86. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +1742 -0
  87. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -0
  88. package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js +107 -0
  89. package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js.map +1 -0
  90. package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js +129 -0
  91. package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js.map +1 -0
  92. package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +227 -0
  93. package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -0
  94. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +454 -0
  95. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -0
  96. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +25 -5
  97. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
  98. package/codeyam-cli/src/utils/__tests__/templateConsistency.test.js +51 -0
  99. package/codeyam-cli/src/utils/__tests__/templateConsistency.test.js.map +1 -0
  100. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +142 -0
  101. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -0
  102. package/codeyam-cli/src/utils/analyzer.js +9 -0
  103. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  104. package/codeyam-cli/src/utils/analyzerFinalization.js +100 -0
  105. package/codeyam-cli/src/utils/analyzerFinalization.js.map +1 -0
  106. package/codeyam-cli/src/utils/backgroundServer.js +94 -18
  107. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  108. package/codeyam-cli/src/utils/database.js +37 -2
  109. package/codeyam-cli/src/utils/database.js.map +1 -1
  110. package/codeyam-cli/src/utils/devServerState.js +71 -0
  111. package/codeyam-cli/src/utils/devServerState.js.map +1 -0
  112. package/codeyam-cli/src/utils/editorApi.js +79 -0
  113. package/codeyam-cli/src/utils/editorApi.js.map +1 -0
  114. package/codeyam-cli/src/utils/editorAudit.js +135 -7
  115. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  116. package/codeyam-cli/src/utils/editorCapture.js +102 -0
  117. package/codeyam-cli/src/utils/editorCapture.js.map +1 -0
  118. package/codeyam-cli/src/utils/editorDevServer.js +100 -1
  119. package/codeyam-cli/src/utils/editorDevServer.js.map +1 -1
  120. package/codeyam-cli/src/utils/editorEntityChangeStatus.js +44 -0
  121. package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -0
  122. package/codeyam-cli/src/utils/editorImageVerifier.js +155 -0
  123. package/codeyam-cli/src/utils/editorImageVerifier.js.map +1 -0
  124. package/codeyam-cli/src/utils/editorJournal.js +92 -4
  125. package/codeyam-cli/src/utils/editorJournal.js.map +1 -1
  126. package/codeyam-cli/src/utils/editorLoaderHelpers.js +113 -0
  127. package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -0
  128. package/codeyam-cli/src/utils/editorMockState.js +1 -1
  129. package/codeyam-cli/src/utils/editorPreloadHelpers.js +72 -1
  130. package/codeyam-cli/src/utils/editorPreloadHelpers.js.map +1 -1
  131. package/codeyam-cli/src/utils/editorPreview.js +72 -1
  132. package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
  133. package/codeyam-cli/src/utils/editorScenarioSwitch.js +112 -0
  134. package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -0
  135. package/codeyam-cli/src/utils/editorScenarios.js +331 -0
  136. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  137. package/codeyam-cli/src/utils/editorSeedAdapter.js +173 -0
  138. package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -0
  139. package/codeyam-cli/src/utils/entityChangeStatus.js +349 -0
  140. package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -0
  141. package/codeyam-cli/src/utils/entityChangeStatus.server.js +158 -0
  142. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -0
  143. package/codeyam-cli/src/utils/install-skills.js +1 -1
  144. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  145. package/codeyam-cli/src/utils/parseRegisterArg.js +31 -0
  146. package/codeyam-cli/src/utils/parseRegisterArg.js.map +1 -0
  147. package/codeyam-cli/src/utils/progress.js +2 -2
  148. package/codeyam-cli/src/utils/progress.js.map +1 -1
  149. package/codeyam-cli/src/utils/scenarioCoverage.js +75 -0
  150. package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -0
  151. package/codeyam-cli/src/utils/scenariosManifest.js +241 -0
  152. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -0
  153. package/codeyam-cli/src/utils/serverState.js +30 -0
  154. package/codeyam-cli/src/utils/serverState.js.map +1 -1
  155. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +46 -16
  156. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
  157. package/codeyam-cli/src/utils/simulationGateMiddleware.js +8 -1
  158. package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -1
  159. package/codeyam-cli/src/utils/slugUtils.js +25 -0
  160. package/codeyam-cli/src/utils/slugUtils.js.map +1 -0
  161. package/codeyam-cli/src/utils/syncMocksMiddleware.js +2 -2
  162. package/codeyam-cli/src/utils/syncMocksMiddleware.js.map +1 -1
  163. package/codeyam-cli/src/utils/webappDetection.js +21 -0
  164. package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
  165. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +40 -0
  166. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -0
  167. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +567 -0
  168. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -0
  169. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +146 -0
  170. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -0
  171. package/codeyam-cli/src/webserver/app/lib/clientErrors.js +65 -0
  172. package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -0
  173. package/codeyam-cli/src/webserver/app/lib/git.js +397 -0
  174. package/codeyam-cli/src/webserver/app/lib/git.js.map +1 -0
  175. package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-DmJveP3T.js → CopyButton-BPXZwM4t.js} +1 -1
  176. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-C76mRRiF.js → EntityItem-BcgbViKV.js} +3 -3
  177. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CobE682z.js → EntityTypeIcon-CQIG2qda.js} +9 -9
  178. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-djPLI-WV.js → ReportIssueModal-BzHcG7SE.js} +3 -3
  179. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-B76aig_2.js → ScenarioViewer-TSD3C211.js} +3 -3
  180. package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-oAf2Kqsf.js +1 -0
  181. package/codeyam-cli/src/webserver/build/client/assets/{_index-C96V0n15.js → _index-DLxKhri3.js} +3 -3
  182. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BpKzcsJz.js → activity.(_tab)-BcY3q6nt.js} +6 -6
  183. package/codeyam-cli/src/webserver/build/client/assets/addon-canvas-DpzMmAy5.js +1 -0
  184. package/codeyam-cli/src/webserver/build/client/assets/addon-fit-YJmn1quW.js +12 -0
  185. package/codeyam-cli/src/webserver/build/client/assets/addon-webgl-DI8QOUvO.js +58 -0
  186. package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-D9hemwl6.js → agent-transcripts-Bni3iiUj.js} +5 -5
  187. package/codeyam-cli/src/webserver/build/client/assets/api.editor-file-diff-l0sNRNKZ.js +1 -0
  188. package/codeyam-cli/src/webserver/build/client/assets/api.editor-file-l0sNRNKZ.js +1 -0
  189. package/codeyam-cli/src/webserver/build/client/assets/api.editor-project-info-l0sNRNKZ.js +1 -0
  190. package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-coverage-l0sNRNKZ.js +1 -0
  191. package/codeyam-cli/src/webserver/build/client/assets/{book-open-D_nMCFmP.js → book-open-BYOypzCa.js} +2 -2
  192. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-BH2h1Ea2.js → chevron-down-C_Pmso5S.js} +2 -2
  193. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-DyIKORY6.js → circle-check-BVMi9VA5.js} +2 -2
  194. package/codeyam-cli/src/webserver/build/client/assets/{copy-NDbZjXao.js → copy-n2FB0_Sw.js} +3 -3
  195. package/codeyam-cli/src/webserver/build/client/assets/createLucideIcon-CC6AbExI.js +41 -0
  196. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-Ii3inc0_.js +1 -0
  197. package/codeyam-cli/src/webserver/build/client/assets/editor-COWCNVyV.js +10 -0
  198. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-CNB06EIa.js +41 -0
  199. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-CrjR3zZW.js → entity._sha._-DwCV5__E.js} +3 -3
  200. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-CXSi2aeZ.js +6 -0
  201. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CHMiAog3.js +6 -0
  202. package/codeyam-cli/src/webserver/build/client/assets/{files-DO4CZ16O.js → files-BZrlFE1F.js} +1 -1
  203. package/codeyam-cli/src/webserver/build/client/assets/git-DdZcvjGh.js +1 -0
  204. package/codeyam-cli/src/webserver/build/client/assets/globals-phvmGvat.css +1 -0
  205. package/codeyam-cli/src/webserver/build/client/assets/index-yHOVb4rc.js +15 -0
  206. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-BAXYRVEO.js → loader-circle-DaAZ_H2w.js} +2 -2
  207. package/codeyam-cli/src/webserver/build/client/assets/manifest-6134dc40.js +1 -0
  208. package/codeyam-cli/src/webserver/build/client/assets/memory-9gnxSZlb.js +101 -0
  209. package/codeyam-cli/src/webserver/build/client/assets/{pause-DTAcYxBt.js → pause-f5-1lKBt.js} +3 -3
  210. package/codeyam-cli/src/webserver/build/client/assets/root-BWAyuj0r.js +67 -0
  211. package/codeyam-cli/src/webserver/build/client/assets/{search-fKo7v0Zo.js → search-Di64LWVb.js} +2 -2
  212. package/codeyam-cli/src/webserver/build/client/assets/{settings-DfuTtcJP.js → settings-0OrEMU6J.js} +1 -1
  213. package/codeyam-cli/src/webserver/build/client/assets/{simulations-B3aOzpCZ.js → simulations-DWT-CvLy.js} +1 -1
  214. package/codeyam-cli/src/webserver/build/client/assets/{terminal-BG4heKCG.js → terminal-Br7MOqts.js} +3 -3
  215. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-DtSmdtM4.js → triangle-alert-BLdiCuG-.js} +2 -2
  216. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-C-_hOl_g.js +1 -0
  217. package/codeyam-cli/src/webserver/build/client/sound-test.html +98 -0
  218. package/codeyam-cli/src/webserver/build/server/assets/index-ChX0hPcu.js +1 -0
  219. package/codeyam-cli/src/webserver/build/server/assets/init-kSNsMjj8.js +10 -0
  220. package/codeyam-cli/src/webserver/build/server/assets/server-build-Bm2xIhmh.js +439 -0
  221. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  222. package/codeyam-cli/src/webserver/build-info.json +5 -5
  223. package/codeyam-cli/src/webserver/editorProxy.js +487 -50
  224. package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
  225. package/codeyam-cli/src/webserver/idleDetector.js +73 -0
  226. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -0
  227. package/codeyam-cli/src/webserver/public/sound-test.html +98 -0
  228. package/codeyam-cli/src/webserver/scripts/codeyam-preload.mjs +242 -3
  229. package/codeyam-cli/src/webserver/scripts/journalCapture.ts +94 -4
  230. package/codeyam-cli/src/webserver/server.js +90 -16
  231. package/codeyam-cli/src/webserver/server.js.map +1 -1
  232. package/codeyam-cli/src/webserver/terminalServer.js +71 -34
  233. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  234. package/codeyam-cli/templates/chrome-extension-react/EXTENSION_SETUP.md +75 -0
  235. package/codeyam-cli/templates/chrome-extension-react/README.md +46 -0
  236. package/codeyam-cli/templates/chrome-extension-react/gitignore +15 -0
  237. package/codeyam-cli/templates/chrome-extension-react/index.html +12 -0
  238. package/codeyam-cli/templates/chrome-extension-react/package.json +27 -0
  239. package/codeyam-cli/templates/chrome-extension-react/popup.html +12 -0
  240. package/codeyam-cli/templates/chrome-extension-react/public/manifest.json +15 -0
  241. package/codeyam-cli/templates/chrome-extension-react/src/background/service-worker.ts +7 -0
  242. package/codeyam-cli/templates/chrome-extension-react/src/globals.css +6 -0
  243. package/codeyam-cli/templates/chrome-extension-react/src/lib/storage.ts +37 -0
  244. package/codeyam-cli/templates/chrome-extension-react/src/popup/App.tsx +12 -0
  245. package/codeyam-cli/templates/chrome-extension-react/src/popup/main.tsx +10 -0
  246. package/codeyam-cli/templates/chrome-extension-react/tsconfig.json +24 -0
  247. package/codeyam-cli/templates/chrome-extension-react/vite.config.ts +41 -0
  248. package/codeyam-cli/templates/codeyam-editor-claude.md +84 -5
  249. package/codeyam-cli/templates/editor-step-hook.py +98 -8
  250. package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +89 -0
  251. package/codeyam-cli/templates/expo-react-native/README.md +41 -0
  252. package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +33 -0
  253. package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +12 -0
  254. package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +12 -0
  255. package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +12 -0
  256. package/codeyam-cli/templates/expo-react-native/app.json +18 -0
  257. package/codeyam-cli/templates/expo-react-native/babel.config.js +9 -0
  258. package/codeyam-cli/templates/expo-react-native/gitignore +12 -0
  259. package/codeyam-cli/templates/expo-react-native/global.css +3 -0
  260. package/codeyam-cli/templates/expo-react-native/lib/storage.ts +32 -0
  261. package/codeyam-cli/templates/expo-react-native/metro.config.js +6 -0
  262. package/codeyam-cli/templates/expo-react-native/nativewind-env.d.ts +1 -0
  263. package/codeyam-cli/templates/expo-react-native/package.json +38 -0
  264. package/codeyam-cli/templates/expo-react-native/tailwind.config.js +10 -0
  265. package/codeyam-cli/templates/expo-react-native/tsconfig.json +10 -0
  266. package/codeyam-cli/templates/nextjs-prisma-sqlite/AUTH_PATTERNS.md +308 -0
  267. package/codeyam-cli/templates/nextjs-prisma-sqlite/AUTH_UPGRADE.md +304 -0
  268. package/codeyam-cli/templates/nextjs-prisma-sqlite/DATABASE.md +126 -0
  269. package/codeyam-cli/templates/nextjs-prisma-sqlite/FEATURE_PATTERNS.md +37 -0
  270. package/codeyam-cli/templates/nextjs-prisma-sqlite/README.md +53 -0
  271. package/codeyam-cli/templates/nextjs-prisma-sqlite/app/codeyam-isolate/layout.tsx +12 -0
  272. package/codeyam-cli/templates/nextjs-prisma-sqlite/app/lib/prisma.ts +9 -4
  273. package/codeyam-cli/templates/nextjs-prisma-sqlite/env +4 -0
  274. package/codeyam-cli/templates/nextjs-prisma-sqlite/gitignore +21 -0
  275. package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +5 -1
  276. package/codeyam-cli/templates/nextjs-prisma-sqlite/prisma/seed.ts +4 -1
  277. package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +92 -0
  278. package/codeyam-cli/templates/nextjs-prisma-sqlite/vitest.config.ts +13 -0
  279. package/codeyam-cli/templates/nextjs-prisma-supabase/README.md +52 -0
  280. package/codeyam-cli/templates/{nextjs-prisma-sqlite/PRISMA_SETUP.md → nextjs-prisma-supabase/SUPABASE_SETUP.md} +37 -17
  281. package/codeyam-cli/templates/nextjs-prisma-supabase/app/api/todos/route.ts +17 -0
  282. package/codeyam-cli/templates/nextjs-prisma-supabase/app/globals.css +26 -0
  283. package/codeyam-cli/templates/nextjs-prisma-supabase/app/layout.tsx +34 -0
  284. package/codeyam-cli/templates/nextjs-prisma-supabase/app/lib/prisma.ts +20 -0
  285. package/codeyam-cli/templates/nextjs-prisma-supabase/app/lib/supabase.ts +12 -0
  286. package/codeyam-cli/templates/nextjs-prisma-supabase/app/page.tsx +10 -0
  287. package/codeyam-cli/templates/nextjs-prisma-supabase/env +9 -0
  288. package/codeyam-cli/templates/nextjs-prisma-supabase/eslint.config.mjs +11 -0
  289. package/codeyam-cli/templates/nextjs-prisma-supabase/gitignore +40 -0
  290. package/codeyam-cli/templates/nextjs-prisma-supabase/next.config.ts +11 -0
  291. package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +37 -0
  292. package/codeyam-cli/templates/nextjs-prisma-supabase/postcss.config.mjs +7 -0
  293. package/codeyam-cli/templates/nextjs-prisma-supabase/prisma/schema.prisma +27 -0
  294. package/codeyam-cli/templates/nextjs-prisma-supabase/prisma/seed.ts +39 -0
  295. package/codeyam-cli/templates/nextjs-prisma-supabase/prisma.config.ts +12 -0
  296. package/codeyam-cli/templates/nextjs-prisma-supabase/tsconfig.json +34 -0
  297. package/codeyam-cli/templates/skills/codeyam-dev-mode/SKILL.md +2 -2
  298. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +97 -17
  299. package/codeyam-cli/templates/skills/codeyam-memory/SKILL.md +10 -10
  300. package/codeyam-cli/templates/skills/codeyam-memory/scripts/holistic-analysis/detect-deprecated-patterns.mjs +139 -0
  301. package/codeyam-cli/templates/skills/codeyam-memory/scripts/holistic-analysis/find-exports.mjs +52 -0
  302. package/codeyam-cli/templates/skills/codeyam-memory/scripts/lib/read-json-field.mjs +61 -0
  303. package/codeyam-cli/templates/skills/codeyam-memory/scripts/lib/ripgrep-fallback.mjs +155 -0
  304. package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/cleanup.mjs +13 -0
  305. package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/filter-session.mjs +95 -0
  306. package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/preprocess.mjs +160 -0
  307. package/package.json +14 -9
  308. package/packages/ai/src/lib/generateExecutionFlows.js +0 -11
  309. package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
  310. package/packages/analyze/src/lib/ProjectAnalyzer.js +10 -4
  311. package/packages/analyze/src/lib/ProjectAnalyzer.js.map +1 -1
  312. package/packages/analyze/src/lib/asts/index.js +4 -2
  313. package/packages/analyze/src/lib/asts/index.js.map +1 -1
  314. package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js +0 -40
  315. package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js.map +1 -1
  316. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +76 -0
  317. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  318. package/packages/database/src/lib/loadEntities.js +0 -6
  319. package/packages/database/src/lib/loadEntities.js.map +1 -1
  320. package/packages/database/src/lib/updateCommitMetadata.js +0 -25
  321. package/packages/database/src/lib/updateCommitMetadata.js.map +1 -1
  322. package/packages/types/src/enums/ProjectFramework.js +2 -0
  323. package/packages/types/src/enums/ProjectFramework.js.map +1 -1
  324. package/codeyam-cli/src/webserver/build/client/assets/Terminal-Dnj5CY9R.js +0 -41
  325. package/codeyam-cli/src/webserver/build/client/assets/addon-fit-CUXOrorO.js +0 -1
  326. package/codeyam-cli/src/webserver/build/client/assets/createLucideIcon-CMT1jU2q.js +0 -21
  327. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-BiM6z3Do.js +0 -1
  328. package/codeyam-cli/src/webserver/build/client/assets/editor-D1DAKXtT.js +0 -8
  329. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-DkzqFzFj.js +0 -6
  330. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-C28BiQzt.js +0 -6
  331. package/codeyam-cli/src/webserver/build/client/assets/git-CFCTYk9I.js +0 -15
  332. package/codeyam-cli/src/webserver/build/client/assets/globals-B17TBSS6.css +0 -1
  333. package/codeyam-cli/src/webserver/build/client/assets/manifest-a632de18.js +0 -1
  334. package/codeyam-cli/src/webserver/build/client/assets/memory-Dg0mvYrI.js +0 -96
  335. package/codeyam-cli/src/webserver/build/client/assets/root-DUKqhFlb.js +0 -67
  336. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-ByhSyh0W.js +0 -1
  337. package/codeyam-cli/src/webserver/build/server/assets/index-HfLydfDq.js +0 -1
  338. package/codeyam-cli/src/webserver/build/server/assets/server-build-CUu_F-oo.js +0 -366
  339. package/codeyam-cli/templates/skills/codeyam-memory/scripts/holistic-analysis/detect-deprecated-patterns.sh +0 -108
  340. package/codeyam-cli/templates/skills/codeyam-memory/scripts/holistic-analysis/find-exports.sh +0 -69
  341. package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/cleanup.sh +0 -12
  342. package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/filter.jq +0 -45
  343. package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/preprocess.sh +0 -139
@@ -4,10 +4,22 @@ import * as os from 'os';
4
4
  import { fileURLToPath } from 'url';
5
5
  import chalk from 'chalk';
6
6
  import { runAnalysisForEntities } from "../utils/analysisRunner.js";
7
- import { ProgressReporter } from "../utils/progress.js";
8
- import { initializeEnvironment } from "../utils/database.js";
9
- import { loadEntities, loadAnalyses } from "../../../packages/database/index.js";
7
+ import { error as errorLog, ProgressReporter, withoutSpinner, } from "../utils/progress.js";
8
+ import { initializeEnvironment, requireBranchAndProject, testEnvironment, } from "../utils/database.js";
9
+ import { loadAnalyses, loadEntities, updateProjectMetadata, } from "../../../packages/database/index.js";
10
10
  import { IS_INTERNAL_BUILD } from "../utils/buildFlags.js";
11
+ import { startBackgroundServer } from "../utils/backgroundServer.js";
12
+ import { installClaudeCodeSkills } from "../utils/install-skills.js";
13
+ import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
14
+ import { ensureAnalyzerFinalized, } from "../utils/analyzerFinalization.js";
15
+ import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
16
+ import { getProjectRoot as getStateProjectRoot } from "../state.js";
17
+ import initCommand from "./init.js";
18
+ import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
19
+ import { clearEditorState, clearEditorUserPrompt, validateStepTransition, } from "../utils/editorScenarios.js";
20
+ import { validateSeedData, detectSeedAdapter, } from "../utils/editorSeedAdapter.js";
21
+ import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
22
+ import { parseRegisterArg } from "../utils/parseRegisterArg.js";
11
23
  const __filename = fileURLToPath(import.meta.url);
12
24
  const __dirname = path.dirname(__filename);
13
25
  const STEP_LABELS = {
@@ -23,6 +35,10 @@ const STEP_LABELS = {
23
35
  10: 'Verify',
24
36
  11: 'Journal',
25
37
  12: 'Review',
38
+ 13: 'Present',
39
+ 14: 'Commit',
40
+ 15: 'Finalize',
41
+ 16: 'Push',
26
42
  };
27
43
  /**
28
44
  * Append a JSONL log entry to .codeyam/logs/editor-log.jsonl
@@ -43,6 +59,18 @@ function logEvent(root, event, data) {
43
59
  // Logging is best-effort
44
60
  }
45
61
  }
62
+ /**
63
+ * Read the design system file if it exists.
64
+ */
65
+ function readDesignSystem(root) {
66
+ const designSystemPath = path.join(root, '.codeyam', 'design-system.md');
67
+ try {
68
+ return fs.readFileSync(designSystemPath, 'utf8');
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
46
74
  /**
47
75
  * Get the project root (where .codeyam/ lives) or cwd.
48
76
  */
@@ -79,15 +107,11 @@ function writeState(root, state) {
79
107
  }
80
108
  /**
81
109
  * Clear the editor state (for starting a new feature).
110
+ * Does NOT clear the user prompt file — that's handled separately
111
+ * via clearEditorUserPrompt when the previous feature commits.
82
112
  */
83
113
  function clearState(root) {
84
- const statePath = getStatePath(root);
85
- try {
86
- fs.unlinkSync(statePath);
87
- }
88
- catch {
89
- // File doesn't exist, that's fine
90
- }
114
+ clearEditorState(root);
91
115
  }
92
116
  /**
93
117
  * Check if a project has been scaffolded (package.json exists).
@@ -96,11 +120,62 @@ function hasProject(root) {
96
120
  return fs.existsSync(path.join(root, 'package.json'));
97
121
  }
98
122
  /**
99
- * Get the CodeYam server port from env or default.
123
+ * Get the CodeYam server port.
124
+ * Reads from the server state file first (actual running port), then falls
125
+ * back to CODEYAM_PORT env var, then to the default 3111.
126
+ * This is critical when the server auto-selected a different port because
127
+ * the default was occupied by another project's server.
100
128
  */
101
129
  function getServerPort() {
130
+ try {
131
+ const root = getProjectRoot();
132
+ if (root) {
133
+ const statePath = path.join(root, '.codeyam', 'server.json');
134
+ if (fs.existsSync(statePath)) {
135
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
136
+ if (state.port)
137
+ return String(state.port);
138
+ }
139
+ }
140
+ }
141
+ catch {
142
+ // Fall through to defaults
143
+ }
102
144
  return process.env.CODEYAM_PORT || '3111';
103
145
  }
146
+ /**
147
+ * Read the project's default dimension name and available screen size names.
148
+ * Used by step instructions to show project-specific dimension examples
149
+ * instead of hardcoded "Desktop".
150
+ */
151
+ function getProjectDimensions(root) {
152
+ try {
153
+ const configPath = path.join(root, '.codeyam', 'config.json');
154
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
155
+ const defaultName = config.defaultScreenSize?.name ||
156
+ (config.screenSizes ? Object.keys(config.screenSizes)[0] : null) ||
157
+ 'Desktop';
158
+ const names = config.screenSizes ? Object.keys(config.screenSizes) : [];
159
+ return { defaultName, names };
160
+ }
161
+ catch {
162
+ return { defaultName: 'Desktop', names: [] };
163
+ }
164
+ }
165
+ /**
166
+ * Print dimension guidance when the project has multiple screen sizes.
167
+ * Tells Claude to pick the right dimension for the content being previewed
168
+ * instead of blindly using the default for everything.
169
+ */
170
+ function printDimensionGuidance(defaultName, names) {
171
+ if (names.length <= 1)
172
+ return;
173
+ // Find a non-default dimension to suggest as the "other" option
174
+ const otherName = names.find((n) => n !== defaultName) || names[1];
175
+ console.log(chalk.yellow(` IMPORTANT: Choose the dimension that matches what you're previewing. Available: ${names.map((n) => `"${n}"`).join(', ')}.`));
176
+ console.log(chalk.yellow(` Do NOT always use "${defaultName}". Full pages, standalone views, and desktop layouts should use "${otherName}".`));
177
+ console.log(chalk.yellow(` Think about the CONTENT — a full-page library view needs a large viewport, not a popup-sized one.`));
178
+ }
104
179
  /**
105
180
  * Print a checklist item.
106
181
  * Inline backtick-wrapped text is highlighted in cyan for visibility.
@@ -122,13 +197,13 @@ function stepHeader(step, title, feature) {
122
197
  console.log();
123
198
  }
124
199
  /**
125
- * Print a colored progress tracker showing all 11 steps.
200
+ * Print a colored progress tracker showing all 16 steps.
126
201
  * Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
127
202
  */
128
203
  function printProgressTracker(current) {
129
204
  console.log();
130
- console.log(chalk.dim('┌─────────────────────────────────────┐'));
131
- for (let i = 1; i <= 12; i++) {
205
+ console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
206
+ for (let i = 1; i <= 16; i++) {
132
207
  const label = STEP_LABELS[i];
133
208
  const num = i < 10 ? ` ${i}` : `${i}`;
134
209
  const content = `${num}. ${label.padEnd(28)}`;
@@ -166,7 +241,7 @@ function stopGate(current, opts) {
166
241
  console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
167
242
  console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
168
243
  console.log();
169
- if (current < 12) {
244
+ if (current < 16) {
170
245
  console.log(chalk.green('When done, run: ') +
171
246
  chalk.bold(`codeyam editor ${current + 1}`));
172
247
  }
@@ -199,18 +274,25 @@ function printResumptionHeader(step) {
199
274
  'Check if glossary already populated:\n `cat .codeyam/glossary.json`',
200
275
  ],
201
276
  7: ['Check if isolation routes and registered scenarios already exist'],
202
- 8: [
203
- `Check existing scenarios:\n \`curl -s http://localhost:${port}/api/editor-scenarios\``,
204
- ],
277
+ 8: ['Check existing scenarios:\n `codeyam editor scenarios`'],
205
278
  9: [
206
- `Check existing user-persona scenarios:\n \`curl -s http://localhost:${port}/api/editor-scenarios\``,
279
+ 'Check existing user-persona scenarios:\n `codeyam editor scenarios`',
207
280
  ],
208
281
  10: ['Re-verify is safe to repeat — just re-run the checks'],
209
282
  11: [
210
- `Check if a journal entry already exists for this feature:\n \`curl -s http://localhost:${port}/api/editor-journal\``,
283
+ 'Check if a journal entry already exists for this feature:\n `codeyam editor journal-list`',
211
284
  'If an entry exists, use PATCH to update it — do NOT create a new one',
212
285
  ],
213
- 12: ['Check if commit already made:\n `git log --oneline -3`'],
286
+ 12: ['Re-verify is safe to repeat just re-run the checks'],
287
+ 13: ['Check if commit already made:\n `git log --oneline -3`'],
288
+ 14: ['Check if commit already made:\n `git log --oneline -3`'],
289
+ 15: [
290
+ 'Check if journal was already updated with commit SHA:\n `codeyam editor journal-list`',
291
+ 'Check if commit was already amended:\n `git log --oneline -3`',
292
+ ],
293
+ 16: [
294
+ 'Check if already pushed:\n `git remote -v && git log --oneline origin/HEAD..HEAD 2>/dev/null`',
295
+ ],
214
296
  };
215
297
  const label = STEP_LABELS[step] || 'Unknown';
216
298
  console.log(chalk.bold.yellow(`━━━ RESUMING Step ${step}: ${label} ━━━`));
@@ -315,7 +397,7 @@ function parseDebugTargets(target) {
315
397
  }
316
398
  if (entry.startsWith('step-')) {
317
399
  const num = parseInt(entry.replace('step-', ''), 10);
318
- if (!isNaN(num) && num >= 1 && num <= 12) {
400
+ if (!isNaN(num) && num >= 1 && num <= 16) {
319
401
  valid.add(`step-${num}`);
320
402
  continue;
321
403
  }
@@ -399,16 +481,89 @@ function printSetup(root) {
399
481
  console.log();
400
482
  console.log("No project detected. Let's get started.");
401
483
  console.log();
484
+ // ── Design System ────────────────────────────────────────────────
485
+ console.log(chalk.bold('Design System (ask FIRST):'));
486
+ console.log(chalk.dim(' Ask: "Do you have a design system, brand guidelines, or style preferences you\'d like me to follow?"'));
487
+ console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
488
+ console.log(chalk.yellow(' Option 1 label: "Yes, I\'ll paste my design system"'));
489
+ console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
490
+ console.log(chalk.yellow(' Option 2 label: "No, use sensible defaults"'));
491
+ console.log(chalk.dim(' → Skip'));
492
+ console.log();
402
493
  console.log(chalk.bold('Checklist:'));
403
494
  checkbox('Read `.codeyam/editor-mode-context.md` for session state');
404
495
  checkbox('Ask the user what they want to build');
405
496
  console.log();
497
+ // ── App Format Selection ───────────────────────────────────────────
498
+ console.log(chalk.bold('App Format Selection:'));
499
+ console.log(chalk.dim(' After the user describes their project, ask which app formats they want to target.'));
500
+ console.log(chalk.dim(' Use AskUserQuestion with CHECKBOXES (multiple selections allowed):'));
501
+ console.log();
502
+ for (const format of APP_FORMATS) {
503
+ console.log(chalk.yellow(` [ ] ${format.label}`) +
504
+ chalk.dim(` — ${format.description}`));
505
+ }
506
+ console.log();
507
+ // ── Tech Stack Selection ───────────────────────────────────────────
508
+ console.log(chalk.bold('Tech Stack Selection:'));
509
+ console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
510
+ console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
511
+ console.log(chalk.dim(' Show ALL matching stacks even if there is only one. Mark the recommended option.'));
512
+ console.log(chalk.dim(' Use AskUserQuestion to let the user pick one.'));
513
+ console.log();
514
+ console.log(chalk.bold(' Available Tech Stacks:'));
515
+ for (const stack of TECH_STACKS) {
516
+ const recommended = stack.recommended ? chalk.green(' (Recommended)') : '';
517
+ const formatLabels = stack.supportedFormats
518
+ .map((f) => APP_FORMATS.find((af) => af.id === f)?.label)
519
+ .join(', ');
520
+ console.log(chalk.bold.yellow(` ${stack.name}`) +
521
+ recommended +
522
+ chalk.dim(` [id: ${stack.id}]`));
523
+ console.log(chalk.dim(` ${stack.description}`));
524
+ console.log(chalk.dim(' Technologies:'));
525
+ for (const tech of stack.technologies) {
526
+ console.log(chalk.dim(` - ${tech}`));
527
+ }
528
+ console.log(chalk.dim(` Supports: ${formatLabels}`));
529
+ console.log();
530
+ }
531
+ console.log(chalk.dim(' If NO stacks match the selected formats, tell the user that support for that format is coming soon'));
532
+ console.log(chalk.dim(' and suggest they pick a supported format for now.'));
533
+ console.log();
534
+ // ── Default Screen Size Selection ──────────────────────────────────
535
+ console.log(chalk.bold('Default Screen Size:'));
536
+ console.log(chalk.dim(' After selecting a tech stack, ask the user to choose the default screen size for previews and captures.'));
537
+ console.log(chalk.dim(' Use AskUserQuestion with RADIO buttons (single selection):'));
538
+ console.log();
539
+ console.log(chalk.yellow(' ( ) Desktop') + chalk.dim(' — 1440 × 900'));
540
+ console.log(chalk.yellow(' ( ) Laptop') + chalk.dim(' — 1024 × 768'));
541
+ console.log(chalk.yellow(' ( ) Tablet') + chalk.dim(' — 768 × 1024'));
542
+ console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
543
+ console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
544
+ console.log();
545
+ console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app → Mobile, chrome-extension → Custom (400×600).'));
546
+ console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
547
+ console.log(chalk.dim(` Save the choice via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"defaultScreenSize":{"name":"Desktop","width":1440,"height":900}}'`));
548
+ console.log();
549
+ console.log(chalk.bold('Named Screen Sizes (for multi-resolution apps):'));
550
+ console.log(chalk.dim(' For mobile-responsive web apps or apps that need screenshots at multiple resolutions,'));
551
+ console.log(chalk.dim(' also save named screen sizes. Each scenario can reference a dimension name.'));
552
+ console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
553
+ console.log(chalk.dim(' -d \'{"screenSizes":{"Desktop":{"width":1440,"height":900},"Mobile":{"width":375,"height":667}}}\''));
554
+ console.log(chalk.dim(' If you discover a new viewport is needed mid-workflow, add it to screenSizes the same way and reference by name.'));
555
+ console.log(chalk.dim(' NOTE: This REPLACES all screenSizes — always include every named size, not just the new one.'));
556
+ console.log();
406
557
  console.log(chalk.bold.red('━━━ STOP ━━━'));
407
558
  console.log();
408
- console.log(chalk.red('Ask the user what they want to build, then run:'));
559
+ console.log(chalk.red('Ask the user what they want to build, then ask about app formats, then present tech stacks, then choose a default screen size. Then run:'));
409
560
  console.log();
410
- console.log(chalk.green(' ') + chalk.bold('codeyam editor 1 --feature "Feature Name"'));
561
+ console.log(chalk.green(' ') +
562
+ chalk.bold('codeyam editor 1 --feature "Feature Name" --app-formats "FORMAT_IDS" --tech-stack "STACK_ID" --prompt "the user\'s original message"'));
411
563
  console.log(chalk.dim(' Replace "Feature Name" with a short title for what the user described.'));
564
+ console.log(chalk.dim(' Replace FORMAT_IDS with the comma-separated format IDs the user selected (e.g., "chrome-extension" or "mobile-responsive-web-app").'));
565
+ console.log(chalk.dim(' Replace STACK_ID with the tech stack id shown in [brackets] above (e.g., "nextjs-prisma-sqlite" or "chrome-extension-react").'));
566
+ console.log(chalk.dim(" Pass --prompt with the user's exact original request so it appears in the journal."));
412
567
  console.log(chalk.dim(' Step 1 will guide you through planning and getting user confirmation before any code is written.'));
413
568
  console.log();
414
569
  }
@@ -426,9 +581,13 @@ function printCycleOverview(root, state) {
426
581
  console.log(chalk.dim('Or run ') +
427
582
  chalk.bold('codeyam editor 1') +
428
583
  chalk.dim(' to start a new feature'));
584
+ console.log();
585
+ console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
586
+ chalk.bold('codeyam editor change'));
587
+ console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
429
588
  }
430
589
  else {
431
- console.log('Each feature follows 12 steps. You MUST run each command in order:');
590
+ console.log('Each feature follows 16 steps. You MUST run each command in order:');
432
591
  console.log();
433
592
  console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
434
593
  console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prototype')} — Build a working prototype fast`);
@@ -441,29 +600,43 @@ function printCycleOverview(root, state) {
441
600
  console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('User Scenarios')} — Create user-persona scenarios`);
442
601
  console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('Verify')} — Review screenshots, check for errors`);
443
602
  console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('Journal')} — Create/update journal entry`);
444
- console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Review')} — Present summary, get approval`);
603
+ console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Review')} — Verify screenshots and audit`);
604
+ console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Present')} — Present summary, get approval`);
605
+ console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('Commit')} — Commit all changes`);
606
+ console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
607
+ console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('Push')} — Push to remote`);
445
608
  console.log();
446
609
  console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
610
+ console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
447
611
  }
448
612
  console.log();
449
613
  }
450
614
  // ─── Step 1: Plan ─────────────────────────────────────────────────────
451
- function printStep1(root, feature) {
615
+ function printStep1(root, feature, options, userPrompt) {
616
+ const port = getServerPort();
452
617
  const prevState = readState(root);
453
618
  const isResuming = prevState?.step === 1;
454
619
  if (!isResuming) {
455
620
  clearState(root);
456
621
  }
457
- // If feature is provided, save initial state so step 2 can pick it up
458
- if (feature) {
459
- const now = new Date().toISOString();
460
- writeState(root, {
461
- feature,
462
- step: 1,
463
- label: STEP_LABELS[1],
464
- startedAt: isResuming ? prevState.startedAt : now,
465
- featureStartedAt: isResuming ? prevState.featureStartedAt : now,
466
- });
622
+ // Always persist state so step 2's validation sees that step 1 ran.
623
+ // The feature name may not be known yet (it's an output of planning)
624
+ // use an empty string as placeholder; step 2 requires --feature anyway.
625
+ const now = new Date().toISOString();
626
+ writeState(root, {
627
+ feature: feature || prevState?.feature || '',
628
+ step: 1,
629
+ label: STEP_LABELS[1],
630
+ startedAt: isResuming ? prevState.startedAt : now,
631
+ featureStartedAt: isResuming ? prevState.featureStartedAt : now,
632
+ appFormats: options?.appFormats || prevState?.appFormats,
633
+ techStackId: options?.techStackId || prevState?.techStackId,
634
+ });
635
+ // Save the user's original prompt to a separate file
636
+ if (userPrompt) {
637
+ const promptPath = path.join(root, '.codeyam', 'editor-user-prompt.txt');
638
+ fs.mkdirSync(path.dirname(promptPath), { recursive: true });
639
+ fs.writeFileSync(promptPath, userPrompt, 'utf8');
467
640
  }
468
641
  logEvent(root, 'step', { step: 1, label: 'Plan', feature });
469
642
  stepHeader(1, 'Plan', feature);
@@ -483,11 +656,24 @@ function printStep1(root, feature) {
483
656
  console.log(chalk.dim(' Bad: Free-form text asking "What do you think about the data model?"'));
484
657
  console.log(chalk.dim(' You can ask up to 4 questions at once. Bundle related questions into a single AskUserQuestion call.'));
485
658
  checkbox("Summarize what you'll build in plain language the user can verify against their vision");
486
- console.log();
487
- console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion):'));
488
- console.log(chalk.green(' "Looks good, start building!"') +
489
- chalk.dim(' proceed to step 2'));
490
- console.log(chalk.yellow(' "I\'d like some changes"') +
659
+ checkbox('Include a brief note on what interesting data states the scenarios should demonstrate');
660
+ console.log(chalk.dim(' Think: what seed data would put this feature through its paces? Diverse content, edge cases, empty vs rich.'));
661
+ checkbox('Set the project title and description for the App tab:');
662
+ console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
663
+ console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
664
+ console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
665
+ console.log();
666
+ const designSystem = readDesignSystem(root);
667
+ if (designSystem) {
668
+ console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
669
+ console.log(chalk.dim(' Keep these design tokens in mind during planning.'));
670
+ console.log();
671
+ console.log(designSystem);
672
+ console.log();
673
+ }
674
+ console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
675
+ console.log(chalk.green(' Option 1 label: "This plan is accurate, let\'s prototype it!"') + chalk.dim(' — proceed to step 2'));
676
+ console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
491
677
  chalk.dim(' — user describes changes, you revise the plan, then re-present'));
492
678
  console.log();
493
679
  console.log(chalk.dim('This step is for understanding user goals and getting buy-in. Code comes in Step 2.'));
@@ -512,6 +698,7 @@ function printStep1(root, feature) {
512
698
  // ─── Step 2: Prototype ────────────────────────────────────────────────
513
699
  function printStep2(root, feature) {
514
700
  const port = getServerPort();
701
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
515
702
  const projectExists = hasProject(root);
516
703
  const prevState = readState(root);
517
704
  const isResuming = prevState?.step === 2;
@@ -522,6 +709,8 @@ function printStep2(root, feature) {
522
709
  label: STEP_LABELS[2],
523
710
  startedAt: isResuming ? prevState.startedAt : now,
524
711
  featureStartedAt: isResuming ? prevState.featureStartedAt : now,
712
+ appFormats: prevState?.appFormats,
713
+ techStackId: prevState?.techStackId,
525
714
  });
526
715
  logEvent(root, 'step', { step: 2, label: 'Prototype', feature });
527
716
  stepHeader(2, 'Prototype', feature);
@@ -532,68 +721,81 @@ function printStep2(root, feature) {
532
721
  console.log();
533
722
  // If no project exists yet, include scaffolding instructions first
534
723
  if (!projectExists) {
535
- const templateDir = path.join(__dirname, '..', '..', 'templates', 'nextjs-prisma-sqlite');
536
- const hasTemplate = fs.existsSync(templateDir);
537
724
  console.log(chalk.bold('Scaffold the project:'));
538
- if (hasTemplate) {
539
- checkbox('Copy the project template and install dependencies');
540
- console.log();
541
- console.log(chalk.dim(' Copy template (Next.js + Prisma 7 + SQLite, pre-configured):'));
542
- console.log(chalk.dim(` cp -r ${templateDir}/* .`));
543
- console.log(chalk.dim(` cp ${templateDir}/.env .`));
544
- console.log(chalk.dim(` cp ${templateDir}/gitignore .gitignore`));
545
- console.log(chalk.dim(' npm install'));
546
- console.log();
547
- checkbox('Define your data models in `prisma/schema.prisma`');
548
- console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
549
- console.log();
550
- checkbox('Push schema and seed the database');
551
- console.log(chalk.dim(' npm run db:push'));
552
- console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
553
- console.log(chalk.dim(' npm run db:seed'));
554
- console.log();
555
- console.log(chalk.dim(' See PRISMA_SETUP.md for Prisma patterns and important warnings.'));
556
- console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
557
- console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
558
- }
559
- else {
560
- checkbox('Scaffold the project (Next.js App Router) using temp-dir-rsync pattern');
561
- console.log();
562
- console.log(chalk.dim(' Scaffolding pattern (avoids .claude/ conflicts):'));
563
- console.log(chalk.dim(' SCAFFOLD_DIR="/tmp/codeyam-scaffold-$$" && mkdir -p "$SCAFFOLD_DIR"'));
564
- console.log(chalk.dim(' npx create-next-app@latest "$SCAFFOLD_DIR" --typescript --tailwind --eslint \\'));
565
- console.log(chalk.dim(' --app --no-src-dir --no-import-alias --no-react-compiler --turbopack'));
566
- console.log(chalk.dim(' rsync -a --exclude=\'.git\' "$SCAFFOLD_DIR/" . && rm -rf "$SCAFFOLD_DIR"'));
567
- console.log();
568
- checkbox('Set up Prisma + SQLite database');
569
- console.log(chalk.dim(' npm install prisma @prisma/client @prisma/adapter-better-sqlite3 better-sqlite3'));
570
- console.log(chalk.dim(' npm install -D @types/better-sqlite3'));
571
- console.log(chalk.dim(' npx prisma init --datasource-provider sqlite'));
572
- console.log(chalk.dim(' # Define schema, then: npx prisma db push'));
573
- }
725
+ checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
726
+ console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
727
+ console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
574
728
  console.log();
575
- checkbox('Initialize git so analysis and commits work');
576
- console.log(chalk.dim(' git init && git add -A && git commit -m "Initial commit"'));
729
+ checkbox('Define your data models in `prisma/schema.prisma`');
730
+ console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
577
731
  console.log();
578
- checkbox('Run `codeyam init --force --keep-server` to detect the project and configure it');
579
- console.log(chalk.dim(' This auto-detects webapps and configures the start command for analysis'));
732
+ checkbox('Push schema and seed the database');
733
+ console.log(chalk.dim(' npm run db:push'));
734
+ console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
735
+ console.log(chalk.dim(' npm run db:seed'));
736
+ console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
737
+ console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
580
738
  console.log();
581
- checkbox('Verify `.codeyam/config.json` has `startCommand` in the webapp entry');
582
- console.log(chalk.dim(' Should look like: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
583
- console.log(chalk.dim(' If missing, add it manually. $PORT is replaced at runtime by the analyzer.'));
739
+ console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
740
+ console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
741
+ console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
742
+ console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
743
+ console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
584
744
  console.log();
585
- checkbox(`Notify CodeYam: \`curl -s -X POST http://localhost:${port}/api/editor-refresh\``);
745
+ console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
746
+ console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
747
+ console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
586
748
  console.log();
587
749
  console.log(chalk.bold('Build the feature:'));
588
750
  }
589
751
  console.log(chalk.bold('Checklist:'));
590
- checkbox('Create/update API routes (real database reads via Prisma)');
752
+ checkbox('Create API routes that read from the database via Prisma');
591
753
  checkbox('Seed the database with demo data');
592
- checkbox('Build the page/feature UI components');
754
+ checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
755
+ console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
756
+ console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
757
+ console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
593
758
  checkbox('Verify the dev server shows the changes');
759
+ checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
760
+ // Responsive design guidance when building a mobile-responsive web app
761
+ if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
762
+ console.log();
763
+ console.log(chalk.bold.magenta('Responsive Design (mobile-responsive-web-app):'));
764
+ console.log(chalk.magenta(' This app must look great on BOTH desktop AND mobile. It is NOT a mobile-only app.'));
765
+ console.log(chalk.magenta(' Design desktop-first with a full-width layout, then ensure it adapts gracefully to mobile.'));
766
+ console.log(chalk.dim(' Use Tailwind responsive prefixes (sm:, md:, lg:) for layout shifts.'));
767
+ console.log(chalk.dim(' Test at both Desktop (1280×800) and Mobile (390×844) sizes before presenting.'));
768
+ }
769
+ const designSystem = readDesignSystem(root);
770
+ if (designSystem) {
771
+ console.log();
772
+ console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
773
+ console.log();
774
+ console.log(designSystem);
775
+ console.log();
776
+ checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
777
+ console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
778
+ console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
779
+ console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
780
+ console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
781
+ console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
782
+ checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
783
+ console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
784
+ console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
785
+ console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
786
+ }
787
+ console.log();
788
+ console.log(chalk.bold.cyan('Keep the preview moving:'));
789
+ console.log(chalk.cyan(' The user is watching the preview. Refresh it after each meaningful change:'));
790
+ console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
791
+ console.log(chalk.cyan(' Refresh after: first visible page, adding each UI section, seeding data, styling.'));
792
+ console.log(chalk.cyan(' Aim for 4-8+ refreshes during prototyping — not one big reveal at the end.'));
793
+ console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
794
+ console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
795
+ printDimensionGuidance(dim, dimNames);
594
796
  console.log();
595
797
  console.log(chalk.bold('Verify the dev server:'));
596
- console.log(chalk.dim(` # Get dev server URL: curl -s http://localhost:${port}/api/editor-dev-server`));
798
+ console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
597
799
  console.log(chalk.dim(' # Check page loads: curl -s -o /dev/null -w "%{http_code}" http://localhost:<dev-port>'));
598
800
  console.log(chalk.dim(' # Check API routes: curl -s http://localhost:<dev-port>/api/your-route'));
599
801
  console.log();
@@ -601,16 +803,29 @@ function printStep2(root, feature) {
601
803
  console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
602
804
  checkbox('Verify the page loads: curl the dev server URL and confirm HTTP 200 (not an error page)');
603
805
  checkbox('Verify API routes return valid JSON: curl each route and confirm no error responses');
604
- checkbox('Check for broken images: look at any <img> src attributes and verify the assets exist');
806
+ checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
807
+ console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
808
+ console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
809
+ console.log(chalk.dim(' Fix or replace any broken image URLs in the seed data before proceeding.'));
605
810
  checkbox('Check the dev server terminal output for runtime errors (missing modules, failed imports)');
811
+ checkbox('Verify the live preview renders: `codeyam editor client-errors`');
812
+ console.log(chalk.dim(' If `hasContent=false` or `liveErrors>0`, the preview is broken — fix the issue before proceeding.'));
813
+ console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
606
814
  console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
607
815
  console.log();
816
+ console.log(chalk.bold('Update README and setup script:'));
817
+ checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
818
+ checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
819
+ console.log(chalk.dim(' The README and setup script must stay accurate as you make changes.'));
820
+ console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
821
+ console.log();
608
822
  console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
609
823
  stopGate(2);
610
824
  }
611
825
  // ─── Step 3: Confirm ──────────────────────────────────────────────────
612
826
  function printStep3(root, feature) {
613
827
  const port = getServerPort();
828
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
614
829
  const prevState = readState(root);
615
830
  const isResuming = prevState?.step === 3;
616
831
  const now = new Date().toISOString();
@@ -629,29 +844,34 @@ function printStep3(root, feature) {
629
844
  console.log('Summarize what was built and get user confirmation.');
630
845
  console.log();
631
846
  console.log(chalk.bold('Before presenting — verify everything works:'));
632
- checkbox(`Refresh the preview: \`curl -s -X POST http://localhost:${port}/api/editor-refresh\``);
633
- checkbox(`Refresh and check for SSR errors: \`curl -s -X POST http://localhost:${port}/api/dev-mode-preview\` — look at the \`preview\` field for \`healthy: false\``);
847
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
634
848
  checkbox('Verify API routes return valid data (curl each route)');
635
- checkbox(`Navigate to each page and check health: \`curl -s -X POST http://localhost:${port}/api/dev-mode-preview -H 'Content-Type: application/json' -d '{"path":"/your-route"}'\``);
636
849
  console.log();
637
- console.log(chalk.bold(' Check for broken images on each page:'));
638
- checkbox('Get the dev server URL from `curl -s http://localhost:' +
639
- port +
640
- '/api/editor-dev-server` (the `url` field)');
641
- checkbox('For each page: `curl -s <dev-server-url>/your-route` and inspect `<img>` src attributes');
642
- checkbox('Verify every image src is a valid path (not a missing file, placeholder URL, or broken reference)');
643
- checkbox('If any images are broken: fix the src (use real assets, placeholders from a CDN, or remove the image)');
644
- console.log(chalk.yellow(' Do not present to the user until all checks pass. Fix issues first.'));
850
+ console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
851
+ checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
852
+ console.log(chalk.dim(' Include ALL page paths and ALL image URLs from seed data / API responses.'));
853
+ console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images.'));
854
+ checkbox('Fix or remove any image that returns non-200 before continuing');
855
+ checkbox('Check for client-side errors: `codeyam editor client-errors`');
856
+ console.log(chalk.dim(' If there are errors, fix the underlying issue before presenting.'));
857
+ checkbox('Verify `hasContent=true` and `liveErrors=0` — do NOT ask the user to confirm if the preview is broken');
645
858
  console.log();
646
859
  console.log(chalk.bold('Then present to the user:'));
647
860
  checkbox('Summarize what was built (routes, components, data)');
648
- checkbox('Show the user the current state of the prototype');
861
+ checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
862
+ printDimensionGuidance(dim, dimNames);
863
+ console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
864
+ console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
649
865
  console.log();
650
- console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion):'));
651
- console.log(chalk.green(' "Yes, this looks right!"') +
652
- chalk.dim(' proceed to step 4'));
653
- console.log(chalk.yellow(' "I\'d like some changes"') +
654
- chalk.dim(' — user describes changes, you make them, then re-present'));
866
+ console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
867
+ checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
868
+ checkbox('Prompt the user to try key interactions: forms, navigation, buttons, etc.');
869
+ checkbox('Ask the user: "Does everything work as expected?"');
870
+ console.log();
871
+ console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
872
+ console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 4'));
873
+ console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
874
+ chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 3`'));
655
875
  console.log();
656
876
  console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
657
877
  stopGate(3, { confirm: true });
@@ -708,6 +928,8 @@ function printStep4(root, feature) {
708
928
  }
709
929
  // ─── Step 5: Extract ──────────────────────────────────────────────────
710
930
  function printStep5(root, feature) {
931
+ const port = getServerPort();
932
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
711
933
  const prevState = readState(root);
712
934
  const isResuming = prevState?.step === 5;
713
935
  const now = new Date().toISOString();
@@ -731,12 +953,13 @@ function printStep5(root, feature) {
731
953
  checkbox('Every component that renders multiple sections must be split into sub-components');
732
954
  console.log(chalk.dim(' No tests needed — visual verification happens in step 7'));
733
955
  console.log();
734
- console.log(chalk.bold('Library functions (TDD):'));
735
- checkbox('For each function: write MULTIPLE failing tests FIRST, then extract to make them pass');
956
+ console.log(chalk.bold('Library functions AND hooks (TDD):'));
957
+ checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
736
958
  console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
737
959
  console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
960
+ console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
738
961
  checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
739
- console.log(chalk.yellow(' Tests ARE the only coverage for library functions — step 7 only captures component screenshots.'));
962
+ console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 7 only captures component screenshots.'));
740
963
  console.log();
741
964
  console.log(chalk.bold('Recursive pass:'));
742
965
  checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
@@ -748,6 +971,9 @@ function printStep5(root, feature) {
748
971
  checkbox('Run all tests and verify they pass');
749
972
  checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
750
973
  checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
974
+ checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
975
+ printDimensionGuidance(dim, dimNames);
976
+ console.log(chalk.dim(' The user should see the preview stay healthy as you refactor — refresh periodically, not just at the end.'));
751
977
  console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
752
978
  console.log();
753
979
  console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
@@ -776,7 +1002,8 @@ function printStep6(root, feature) {
776
1002
  checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
777
1003
  checkbox('Add an entry for each new function/component extracted in step 5');
778
1004
  checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
779
- checkbox('For each function with a test file from step 5, set `testFile` to the relative path');
1005
+ checkbox('Every non-component entry MUST have `testFile` set hooks and functions all need tests');
1006
+ console.log(chalk.yellow(' If a function/hook has no test yet, go back and write one (TDD). The audit will block you.'));
780
1007
  console.log();
781
1008
  console.log(chalk.bold('Entry format:'));
782
1009
  console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
@@ -792,6 +1019,7 @@ function printStep6(root, feature) {
792
1019
  // ─── Step 7: Analyze ──────────────────────────────────────────────────
793
1020
  function printStep7(root, feature) {
794
1021
  const port = getServerPort();
1022
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
795
1023
  const prevState = readState(root);
796
1024
  const isResuming = prevState?.step === 7;
797
1025
  const now = new Date().toISOString();
@@ -811,7 +1039,13 @@ function printStep7(root, feature) {
811
1039
  console.log();
812
1040
  console.log(chalk.bold('Visual Components — Component Isolation:'));
813
1041
  checkbox('List all files with new/modified visual components from step 5');
814
- checkbox('Ensure `.gitignore` includes `**/codeyam-isolate*` (add if missing)');
1042
+ checkbox('Create isolation route dirs: `codeyam editor isolate "ComponentA ComponentB ..."`');
1043
+ console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
1044
+ console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
1045
+ checkbox('Check existing scenarios: `codeyam editor scenarios`');
1046
+ console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
1047
+ console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
1048
+ console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
815
1049
  checkbox('For each visual component:');
816
1050
  console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
817
1051
  console.log(chalk.dim(' — Props/interface'));
@@ -822,8 +1056,8 @@ function printStep7(root, feature) {
822
1056
  console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
823
1057
  console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
824
1058
  console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
825
- console.log(chalk.dim(' Remix: app/routes/codeyam-isolate.ComponentName.tsx → /codeyam-isolate/ComponentName'));
826
- console.log(chalk.dim(' Next.js: app/codeyam-isolate/ComponentName/page.tsx → /codeyam-isolate/ComponentName'));
1059
+ console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
1060
+ console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
827
1061
  console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
828
1062
  console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
829
1063
  console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
@@ -836,15 +1070,25 @@ function printStep7(root, feature) {
836
1070
  console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
837
1071
  console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
838
1072
  console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
839
- console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
1073
+ console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
1074
+ console.log(chalk.dim(` "dimensions":["${dim}"],`));
840
1075
  console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
1076
+ console.log(chalk.yellow(' ALWAYS include "dimensions" — use the named screen size that matches the component\'s context.'));
1077
+ console.log(chalk.dim(dimNames.length > 0
1078
+ ? ` Available dimensions: ${dimNames.map((n) => `"${n}"`).join(', ')}. Default: "${dim}".`
1079
+ : ` Use "${dim}" for most components. Names must match a key in screenSizes from setup.`));
1080
+ console.log(chalk.dim(' Names must match a key in screenSizes from setup. If you need a new size, add it first:'));
1081
+ console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
1082
+ console.log(chalk.dim(` -d '{"screenSizes":{...existing..., "New Name":{"width":W,"height":H}}}' (replaces all — include every size)`));
841
1083
  console.log(chalk.dim(' url is a PATH (starts with /) — the proxy routes it and intercepts API calls'));
842
1084
  console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
843
1085
  console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
1086
+ console.log(chalk.dim(' For large JSON: use your Write tool to create .codeyam/tmp/scenario.json,'));
1087
+ console.log(chalk.dim(' then: codeyam editor register @.codeyam/tmp/scenario.json'));
844
1088
  console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
845
1089
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
846
1090
  console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
847
- console.log(chalk.dim(' Isolation routes stay in the project (in .gitignore) so the editor preview can display them'));
1091
+ console.log(chalk.dim(' Isolation routes are committed to git the layout guard blocks them in production'));
848
1092
  console.log();
849
1093
  console.log(chalk.bold('Library Functions — run tests:'));
850
1094
  checkbox('Run ALL test files created in step 5');
@@ -854,12 +1098,16 @@ function printStep7(root, feature) {
854
1098
  console.log();
855
1099
  console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
856
1100
  console.log();
857
- checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions have tests');
1101
+ checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
1102
+ console.log(chalk.red.bold(' The audit is a HARD GATE — step 8 will refuse to run until the audit passes.'));
1103
+ console.log(chalk.dim(' When audit passes, the import graph is built automatically for change tracking.'));
1104
+ console.log();
858
1105
  stopGate(7);
859
1106
  }
860
1107
  // ─── Step 8: App Scenarios ────────────────────────────────────────────
861
1108
  function printStep8(root, feature) {
862
1109
  const port = getServerPort();
1110
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
863
1111
  const prevState = readState(root);
864
1112
  const isResuming = prevState?.step === 8;
865
1113
  const now = new Date().toISOString();
@@ -875,19 +1123,83 @@ function printStep8(root, feature) {
875
1123
  if (isResuming) {
876
1124
  printResumptionHeader(8);
877
1125
  }
878
- console.log('Create app-level scenarios representing different data states.');
1126
+ console.log('Create app-level scenarios with rich data that robustly demonstrates this feature.');
1127
+ console.log();
1128
+ console.log(chalk.bold('Goal: Every scenario should thoroughly exercise the feature with realistic data.'));
1129
+ console.log(chalk.dim(' Scenarios with minimal or generic data ("Test Item 1") won\'t reveal whether the feature works.'));
1130
+ console.log(chalk.dim(' Use rich, diverse seed data that puts the feature through its paces.'));
1131
+ console.log();
1132
+ console.log(chalk.bold.cyan('Make seed data work hard:'));
1133
+ console.log(chalk.cyan(' Every scenario — new or existing — should have data that EXERCISES the feature:'));
1134
+ console.log(chalk.cyan(' • Add data that uses new fields, relationships, or states the feature introduces'));
1135
+ console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
1136
+ console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
1137
+ console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
1138
+ console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
1139
+ console.log();
1140
+ console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
1141
+ console.log(chalk.cyan(' • New pages or pages with no scenarios yet → create new scenarios'));
1142
+ console.log(chalk.cyan(' • Existing scenarios on affected pages → re-register with enhanced data'));
1143
+ console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
1144
+ console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
879
1145
  console.log();
880
1146
  console.log(chalk.bold('Checklist:'));
881
- checkbox('Create scenarios for key data states (empty, loaded, error)');
882
- console.log(chalk.dim(' Each scenario provides data across ALL API routes for that state'));
883
- console.log(chalk.dim(' Name app scenarios with "App - " prefix: "App - Empty", "App - Full Catalog", "App - API Error"'));
884
- checkbox('Register each scenario (auto-captures screenshot):');
885
- console.log(chalk.dim(` codeyam editor register '{"name":"App - Empty","description":"...","mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
1147
+ checkbox('Run `codeyam editor scenarios` review all existing scenarios');
1148
+ checkbox("Create new scenarios for any pages that don't have scenarios yet");
1149
+ console.log(chalk.yellow(' Each page needs its own scenarios a detail page needs different data than the catalog'));
1150
+ checkbox('Re-register every existing scenario whose page was affected by this feature');
1151
+ console.log(chalk.dim(' Use the SAME name to update. Enhance seed data to showcase this feature where relevant.'));
1152
+ console.log(chalk.yellow(" Don't just re-register unchanged data — enrich it so the feature is thoroughly demonstrated."));
1153
+ checkbox('ALWAYS include "dimensions" — pick the viewport that best demonstrates the page');
1154
+ console.log(chalk.yellow(' Every app scenario MUST have a "dimensions" field referencing a named screen size from setup.'));
1155
+ console.log(chalk.dim(dimNames.length > 0
1156
+ ? ` Available dimensions: ${dimNames.map((n) => `"${n}"`).join(', ')}. Default: "${dim}".`
1157
+ : ` Use "${dim}" for most scenarios. Names must match a key in screenSizes from setup.`));
1158
+ if (dimNames.length > 1) {
1159
+ const otherDim = dimNames.find((n) => n !== dim) || dimNames[1];
1160
+ console.log(chalk.yellow(` Think about what fits each scenario: full pages/standalone views → "${otherDim}", popups/widgets → "${dim}".`));
1161
+ }
1162
+ console.log(chalk.dim(' For responsive apps, include multiple dimensions: "dimensions":["Desktop","Mobile"] captures both viewports.'));
1163
+ console.log(chalk.dim(' If you need a new named size mid-workflow, add it via editor-project-info API (include ALL existing sizes — the API replaces, not merges).'));
1164
+ checkbox('If the project has a database + seed adapter, use seed-based scenarios:');
1165
+ console.log(chalk.dim(` codeyam editor register '{"name":"Full Catalog","type":"application","url":"/","dimensions":["${dim}"],"seed":{"products":[...],"categories":[...]}}'`));
1166
+ console.log(chalk.dim(' Seed data is written to the DB via the seed adapter. Real app renders real data.'));
1167
+ checkbox('IMPORTANT: Always include "url" — the page path to screenshot for this scenario');
1168
+ console.log(chalk.dim(' Use "/" for home page, "/drinks/1" for detail pages, etc.'));
1169
+ console.log(chalk.dim(' Without url, the screenshot captures the root page regardless of the scenario.'));
1170
+ console.log(chalk.yellow(' Create separate scenarios for each page that shows different data.'));
1171
+ checkbox('For large seed data, use your Write tool to create .codeyam/tmp/scenario.json, then:');
1172
+ console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
1173
+ console.log(chalk.yellow(' IMPORTANT: Use the Write tool — NOT cat/heredoc — to avoid permission prompts'));
1174
+ checkbox('For external API mocks (Stripe, weather, etc.), add externalApis:');
1175
+ console.log(chalk.dim(` "externalApis":{"GET https://api.stripe.com/v1/prices":{"body":[...],"status":200}}`));
1176
+ checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
1177
+ checkbox('If the app uses localStorage/sessionStorage instead of a database, use localStorage scenarios:');
1178
+ console.log(chalk.dim(` codeyam editor register '{"name":"Full Library","type":"application","url":"/","dimensions":["${dim}"],"localStorage":{"articles":[...],"collections":[...]}}'`));
1179
+ console.log(chalk.dim(' The proxy injects data into localStorage before the app loads. Fully interactive — the app can read and write normally.'));
1180
+ console.log(chalk.dim(' For an empty state, register with an empty localStorage or omit it entirely.'));
1181
+ checkbox('If no database and no localStorage, use component-style mock scenarios:');
1182
+ console.log(chalk.dim(` codeyam editor register '{"name":"Empty","description":"...","url":"/","dimensions":["${dim}"],"mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
886
1183
  checkbox('After each registration, check the response for `clientErrors`');
887
1184
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
888
1185
  console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
1186
+ checkbox('CRITICAL: After EACH registration, view the captured screenshot to verify it');
1187
+ console.log(chalk.yellow(' Read the screenshot file path from the registration response and view it.'));
1188
+ console.log(chalk.yellow(' Does the screenshot actually SHOW the data you put in? If you seeded 5 articles'));
1189
+ console.log(chalk.yellow(' but the screenshot shows an empty page, the scenario is broken — fix the code or data.'));
1190
+ console.log(chalk.yellow(' Do NOT create scenarios with data the app cannot yet render.'));
889
1191
  console.log();
890
1192
  console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 10 if needed.'));
1193
+ console.log();
1194
+ console.log(chalk.bold.cyan("Verify your work — screenshots don't lie:"));
1195
+ console.log(chalk.cyan(' • View every captured screenshot. Does it show what the scenario name promises?'));
1196
+ console.log(chalk.cyan(' • A "Library with Articles" screenshot showing an empty page means the scenario is BROKEN.'));
1197
+ console.log(chalk.cyan(' • Is the seed data diverse — different lengths, categories, counts — or just placeholders?'));
1198
+ console.log(chalk.cyan(' • Only create scenarios for states the CURRENT code can render.'));
1199
+ console.log();
1200
+ console.log(chalk.bold.yellow('GATE: Before proceeding, run `codeyam editor scenario-coverage`'));
1201
+ console.log(chalk.yellow(' This checks which existing scenarios have stale screenshots.'));
1202
+ console.log(chalk.yellow(' Re-register every stale scenario listed until the check passes.'));
891
1203
  stopGate(8);
892
1204
  }
893
1205
  // ─── Step 9: User Scenarios ───────────────────────────────────────────
@@ -908,26 +1220,37 @@ function printStep9(root, feature) {
908
1220
  if (isResuming) {
909
1221
  printResumptionHeader(9);
910
1222
  }
911
- console.log('Create per-persona scenarios if the app has users. Skip to step 10 if no users.');
1223
+ console.log('Create per-persona variations of existing scenarios. Skip to step 10 if no users.');
1224
+ console.log();
1225
+ console.log(chalk.bold('If the app has NO users/auth:'));
1226
+ console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
1227
+ console.log();
1228
+ console.log(chalk.bold('If the app has users/auth:'));
1229
+ console.log();
1230
+ console.log(chalk.bold('Goal: Create persona variations of EXISTING app scenarios.'));
1231
+ console.log(chalk.dim(' Do NOT create standalone persona scenarios. Each user-persona scenario should be'));
1232
+ console.log(chalk.dim(' a variation of an existing app scenario from step 8, with user-specific state layered on.'));
912
1233
  console.log();
913
- console.log(chalk.bold('If the app has users:'));
914
- checkbox('Create scenarios for each user persona (admin, regular user, new user)');
915
- console.log(chalk.dim(' Each persona scenario provides data across ALL API routes for that user type'));
916
- checkbox('Register each persona scenario (auto-captures screenshot):');
917
- console.log(chalk.dim(` codeyam editor register '{"name":"...","description":"...","mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
1234
+ console.log(chalk.bold('Checklist:'));
1235
+ checkbox('Run `codeyam editor scenarios` list all existing app scenarios');
1236
+ checkbox('For EACH existing app scenario, create a logged-in variation:');
1237
+ console.log(chalk.dim(' Copy the scenario seed data and add "session":{"cookieValue":"sess_alice"} + user seed'));
1238
+ console.log(chalk.dim(' Name: "<Original Name> - Logged In" (e.g. "Full Catalog - Logged In")'));
1239
+ console.log(chalk.dim(' Step 8 scenarios already serve as logged-out versions (no session cookie)'));
1240
+ checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
1241
+ console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
1242
+ checkbox('Include "dimensions" — inherit from the base app scenario, or override if the persona implies a different device');
1243
+ console.log(chalk.dim(' e.g. a mobile-first user persona should use "dimensions":["Mobile"] even if the base scenario is Desktop.'));
918
1244
  checkbox('After each registration, check the response for `clientErrors`');
919
1245
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
920
- console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
921
- console.log();
922
- console.log(chalk.bold('If the app has NO users:'));
923
- console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
924
1246
  console.log();
925
- console.log(chalk.dim('Focus on creating user-persona scenarios (or skip if no users). Code fixes happen in step 10 if needed.'));
1247
+ console.log(chalk.dim('See FEATURE_PATTERNS.md and AUTH_PATTERNS.md for auth scenario guidance.'));
926
1248
  stopGate(9);
927
1249
  }
928
1250
  // ─── Step 10: Verify ──────────────────────────────────────────────────
929
1251
  function printStep10(root, feature) {
930
1252
  const port = getServerPort();
1253
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
931
1254
  const prevState = readState(root);
932
1255
  const isResuming = prevState?.step === 10;
933
1256
  const now = new Date().toISOString();
@@ -952,13 +1275,16 @@ function printStep10(root, feature) {
952
1275
  console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",...}'`));
953
1276
  console.log();
954
1277
  console.log(chalk.bold('Editor scenarios (App tab) — visual + error check:'));
955
- checkbox(`Refresh the preview: \`curl -s -X POST http://localhost:${port}/api/editor-refresh\``);
1278
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1279
+ printDimensionGuidance(dim, dimNames);
956
1280
  checkbox('Click through each app-level and user-persona scenario in the preview');
957
- checkbox(`Check for client-side errors: \`curl -s http://localhost:${port}/api/editor-client-errors\``);
1281
+ checkbox('For seed-based scenarios: verify data renders correctly after switching');
1282
+ console.log(chalk.dim(' Switch between scenarios and confirm the app reflects the seeded data each time'));
1283
+ checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
958
1284
  console.log(chalk.yellow(' If `hasErrors` is true: list each scenario with errors, fix the source code,'));
959
1285
  console.log(chalk.yellow(' re-register the affected scenarios, and re-check until hasErrors is false'));
960
1286
  console.log(chalk.dim(' Common errors: React errors, failed fetches, undefined references, hydration mismatches'));
961
- checkbox('Verify no broken images in any scenario screenshots');
1287
+ checkbox('Verify no broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
962
1288
  console.log();
963
1289
  console.log(chalk.bold('Library functions — test check:'));
964
1290
  checkbox('Re-run all test files from step 5 to confirm they still pass');
@@ -991,21 +1317,19 @@ function printStep11(root, feature) {
991
1317
  console.log(chalk.bold('Checklist:'));
992
1318
  checkbox('Write a concise description of what was built (2-3 sentences)');
993
1319
  checkbox(`First time at step 11 — create journal entry with ALL session screenshots:`);
994
- console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-journal-entry \\`));
995
- console.log(chalk.dim(` -H 'Content-Type: application/json' \\`));
996
- console.log(chalk.dim(` -d '{"title":"...","type":"feature","description":"...","includeSessionScenarios":true}'`));
1320
+ console.log(chalk.dim(` codeyam editor journal '{"title":"...","type":"feature","description":"...","includeSessionScenarios":true}'`));
997
1321
  console.log(chalk.dim(' includeSessionScenarios auto-discovers component + app + user persona screenshots'));
998
- checkbox(`Returning after changes — update the existing journal entry:`);
999
- console.log(chalk.dim(` curl -s -X PATCH http://localhost:${port}/api/editor-journal-update \\`));
1000
- console.log(chalk.dim(` -H 'Content-Type: application/json' \\`));
1001
- console.log(chalk.dim(` -d '{"time":"<journal entry time>","description":"<updated>","includeSessionScenarios":true}'`));
1322
+ checkbox(`Returning to step 11 (before commit) — update the existing journal entry:`);
1323
+ console.log(chalk.dim(` codeyam editor journal-update '{"time":"<journal entry time>","description":"<updated>","includeSessionScenarios":true}'`));
1324
+ console.log(chalk.dim(' Note: PATCH only works before the entry is committed. After commit, use POST to create a new entry.'));
1002
1325
  console.log();
1003
- console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and review happen in step 12.'));
1326
+ console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step 13.'));
1004
1327
  stopGate(11);
1005
1328
  }
1006
1329
  // ─── Step 12: Review ──────────────────────────────────────────────────
1007
1330
  function printStep12(root, feature) {
1008
1331
  const port = getServerPort();
1332
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1009
1333
  const prevState = readState(root);
1010
1334
  const isResuming = prevState?.step === 12;
1011
1335
  const now = new Date().toISOString();
@@ -1021,52 +1345,158 @@ function printStep12(root, feature) {
1021
1345
  if (isResuming) {
1022
1346
  printResumptionHeader(12);
1023
1347
  }
1024
- console.log('Verify everything, then present the scenario table to the user.');
1348
+ console.log('Verify all screenshots and checks pass before presenting to the user.');
1025
1349
  console.log();
1026
- console.log(chalk.bold('Phase 1 — Verify (do all of this silently):'));
1027
- checkbox(`Refresh the preview: \`curl -s -X POST http://localhost:${port}/api/dev-mode-preview\``);
1350
+ console.log(chalk.bold('Checklist (do all of this silently):'));
1351
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1352
+ printDimensionGuidance(dim, dimNames);
1028
1353
  checkbox('Verify each component has screenshots in the App tab (grouped under Components)');
1029
1354
  checkbox('If any are missing, re-register them using `codeyam editor register`');
1030
- checkbox(`Check for client errors: \`curl -s http://localhost:${port}/api/editor-client-errors\``);
1355
+ checkbox(`Check for client errors: \`codeyam editor client-errors\``);
1031
1356
  checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
1357
+ checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
1358
+ checkbox('Fix or remove any broken images before continuing');
1032
1359
  checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
1033
1360
  checkbox('Do not proceed until all checks pass');
1361
+ stopGate(12);
1362
+ }
1363
+ // ─── Step 13: Present ─────────────────────────────────────────────────
1364
+ function printStep13(root, feature) {
1365
+ const port = getServerPort();
1366
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1367
+ const prevState = readState(root);
1368
+ const isResuming = prevState?.step === 13;
1369
+ const now = new Date().toISOString();
1370
+ writeState(root, {
1371
+ feature,
1372
+ step: 13,
1373
+ label: STEP_LABELS[13],
1374
+ startedAt: isResuming ? prevState.startedAt : now,
1375
+ featureStartedAt: prevState?.featureStartedAt || now,
1376
+ });
1377
+ logEvent(root, 'step', { step: 13, label: 'Present', feature });
1378
+ stepHeader(13, 'Present', feature);
1379
+ if (isResuming) {
1380
+ printResumptionHeader(13);
1381
+ }
1382
+ console.log('Present the results to the user and get their approval.');
1034
1383
  console.log();
1035
- console.log(chalk.bold('Phase 2 — Present to the user:'));
1036
- checkbox('Write a 1-2 sentence summary of what was built');
1037
- checkbox('Report test count and audit status (one line)');
1038
- checkbox('Switch the active scenario to the best demo state for the feature:');
1039
- console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/dev-mode-preview \\`));
1040
- console.log(chalk.dim(` -H 'Content-Type: application/json' -d '{"scenarioSlug":"...","scenarioId":"..."}'`));
1041
- checkbox(`Show the results panel: \`curl -s -X POST http://localhost:${port}/api/editor-show-results\``);
1384
+ console.log(chalk.bold('Checklist:'));
1385
+ checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1386
+ console.log(chalk.dim(' Each scenario has a `changeStatus` field: "new", "edited", "impacted", or null.'));
1387
+ checkbox('Pick the best scenario to showcase from this working session:');
1388
+ console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
1389
+ console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
1390
+ console.log(chalk.dim(' NEVER pick a scenario with changeStatus null those are unchanged from a previous commit.'));
1391
+ checkbox('Switch the preview to that scenario using its `id` and `dimension`:');
1392
+ console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1393
+ console.log(chalk.dim(' Use the dimension that matches the scenario — check its registered dimensions.'));
1394
+ printDimensionGuidance(dim, dimNames);
1395
+ checkbox(`Show the results panel: \`codeyam editor show-results\``);
1042
1396
  console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
1043
1397
  console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
1398
+ checkbox('Write a 1-2 sentence summary of what was built');
1399
+ checkbox('Report test count and audit status (one line)');
1044
1400
  console.log();
1045
- console.log(chalk.bold('Phase 3 — Present a selection menu to the user (use AskUserQuestion):'));
1046
- console.log(chalk.green(' "Save & commit"') +
1401
+ console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
1402
+ console.log(chalk.green(' Option 1 label: "Save & commit"') +
1047
1403
  chalk.dim(' — git commit all changes and record in journal'));
1048
- console.log(chalk.yellow(' "I\'d like to make some changes"') +
1404
+ console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
1049
1405
  chalk.dim(' — describe changes, then re-verify'));
1050
1406
  console.log();
1051
1407
  console.log(chalk.bold('If the user chooses "Save & commit":'));
1052
- checkbox(`Git commit using the journal description: \`curl -s -X POST http://localhost:${port}/api/editor-commit -H 'Content-Type: application/json' -d '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
1408
+ checkbox('Advance to the commit step: `codeyam editor 14`');
1409
+ console.log();
1410
+ console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
1411
+ checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
1412
+ checkbox('Ask what changes the user wants (if not already clear)');
1413
+ checkbox(`Run: \`codeyam editor change "${feature}"\` — this gives you the change checklist`);
1414
+ checkbox('THEN make the requested changes and follow the checklist');
1415
+ console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
1416
+ stopGate(13, { confirm: true });
1417
+ }
1418
+ // ─── Step 14: Commit ─────────────────────────────────────────────────
1419
+ function printStep14(root, feature) {
1420
+ const prevState = readState(root);
1421
+ const isResuming = prevState?.step === 14;
1422
+ const now = new Date().toISOString();
1423
+ writeState(root, {
1424
+ feature,
1425
+ step: 14,
1426
+ label: STEP_LABELS[14],
1427
+ startedAt: isResuming ? prevState.startedAt : now,
1428
+ featureStartedAt: prevState?.featureStartedAt || now,
1429
+ });
1430
+ logEvent(root, 'step', { step: 14, label: 'Commit', feature });
1431
+ stepHeader(14, 'Commit', feature);
1432
+ if (isResuming) {
1433
+ printResumptionHeader(14);
1434
+ }
1435
+ console.log('Commit all changes for this feature.');
1436
+ console.log();
1437
+ console.log(chalk.bold('Checklist:'));
1438
+ checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
1439
+ checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
1053
1440
  console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
1054
- checkbox(`Update journal with commit SHA: \`curl -s -X PATCH http://localhost:${port}/api/editor-journal-update -H 'Content-Type: application/json' -d '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
1055
- console.log(chalk.green(' Then run: ') +
1056
- chalk.bold('codeyam editor 1') +
1057
- chalk.green(' to start the next feature'));
1058
- console.log();
1059
- console.log(chalk.bold('If the user chooses "Make changes":'));
1060
- checkbox('Ask what changes the user wants');
1061
- checkbox('Make the requested changes');
1062
- console.log(chalk.yellow(' Then run: ') +
1063
- chalk.bold(`codeyam editor change "${feature}"`) +
1064
- chalk.yellow(' — this prints a post-change checklist to re-register scenarios,'));
1065
- console.log(chalk.yellow(' re-run tests, update journal screenshots, and loop back to this review step.'));
1066
- console.log(chalk.red.bold(' IMPORTANT: You MUST run the change command. Do not skip this or try to re-verify manually.'));
1067
- console.log();
1068
- console.log(chalk.dim('Complete all phases in order: summary, screenshot verification, user menu.'));
1069
- stopGate(12, { confirm: true });
1441
+ stopGate(14);
1442
+ }
1443
+ // ─── Step 15: Finalize ───────────────────────────────────────────────
1444
+ function printStep15(root, feature) {
1445
+ const prevState = readState(root);
1446
+ const isResuming = prevState?.step === 15;
1447
+ const now = new Date().toISOString();
1448
+ writeState(root, {
1449
+ feature,
1450
+ step: 15,
1451
+ label: STEP_LABELS[15],
1452
+ startedAt: isResuming ? prevState.startedAt : now,
1453
+ featureStartedAt: prevState?.featureStartedAt || now,
1454
+ });
1455
+ logEvent(root, 'step', { step: 15, label: 'Finalize', feature });
1456
+ stepHeader(15, 'Finalize', feature);
1457
+ if (isResuming) {
1458
+ printResumptionHeader(15);
1459
+ }
1460
+ console.log('Update the journal with the commit SHA and amend the commit.');
1461
+ console.log();
1462
+ console.log(chalk.bold('Checklist:'));
1463
+ checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
1464
+ checkbox('Amend the commit to include the journal update: `git add .codeyam/journal/ && git commit --amend --no-edit`');
1465
+ console.log(chalk.dim(' The journal-update modifies journal files after the commit — amend to keep the tree clean.'));
1466
+ stopGate(15);
1467
+ }
1468
+ // ─── Step 16: Push ───────────────────────────────────────────────────
1469
+ function printStep16(root, feature) {
1470
+ const prevState = readState(root);
1471
+ const isResuming = prevState?.step === 16;
1472
+ const now = new Date().toISOString();
1473
+ writeState(root, {
1474
+ feature,
1475
+ step: 16,
1476
+ label: STEP_LABELS[16],
1477
+ startedAt: isResuming ? prevState.startedAt : now,
1478
+ featureStartedAt: prevState?.featureStartedAt || now,
1479
+ });
1480
+ logEvent(root, 'step', { step: 16, label: 'Push', feature });
1481
+ stepHeader(16, 'Push', feature);
1482
+ if (isResuming) {
1483
+ printResumptionHeader(16);
1484
+ }
1485
+ console.log('Push the commit to the remote repository.');
1486
+ console.log();
1487
+ console.log(chalk.bold('Checklist:'));
1488
+ checkbox('Check if a git remote is configured: `git remote -v`');
1489
+ checkbox("Offer to push to remote (AskUserQuestion — STOP and wait for the user's answer before proceeding):");
1490
+ console.log(chalk.dim(' If a remote exists, ask (AskUserQuestion): "Would you like me to push this commit to the remote?" with options "Yes, push" and "No, skip"'));
1491
+ console.log(chalk.dim(' If the user says yes, run: `git push`'));
1492
+ console.log(chalk.dim(' If NO remote exists, ask (AskUserQuestion): "This project doesn\'t have a git remote yet. Would you like help setting one up? I can walk you through creating a GitHub repository." with options "Yes, set up remote" and "No, skip"'));
1493
+ console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
1494
+ checkbox('After the user responds, run: `codeyam editor steps` to start the next feature');
1495
+ console.log();
1496
+ console.log(chalk.red.bold(' If the user reports a bug or requests a fix after committing:'));
1497
+ console.log(chalk.red.bold(' You MUST still run `codeyam editor change` before making any changes.'));
1498
+ console.log(chalk.red.bold(' The change workflow applies to ALL changes — post-commit fixes are not an exception.'));
1499
+ stopGate(16, { confirm: true });
1070
1500
  }
1071
1501
  // ─── Command definition ───────────────────────────────────────────────
1072
1502
  // ─── Analyze-imports subcommand ────────────────────────────────────────
@@ -1076,11 +1506,15 @@ function printStep12(root, feature) {
1076
1506
  * Runs data-structure-only analysis for all glossary entities, then outputs
1077
1507
  * an import graph and entity data structures as JSON to stdout.
1078
1508
  */
1079
- async function handleAnalyzeImports() {
1509
+ async function handleAnalyzeImports(options = {}) {
1080
1510
  const root = getProjectRoot();
1081
1511
  // Read glossary
1082
1512
  const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
1083
1513
  if (!fs.existsSync(glossaryPath)) {
1514
+ if (options.silent) {
1515
+ // Internal caller — glossary doesn't exist yet, nothing to analyze
1516
+ return;
1517
+ }
1084
1518
  console.error(chalk.red('Error: .codeyam/glossary.json not found.'));
1085
1519
  console.error(chalk.dim(' Run codeyam editor 6 to create the glossary first.'));
1086
1520
  process.exit(1);
@@ -1098,15 +1532,24 @@ async function handleAnalyzeImports() {
1098
1532
  process.exit(1);
1099
1533
  }
1100
1534
  const filePaths = glossaryEntries.map((e) => e.filePath);
1101
- const entityNames = glossaryEntries.map((e) => e.name);
1535
+ // Include page files so pages get analyzed as entities too.
1536
+ // This allows the import graph to map page names to their dependencies.
1537
+ const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
1538
+ const pageFilePaths = scanPageFilePaths(root);
1539
+ for (const pageFile of Object.values(pageFilePaths)) {
1540
+ if (!filePaths.includes(pageFile)) {
1541
+ filePaths.push(pageFile);
1542
+ }
1543
+ }
1102
1544
  const progress = new ProgressReporter();
1103
- // Run data-structure-only analysis for all entities
1104
- progress.start('Running import analysis for all glossary entities...');
1545
+ // Run data-structure-only analysis for all entities (glossary + pages)
1546
+ // Don't pass entityNames entities may not exist yet (fresh clone).
1547
+ // The analyzer will discover and create them from file paths.
1548
+ progress.start('Running import analysis for all entities...');
1105
1549
  try {
1106
1550
  await runAnalysisForEntities({
1107
1551
  projectRoot: root,
1108
1552
  filePaths,
1109
- entityNames,
1110
1553
  progress,
1111
1554
  onlyDataStructure: true,
1112
1555
  });
@@ -1123,8 +1566,11 @@ async function handleAnalyzeImports() {
1123
1566
  await initializeEnvironment();
1124
1567
  const entities = await loadEntities({});
1125
1568
  if (!entities || entities.length === 0) {
1126
- progress.fail('No entities found in database');
1127
- process.exit(1);
1569
+ progress.succeed('No entities found in database yet — skipping import analysis. This is normal on the first feature.');
1570
+ if (!options.silent) {
1571
+ console.log(JSON.stringify({ imports: {}, entities: {} }));
1572
+ }
1573
+ return;
1128
1574
  }
1129
1575
  // Deduplicate to latest versions
1130
1576
  const latestByKey = new Map();
@@ -1138,7 +1584,7 @@ async function handleAnalyzeImports() {
1138
1584
  latestByKey.set(key, entity);
1139
1585
  }
1140
1586
  }
1141
- const entityNameSet = new Set(entityNames);
1587
+ const entityNameSet = new Set(glossaryEntries.map((e) => e.name));
1142
1588
  const latestEntities = [...latestByKey.values()].filter((e) => entityNameSet.has(e.name));
1143
1589
  // Build import graph from importedExports metadata
1144
1590
  const imports = {};
@@ -1176,11 +1622,211 @@ async function handleAnalyzeImports() {
1176
1622
  };
1177
1623
  }
1178
1624
  progress.succeed('Done');
1179
- // Output combined JSON
1180
- const output = { imports, entities: entityData };
1181
- console.log(JSON.stringify(output, null, 2));
1625
+ // Output combined JSON (suppressed when called internally during startup)
1626
+ if (!options.silent) {
1627
+ const summary = formatApiSubcommandResult('analyze-imports', {
1628
+ imports,
1629
+ entities: entityData,
1630
+ });
1631
+ console.log(summary || JSON.stringify({ imports, entities: entityData }));
1632
+ }
1633
+ }
1634
+ // ─── Validate-seed subcommand ─────────────────────────────────────────
1635
+ /**
1636
+ * Validate seed data structure and check for a seed adapter.
1637
+ * Usage: codeyam editor validate-seed '{"products":[...]}'
1638
+ */
1639
+ function handleValidateSeed(jsonArg) {
1640
+ const root = process.cwd();
1641
+ // Check for seed adapter
1642
+ const adapterPath = detectSeedAdapter(root);
1643
+ if (adapterPath) {
1644
+ console.log(chalk.green(`Seed adapter found: ${adapterPath}`));
1645
+ }
1646
+ else {
1647
+ console.log(chalk.yellow('No seed adapter found. Create .codeyam/seed-adapter.ts to use seed-based scenarios.'));
1648
+ }
1649
+ if (!jsonArg) {
1650
+ return;
1651
+ }
1652
+ let data;
1653
+ try {
1654
+ data = JSON.parse(jsonArg);
1655
+ }
1656
+ catch {
1657
+ console.error(chalk.red('Error: Invalid JSON.'));
1658
+ process.exit(1);
1659
+ }
1660
+ const result = validateSeedData(data);
1661
+ if (result.valid) {
1662
+ const tables = Object.keys(data);
1663
+ const totalRows = tables.reduce((sum, t) => sum + (Array.isArray(data[t]) ? data[t].length : 0), 0);
1664
+ console.log(chalk.green(`Seed data valid: ${tables.length} table(s), ${totalRows} total row(s)`));
1665
+ for (const table of tables) {
1666
+ const rows = Array.isArray(data[table])
1667
+ ? data[table].length
1668
+ : 0;
1669
+ console.log(chalk.dim(` ${table}: ${rows} row(s)`));
1670
+ }
1671
+ }
1672
+ else {
1673
+ console.error(chalk.red('Seed data validation failed:'));
1674
+ for (const err of result.errors) {
1675
+ console.error(chalk.red(` - ${err}`));
1676
+ }
1677
+ process.exit(1);
1678
+ }
1679
+ }
1680
+ // ─── Isolate subcommand ───────────────────────────────────────────────
1681
+ /**
1682
+ * `codeyam editor isolate "StarRating CategoryBadge DrinkCard"`
1683
+ *
1684
+ * Creates isolation route directories and the layout guard file.
1685
+ * This avoids brace-expansion permission prompts in the embedded terminal.
1686
+ */
1687
+ function handleIsolate(componentNames) {
1688
+ const root = process.cwd();
1689
+ if (componentNames.length === 0) {
1690
+ console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
1691
+ process.exit(1);
1692
+ }
1693
+ const isolateDir = path.join(root, 'app', 'isolated-components');
1694
+ // Create layout.tsx with production guard if missing
1695
+ const layoutPath = path.join(isolateDir, 'layout.tsx');
1696
+ if (!fs.existsSync(layoutPath)) {
1697
+ fs.mkdirSync(isolateDir, { recursive: true });
1698
+ fs.writeFileSync(layoutPath, [
1699
+ 'import { notFound } from "next/navigation";',
1700
+ '',
1701
+ 'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
1702
+ ' if (process.env.NODE_ENV === "production") notFound();',
1703
+ ' return <>{children}</>;',
1704
+ '}',
1705
+ '',
1706
+ ].join('\n'), 'utf8');
1707
+ console.log(chalk.green(`Created layout guard: app/isolated-components/layout.tsx`));
1708
+ }
1709
+ // Create a directory for each component
1710
+ const created = [];
1711
+ const existed = [];
1712
+ for (const name of componentNames) {
1713
+ const dir = path.join(isolateDir, name);
1714
+ if (fs.existsSync(dir)) {
1715
+ existed.push(name);
1716
+ }
1717
+ else {
1718
+ fs.mkdirSync(dir, { recursive: true });
1719
+ created.push(name);
1720
+ }
1721
+ }
1722
+ if (created.length > 0) {
1723
+ console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
1724
+ }
1725
+ if (existed.length > 0) {
1726
+ console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
1727
+ }
1182
1728
  }
1183
1729
  // ─── Register subcommand ──────────────────────────────────────────────
1730
+ /**
1731
+ * Format API subcommand results as concise one-line summaries.
1732
+ * Prevents Claude from piping output to python3 just to extract key fields.
1733
+ * Returns null for subcommands that should keep raw JSON output.
1734
+ */
1735
+ function formatApiSubcommandResult(subcommand, data) {
1736
+ if (!data || typeof data !== 'object')
1737
+ return null;
1738
+ switch (subcommand) {
1739
+ case 'journal': {
1740
+ const parts = [`success=${data.success}`];
1741
+ if (data.entry?.time)
1742
+ parts.push(`time="${data.entry.time}"`);
1743
+ if (data.entry?.title)
1744
+ parts.push(`title="${data.entry.title}"`);
1745
+ if (data.scenarioScreenshotsFound != null) {
1746
+ parts.push(`screenshots=${data.scenarioScreenshotsFound}`);
1747
+ }
1748
+ return parts.join(' ');
1749
+ }
1750
+ case 'journal-update': {
1751
+ const parts = [`success=${data.success}`];
1752
+ if (data.entry?.time)
1753
+ parts.push(`time="${data.entry.time}"`);
1754
+ if (data.scenarioScreenshotsFound != null) {
1755
+ parts.push(`screenshots=${data.scenarioScreenshotsFound}`);
1756
+ }
1757
+ return parts.join(' ');
1758
+ }
1759
+ case 'commit': {
1760
+ const parts = [`success=${data.success}`];
1761
+ if (data.sha)
1762
+ parts.push(`sha=${data.sha}`);
1763
+ if (data.message)
1764
+ parts.push(`message="${data.message.split('\n')[0]}"`);
1765
+ return parts.join(' ');
1766
+ }
1767
+ case 'preview': {
1768
+ const parts = [
1769
+ `success=${!!data.ok || data.success !== false}`,
1770
+ ];
1771
+ if (data.path)
1772
+ parts.push(`path="${data.path}"`);
1773
+ if (data.scenarioId)
1774
+ parts.push(`scenarioId="${data.scenarioId}"`);
1775
+ if (data.sessionsNotified != null)
1776
+ parts.push(`notified=${data.sessionsNotified}`);
1777
+ return parts.join(' ');
1778
+ }
1779
+ case 'dev-server': {
1780
+ if (data.status) {
1781
+ const parts = [`status=${data.status}`, `pid=${data.pid || 'none'}`];
1782
+ if (data.url)
1783
+ parts.push(`url=${data.url}`);
1784
+ if (data.proxyUrl)
1785
+ parts.push(`proxyUrl=${data.proxyUrl}`);
1786
+ if (data.errorMessage)
1787
+ parts.push(`error: ${data.errorMessage}`);
1788
+ return parts.join(' ');
1789
+ }
1790
+ return null;
1791
+ }
1792
+ case 'client-errors': {
1793
+ const parts = [`hasErrors=${data.hasErrors}`];
1794
+ parts.push(`totalErrors=${data.totalErrors ?? 0}`);
1795
+ if (data.livePreview) {
1796
+ const lp = data.livePreview;
1797
+ parts.push(`livePreview=${lp.loaded ? 'loaded' : 'not-loaded'}`);
1798
+ if (lp.loaded) {
1799
+ parts.push(`hasContent=${lp.hasContent}`);
1800
+ }
1801
+ const liveErrors = lp.errors?.length ?? 0;
1802
+ parts.push(`liveErrors=${liveErrors}`);
1803
+ if (liveErrors > 0) {
1804
+ // Show first few error messages for context
1805
+ for (const err of lp.errors.slice(0, 3)) {
1806
+ parts.push(` error: ${err.message}`);
1807
+ }
1808
+ }
1809
+ }
1810
+ else {
1811
+ parts.push('livePreview=not-loaded');
1812
+ }
1813
+ return parts.join(' ');
1814
+ }
1815
+ case 'analyze-imports': {
1816
+ const importEntries = Object.entries(data.imports || {});
1817
+ const entityEntries = Object.entries(data.entities || {});
1818
+ const parts = [];
1819
+ parts.push(`entities=${entityEntries.length}`);
1820
+ parts.push(`withImports=${importEntries.length}`);
1821
+ if (importEntries.length > 0) {
1822
+ parts.push(`imports: ${importEntries.map(([name, deps]) => `${name}→[${deps.join(',')}]`).join(' ')}`);
1823
+ }
1824
+ return parts.join(' ');
1825
+ }
1826
+ default:
1827
+ return null; // journal-list, show/hide-results: keep full JSON
1828
+ }
1829
+ }
1184
1830
  /**
1185
1831
  * `codeyam editor register '{"name":"...","componentName":"...",...}'`
1186
1832
  *
@@ -1189,38 +1835,88 @@ async function handleAnalyzeImports() {
1189
1835
  */
1190
1836
  async function handleRegister(jsonArg) {
1191
1837
  if (!jsonArg) {
1838
+ const { defaultName: dim } = getProjectDimensions(getProjectRoot());
1192
1839
  console.error(chalk.red('Error: JSON argument required.'));
1193
- console.error(chalk.dim(' Usage: codeyam editor register \'{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/codeyam-isolate/DrinkCard?s=Default"}\''));
1840
+ console.error(chalk.dim(` Usage: codeyam editor register '{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/isolated-components/DrinkCard?s=Default","dimensions":["${dim}"]}'`));
1841
+ console.error(chalk.dim(' For large payloads: codeyam editor register @/tmp/scenario.json'));
1194
1842
  process.exit(1);
1195
1843
  }
1196
- let body;
1197
- try {
1198
- body = JSON.parse(jsonArg);
1199
- }
1200
- catch {
1201
- console.error(chalk.red('Error: Invalid JSON.'));
1202
- console.error(chalk.dim(` Received: ${jsonArg.slice(0, 200)}`));
1844
+ const parsed = parseRegisterArg(jsonArg);
1845
+ if (parsed.error) {
1846
+ console.error(chalk.red(`Error: ${parsed.error}`));
1847
+ if (parsed.error === 'Invalid JSON') {
1848
+ console.error(chalk.dim(` Received: ${jsonArg.slice(0, 200)}`));
1849
+ }
1203
1850
  process.exit(1);
1204
1851
  }
1852
+ // Normalize to array for uniform handling
1853
+ const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
1205
1854
  const port = getServerPort();
1206
1855
  const url = `http://localhost:${port}/api/editor-register-scenario`;
1207
- try {
1208
- const res = await fetch(url, {
1209
- method: 'POST',
1210
- headers: { 'Content-Type': 'application/json' },
1211
- body: JSON.stringify(body),
1212
- });
1213
- const data = await res.json();
1214
- console.log(JSON.stringify(data, null, 2));
1215
- if (!res.ok) {
1216
- process.exit(1);
1856
+ const isBatch = items.length > 1;
1857
+ let succeeded = 0;
1858
+ let failed = 0;
1859
+ for (let i = 0; i < items.length; i++) {
1860
+ const body = items[i];
1861
+ const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
1862
+ try {
1863
+ const res = await fetch(url, {
1864
+ method: 'POST',
1865
+ headers: { 'Content-Type': 'application/json' },
1866
+ body: JSON.stringify(body),
1867
+ });
1868
+ const data = await res.json();
1869
+ // Print concise summary instead of raw JSON so Claude doesn't need python3
1870
+ const parts = [];
1871
+ parts.push(`success=${data.success}`);
1872
+ if (data.scenario?.name)
1873
+ parts.push(`name="${data.scenario.name}"`);
1874
+ parts.push(`screenshot=${!!data.screenshotCaptured}`);
1875
+ if (data.capturedViewport) {
1876
+ parts.push(`viewport=${data.capturedViewport.width}×${data.capturedViewport.height}`);
1877
+ }
1878
+ if (data.scenario?.dimension)
1879
+ parts.push(`dimension="${data.scenario.dimension}"`);
1880
+ if (data.clientErrors?.length > 0) {
1881
+ parts.push(chalk.red(`errors=${data.clientErrors.length}`));
1882
+ }
1883
+ else {
1884
+ parts.push(`errors=0`);
1885
+ }
1886
+ if (data.seedResult)
1887
+ parts.push(`seed=${data.seedResult.success}`);
1888
+ if (data.captureError)
1889
+ parts.push(chalk.yellow(`captureError="${data.captureError}"`));
1890
+ console.log(prefix + parts.join(' '));
1891
+ // Surface client errors prominently so they can't be missed
1892
+ if (data.clientErrors && data.clientErrors.length > 0) {
1893
+ console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
1894
+ for (const err of data.clientErrors) {
1895
+ console.log(chalk.red(` → ${err}`));
1896
+ }
1897
+ console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
1898
+ console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
1899
+ }
1900
+ if (!res.ok) {
1901
+ console.error(chalk.dim(JSON.stringify(data, null, 2)));
1902
+ failed++;
1903
+ }
1904
+ else {
1905
+ succeeded++;
1906
+ }
1907
+ }
1908
+ catch (error) {
1909
+ const msg = error instanceof Error ? error.message : String(error);
1910
+ console.error(prefix + chalk.red(`Error: Could not reach editor server at ${url}`));
1911
+ console.error(chalk.dim(` ${msg}`));
1912
+ console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
1913
+ failed++;
1217
1914
  }
1218
1915
  }
1219
- catch (error) {
1220
- const msg = error instanceof Error ? error.message : String(error);
1221
- console.error(chalk.red(`Error: Could not reach editor server at ${url}`));
1222
- console.error(chalk.dim(` ${msg}`));
1223
- console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
1916
+ if (isBatch) {
1917
+ console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
1918
+ }
1919
+ if (failed > 0) {
1224
1920
  process.exit(1);
1225
1921
  }
1226
1922
  }
@@ -1241,11 +1937,23 @@ async function handleDependents(entityName) {
1241
1937
  const progress = new ProgressReporter();
1242
1938
  progress.start('Loading entities from database...');
1243
1939
  await initializeEnvironment();
1244
- const allEntities = await loadEntities({});
1940
+ let allEntities = await loadEntities({});
1245
1941
  if (!allEntities || allEntities.length === 0) {
1246
- progress.fail('No entities found in database');
1247
- console.error(chalk.dim(' Run codeyam editor analyze-imports first to populate entity data.'));
1248
- process.exit(1);
1942
+ progress.succeed('No entities found running analyze-imports first...');
1943
+ try {
1944
+ await handleAnalyzeImports({ silent: true });
1945
+ }
1946
+ catch {
1947
+ console.error(chalk.red('Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
1948
+ process.exit(1);
1949
+ }
1950
+ // Reload entities after analysis
1951
+ const reloaded = await loadEntities({});
1952
+ if (!reloaded || reloaded.length === 0) {
1953
+ progress.fail('No entities found even after analyze-imports');
1954
+ process.exit(1);
1955
+ }
1956
+ allEntities = reloaded;
1249
1957
  }
1250
1958
  // Find the target entity by name (case-insensitive match)
1251
1959
  const targetEntities = allEntities.filter((e) => e.name.toLowerCase() === entityName.toLowerCase());
@@ -1332,15 +2040,16 @@ async function handleDependents(entityName) {
1332
2040
  * `codeyam editor change <feature>`
1333
2041
  *
1334
2042
  * Prints a condensed post-change checklist that guides Claude through
1335
- * re-verifying after user-requested modifications. This is the "change
1336
- * loop" it replaces the freeform "make changes" path with structured
1337
- * instructions that always loop back to step 12 review.
2043
+ * re-verifying after user-requested modifications. When called from
2044
+ * step 13+, this loops back to step 13 (present). When called from an
2045
+ * earlier step, it returns to that step so the normal flow continues.
1338
2046
  */
1339
2047
  function handleChange(feature) {
1340
2048
  const root = getProjectRoot();
2049
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
2050
+ const state = readState(root);
1341
2051
  if (!feature) {
1342
2052
  // Try to read feature from state
1343
- const state = readState(root);
1344
2053
  if (state?.feature) {
1345
2054
  feature = state.feature;
1346
2055
  }
@@ -1350,6 +2059,7 @@ function handleChange(feature) {
1350
2059
  process.exit(1);
1351
2060
  }
1352
2061
  }
2062
+ const currentStep = state?.step ?? 13;
1353
2063
  const port = getServerPort();
1354
2064
  console.log();
1355
2065
  console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
@@ -1357,36 +2067,108 @@ function handleChange(feature) {
1357
2067
  console.log();
1358
2068
  console.log('The user has requested changes. Follow this checklist after making them.');
1359
2069
  console.log();
2070
+ const designSystem = readDesignSystem(root);
2071
+ if (designSystem) {
2072
+ console.log(chalk.bold.magenta('Design System (active):'));
2073
+ console.log(chalk.magenta(' Use existing CSS custom property tokens when making visual changes — no hardcoded px or hex values.'));
2074
+ console.log(chalk.magenta(' Use var(--text-sm) not 14, var(--spacing-lg) not 16px, var(--text-primary) not #1A1B25.'));
2075
+ console.log();
2076
+ console.log(designSystem);
2077
+ console.log();
2078
+ }
2079
+ console.log(chalk.bold.cyan('Keep the preview moving:'));
2080
+ console.log(chalk.cyan(` Refresh after EACH individual change — not after all changes are done:`));
2081
+ console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
2082
+ printDimensionGuidance(dim, dimNames);
2083
+ console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
2084
+ console.log();
2085
+ console.log(chalk.bold('0. Close the results panel:'));
2086
+ checkbox(`Hide results: \`codeyam editor hide-results\``);
2087
+ console.log();
1360
2088
  console.log(chalk.bold('1. Re-register affected component scenarios:'));
1361
2089
  checkbox('For each component you modified, re-register ALL its scenarios');
1362
- console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","description":"...","componentName":"ComponentName","componentPath":"app/components/ComponentName.tsx"}'`));
1363
- checkbox('For any NEW components, create isolation routes and register scenarios');
2090
+ console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","componentName":"ComponentName","componentPath":"app/components/ComponentName.tsx","url":"/isolated-components/ComponentName?s=ScenarioName","dimensions":["${dim}"]}'`));
2091
+ checkbox('For any NEW components: `codeyam editor isolate NewComponent` then create isolation routes and register scenarios');
1364
2092
  console.log();
1365
2093
  console.log(chalk.bold('2. Re-run affected tests:'));
1366
2094
  checkbox('Re-run test files for any functions you modified');
1367
2095
  console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
1368
2096
  checkbox('If any test fails, fix the source code and re-run');
2097
+ checkbox('If you re-seeded the database, restart the dev server to pick up fresh data');
2098
+ console.log(chalk.dim(` codeyam editor dev-server '{"action":"restart"}'`));
1369
2099
  console.log();
1370
2100
  console.log(chalk.bold('3. Re-capture app-level scenarios:'));
1371
- checkbox('If component changes affect app-level pages, re-register affected app scenarios');
1372
- checkbox(`Use \`codeyam editor dependents ComponentName\` to find what needs recapturing`);
2101
+ checkbox('For each changed component, run `codeyam editor dependents ComponentName` to find affected pages');
2102
+ checkbox('Run `codeyam editor scenarios` to list all existing scenarios');
2103
+ checkbox('For EACH page listed by dependents: find its existing scenarios in the list and re-register every one');
2104
+ console.log(chalk.dim(' Shared components (headers, navbars, layouts) affect MANY pages — re-register all of them.'));
2105
+ console.log(chalk.dim(' Re-register with the SAME name to update — do NOT create new/duplicate scenarios.'));
2106
+ console.log(chalk.dim(' Example: if Header changed and Home, Catalog, Detail pages use it,'));
2107
+ console.log(chalk.dim(' re-register "Home - Default", "Catalog - Full", "Detail - WithReviews", etc.'));
2108
+ checkbox("Enrich existing scenario data to exercise the change — don't just re-register unchanged data");
2109
+ console.log(chalk.dim(' Add data that demonstrates what changed: new fields, relationships, states, content variety.'));
2110
+ checkbox('After each re-registration, view the screenshot to verify data is visible');
2111
+ console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
1373
2112
  console.log();
1374
2113
  console.log(chalk.bold('4. Verify completeness:'));
1375
- checkbox(`Refresh the preview: \`curl -s -X POST http://localhost:${port}/api/dev-mode-preview\``);
1376
- checkbox(`Navigate to key pages to verify changes: \`curl -s -X POST http://localhost:${port}/api/dev-mode-preview -H 'Content-Type: application/json' -d '{"path":"/your-route"}'\``);
2114
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
2115
+ checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
2116
+ printDimensionGuidance(dim, dimNames);
2117
+ checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
1377
2118
  checkbox('Run `codeyam editor audit` — all checks must pass');
1378
- checkbox(`Check for client-side errors: \`curl -s http://localhost:${port}/api/editor-client-errors\``);
2119
+ checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
2120
+ console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
2121
+ console.log(chalk.dim(' If `liveErrors>0`, there are JS errors in the preview — fix them.'));
1379
2122
  checkbox('Fix any errors, then re-register affected scenarios');
1380
- console.log();
1381
- console.log(chalk.bold('5. Update the journal:'));
1382
- checkbox(`Update journal entry with new screenshots: \`curl -s -X PATCH http://localhost:${port}/api/editor-journal-update -H 'Content-Type: application/json' -d '{"time":"<journal entry time>","description":"<updated>","includeSessionScenarios":true}'\``);
1383
- console.log(chalk.dim(' includeSessionScenarios refreshes ALL screenshots to reflect the changes'));
1384
- console.log();
1385
- console.log(chalk.bold.green('When all checks pass, run: ') +
1386
- chalk.bold(`codeyam editor 12`));
1387
- console.log(chalk.dim(' This re-enters the review step to present the updated summary to the user.'));
2123
+ checkbox('If changes affect setup, new dependencies, or new scripts: update `README.md` and `npm run setup`');
2124
+ console.log();
2125
+ console.log(chalk.bold('5. Update the existing journal entry:'));
2126
+ checkbox('Get existing entry: `codeyam editor journal-list`');
2127
+ checkbox('Find the uncommitted entry (commitSha is null) from this feature session');
2128
+ checkbox(`Update it: \`codeyam editor journal-update '{"time":"<entry time>","description":"<updated description including changes>","includeSessionScenarios":true}'\``);
2129
+ console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
2130
+ console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
2131
+ console.log();
2132
+ // If the change was initiated from a step before 13, return to that step
2133
+ // instead of jumping to step 13. The change workflow should only loop to
2134
+ // step 13 when changes are requested FROM step 13.
2135
+ if (currentStep < 13) {
2136
+ console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2137
+ console.log(chalk.red.bold(' REQUIRED: Return to current step'));
2138
+ console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
2139
+ chalk.bold(`codeyam editor ${currentStep}`));
2140
+ console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2141
+ }
2142
+ else {
2143
+ console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2144
+ console.log(chalk.red.bold(' REQUIRED: Show Results'));
2145
+ console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
2146
+ chalk.bold(`codeyam editor 13`));
2147
+ console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
2148
+ console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2149
+ }
1388
2150
  console.log();
1389
2151
  }
2152
+ // ─── Audit gate ─────────────────────────────────────────────────────
2153
+ /**
2154
+ * Silently check whether the audit passes. Returns true if allPassing,
2155
+ * false if any issues remain or the server is unreachable.
2156
+ * Used as a hard gate before steps 8+.
2157
+ */
2158
+ async function checkAuditGate() {
2159
+ const port = getServerPort();
2160
+ try {
2161
+ const res = await fetch(`http://localhost:${port}/api/editor-audit`);
2162
+ if (!res.ok)
2163
+ return false;
2164
+ const data = await res.json();
2165
+ return data?.summary?.allPassing === true;
2166
+ }
2167
+ catch {
2168
+ // Server not running or unreachable — can't verify, so don't block
2169
+ return true;
2170
+ }
2171
+ }
1390
2172
  // ─── Audit subcommand ────────────────────────────────────────────────
1391
2173
  /**
1392
2174
  * `codeyam editor audit`
@@ -1421,10 +2203,25 @@ async function handleAudit() {
1421
2203
  console.log(chalk.bold('Components (scenarios):'));
1422
2204
  for (const c of components) {
1423
2205
  const icon = c.status === 'ok' ? chalk.green('✓') : chalk.red('✗');
1424
- const count = c.status === 'ok'
1425
- ? chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`)
1426
- : chalk.red('no scenarios registered');
1427
- console.log(` ${icon} ${c.name}${count}`);
2206
+ let detail;
2207
+ if (c.status === 'has_errors') {
2208
+ detail = chalk.red(`${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
2209
+ }
2210
+ else if (c.status === 'ok') {
2211
+ detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
2212
+ }
2213
+ else {
2214
+ detail = chalk.red(' — no scenarios registered');
2215
+ }
2216
+ console.log(` ${icon} ${c.name}${detail}`);
2217
+ if (c.clientErrors && c.clientErrors.length > 0) {
2218
+ for (const err of c.clientErrors.slice(0, 3)) {
2219
+ console.log(chalk.red(` → ${err}`));
2220
+ }
2221
+ if (c.clientErrors.length > 3) {
2222
+ console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
2223
+ }
2224
+ }
1428
2225
  }
1429
2226
  console.log();
1430
2227
  }
@@ -1433,11 +2230,24 @@ async function handleAudit() {
1433
2230
  console.log(chalk.bold('Functions (test files):'));
1434
2231
  for (const f of functions) {
1435
2232
  const icon = f.status === 'ok' ? chalk.green('✓') : chalk.red('✗');
1436
- const detail = f.status === 'ok'
1437
- ? chalk.dim(` (${f.testFile})`)
1438
- : f.testFile
1439
- ? chalk.red(` — test file missing: ${f.testFile}`)
1440
- : chalk.red(' — no test file specified');
2233
+ let detail;
2234
+ switch (f.status) {
2235
+ case 'ok':
2236
+ detail = chalk.dim(` (${f.testFile})`);
2237
+ break;
2238
+ case 'failing':
2239
+ detail = chalk.red(` — tests failing: ${f.testFile}`);
2240
+ break;
2241
+ case 'name_mismatch':
2242
+ detail = chalk.red(` — tests pass but missing top-level describe("${f.name}", ...) — tests won't display in UI`);
2243
+ break;
2244
+ case 'missing':
2245
+ default:
2246
+ detail = f.testFile
2247
+ ? chalk.red(` — test file missing: ${f.testFile}`)
2248
+ : chalk.red(' — no test file specified');
2249
+ break;
2250
+ }
1441
2251
  console.log(` ${icon} ${f.name}${detail}`);
1442
2252
  }
1443
2253
  console.log();
@@ -1453,15 +2263,35 @@ async function handleAudit() {
1453
2263
  if (summary.componentsMissing > 0) {
1454
2264
  parts.push(`${summary.componentsMissing} component${summary.componentsMissing !== 1 ? 's' : ''} missing scenarios`);
1455
2265
  }
2266
+ if (summary.componentsWithErrors > 0) {
2267
+ parts.push(`${summary.componentsWithErrors} component${summary.componentsWithErrors !== 1 ? 's' : ''} with client errors`);
2268
+ }
1456
2269
  if (summary.functionsMissing > 0) {
1457
2270
  parts.push(`${summary.functionsMissing} function${summary.functionsMissing !== 1 ? 's' : ''} missing tests`);
1458
2271
  }
2272
+ if (summary.functionsFailing > 0) {
2273
+ parts.push(`${summary.functionsFailing} function${summary.functionsFailing !== 1 ? 's' : ''} with failing tests`);
2274
+ }
2275
+ if (summary.functionsNameMismatch > 0) {
2276
+ parts.push(`${summary.functionsNameMismatch} function${summary.functionsNameMismatch !== 1 ? 's' : ''} with test name mismatch (missing top-level describe)`);
2277
+ }
1459
2278
  console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
1460
2279
  }
1461
2280
  console.log();
1462
2281
  if (!allOk) {
1463
2282
  process.exit(1);
1464
2283
  }
2284
+ // Auto-run analyze-imports when audit passes — this builds the import graph
2285
+ // so the App tab can show component/function dependencies for each page.
2286
+ // Previously this was a manual step that Claude had to remember to run.
2287
+ console.log(chalk.dim('Building import graph...'));
2288
+ try {
2289
+ await handleAnalyzeImports({ silent: true });
2290
+ }
2291
+ catch {
2292
+ // Non-fatal — audit passed, import graph is a bonus
2293
+ console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
2294
+ }
1465
2295
  }
1466
2296
  // ─── Scenarios subcommand ─────────────────────────────────────────────
1467
2297
  async function handleScenarios() {
@@ -1549,8 +2379,347 @@ async function handleScenarios() {
1549
2379
  const total = scenarios.length;
1550
2380
  const groupCount = groups.size;
1551
2381
  console.log(chalk.dim(`${total} scenario${total !== 1 ? 's' : ''} across ${groupCount} component${groupCount !== 1 ? 's' : ''}`));
2382
+ // Show screenshot paths so Claude can verify images with the Read tool
2383
+ const withScreenshots = scenarios.filter((s) => s.screenshotPath);
2384
+ if (withScreenshots.length > 0) {
2385
+ console.log();
2386
+ console.log(chalk.bold.cyan('Screenshots:'));
2387
+ for (const s of withScreenshots) {
2388
+ console.log(chalk.dim(` ${s.name}: ${s.screenshotPath}`));
2389
+ }
2390
+ }
2391
+ console.log();
2392
+ }
2393
+ // ─── Scenario Coverage subcommand ───────────────────────────────────
2394
+ async function handleScenarioCoverage() {
2395
+ const port = getServerPort();
2396
+ const url = `http://localhost:${port}/api/editor-scenario-coverage`;
2397
+ let data;
2398
+ try {
2399
+ const res = await fetch(url);
2400
+ if (!res.ok) {
2401
+ console.error(chalk.red(`Error: Scenario coverage endpoint returned ${res.status}`));
2402
+ process.exit(1);
2403
+ }
2404
+ data = await res.json();
2405
+ }
2406
+ catch (err) {
2407
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
2408
+ console.error(chalk.dim(` ${err.message}`));
2409
+ process.exit(1);
2410
+ }
2411
+ console.log();
2412
+ console.log(chalk.bold.cyan('━━━ Scenario Coverage ━━━'));
2413
+ console.log();
2414
+ if (data.note) {
2415
+ console.log(chalk.yellow(data.note));
2416
+ console.log();
2417
+ return;
2418
+ }
2419
+ // Fresh scenarios (already recaptured this feature)
2420
+ if (data.freshScenarios.length > 0) {
2421
+ console.log(chalk.green(`✓ ${data.freshScenarios.length} scenario(s) captured this feature:`));
2422
+ for (const s of data.freshScenarios) {
2423
+ console.log(chalk.dim(` ${s.name} (${s.url || '/'})`));
2424
+ }
2425
+ console.log();
2426
+ }
2427
+ // Stale scenarios (need recapturing)
2428
+ if (data.staleScenarios.length > 0) {
2429
+ console.log(chalk.red(`✗ ${data.staleScenarios.length} scenario(s) need re-registering (stale screenshots):`));
2430
+ for (const s of data.staleScenarios) {
2431
+ console.log(chalk.yellow(` ${s.name} (${s.url || '/'}) — ${s.changeStatus} but not recaptured`));
2432
+ }
2433
+ console.log();
2434
+ console.log(chalk.yellow('Re-register each stale scenario with the SAME name to update its screenshot.'));
2435
+ console.log(chalk.yellow("Enhance the seed data to reflect this feature's changes where relevant."));
2436
+ console.log();
2437
+ }
2438
+ // Uncovered pages (changed but no scenarios at all)
2439
+ if (data.uncoveredPages.length > 0) {
2440
+ console.log(chalk.red(`✗ ${data.uncoveredPages.length} page(s) affected by changes with no scenarios:`));
2441
+ for (const p of data.uncoveredPages) {
2442
+ console.log(chalk.yellow(` ${p.entityName} — ${p.changeStatus}`));
2443
+ }
2444
+ console.log();
2445
+ }
2446
+ // Summary
2447
+ if (data.pass) {
2448
+ console.log(chalk.green.bold('PASS — all affected scenarios are fresh'));
2449
+ }
2450
+ else {
2451
+ console.log(chalk.red.bold('FAIL — re-register stale scenarios before proceeding'));
2452
+ }
1552
2453
  console.log();
1553
2454
  }
2455
+ // ─── Template subcommand ─────────────────────────────────────────────
2456
+ async function handleTemplate() {
2457
+ const root = getProjectRoot();
2458
+ const port = getServerPort();
2459
+ if (hasProject(root)) {
2460
+ console.log(chalk.yellow('Project already exists (package.json found). Skipping.'));
2461
+ return;
2462
+ }
2463
+ // Determine which template to use from editor state
2464
+ const state = readState(root);
2465
+ const techStackId = state?.techStackId;
2466
+ const stack = techStackId
2467
+ ? TECH_STACKS.find((s) => s.id === techStackId)
2468
+ : undefined;
2469
+ const templateName = stack?.templateDir || 'nextjs-prisma-sqlite';
2470
+ const templateDir = path.join(__dirname, '..', '..', 'templates', templateName);
2471
+ if (!fs.existsSync(templateDir)) {
2472
+ console.error(chalk.red('Error: Template directory not found at ' + templateDir));
2473
+ process.exit(1);
2474
+ }
2475
+ const { execSync } = await import('child_process');
2476
+ // 1. Copy template files
2477
+ console.log(chalk.bold(`Copying ${stack?.name || templateName} template files...`));
2478
+ execSync(`cp -r ${templateDir}/* .`, { cwd: root, stdio: 'inherit' });
2479
+ // Copy dotfiles that are stored without the dot prefix in templates
2480
+ const gitignorePath = path.join(templateDir, 'gitignore');
2481
+ if (fs.existsSync(gitignorePath)) {
2482
+ execSync(`cp ${templateDir}/gitignore .gitignore`, {
2483
+ cwd: root,
2484
+ stdio: 'inherit',
2485
+ });
2486
+ }
2487
+ const envPath = path.join(templateDir, 'env');
2488
+ if (fs.existsSync(envPath)) {
2489
+ execSync(`cp ${templateDir}/env .env`, { cwd: root, stdio: 'inherit' });
2490
+ }
2491
+ // Copy seed adapter into .codeyam/ (stored outside .codeyam/ in the template
2492
+ // to avoid gitignore issues — .codeyam/ is selectively ignored in user projects)
2493
+ const seedAdapterSrc = path.join(templateDir, 'seed-adapter.ts');
2494
+ if (fs.existsSync(seedAdapterSrc)) {
2495
+ const codeyamDir = path.join(root, '.codeyam');
2496
+ if (!fs.existsSync(codeyamDir)) {
2497
+ fs.mkdirSync(codeyamDir, { recursive: true });
2498
+ }
2499
+ fs.copyFileSync(seedAdapterSrc, path.join(codeyamDir, 'seed-adapter.ts'));
2500
+ }
2501
+ console.log(chalk.green(' Template files copied.'));
2502
+ // 2. Install dependencies
2503
+ console.log(chalk.bold('Installing dependencies...'));
2504
+ execSync('npm install', { cwd: root, stdio: 'inherit' });
2505
+ console.log(chalk.green(' Dependencies installed.'));
2506
+ // 3. Initialize git
2507
+ const hasGit = fs.existsSync(path.join(root, '.git'));
2508
+ if (!hasGit) {
2509
+ console.log(chalk.bold('Initializing git...'));
2510
+ execSync('git init && git add -A && git commit -m "Initial commit"', {
2511
+ cwd: root,
2512
+ stdio: 'inherit',
2513
+ });
2514
+ console.log(chalk.green(' Git initialized.'));
2515
+ }
2516
+ // 4. Run codeyam init
2517
+ console.log(chalk.bold('Running codeyam init...'));
2518
+ await initCommand.handler({
2519
+ force: true,
2520
+ 'keep-server': true,
2521
+ autoInit: false,
2522
+ $0: '',
2523
+ _: [],
2524
+ });
2525
+ console.log(chalk.green(' CodeYam initialized.'));
2526
+ // 5. Verify config has startCommand
2527
+ const configPath = path.join(root, '.codeyam', 'config.json');
2528
+ if (fs.existsSync(configPath)) {
2529
+ try {
2530
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
2531
+ const webapps = config.webapps || [];
2532
+ const hasStartCommand = webapps.some((w) => w.startCommand);
2533
+ if (!hasStartCommand) {
2534
+ console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
2535
+ console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
2536
+ }
2537
+ }
2538
+ catch {
2539
+ // Config parse error is non-fatal
2540
+ }
2541
+ }
2542
+ // 5b. Write a fresh prototypeId so the proxy clears stale localStorage
2543
+ const activeScenarioPath = path.join(root, '.codeyam', 'active-scenario.json');
2544
+ fs.writeFileSync(activeScenarioPath, JSON.stringify({ prototypeId: Date.now().toString() }), 'utf-8');
2545
+ // 6. Trigger editor-refresh so the server picks up the new project
2546
+ console.log(chalk.bold('Refreshing editor...'));
2547
+ try {
2548
+ await fetch(`http://localhost:${port}/api/editor-refresh`, {
2549
+ method: 'POST',
2550
+ });
2551
+ console.log(chalk.green(' Editor refreshed.'));
2552
+ }
2553
+ catch {
2554
+ console.log(chalk.yellow(' Could not reach the CodeYam server for refresh. You may need to restart it.'));
2555
+ }
2556
+ console.log();
2557
+ console.log(chalk.green.bold('Project scaffolded and ready!'));
2558
+ console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
2559
+ }
2560
+ // ─── Sync subcommand ─────────────────────────────────────────────────
2561
+ /**
2562
+ * `codeyam editor sync`
2563
+ * Import scenarios from editor-scenarios/ files into the local database.
2564
+ * Falls back to legacy scenarios-manifest.json if no _metadata files found.
2565
+ */
2566
+ async function handleSync() {
2567
+ const root = getProjectRoot();
2568
+ const entries = scanScenarioFiles(root);
2569
+ if (entries.length === 0) {
2570
+ console.log(chalk.yellow('No scenario files with metadata found. Nothing to sync.'));
2571
+ return;
2572
+ }
2573
+ const configPath = path.join(root, '.codeyam', 'config.json');
2574
+ let projectSlug = null;
2575
+ try {
2576
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
2577
+ projectSlug = config.projectSlug || null;
2578
+ }
2579
+ catch {
2580
+ // fall through
2581
+ }
2582
+ if (!projectSlug) {
2583
+ console.error(chalk.red('Error: No project slug found. Run codeyam init first.'));
2584
+ process.exit(1);
2585
+ }
2586
+ const connectionOk = await withoutSpinner(() => testEnvironment());
2587
+ if (!connectionOk) {
2588
+ errorLog('Environment validation failed');
2589
+ return;
2590
+ }
2591
+ const { project } = await requireBranchAndProject(projectSlug);
2592
+ const { getDatabase } = await import('../../../packages/database/index.js');
2593
+ const db = getDatabase();
2594
+ const existingRows = await db
2595
+ .selectFrom('editor_scenarios')
2596
+ .select(['id', 'updated_at'])
2597
+ .where('project_id', '=', project.id)
2598
+ .execute();
2599
+ const result = await syncScenarioFilesToDatabase(root, project.id, existingRows, async (row) => {
2600
+ await db
2601
+ .insertInto('editor_scenarios')
2602
+ .values(row)
2603
+ .execute();
2604
+ }, async (id, row) => {
2605
+ await db
2606
+ .updateTable('editor_scenarios')
2607
+ .set(row)
2608
+ .where('id', '=', id)
2609
+ .execute();
2610
+ });
2611
+ if (result.inserted === 0 && result.updated === 0) {
2612
+ console.log(chalk.dim('All scenarios up to date.'));
2613
+ }
2614
+ else {
2615
+ const parts = [];
2616
+ if (result.inserted > 0)
2617
+ parts.push(`${result.inserted} imported`);
2618
+ if (result.updated > 0)
2619
+ parts.push(`${result.updated} updated`);
2620
+ if (result.skipped > 0)
2621
+ parts.push(`${result.skipped} unchanged`);
2622
+ console.log(chalk.green(`Synced scenarios: ${parts.join(', ')}`));
2623
+ }
2624
+ // Migrate legacy scenario formats after sync, then re-sync if anything was fixed
2625
+ try {
2626
+ const migrateResult = migrateScenarioFormats(root);
2627
+ if (migrateResult.fixed > 0) {
2628
+ console.log(chalk.green(`Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
2629
+ // Re-sync so the DB reflects the corrected file metadata
2630
+ const refreshedRows = await db
2631
+ .selectFrom('editor_scenarios')
2632
+ .select(['id', 'updated_at'])
2633
+ .where('project_id', '=', project.id)
2634
+ .execute();
2635
+ await syncScenarioFilesToDatabase(root, project.id, refreshedRows, async (row) => {
2636
+ await db
2637
+ .insertInto('editor_scenarios')
2638
+ .values(row)
2639
+ .execute();
2640
+ }, async (id, row) => {
2641
+ await db
2642
+ .updateTable('editor_scenarios')
2643
+ .set(row)
2644
+ .where('id', '=', id)
2645
+ .execute();
2646
+ });
2647
+ }
2648
+ }
2649
+ catch {
2650
+ // Non-fatal
2651
+ }
2652
+ }
2653
+ // ─── Verify Images subcommand ─────────────────────────────────────────
2654
+ async function handleVerifyImages(jsonArg) {
2655
+ const port = getServerPort();
2656
+ const { parseVerifyImagesInput, extractImageUrls, resolveImageUrl, verifyImageUrls, buildVerifyImagesReport, extractImageUrlsFromScenarioFiles, } = await import('../utils/editorImageVerifier.js');
2657
+ let paths;
2658
+ let explicitImageUrls;
2659
+ try {
2660
+ const input = parseVerifyImagesInput(jsonArg);
2661
+ paths = input.paths;
2662
+ explicitImageUrls = input.imageUrls;
2663
+ }
2664
+ catch {
2665
+ console.error(chalk.red('Error: Invalid JSON argument'));
2666
+ process.exit(1);
2667
+ }
2668
+ // Get dev server URL
2669
+ let devServerUrl;
2670
+ try {
2671
+ const res = await fetch(`http://localhost:${port}/api/editor-dev-server`);
2672
+ const data = await res.json();
2673
+ devServerUrl = data.url || `http://localhost:${data.port || 3000}`;
2674
+ }
2675
+ catch {
2676
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
2677
+ process.exit(1);
2678
+ }
2679
+ const allUrls = new Set();
2680
+ let pagesChecked = 0;
2681
+ for (const pagePath of paths) {
2682
+ const pageUrl = `${devServerUrl}${pagePath}`;
2683
+ try {
2684
+ const res = await fetch(pageUrl);
2685
+ if (!res.ok) {
2686
+ console.error(chalk.red(` ✗ ${pagePath} — HTTP ${res.status}`));
2687
+ continue;
2688
+ }
2689
+ const html = await res.text();
2690
+ const imageUrls = extractImageUrls(html);
2691
+ for (const url of imageUrls) {
2692
+ allUrls.add(resolveImageUrl(url, devServerUrl));
2693
+ }
2694
+ pagesChecked++;
2695
+ }
2696
+ catch (err) {
2697
+ console.error(chalk.red(` ✗ ${pagePath} — ${err.message}`));
2698
+ }
2699
+ }
2700
+ // Add explicitly-provided image URLs (Claude passes these directly)
2701
+ for (const url of explicitImageUrls) {
2702
+ allUrls.add(resolveImageUrl(url, devServerUrl));
2703
+ }
2704
+ // Also scan scenario mock data files for image URLs (client-rendered pages)
2705
+ const scenariosDir = path.join(getProjectRoot(), '.codeyam', 'editor-scenarios');
2706
+ const { urls: scenarioUrls, filesScanned: scenarioFilesScanned } = extractImageUrlsFromScenarioFiles(scenariosDir);
2707
+ for (const url of scenarioUrls) {
2708
+ allUrls.add(resolveImageUrl(url, devServerUrl));
2709
+ }
2710
+ const urlList = [...allUrls];
2711
+ const results = urlList.length > 0 ? await verifyImageUrls(urlList) : [];
2712
+ const report = buildVerifyImagesReport({
2713
+ pagesChecked,
2714
+ imageUrls: urlList,
2715
+ results,
2716
+ scenarioFilesScanned,
2717
+ });
2718
+ console.log(JSON.stringify(report, null, 2));
2719
+ if (report.failures.length > 0) {
2720
+ process.exit(1);
2721
+ }
2722
+ }
1554
2723
  // ─── Debug subcommand ────────────────────────────────────────────────
1555
2724
  function handleEditorDebug(args) {
1556
2725
  const root = getProjectRoot();
@@ -1611,8 +2780,12 @@ function handleEditorDebug(args) {
1611
2780
  10: printStep10,
1612
2781
  11: printStep11,
1613
2782
  12: printStep12,
2783
+ 13: printStep13,
2784
+ 14: printStep14,
2785
+ 15: printStep15,
2786
+ 16: printStep16,
1614
2787
  };
1615
- for (let step = 1; step <= 12; step++) {
2788
+ for (let step = 1; step <= 16; step++) {
1616
2789
  const stepId = `step-${step}`;
1617
2790
  if (!wants(stepId))
1618
2791
  continue;
@@ -1691,8 +2864,8 @@ const editorCommand = {
1691
2864
  describe: 'Editor mode guided workflow',
1692
2865
  builder: (yargs) => {
1693
2866
  const stepDescription = IS_INTERNAL_BUILD
1694
- ? 'Step number (1-12) or subcommand (register, analyze-imports, dependents, audit, scenarios, change, debug)'
1695
- : 'Step number (1-12) or subcommand (register, analyze-imports, dependents, audit, scenarios, change)';
2867
+ ? 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)'
2868
+ : 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)';
1696
2869
  let builder = yargs
1697
2870
  .positional('step', {
1698
2871
  type: 'string',
@@ -1705,12 +2878,30 @@ const editorCommand = {
1705
2878
  .option('feature', {
1706
2879
  type: 'string',
1707
2880
  describe: 'Feature name (required for step 2)',
2881
+ })
2882
+ .option('app-formats', {
2883
+ type: 'string',
2884
+ describe: 'Comma-separated app formats (mobile-responsive-web-app, desktop-app, mobile-app)',
2885
+ })
2886
+ .option('tech-stack', {
2887
+ type: 'string',
2888
+ describe: 'Selected tech stack ID (e.g., nextjs-prisma-sqlite)',
2889
+ })
2890
+ .option('prompt', {
2891
+ type: 'string',
2892
+ describe: "User's original prompt (captured for journal/results)",
2893
+ })
2894
+ .option('port', {
2895
+ type: 'number',
2896
+ alias: 'p',
2897
+ describe: 'Port to run the web server on',
2898
+ default: 3111,
1708
2899
  });
1709
2900
  if (IS_INTERNAL_BUILD) {
1710
2901
  builder = builder
1711
2902
  .option('target', {
1712
2903
  type: 'string',
1713
- describe: 'Debug target (setup, overview, overview-with-state, step-1..step-12, or comma-separated list)',
2904
+ describe: 'Debug target (setup, overview, overview-with-state, step-1..step-16, or comma-separated list)',
1714
2905
  })
1715
2906
  .option('resume', {
1716
2907
  type: 'boolean',
@@ -1730,6 +2921,36 @@ const editorCommand = {
1730
2921
  },
1731
2922
  handler: async (argv) => {
1732
2923
  const root = getProjectRoot();
2924
+ // API subcommands: preview, show-results, hide-results, commit,
2925
+ // journal, journal-update, dev-server, client-errors
2926
+ if (argv.step && EDITOR_API_SUBCOMMANDS.includes(argv.step)) {
2927
+ const port = getServerPort();
2928
+ try {
2929
+ const request = buildEditorApiRequest(argv.step, argv.json || undefined);
2930
+ const result = await callEditorApi(request, port);
2931
+ if (!result.ok) {
2932
+ console.error(chalk.red(`Error: ${request.endpoint} returned ${result.status}`));
2933
+ if (result.data)
2934
+ console.error(JSON.stringify(result.data, null, 2));
2935
+ process.exit(1);
2936
+ }
2937
+ if (result.data != null) {
2938
+ const summary = formatApiSubcommandResult(argv.step, result.data);
2939
+ if (summary) {
2940
+ console.log(summary);
2941
+ }
2942
+ else {
2943
+ console.log(JSON.stringify(result.data, null, 2));
2944
+ }
2945
+ }
2946
+ }
2947
+ catch (err) {
2948
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
2949
+ console.error(chalk.dim(` ${err.message}`));
2950
+ process.exit(1);
2951
+ }
2952
+ return;
2953
+ }
1733
2954
  // Subcommand: codeyam editor register '{"name":"..."}'
1734
2955
  if (argv.step === 'register') {
1735
2956
  await handleRegister(argv.json || '');
@@ -1755,11 +2976,47 @@ const editorCommand = {
1755
2976
  await handleScenarios();
1756
2977
  return;
1757
2978
  }
2979
+ // Subcommand: codeyam editor scenario-coverage
2980
+ if (argv.step === 'scenario-coverage') {
2981
+ await handleScenarioCoverage();
2982
+ return;
2983
+ }
1758
2984
  // Subcommand: codeyam editor change <feature>
1759
2985
  if (argv.step === 'change') {
1760
2986
  handleChange(argv.json || '');
1761
2987
  return;
1762
2988
  }
2989
+ // Subcommand: codeyam editor verify-images '{"paths":["/","/drinks/1"]}'
2990
+ if (argv.step === 'verify-images') {
2991
+ await handleVerifyImages(argv.json || '');
2992
+ return;
2993
+ }
2994
+ // Subcommand: codeyam editor validate-seed '{"products":[...]}'
2995
+ if (argv.step === 'validate-seed') {
2996
+ await handleValidateSeed(argv.json || '');
2997
+ return;
2998
+ }
2999
+ // Subcommand: codeyam editor isolate "StarRating CategoryBadge DrinkCard"
3000
+ if (argv.step === 'isolate') {
3001
+ const names = [];
3002
+ if (argv.json)
3003
+ names.push(...argv.json.split(/[\s,]+/).filter(Boolean));
3004
+ // Collect any extra positional args (yargs puts them in argv._)
3005
+ const extras = argv._.filter((a) => typeof a === 'string' && a !== 'editor');
3006
+ names.push(...extras);
3007
+ handleIsolate(names);
3008
+ return;
3009
+ }
3010
+ // Subcommand: codeyam editor template — scaffold project from template
3011
+ if (argv.step === 'template') {
3012
+ await handleTemplate();
3013
+ return;
3014
+ }
3015
+ // Subcommand: codeyam editor sync — import scenarios from manifest
3016
+ if (argv.step === 'sync') {
3017
+ await handleSync();
3018
+ return;
3019
+ }
1763
3020
  // Subcommand: codeyam editor debug [--target ...]
1764
3021
  if (argv.step === 'debug') {
1765
3022
  if (!IS_INTERNAL_BUILD) {
@@ -1769,27 +3026,289 @@ const editorCommand = {
1769
3026
  await handleEditorDebug(argv);
1770
3027
  return;
1771
3028
  }
1772
- const step = argv.step ? parseInt(argv.step, 10) : undefined;
1773
- if (step != null && (isNaN(step) || step < 1 || step > 12)) {
1774
- console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-12.`));
1775
- process.exit(1);
1776
- }
1777
- if (step == null) {
1778
- // No step specified: setup or overview
3029
+ // Subcommand: codeyam editor steps show setup or cycle overview
3030
+ if (argv.step === 'steps') {
1779
3031
  if (!hasProject(root)) {
1780
3032
  printSetup(root);
1781
3033
  }
1782
3034
  else {
1783
3035
  const state = readState(root);
3036
+ // Clear prompt file when feature is done (step 16) so the hook
3037
+ // can capture the next feature request from the user.
3038
+ if (state?.step === 16) {
3039
+ clearEditorUserPrompt(root);
3040
+ }
1784
3041
  printCycleOverview(root, state);
1785
3042
  }
1786
3043
  return;
1787
3044
  }
3045
+ const step = argv.step ? parseInt(argv.step, 10) : undefined;
3046
+ if (step != null && (isNaN(step) || step < 1 || step > 16)) {
3047
+ console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-16.`));
3048
+ process.exit(1);
3049
+ }
3050
+ if (step == null) {
3051
+ // No step specified: launch editor server + open browser
3052
+ // Auto-init if needed
3053
+ let projectRoot = getStateProjectRoot();
3054
+ if (!projectRoot) {
3055
+ await initCommand.handler({
3056
+ force: false,
3057
+ autoInit: true,
3058
+ $0: '',
3059
+ _: [],
3060
+ });
3061
+ projectRoot = getStateProjectRoot();
3062
+ if (!projectRoot) {
3063
+ return;
3064
+ }
3065
+ }
3066
+ const configPath = path.join(projectRoot, '.codeyam', 'config.json');
3067
+ try {
3068
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
3069
+ const { projectSlug } = config;
3070
+ if (!projectSlug) {
3071
+ errorLog('Missing project slug. Try reinitializing with: codeyam init --force');
3072
+ return;
3073
+ }
3074
+ const connectionOk = await withoutSpinner(() => testEnvironment());
3075
+ if (!connectionOk) {
3076
+ errorLog('Environment validation failed');
3077
+ return;
3078
+ }
3079
+ const { project, branch } = await requireBranchAndProject(projectSlug);
3080
+ // Backfill _metadata into existing scenario files that predate the embedded metadata feature.
3081
+ // This reads metadata from the DB and writes it into files that don't have _metadata yet.
3082
+ try {
3083
+ const { getDatabase } = await import('../../../packages/database/index.js');
3084
+ const db = getDatabase();
3085
+ const dbScenarios = await db
3086
+ .selectFrom('editor_scenarios')
3087
+ .select([
3088
+ 'id',
3089
+ 'name',
3090
+ 'description',
3091
+ 'component_name',
3092
+ 'component_path',
3093
+ 'url',
3094
+ 'type',
3095
+ 'screenshot_path',
3096
+ 'viewport_width',
3097
+ 'viewport_height',
3098
+ 'created_at',
3099
+ 'updated_at',
3100
+ ])
3101
+ .where('project_id', '=', project.id)
3102
+ .execute();
3103
+ if (dbScenarios.length > 0) {
3104
+ const backfilled = backfillScenarioMetadata(projectRoot, dbScenarios);
3105
+ if (backfilled > 0) {
3106
+ console.log(chalk.green(` Backfilled metadata into ${backfilled} scenario file${backfilled > 1 ? 's' : ''}`));
3107
+ }
3108
+ }
3109
+ }
3110
+ catch {
3111
+ // Non-fatal — backfill is best-effort
3112
+ }
3113
+ // Auto-sync scenario files (with _metadata) to database
3114
+ const scenarioEntries = scanScenarioFiles(projectRoot);
3115
+ if (scenarioEntries.length > 0) {
3116
+ try {
3117
+ const { getDatabase } = await import('../../../packages/database/index.js');
3118
+ const db = getDatabase();
3119
+ const existingRows = await db
3120
+ .selectFrom('editor_scenarios')
3121
+ .select(['id', 'updated_at'])
3122
+ .where('project_id', '=', project.id)
3123
+ .execute();
3124
+ const syncResult = await syncScenarioFilesToDatabase(projectRoot, project.id, existingRows, async (row) => {
3125
+ await db
3126
+ .insertInto('editor_scenarios')
3127
+ .values(row)
3128
+ .execute();
3129
+ }, async (id, row) => {
3130
+ await db
3131
+ .updateTable('editor_scenarios')
3132
+ .set(row)
3133
+ .where('id', '=', id)
3134
+ .execute();
3135
+ });
3136
+ if (syncResult.inserted > 0 || syncResult.updated > 0) {
3137
+ const parts = [];
3138
+ if (syncResult.inserted > 0)
3139
+ parts.push(`${syncResult.inserted} imported`);
3140
+ if (syncResult.updated > 0)
3141
+ parts.push(`${syncResult.updated} updated`);
3142
+ console.log(chalk.green(` Synced scenarios from files: ${parts.join(', ')}`));
3143
+ }
3144
+ }
3145
+ catch {
3146
+ // Non-fatal — sync failure shouldn't block editor startup
3147
+ }
3148
+ }
3149
+ // Migrate legacy scenario formats: resolve null viewports, populate
3150
+ // dimensions arrays, and build screenshotPaths maps from single values.
3151
+ // If files were fixed, re-sync to update the database with corrected values.
3152
+ try {
3153
+ const migrateResult = migrateScenarioFormats(projectRoot);
3154
+ if (migrateResult.fixed > 0) {
3155
+ console.log(chalk.green(` Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
3156
+ // Re-sync so the DB reflects the fixed file metadata
3157
+ try {
3158
+ const { getDatabase } = await import('../../../packages/database/index.js');
3159
+ const db = getDatabase();
3160
+ const rows = await db
3161
+ .selectFrom('editor_scenarios')
3162
+ .select(['id', 'updated_at'])
3163
+ .where('project_id', '=', project.id)
3164
+ .execute();
3165
+ await syncScenarioFilesToDatabase(projectRoot, project.id, rows, async (row) => {
3166
+ await db
3167
+ .insertInto('editor_scenarios')
3168
+ .values(row)
3169
+ .execute();
3170
+ }, async (id, row) => {
3171
+ await db
3172
+ .updateTable('editor_scenarios')
3173
+ .set(row)
3174
+ .where('id', '=', id)
3175
+ .execute();
3176
+ });
3177
+ }
3178
+ catch {
3179
+ // Non-fatal — DB re-sync failure shouldn't block startup
3180
+ }
3181
+ }
3182
+ }
3183
+ catch {
3184
+ // Non-fatal — migration failure shouldn't block editor startup
3185
+ }
3186
+ // `codeyam editor` (no step) always implies editor mode.
3187
+ // The empty-folder heuristic is no longer needed here — running this
3188
+ // command IS the signal. We still detect empty folders so that
3189
+ // the very first `codeyam editor` in a bare directory works before
3190
+ // metadata has been persisted.
3191
+ const editorMode = true;
3192
+ if (editorMode && !project.metadata?.labs?.simulations) {
3193
+ try {
3194
+ await updateProjectMetadata({
3195
+ projectSlug,
3196
+ metadataUpdate: {
3197
+ editorMode: true,
3198
+ labs: { simulations: true },
3199
+ },
3200
+ });
3201
+ const refreshed = await requireBranchAndProject(projectSlug);
3202
+ Object.assign(project, refreshed.project);
3203
+ }
3204
+ catch {
3205
+ // Non-fatal
3206
+ }
3207
+ }
3208
+ // Install skills/hooks
3209
+ const simulationsEnabled = project.metadata?.labs?.simulations ?? false;
3210
+ const skillMode = simulationsEnabled
3211
+ ? 'full'
3212
+ : editorMode
3213
+ ? 'editor'
3214
+ : 'memory';
3215
+ await installClaudeCodeSkills(projectRoot, {
3216
+ mode: skillMode,
3217
+ editorMode,
3218
+ });
3219
+ setupClaudeCodeSettings(projectRoot, {
3220
+ mode: skillMode,
3221
+ editorMode,
3222
+ });
3223
+ // Auto-finalize analyzer so codeyam analyze works
3224
+ if (editorMode) {
3225
+ const stepLabels = {
3226
+ 'npm-install': 'Installing simulation dependencies...',
3227
+ 'playwright-install': 'Installing browser (Chromium)...',
3228
+ build: 'Building simulation engine...',
3229
+ };
3230
+ const simProgress = new ProgressReporter();
3231
+ const finalization = ensureAnalyzerFinalized({
3232
+ onProgress: (step) => simProgress.start(stepLabels[step]),
3233
+ });
3234
+ if (finalization.errors.length > 0) {
3235
+ for (const err of finalization.errors) {
3236
+ simProgress.warn(`${stepLabels[err.step].replace('...', '')} failed: ${err.message}`);
3237
+ }
3238
+ }
3239
+ else if (finalization.needed) {
3240
+ simProgress.succeed('Simulation engine ready');
3241
+ }
3242
+ }
3243
+ // Start background server (handles killing existing servers internally)
3244
+ const editorPort = argv.port || 3111;
3245
+ const { url } = await startBackgroundServer({
3246
+ port: editorPort,
3247
+ rootPath: projectRoot,
3248
+ project,
3249
+ branch,
3250
+ });
3251
+ // Build import graph on first startup if glossary exists but no entities yet
3252
+ const glossaryPath = path.join(projectRoot, '.codeyam', 'glossary.json');
3253
+ if (fs.existsSync(glossaryPath)) {
3254
+ const entities = await loadEntities({});
3255
+ if (!entities || entities.length === 0) {
3256
+ try {
3257
+ await handleAnalyzeImports({ silent: true });
3258
+ }
3259
+ catch {
3260
+ // Non-fatal
3261
+ }
3262
+ }
3263
+ }
3264
+ console.log();
3265
+ console.log(` Dashboard: ${url}`);
3266
+ console.log(' Run "codeyam --help" for all commands');
3267
+ console.log(chalk.bold(' Run "codeyam editor" to launch the editor at any time'));
3268
+ console.log();
3269
+ // Open browser to /editor
3270
+ try {
3271
+ const { execSync } = await import('child_process');
3272
+ const openCommand = process.platform === 'darwin'
3273
+ ? 'open'
3274
+ : process.platform === 'win32'
3275
+ ? 'start ""'
3276
+ : 'xdg-open';
3277
+ execSync(`${openCommand} "${url}/editor"`, { stdio: 'ignore' });
3278
+ }
3279
+ catch {
3280
+ // Silently fail if open command doesn't work
3281
+ }
3282
+ }
3283
+ catch (err) {
3284
+ errorLog('Failed to start CodeYam editor server');
3285
+ errorLog(err);
3286
+ process.exit(1);
3287
+ }
3288
+ return;
3289
+ }
1788
3290
  const state = readState(root);
3291
+ // Validate step transition — prevent skipping steps.
3292
+ // Exception: step 2 with --feature is always allowed because step 1's
3293
+ // instructions explicitly tell Claude to run `codeyam editor 2 --feature "..."`.
3294
+ // Step 1 is planning-only and may not persist state (no --feature flag).
3295
+ const skipValidation = step === 2 && argv.feature;
3296
+ if (!skipValidation) {
3297
+ const stepError = validateStepTransition(step, state?.step ?? null);
3298
+ if (stepError) {
3299
+ console.error(chalk.red(`Error: ${stepError}`));
3300
+ process.exit(1);
3301
+ }
3302
+ }
1789
3303
  switch (step) {
1790
3304
  case 1: {
1791
3305
  const feature = argv.feature || undefined;
1792
- printStep1(root, feature);
3306
+ const appFormats = argv['app-formats']
3307
+ ? argv['app-formats'].split(',').map((f) => f.trim())
3308
+ : undefined;
3309
+ const techStackId = argv['tech-stack'] || undefined;
3310
+ const prompt = argv.prompt || undefined;
3311
+ printStep1(root, feature, { appFormats, techStackId }, prompt);
1793
3312
  break;
1794
3313
  }
1795
3314
  case 2: {
@@ -1811,12 +3330,25 @@ const editorCommand = {
1811
3330
  case 9:
1812
3331
  case 10:
1813
3332
  case 11:
1814
- case 12: {
3333
+ case 12:
3334
+ case 13:
3335
+ case 14:
3336
+ case 15:
3337
+ case 16: {
1815
3338
  const feature = argv.feature || state?.feature;
1816
3339
  if (!feature) {
1817
3340
  console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
1818
3341
  process.exit(1);
1819
3342
  }
3343
+ // Hard gate: steps 8+ require audit to have passed
3344
+ if (step >= 8) {
3345
+ const auditOk = await checkAuditGate();
3346
+ if (!auditOk) {
3347
+ console.error(chalk.red.bold('BLOCKED: The audit has not passed. Run `codeyam editor audit` and fix all issues before proceeding.'));
3348
+ console.error(chalk.yellow('Every function and hook must have a test file. Every component must have scenarios.'));
3349
+ process.exit(1);
3350
+ }
3351
+ }
1820
3352
  const stepFns = {
1821
3353
  3: printStep3,
1822
3354
  4: printStep4,
@@ -1828,6 +3360,10 @@ const editorCommand = {
1828
3360
  10: printStep10,
1829
3361
  11: printStep11,
1830
3362
  12: printStep12,
3363
+ 13: printStep13,
3364
+ 14: printStep14,
3365
+ 15: printStep15,
3366
+ 16: printStep16,
1831
3367
  };
1832
3368
  stepFns[step](root, feature);
1833
3369
  break;