@codeyam/codeyam-cli 0.1.0-staging.8778565 → 0.1.0-staging.87dd4be

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 (310) 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 +2 -2
  4. package/analyzer-template/packages/ai/package.json +1 -1
  5. package/analyzer-template/packages/aws/package.json +1 -1
  6. package/analyzer-template/packages/database/package.json +1 -1
  7. package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +82 -0
  8. package/analyzer-template/packages/database/src/lib/loadEntities.ts +0 -6
  9. package/analyzer-template/packages/database/src/lib/updateCommitMetadata.ts +0 -65
  10. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +5 -0
  11. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts.map +1 -1
  12. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js +84 -0
  13. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  14. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts.map +1 -1
  15. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js +0 -6
  16. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js.map +1 -1
  17. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts.map +1 -1
  18. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js +0 -25
  19. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js.map +1 -1
  20. package/codeyam-cli/src/cli.js +9 -0
  21. package/codeyam-cli/src/cli.js.map +1 -1
  22. package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js +51 -0
  23. package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js.map +1 -0
  24. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +56 -0
  25. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -0
  26. package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +101 -47
  27. package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
  28. package/codeyam-cli/src/commands/editor.js +2369 -346
  29. package/codeyam-cli/src/commands/editor.js.map +1 -1
  30. package/codeyam-cli/src/commands/editorIsolateArgs.js +25 -0
  31. package/codeyam-cli/src/commands/editorIsolateArgs.js.map +1 -0
  32. package/codeyam-cli/src/commands/init.js +69 -34
  33. package/codeyam-cli/src/commands/init.js.map +1 -1
  34. package/codeyam-cli/src/commands/telemetry.js +37 -0
  35. package/codeyam-cli/src/commands/telemetry.js.map +1 -0
  36. package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js +173 -0
  37. package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js.map +1 -0
  38. package/codeyam-cli/src/utils/__tests__/editorApi.test.js +18 -8
  39. package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -1
  40. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +2046 -1
  41. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  42. package/codeyam-cli/src/utils/__tests__/editorBroadcastViewport.test.js +76 -0
  43. package/codeyam-cli/src/utils/__tests__/editorBroadcastViewport.test.js.map +1 -0
  44. package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js +137 -0
  45. package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js.map +1 -0
  46. package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js +100 -0
  47. package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js.map +1 -0
  48. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +76 -3
  49. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -1
  50. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +381 -0
  51. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -0
  52. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +202 -1
  53. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -1
  54. package/codeyam-cli/src/utils/__tests__/editorMigration.test.js +435 -0
  55. package/codeyam-cli/src/utils/__tests__/editorMigration.test.js.map +1 -0
  56. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +88 -1
  57. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
  58. package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +47 -1
  59. package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -1
  60. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +70 -0
  61. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -1
  62. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +1335 -1
  63. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  64. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +67 -0
  65. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
  66. package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js +143 -0
  67. package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js.map +1 -0
  68. package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js +66 -0
  69. package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js.map +1 -0
  70. package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js +53 -0
  71. package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js.map +1 -0
  72. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +363 -11
  73. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  74. package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js +177 -0
  75. package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js.map +1 -0
  76. package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js +30 -2
  77. package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js.map +1 -1
  78. package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js +118 -0
  79. package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js.map +1 -0
  80. package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +284 -0
  81. package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -0
  82. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +649 -223
  83. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
  84. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +1 -0
  85. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
  86. package/codeyam-cli/src/utils/__tests__/telemetry.test.js +159 -0
  87. package/codeyam-cli/src/utils/__tests__/telemetry.test.js.map +1 -0
  88. package/codeyam-cli/src/utils/analysisRunner.js +3 -1
  89. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  90. package/codeyam-cli/src/utils/analyzer.js +9 -0
  91. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  92. package/codeyam-cli/src/utils/analyzerFinalization.js +100 -0
  93. package/codeyam-cli/src/utils/analyzerFinalization.js.map +1 -0
  94. package/codeyam-cli/src/utils/backgroundServer.js +3 -9
  95. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  96. package/codeyam-cli/src/utils/database.js +37 -2
  97. package/codeyam-cli/src/utils/database.js.map +1 -1
  98. package/codeyam-cli/src/utils/editorApi.js +11 -5
  99. package/codeyam-cli/src/utils/editorApi.js.map +1 -1
  100. package/codeyam-cli/src/utils/editorAudit.js +372 -5
  101. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  102. package/codeyam-cli/src/utils/editorBroadcastViewport.js +26 -0
  103. package/codeyam-cli/src/utils/editorBroadcastViewport.js.map +1 -0
  104. package/codeyam-cli/src/utils/editorDeleteScenario.js +67 -0
  105. package/codeyam-cli/src/utils/editorDeleteScenario.js.map +1 -0
  106. package/codeyam-cli/src/utils/editorEntityChangeStatus.js +13 -7
  107. package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -1
  108. package/codeyam-cli/src/utils/editorEntityHelpers.js +144 -0
  109. package/codeyam-cli/src/utils/editorEntityHelpers.js.map +1 -0
  110. package/codeyam-cli/src/utils/editorLoaderHelpers.js +72 -1
  111. package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -1
  112. package/codeyam-cli/src/utils/editorMigration.js +224 -0
  113. package/codeyam-cli/src/utils/editorMigration.js.map +1 -0
  114. package/codeyam-cli/src/utils/editorPreview.js +31 -0
  115. package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
  116. package/codeyam-cli/src/utils/editorRecapture.js +109 -0
  117. package/codeyam-cli/src/utils/editorRecapture.js.map +1 -0
  118. package/codeyam-cli/src/utils/editorScenarioSwitch.js +24 -2
  119. package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -1
  120. package/codeyam-cli/src/utils/editorScenarios.js +458 -0
  121. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  122. package/codeyam-cli/src/utils/editorSeedAdapter.js +253 -4
  123. package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
  124. package/codeyam-cli/src/utils/editorShouldRevalidate.js +21 -0
  125. package/codeyam-cli/src/utils/editorShouldRevalidate.js.map +1 -0
  126. package/codeyam-cli/src/utils/entityChangeStatus.js +53 -6
  127. package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
  128. package/codeyam-cli/src/utils/entityChangeStatus.server.js +41 -3
  129. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  130. package/codeyam-cli/src/utils/fileWatcher.js +38 -0
  131. package/codeyam-cli/src/utils/fileWatcher.js.map +1 -1
  132. package/codeyam-cli/src/utils/glossaryAdd.js +74 -0
  133. package/codeyam-cli/src/utils/glossaryAdd.js.map +1 -0
  134. package/codeyam-cli/src/utils/install-skills.js +14 -0
  135. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  136. package/codeyam-cli/src/utils/parseRegisterArg.js.map +1 -1
  137. package/codeyam-cli/src/utils/progress.js +2 -2
  138. package/codeyam-cli/src/utils/progress.js.map +1 -1
  139. package/codeyam-cli/src/utils/routePatternMatching.js +129 -0
  140. package/codeyam-cli/src/utils/routePatternMatching.js.map +1 -0
  141. package/codeyam-cli/src/utils/scenarioCoverage.js +77 -0
  142. package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -0
  143. package/codeyam-cli/src/utils/scenariosManifest.js +269 -74
  144. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  145. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +1 -0
  146. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
  147. package/codeyam-cli/src/utils/simulationGateMiddleware.js +8 -1
  148. package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -1
  149. package/codeyam-cli/src/utils/slugUtils.js +25 -0
  150. package/codeyam-cli/src/utils/slugUtils.js.map +1 -0
  151. package/codeyam-cli/src/utils/syncMocksMiddleware.js +2 -2
  152. package/codeyam-cli/src/utils/syncMocksMiddleware.js.map +1 -1
  153. package/codeyam-cli/src/utils/telemetry.js +106 -0
  154. package/codeyam-cli/src/utils/telemetry.js.map +1 -0
  155. package/codeyam-cli/src/utils/telemetryMiddleware.js +22 -0
  156. package/codeyam-cli/src/utils/telemetryMiddleware.js.map +1 -0
  157. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +35 -0
  158. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -0
  159. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +80 -0
  160. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -0
  161. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +218 -0
  162. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
  163. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +217 -0
  164. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -0
  165. package/codeyam-cli/src/webserver/app/lib/clientErrors.js +71 -0
  166. package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -0
  167. package/codeyam-cli/src/webserver/app/lib/git.js +3 -2
  168. package/codeyam-cli/src/webserver/app/lib/git.js.map +1 -1
  169. package/codeyam-cli/src/webserver/app/types/editor.js +8 -0
  170. package/codeyam-cli/src/webserver/app/types/editor.js.map +1 -0
  171. package/codeyam-cli/src/webserver/backgroundServer.js +60 -61
  172. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  173. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-CLe80MMu.js +1 -0
  174. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-BcgbViKV.js → EntityItem-Crt_KN_U.js} +3 -3
  175. package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-CQgyEGV-.js +1 -0
  176. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CQIG2qda.js → EntityTypeIcon-CD7lGABo.js} +1 -1
  177. package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-CgTNOhnu.js +1 -0
  178. package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-CKeQT5Ty.js +25 -0
  179. package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-D3s1MFkb.js +3 -0
  180. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-BU_OAEMP.js → LoadingDots-By5zI316.js} +1 -1
  181. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-ceAyBX-H.js → LogViewer-CM5zg40N.js} +3 -3
  182. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-BzHcG7SE.js → ReportIssueModal-C2PLkej3.js} +2 -2
  183. package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-DanvyBPb.js +1 -0
  184. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-0DY_NKil.js → ScenarioViewer-DUMfcNVK.js} +3 -3
  185. package/codeyam-cli/src/webserver/build/client/assets/Spinner-D0LgAaSa.js +34 -0
  186. package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-CK7-NaPZ.js +1 -0
  187. package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-BA_Ry-rs.js +1 -0
  188. package/codeyam-cli/src/webserver/build/client/assets/{_index-DLxKhri3.js → _index-BAWd-Xjf.js} +2 -2
  189. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BcY3q6nt.js → activity.(_tab)-BOARiB-g.js} +3 -3
  190. package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-Duc5hnl7.js → addon-web-links-CHx25PAe.js} +1 -1
  191. package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-Bni3iiUj.js → agent-transcripts-Bg3e7q4S.js} +3 -3
  192. package/codeyam-cli/src/webserver/build/client/assets/api.editor-recapture-stale-l0sNRNKZ.js +1 -0
  193. package/codeyam-cli/src/webserver/build/client/assets/api.editor-rename-scenario-l0sNRNKZ.js +1 -0
  194. package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-seed-state-l0sNRNKZ.js +1 -0
  195. package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-coverage-l0sNRNKZ.js +1 -0
  196. package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-prompt-l0sNRNKZ.js +1 -0
  197. package/codeyam-cli/src/webserver/build/client/assets/api.editor-session-l0sNRNKZ.js +1 -0
  198. package/codeyam-cli/src/webserver/build/client/assets/{book-open-BYOypzCa.js → book-open-CL-lMgHh.js} +1 -1
  199. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-C_Pmso5S.js → chevron-down-GmAjGS9-.js} +1 -1
  200. package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-BAdwhyCx.js +43 -0
  201. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-BVMi9VA5.js → circle-check-DFcQkN5j.js} +1 -1
  202. package/codeyam-cli/src/webserver/build/client/assets/{copy-n2FB0_Sw.js → copy-C6iF61Xs.js} +1 -1
  203. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-CC6AbExI.js → createLucideIcon-4ImjHTVC.js} +1 -1
  204. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-C8y4mmyv.js +1 -0
  205. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-Gbk_i5Js.js +1 -0
  206. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-Bnx7yUP0.js +58 -0
  207. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-oepecPae.js +41 -0
  208. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BF4oLwaE.js → entity._sha._-Blfy9UlN.js} +2 -2
  209. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-KTQuL0aj.js +6 -0
  210. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js +6 -0
  211. package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-DQM8E7L4.js +6 -0
  212. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-BMvVHNXU.js → entity._sha_.edit._scenarioId-CAoXLsQr.js} +2 -2
  213. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-DTvKq3TY.js → entry.client-SuW9syRS.js} +6 -6
  214. package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-Daa96Fr1.js +1 -0
  215. package/codeyam-cli/src/webserver/build/client/assets/files-D-xGrg29.js +1 -0
  216. package/codeyam-cli/src/webserver/build/client/assets/git-Bq_fbXP5.js +1 -0
  217. package/codeyam-cli/src/webserver/build/client/assets/globals-fAqOD9ex.css +1 -0
  218. package/codeyam-cli/src/webserver/build/client/assets/{index-BcvgDzbZ.js → index-Bp1l4hSv.js} +1 -1
  219. package/codeyam-cli/src/webserver/build/client/assets/{index-10oVnAAH.js → index-CWV9XZiG.js} +1 -1
  220. package/codeyam-cli/src/webserver/build/client/assets/{index-yHOVb4rc.js → index-DE3jI_dv.js} +1 -1
  221. package/codeyam-cli/src/webserver/build/client/assets/jsx-runtime-D_zvdyIk.js +9 -0
  222. package/codeyam-cli/src/webserver/build/client/assets/labs-B_IX45ih.js +1 -0
  223. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-DaAZ_H2w.js → loader-circle-De-7qQ2u.js} +1 -1
  224. package/codeyam-cli/src/webserver/build/client/assets/manifest-b9d4d267.js +1 -0
  225. package/codeyam-cli/src/webserver/build/client/assets/memory-Cx2xEx7s.js +101 -0
  226. package/codeyam-cli/src/webserver/build/client/assets/{pause-f5-1lKBt.js → pause-CFxEKL1u.js} +1 -1
  227. package/codeyam-cli/src/webserver/build/client/assets/root-DB3O9_9j.js +67 -0
  228. package/codeyam-cli/src/webserver/build/client/assets/{search-Di64LWVb.js → search-BdBb5aqc.js} +1 -1
  229. package/codeyam-cli/src/webserver/build/client/assets/settings-DdE-Untf.js +1 -0
  230. package/codeyam-cli/src/webserver/build/client/assets/simulations-DSCdE99u.js +1 -0
  231. package/codeyam-cli/src/webserver/build/client/assets/{terminal-Br7MOqts.js → terminal-CrplD4b1.js} +1 -1
  232. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-BLdiCuG-.js → triangle-alert-DqJ0j69l.js} +1 -1
  233. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-DhXHbEjP.js +1 -0
  234. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-C14nCb1q.js → useLastLogLine-BNd5hYuW.js} +1 -1
  235. package/codeyam-cli/src/webserver/build/client/assets/useReportContext-Cy5Qg_UR.js +1 -0
  236. package/codeyam-cli/src/webserver/build/client/assets/useToast-5HR2j9ZE.js +1 -0
  237. package/codeyam-cli/src/webserver/build/client/sound-test.html +98 -0
  238. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-CGwTN3V2.js +13 -0
  239. package/codeyam-cli/src/webserver/build/server/assets/index-D4meMKy3.js +1 -0
  240. package/codeyam-cli/src/webserver/build/server/assets/init-odGJ_c2-.js +10 -0
  241. package/codeyam-cli/src/webserver/build/server/assets/progress-CHTtrxFG.js +1 -0
  242. package/codeyam-cli/src/webserver/build/server/assets/server-build-TmPfF7pT.js +552 -0
  243. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  244. package/codeyam-cli/src/webserver/build-info.json +5 -5
  245. package/codeyam-cli/src/webserver/editorProxy.js +208 -17
  246. package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
  247. package/codeyam-cli/src/webserver/idleDetector.js +106 -0
  248. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -0
  249. package/codeyam-cli/src/webserver/mockStateEvents.js +28 -0
  250. package/codeyam-cli/src/webserver/mockStateEvents.js.map +1 -0
  251. package/codeyam-cli/src/webserver/public/sound-test.html +98 -0
  252. package/codeyam-cli/src/webserver/scripts/journalCapture.ts +36 -0
  253. package/codeyam-cli/src/webserver/server.js +87 -4
  254. package/codeyam-cli/src/webserver/server.js.map +1 -1
  255. package/codeyam-cli/src/webserver/terminalServer.js +140 -35
  256. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  257. package/codeyam-cli/templates/chrome-extension-react/README.md +46 -0
  258. package/codeyam-cli/templates/chrome-extension-react/package.json +1 -0
  259. package/codeyam-cli/templates/codeyam-editor-claude.md +84 -5
  260. package/codeyam-cli/templates/codeyam-editor-reference.md +214 -0
  261. package/codeyam-cli/templates/editor-step-hook.py +114 -24
  262. package/codeyam-cli/templates/expo-react-native/README.md +41 -0
  263. package/codeyam-cli/templates/expo-react-native/package.json +1 -0
  264. package/codeyam-cli/templates/nextjs-prisma-sqlite/DATABASE.md +14 -0
  265. package/codeyam-cli/templates/nextjs-prisma-sqlite/README.md +53 -0
  266. package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +2 -1
  267. package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +42 -7
  268. package/codeyam-cli/templates/nextjs-prisma-supabase/README.md +52 -0
  269. package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +2 -1
  270. package/codeyam-cli/templates/seed-adapters/supabase.ts +282 -0
  271. package/codeyam-cli/templates/skills/codeyam-dev-mode/SKILL.md +1 -1
  272. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +76 -10
  273. package/package.json +2 -1
  274. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +84 -0
  275. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  276. package/packages/database/src/lib/loadEntities.js +0 -6
  277. package/packages/database/src/lib/loadEntities.js.map +1 -1
  278. package/packages/database/src/lib/updateCommitMetadata.js +0 -25
  279. package/packages/database/src/lib/updateCommitMetadata.js.map +1 -1
  280. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-BPXZwM4t.js +0 -1
  281. package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-g3saevPb.js +0 -1
  282. package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-Bu6c6aDe.js +0 -1
  283. package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-DYFW3lDD.js +0 -25
  284. package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-DLeucoVX.js +0 -3
  285. package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-BED4B6sP.js +0 -1
  286. package/codeyam-cli/src/webserver/build/client/assets/Spinner-Bb5uFQ5V.js +0 -34
  287. package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-C8OKAR5x.js +0 -1
  288. package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-oAf2Kqsf.js +0 -1
  289. package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-C4pqxYJB.js +0 -51
  290. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-Csi0_PMl.js +0 -1
  291. package/codeyam-cli/src/webserver/build/client/assets/editor-DgN1LTTt.js +0 -10
  292. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-BLQMSKZa.js +0 -41
  293. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-C7YX6r3H.js +0 -6
  294. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CF164ouH.js +0 -6
  295. package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-p9hhkjJM.js +0 -6
  296. package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-cPo8LiG3.js +0 -1
  297. package/codeyam-cli/src/webserver/build/client/assets/files-BZrlFE1F.js +0 -1
  298. package/codeyam-cli/src/webserver/build/client/assets/git-DdZcvjGh.js +0 -1
  299. package/codeyam-cli/src/webserver/build/client/assets/globals-BkWJ_UNc.css +0 -1
  300. package/codeyam-cli/src/webserver/build/client/assets/labs-Zk7ryIM1.js +0 -1
  301. package/codeyam-cli/src/webserver/build/client/assets/manifest-c26eb85b.js +0 -1
  302. package/codeyam-cli/src/webserver/build/client/assets/memory-Bl2rpw8u.js +0 -96
  303. package/codeyam-cli/src/webserver/build/client/assets/root-ClvYBUSA.js +0 -67
  304. package/codeyam-cli/src/webserver/build/client/assets/settings-0OrEMU6J.js +0 -1
  305. package/codeyam-cli/src/webserver/build/client/assets/simulations-DWT-CvLy.js +0 -1
  306. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-CrAK28Bc.js +0 -1
  307. package/codeyam-cli/src/webserver/build/client/assets/useReportContext-O-jkvSPx.js +0 -1
  308. package/codeyam-cli/src/webserver/build/client/assets/useToast-9FIWuYfK.js +0 -1
  309. package/codeyam-cli/src/webserver/build/server/assets/index-DflIr5SD.js +0 -1
  310. package/codeyam-cli/src/webserver/build/server/assets/server-build-OhKy839M.js +0 -416
@@ -11,15 +11,18 @@ import { IS_INTERNAL_BUILD } from "../utils/buildFlags.js";
11
11
  import { startBackgroundServer } from "../utils/backgroundServer.js";
12
12
  import { installClaudeCodeSkills } from "../utils/install-skills.js";
13
13
  import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
14
- import { getAnalyzerTemplatePath, isAnalyzerFinalized, } from "../utils/analyzer.js";
14
+ import { ensureAnalyzerFinalized, } from "../utils/analyzerFinalization.js";
15
15
  import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
16
16
  import { getProjectRoot as getStateProjectRoot } from "../state.js";
17
17
  import initCommand from "./init.js";
18
- import { readManifest, syncManifestToDatabase, } from "../utils/scenariosManifest.js";
19
- import { clearEditorState, clearEditorUserPrompt, } from "../utils/editorScenarios.js";
20
- import { validateSeedData, detectSeedAdapter, } from "../utils/editorSeedAdapter.js";
18
+ import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
19
+ import { clearEditorState, clearEditorUserPrompt, validateStepTransition, backfillEntityShaOnScenarios, } from "../utils/editorScenarios.js";
20
+ import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } from "../utils/editorSeedAdapter.js";
21
+ import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
21
22
  import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
22
23
  import { parseRegisterArg } from "../utils/parseRegisterArg.js";
24
+ import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
25
+ import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
23
26
  const __filename = fileURLToPath(import.meta.url);
24
27
  const __dirname = path.dirname(__filename);
25
28
  const STEP_LABELS = {
@@ -36,6 +39,21 @@ const STEP_LABELS = {
36
39
  11: 'Journal',
37
40
  12: 'Review',
38
41
  13: 'Present',
42
+ 14: 'Commit',
43
+ 15: 'Finalize',
44
+ 16: 'Push',
45
+ };
46
+ const MIGRATION_STEP_LABELS = {
47
+ 1: 'Survey',
48
+ 2: 'App Scenarios',
49
+ 3: 'Component Scenarios',
50
+ 4: 'Preview',
51
+ 5: 'Discuss',
52
+ 6: 'Decompose',
53
+ 7: 'Extract',
54
+ 8: 'Recapture',
55
+ 9: 'Journal',
56
+ 10: 'Present',
39
57
  };
40
58
  /**
41
59
  * Append a JSONL log entry to .codeyam/logs/editor-log.jsonl
@@ -56,6 +74,18 @@ function logEvent(root, event, data) {
56
74
  // Logging is best-effort
57
75
  }
58
76
  }
77
+ /**
78
+ * Read the design system file if it exists.
79
+ */
80
+ function readDesignSystem(root) {
81
+ const designSystemPath = path.join(root, '.codeyam', 'design-system.md');
82
+ try {
83
+ return fs.readFileSync(designSystemPath, 'utf8');
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
59
89
  /**
60
90
  * Get the project root (where .codeyam/ lives) or cwd.
61
91
  */
@@ -128,6 +158,39 @@ function getServerPort() {
128
158
  }
129
159
  return process.env.CODEYAM_PORT || '3111';
130
160
  }
161
+ /**
162
+ * Read the project's default dimension name and available screen size names.
163
+ * Used by step instructions to show project-specific dimension examples
164
+ * instead of hardcoded "Desktop".
165
+ */
166
+ function getProjectDimensions(root) {
167
+ try {
168
+ const configPath = path.join(root, '.codeyam', 'config.json');
169
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
170
+ const defaultName = config.defaultScreenSize?.name ||
171
+ (config.screenSizes ? Object.keys(config.screenSizes)[0] : null) ||
172
+ 'Desktop';
173
+ const names = config.screenSizes ? Object.keys(config.screenSizes) : [];
174
+ return { defaultName, names };
175
+ }
176
+ catch {
177
+ return { defaultName: 'Desktop', names: [] };
178
+ }
179
+ }
180
+ /**
181
+ * Print dimension guidance when the project has multiple screen sizes.
182
+ * Tells Claude to pick the right dimension for the content being previewed
183
+ * instead of blindly using the default for everything.
184
+ */
185
+ function printDimensionGuidance(defaultName, names) {
186
+ if (names.length <= 1)
187
+ return;
188
+ // Find a non-default dimension to suggest as the "other" option
189
+ const otherName = names.find((n) => n !== defaultName) || names[1];
190
+ console.log(chalk.yellow(` IMPORTANT: Choose the dimension that matches what you're previewing. Available: ${names.map((n) => `"${n}"`).join(', ')}.`));
191
+ console.log(chalk.yellow(` Do NOT always use "${defaultName}". Full pages, standalone views, and desktop layouts should use "${otherName}".`));
192
+ console.log(chalk.yellow(` Think about the CONTENT — a full-page library view needs a large viewport, not a popup-sized one.`));
193
+ }
131
194
  /**
132
195
  * Print a checklist item.
133
196
  * Inline backtick-wrapped text is highlighted in cyan for visibility.
@@ -137,6 +200,156 @@ function checkbox(text) {
137
200
  const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
138
201
  console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
139
202
  }
203
+ /**
204
+ * Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
205
+ * Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
206
+ */
207
+ function printAppScenarioInstructions(pageName, route) {
208
+ const root = process.cwd();
209
+ const hasSeedAdapter = !!detectSeedAdapter(root);
210
+ checkbox('Check existing scenarios: `codeyam editor scenarios`');
211
+ console.log(chalk.dim(' Review existing scenarios — reuse and update their data rather than'));
212
+ console.log(chalk.dim(' creating duplicates. Re-register with the same name to update a scenario.'));
213
+ console.log();
214
+ console.log(chalk.bold.yellow('App scenarios vs component scenarios — these are DIFFERENT:'));
215
+ console.log(chalk.yellow(' App scenarios: show the FULL PAGE with seeded data at a real route (/, /library, etc.)'));
216
+ console.log(chalk.yellow(' Component scenarios: show ONE COMPONENT in isolation at /isolated-components/...'));
217
+ console.log(chalk.yellow(' This step is about APP scenarios. Do NOT set "componentName" — that makes it a component scenario.'));
218
+ console.log();
219
+ checkbox('Identify every page/route in the app and ensure each has app-level scenarios');
220
+ console.log(chalk.dim(" Check the app's router/entry points for all distinct pages"));
221
+ console.log(chalk.yellow(' Every page needs at least 2-3 app scenarios — not just the main page'));
222
+ if (pageName) {
223
+ console.log(chalk.dim(` Example: "${pageName} - Full Data", "${pageName} - Empty State"`));
224
+ }
225
+ else {
226
+ console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
227
+ }
228
+ console.log();
229
+ checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
230
+ console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
231
+ console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
232
+ console.log(chalk.yellow(' Required fields: "type":"application", "url" (real route), "pageFilePath" (source file)'));
233
+ console.log(chalk.yellow(' Do NOT include "componentName" — that would make it a component scenario'));
234
+ console.log();
235
+ if (hasSeedAdapter) {
236
+ checkbox(chalk.bold.red('REQUIRED: Every app scenario MUST include "seed" data — without it the page will be empty!'));
237
+ console.log(chalk.yellow(' A seed adapter exists — you MUST include "seed":{...} in every app scenario registration.'));
238
+ console.log(chalk.yellow(' An app scenario without seed data will render an empty page (no database rows = nothing to show).'));
239
+ console.log(chalk.dim(' "seed" maps table names to arrays of rows: "seed":{"user":[{"id":1,"name":"Alice"}],"article":[...]}'));
240
+ console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
241
+ }
242
+ else {
243
+ checkbox('Include data in every app scenario — without it the page will be empty:');
244
+ console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
245
+ console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
246
+ }
247
+ checkbox('For large seed/mock data, write JSON to a temp file and use @ prefix:');
248
+ console.log(chalk.dim(' Write JSON to .codeyam/tmp/scenario.json then register with:'));
249
+ console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
250
+ checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
251
+ checkbox('After each registration, check the response for `clientErrors`');
252
+ console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
253
+ console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
254
+ }
255
+ /**
256
+ * Shared glossary instructions used by editor Step 6 and migration Step 8.
257
+ * Prints format, required fields, and example.
258
+ */
259
+ function printGlossaryInstructions(feature) {
260
+ console.log(chalk.bold.yellow('IMPORTANT: glossary.json is a plain JSON array — NOT an object wrapper.'));
261
+ checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
262
+ checkbox('Add an entry for each new function/component extracted');
263
+ checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
264
+ checkbox('For each function with a test file, set `testFile` to the relative path');
265
+ console.log();
266
+ console.log(chalk.bold('File format:'));
267
+ console.log(chalk.dim(' ['));
268
+ console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
269
+ console.log(chalk.dim(' "description": "Calculates total price including tax and discounts",'));
270
+ console.log(chalk.dim(' "parameters": [{ "name": "items", "type": "CartItem[]" }],'));
271
+ console.log(chalk.dim(' "returnType": "number", "tags": ["pricing"],'));
272
+ console.log(chalk.dim(' "testFile": "app/utils/pricing.test.ts",'));
273
+ console.log(chalk.dim(` "feature": "${feature || '...'}", "createdAt": "..." }`));
274
+ console.log(chalk.dim(' ]'));
275
+ }
276
+ /**
277
+ * Shared extraction plan instructions used by editor Step 4 and migration Step 6.
278
+ * Prints THE RULE, extractable categories, plan format requirements.
279
+ */
280
+ function printExtractionPlanInstructions() {
281
+ console.log(chalk.bold.red('THE RULE: No direct JSX in page files.'));
282
+ console.log(chalk.yellow(' After extraction, a page/route file should import and compose components — nothing else.'));
283
+ console.log(chalk.yellow(' Every distinct visual section in the page is its own component.'));
284
+ console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
285
+ console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
286
+ console.log();
287
+ console.log(chalk.bold('Checklist:'));
288
+ checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
289
+ checkbox('Read EVERY file used by this page/feature');
290
+ console.log(chalk.yellow(' For EACH file, identify EVERY extractable piece:'));
291
+ console.log(chalk.yellow(' Components: headers, nav, loading states, empty states, error states,'));
292
+ console.log(chalk.yellow(' badges/pills, image containers, card sections, form fields, modals,'));
293
+ console.log(chalk.yellow(' titles, descriptions, rating displays, status indicators, buttons'));
294
+ console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
295
+ console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
296
+ console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
297
+ console.log();
298
+ checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
299
+ console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
300
+ console.log(chalk.yellow(' No raw <div>, <span>, <h1>, <p>, <img>, or <ul> in page files.'));
301
+ console.log(chalk.yellow(' Every visual section in every component is itself a component.'));
302
+ console.log();
303
+ console.log(chalk.yellow(' For each item in the plan, note:'));
304
+ console.log(chalk.yellow(' — What it is (component, function, hook)'));
305
+ console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
306
+ console.log(chalk.yellow(' — Where it will go (new file path)'));
307
+ console.log();
308
+ console.log(chalk.dim('Present the numbered plan, then proceed to step 5 to execute it.'));
309
+ }
310
+ /**
311
+ * Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
312
+ * Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
313
+ */
314
+ function printComponentCaptureInstructions() {
315
+ checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
316
+ console.log(chalk.dim(' This creates app/codeyam-isolate/layout.tsx (with production notFound() guard) and'));
317
+ console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
318
+ checkbox('For each visual component:');
319
+ console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
320
+ console.log(chalk.dim(' — Props/interface'));
321
+ console.log(chalk.dim(' — Container width in the real app (e.g. card in a 3-col grid → max-w-sm)'));
322
+ console.log(chalk.dim(' 2. Plan multiple scenarios that exercise the component like tests:'));
323
+ console.log(chalk.dim(' — Default/happy path with typical data'));
324
+ console.log(chalk.dim(' — Edge cases: empty/missing data, long text, maximum items, zero counts'));
325
+ console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
326
+ console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
327
+ console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
328
+ console.log(chalk.dim(' Remix: app/routes/codeyam-isolate.ComponentName.tsx → /codeyam-isolate/ComponentName'));
329
+ console.log(chalk.dim(' Next.js: app/codeyam-isolate/ComponentName/page.tsx → /codeyam-isolate/ComponentName'));
330
+ console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
331
+ console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
332
+ console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
333
+ console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
334
+ console.log(chalk.dim(' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
335
+ console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth:"24rem", full-width component → omit maxWidth'));
336
+ console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
337
+ console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
338
+ console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
339
+ console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
340
+ console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
341
+ console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
342
+ console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
343
+ console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
344
+ console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/codeyam-isolate/... or /isolated-components/...).'));
345
+ console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
346
+ console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
347
+ console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
348
+ console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
349
+ console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
350
+ console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
351
+ console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
352
+ }
140
353
  /**
141
354
  * Print a section header.
142
355
  */
@@ -149,13 +362,13 @@ function stepHeader(step, title, feature) {
149
362
  console.log();
150
363
  }
151
364
  /**
152
- * Print a colored progress tracker showing all 13 steps.
365
+ * Print a colored progress tracker showing all 16 steps.
153
366
  * Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
154
367
  */
155
368
  function printProgressTracker(current) {
156
369
  console.log();
157
- console.log(chalk.dim('┌─────────────────────────────────────┐'));
158
- for (let i = 1; i <= 13; i++) {
370
+ console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
371
+ for (let i = 1; i <= 16; i++) {
159
372
  const label = STEP_LABELS[i];
160
373
  const num = i < 10 ? ` ${i}` : `${i}`;
161
374
  const content = `${num}. ${label.padEnd(28)}`;
@@ -193,7 +406,7 @@ function stopGate(current, opts) {
193
406
  console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
194
407
  console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
195
408
  console.log();
196
- if (current < 13) {
409
+ if (current < 16) {
197
410
  console.log(chalk.green('When done, run: ') +
198
411
  chalk.bold(`codeyam editor ${current + 1}`));
199
412
  }
@@ -237,6 +450,14 @@ function printResumptionHeader(step) {
237
450
  ],
238
451
  12: ['Re-verify is safe to repeat — just re-run the checks'],
239
452
  13: ['Check if commit already made:\n `git log --oneline -3`'],
453
+ 14: ['Check if commit already made:\n `git log --oneline -3`'],
454
+ 15: [
455
+ 'Check if journal was already updated with commit SHA:\n `codeyam editor journal-list`',
456
+ 'Check if commit was already amended:\n `git log --oneline -3`',
457
+ ],
458
+ 16: [
459
+ 'Check if already pushed:\n `git remote -v && git log --oneline origin/HEAD..HEAD 2>/dev/null`',
460
+ ],
240
461
  };
241
462
  const label = STEP_LABELS[step] || 'Unknown';
242
463
  console.log(chalk.bold.yellow(`━━━ RESUMING Step ${step}: ${label} ━━━`));
@@ -341,7 +562,7 @@ function parseDebugTargets(target) {
341
562
  }
342
563
  if (entry.startsWith('step-')) {
343
564
  const num = parseInt(entry.replace('step-', ''), 10);
344
- if (!isNaN(num) && num >= 1 && num <= 13) {
565
+ if (!isNaN(num) && num >= 1 && num <= 16) {
345
566
  valid.add(`step-${num}`);
346
567
  continue;
347
568
  }
@@ -425,6 +646,15 @@ function printSetup(root) {
425
646
  console.log();
426
647
  console.log("No project detected. Let's get started.");
427
648
  console.log();
649
+ // ── Design System ────────────────────────────────────────────────
650
+ console.log(chalk.bold('Design System (ask FIRST):'));
651
+ console.log(chalk.dim(' Ask: "Do you have a design system, brand guidelines, or style preferences you\'d like me to follow?"'));
652
+ console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
653
+ console.log(chalk.yellow(' Option 1 label: "Yes, I\'ll paste my design system"'));
654
+ console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
655
+ console.log(chalk.yellow(' Option 2 label: "No, use sensible defaults"'));
656
+ console.log(chalk.dim(' → Skip'));
657
+ console.log();
428
658
  console.log(chalk.bold('Checklist:'));
429
659
  checkbox('Read `.codeyam/editor-mode-context.md` for session state');
430
660
  checkbox('Ask the user what they want to build');
@@ -479,7 +709,15 @@ function printSetup(root) {
479
709
  console.log();
480
710
  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).'));
481
711
  console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
482
- 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}}\''));
712
+ 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}}'`));
713
+ console.log();
714
+ console.log(chalk.bold('Named Screen Sizes (for multi-resolution apps):'));
715
+ console.log(chalk.dim(' For mobile-responsive web apps or apps that need screenshots at multiple resolutions,'));
716
+ console.log(chalk.dim(' also save named screen sizes. Each scenario can reference a dimension name.'));
717
+ console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
718
+ console.log(chalk.dim(' -d \'{"screenSizes":{"Desktop":{"width":1440,"height":900},"Mobile":{"width":375,"height":667}}}\''));
719
+ 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.'));
720
+ console.log(chalk.dim(' NOTE: This REPLACES all screenSizes — always include every named size, not just the new one.'));
483
721
  console.log();
484
722
  console.log(chalk.bold.red('━━━ STOP ━━━'));
485
723
  console.log();
@@ -497,6 +735,42 @@ function printSetup(root) {
497
735
  // ─── Cycle overview (no args, has project) ────────────────────────────
498
736
  function printCycleOverview(root, state) {
499
737
  logEvent(root, 'overview', state ? { feature: state.feature, step: state.step } : {});
738
+ // Detect active migration (uses both editor-step.json and migration-state.json)
739
+ const migState = readMigrationState(root);
740
+ const resumeInfo = getMigrationResumeInfo(state
741
+ ? { step: state.step, label: state.label, migration: state.migration }
742
+ : null, migState);
743
+ if (resumeInfo.mode === 'survey') {
744
+ console.log();
745
+ console.log(chalk.bold.cyan('━━━ Editor Mode: Project Migration ━━━'));
746
+ console.log();
747
+ console.log(chalk.yellow('Migration survey in progress — Claude needs to explore the project.'));
748
+ console.log();
749
+ console.log(chalk.green('Continue with: ') + chalk.bold(resumeInfo.resumeCommand));
750
+ console.log();
751
+ return;
752
+ }
753
+ if (resumeInfo.mode === 'page-in-progress') {
754
+ console.log();
755
+ console.log(chalk.bold.cyan('━━━ Editor Mode: Project Migration ━━━'));
756
+ console.log();
757
+ console.log(chalk.yellow(`Migration: Page ${resumeInfo.pageIndex + 1}/${resumeInfo.totalPages} (${resumeInfo.pageName})` +
758
+ (resumeInfo.step
759
+ ? `, Step ${resumeInfo.step} (${resumeInfo.stepLabel})`
760
+ : '')));
761
+ console.log();
762
+ console.log(chalk.green('Continue with: ') + chalk.bold(resumeInfo.resumeCommand));
763
+ console.log();
764
+ return;
765
+ }
766
+ if (resumeInfo.mode === 'complete') {
767
+ console.log();
768
+ console.log(chalk.bold.cyan('━━━ Editor Mode: Feature Cycle ━━━'));
769
+ console.log();
770
+ console.log(chalk.green('Migration complete! Start building features:'));
771
+ console.log();
772
+ // Fall through to normal cycle display
773
+ }
500
774
  console.log();
501
775
  console.log(chalk.bold.cyan('━━━ Editor Mode: Feature Cycle ━━━'));
502
776
  console.log();
@@ -514,7 +788,7 @@ function printCycleOverview(root, state) {
514
788
  console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
515
789
  }
516
790
  else {
517
- console.log('Each feature follows 13 steps. You MUST run each command in order:');
791
+ console.log('Each feature follows 16 steps. You MUST run each command in order:');
518
792
  console.log();
519
793
  console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
520
794
  console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prototype')} — Build a working prototype fast`);
@@ -529,10 +803,14 @@ function printCycleOverview(root, state) {
529
803
  console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('Journal')} — Create/update journal entry`);
530
804
  console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Review')} — Verify screenshots and audit`);
531
805
  console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Present')} — Present summary, get approval`);
806
+ console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('Commit')} — Commit all changes`);
807
+ console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
808
+ console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('Push')} — Push to remote`);
532
809
  console.log();
533
810
  console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
534
811
  console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
535
812
  }
813
+ console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
536
814
  console.log();
537
815
  }
538
816
  // ─── Step 1: Plan ─────────────────────────────────────────────────────
@@ -543,19 +821,19 @@ function printStep1(root, feature, options, userPrompt) {
543
821
  if (!isResuming) {
544
822
  clearState(root);
545
823
  }
546
- // If feature is provided, save initial state so step 2 can pick it up
547
- if (feature) {
548
- const now = new Date().toISOString();
549
- writeState(root, {
550
- feature,
551
- step: 1,
552
- label: STEP_LABELS[1],
553
- startedAt: isResuming ? prevState.startedAt : now,
554
- featureStartedAt: isResuming ? prevState.featureStartedAt : now,
555
- appFormats: options?.appFormats || prevState?.appFormats,
556
- techStackId: options?.techStackId || prevState?.techStackId,
557
- });
558
- }
824
+ // Always persist state so step 2's validation sees that step 1 ran.
825
+ // The feature name may not be known yet (it's an output of planning)
826
+ // use an empty string as placeholder; step 2 requires --feature anyway.
827
+ const now = new Date().toISOString();
828
+ writeState(root, {
829
+ feature: feature || prevState?.feature || '',
830
+ step: 1,
831
+ label: STEP_LABELS[1],
832
+ startedAt: isResuming ? prevState.startedAt : now,
833
+ featureStartedAt: isResuming ? prevState.featureStartedAt : now,
834
+ appFormats: options?.appFormats || prevState?.appFormats,
835
+ techStackId: options?.techStackId || prevState?.techStackId,
836
+ });
559
837
  // Save the user's original prompt to a separate file
560
838
  if (userPrompt) {
561
839
  const promptPath = path.join(root, '.codeyam', 'editor-user-prompt.txt');
@@ -580,11 +858,21 @@ function printStep1(root, feature, options, userPrompt) {
580
858
  console.log(chalk.dim(' Bad: Free-form text asking "What do you think about the data model?"'));
581
859
  console.log(chalk.dim(' You can ask up to 4 questions at once. Bundle related questions into a single AskUserQuestion call.'));
582
860
  checkbox("Summarize what you'll build in plain language the user can verify against their vision");
861
+ checkbox('Include a brief note on what interesting data states the scenarios should demonstrate');
862
+ console.log(chalk.dim(' Think: what seed data would put this feature through its paces? Diverse content, edge cases, empty vs rich.'));
583
863
  checkbox('Set the project title and description for the App tab:');
584
864
  console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
585
865
  console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
586
866
  console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
587
867
  console.log();
868
+ const designSystem = readDesignSystem(root);
869
+ if (designSystem) {
870
+ console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
871
+ console.log(chalk.dim(' Keep these design tokens in mind during planning.'));
872
+ console.log();
873
+ console.log(designSystem);
874
+ console.log();
875
+ }
588
876
  console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
589
877
  console.log(chalk.green(' Option 1 label: "This plan is accurate, let\'s prototype it!"') + chalk.dim(' — proceed to step 2'));
590
878
  console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
@@ -612,6 +900,7 @@ function printStep1(root, feature, options, userPrompt) {
612
900
  // ─── Step 2: Prototype ────────────────────────────────────────────────
613
901
  function printStep2(root, feature) {
614
902
  const port = getServerPort();
903
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
615
904
  const projectExists = hasProject(root);
616
905
  const prevState = readState(root);
617
906
  const isResuming = prevState?.step === 2;
@@ -649,19 +938,41 @@ function printStep2(root, feature) {
649
938
  console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
650
939
  console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
651
940
  console.log();
652
- console.log(chalk.dim(' See PRISMA_SETUP.md for Prisma patterns and important warnings.'));
941
+ console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
942
+ console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
943
+ console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
944
+ console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
945
+ console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
946
+ console.log();
947
+ console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
653
948
  console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
654
949
  console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
655
950
  console.log();
656
951
  console.log(chalk.bold('Build the feature:'));
657
952
  }
953
+ else {
954
+ console.log(chalk.bold('Prepare the database for this feature:'));
955
+ checkbox('List existing scenarios: `codeyam editor scenarios`');
956
+ console.log(chalk.dim(' Review existing scenarios to find seed data that best demonstrates the feature.'));
957
+ console.log(chalk.dim(" Don't create throwaway seed data — use what's already been curated."));
958
+ checkbox('Pick the scenario with seed data most relevant to this feature');
959
+ console.log(chalk.dim(" Think: which existing data state best exercises the feature you're building?"));
960
+ console.log(chalk.dim(' A scenario with diverse, realistic data is usually the best starting point.'));
961
+ checkbox('Load that scenario to seed the database and preview it:');
962
+ console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
963
+ console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
964
+ console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
965
+ console.log();
966
+ }
658
967
  console.log(chalk.bold('Checklist:'));
659
968
  checkbox('Create API routes that read from the database via Prisma');
660
- checkbox('Seed the database with demo data');
661
- checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
662
- console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
663
- console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
664
- console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
969
+ if (!projectExists) {
970
+ checkbox('Seed the database with demo data');
971
+ checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
972
+ console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
973
+ console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
974
+ console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
975
+ }
665
976
  checkbox('Verify the dev server shows the changes');
666
977
  checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
667
978
  // Responsive design guidance when building a mobile-responsive web app
@@ -673,14 +984,33 @@ function printStep2(root, feature) {
673
984
  console.log(chalk.dim(' Use Tailwind responsive prefixes (sm:, md:, lg:) for layout shifts.'));
674
985
  console.log(chalk.dim(' Test at both Desktop (1280×800) and Mobile (390×844) sizes before presenting.'));
675
986
  }
987
+ const designSystem = readDesignSystem(root);
988
+ if (designSystem) {
989
+ console.log();
990
+ console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
991
+ console.log();
992
+ console.log(designSystem);
993
+ console.log();
994
+ checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
995
+ console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
996
+ console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
997
+ console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
998
+ console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
999
+ console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
1000
+ checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
1001
+ console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
1002
+ console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
1003
+ console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
1004
+ }
676
1005
  console.log();
677
1006
  console.log(chalk.bold.cyan('Keep the preview moving:'));
678
1007
  console.log(chalk.cyan(' The user is watching the preview. Refresh it after each meaningful change:'));
679
- console.log(chalk.cyan(` codeyam editor preview`));
1008
+ console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
680
1009
  console.log(chalk.cyan(' Refresh after: first visible page, adding each UI section, seeding data, styling.'));
681
1010
  console.log(chalk.cyan(' Aim for 4-8+ refreshes during prototyping — not one big reveal at the end.'));
682
1011
  console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
683
- console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1"}'`));
1012
+ console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
1013
+ printDimensionGuidance(dim, dimNames);
684
1014
  console.log();
685
1015
  console.log(chalk.bold('Verify the dev server:'));
686
1016
  console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
@@ -701,12 +1031,19 @@ function printStep2(root, feature) {
701
1031
  console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
702
1032
  console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
703
1033
  console.log();
1034
+ console.log(chalk.bold('Update README and setup script:'));
1035
+ checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
1036
+ checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
1037
+ console.log(chalk.dim(' The README and setup script must stay accurate as you make changes.'));
1038
+ console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
1039
+ console.log();
704
1040
  console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
705
1041
  stopGate(2);
706
1042
  }
707
1043
  // ─── Step 3: Confirm ──────────────────────────────────────────────────
708
1044
  function printStep3(root, feature) {
709
1045
  const port = getServerPort();
1046
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
710
1047
  const prevState = readState(root);
711
1048
  const isResuming = prevState?.step === 3;
712
1049
  const now = new Date().toISOString();
@@ -725,7 +1062,7 @@ function printStep3(root, feature) {
725
1062
  console.log('Summarize what was built and get user confirmation.');
726
1063
  console.log();
727
1064
  console.log(chalk.bold('Before presenting — verify everything works:'));
728
- checkbox(`Refresh the preview: \`codeyam editor preview\` — check the \`preview\` field for \`healthy: false\``);
1065
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
729
1066
  checkbox('Verify API routes return valid data (curl each route)');
730
1067
  console.log();
731
1068
  console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
@@ -739,7 +1076,8 @@ function printStep3(root, feature) {
739
1076
  console.log();
740
1077
  console.log(chalk.bold('Then present to the user:'));
741
1078
  checkbox('Summarize what was built (routes, components, data)');
742
- checkbox('Navigate the preview to the feature\'s primary page: `codeyam editor preview \'{"path":"/your-page"}\'`');
1079
+ checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
1080
+ printDimensionGuidance(dim, dimNames);
743
1081
  console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
744
1082
  console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
745
1083
  console.log();
@@ -751,7 +1089,7 @@ function printStep3(root, feature) {
751
1089
  console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
752
1090
  console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 4'));
753
1091
  console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
754
- chalk.dim(' — user describes changes, you make them, then re-present'));
1092
+ chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 3`'));
755
1093
  console.log();
756
1094
  console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
757
1095
  stopGate(3, { confirm: true });
@@ -776,39 +1114,13 @@ function printStep4(root, feature) {
776
1114
  console.log(chalk.bold('Goal: pages contain ONLY components. Components contain ONLY sub-components.'));
777
1115
  console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 5.'));
778
1116
  console.log();
779
- console.log(chalk.bold.red('THE RULE: No direct JSX in page files.'));
780
- console.log(chalk.yellow(' After extraction, a page/route file should import and compose components — nothing else.'));
781
- console.log(chalk.yellow(' Every distinct visual section in the page is its own component.'));
782
- console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
783
- console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
784
- console.log();
785
- console.log(chalk.bold('Checklist:'));
786
- checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
787
- checkbox('Read EVERY file created or modified in this session');
788
- console.log(chalk.yellow(' For EACH file, identify EVERY extractable piece:'));
789
- console.log(chalk.yellow(' Components: headers, nav, loading states, empty states, error states,'));
790
- console.log(chalk.yellow(' badges/pills, image containers, card sections, form fields, modals,'));
791
- console.log(chalk.yellow(' titles, descriptions, rating displays, status indicators, buttons'));
792
- console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
793
- console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
794
- console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
795
- console.log();
796
- checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
797
- console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
798
- console.log(chalk.yellow(' No raw <div>, <span>, <h1>, <p>, <img>, or <ul> in page files.'));
799
- console.log(chalk.yellow(' Every visual section in every component is itself a component.'));
800
- console.log();
801
- console.log(chalk.yellow(' For each item in the plan, note:'));
802
- console.log(chalk.yellow(' — What it is (component, function, hook)'));
803
- console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
804
- console.log(chalk.yellow(' — Where it will go (new file path)'));
805
- console.log();
806
- console.log(chalk.dim('Present the numbered plan, then proceed to step 5 to execute it.'));
1117
+ printExtractionPlanInstructions();
807
1118
  stopGate(4);
808
1119
  }
809
1120
  // ─── Step 5: Extract ──────────────────────────────────────────────────
810
1121
  function printStep5(root, feature) {
811
1122
  const port = getServerPort();
1123
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
812
1124
  const prevState = readState(root);
813
1125
  const isResuming = prevState?.step === 5;
814
1126
  const now = new Date().toISOString();
@@ -832,12 +1144,13 @@ function printStep5(root, feature) {
832
1144
  checkbox('Every component that renders multiple sections must be split into sub-components');
833
1145
  console.log(chalk.dim(' No tests needed — visual verification happens in step 7'));
834
1146
  console.log();
835
- console.log(chalk.bold('Library functions (TDD):'));
836
- checkbox('For each function: write MULTIPLE failing tests FIRST, then extract to make them pass');
1147
+ console.log(chalk.bold('Library functions AND hooks (TDD):'));
1148
+ checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
837
1149
  console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
838
1150
  console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
1151
+ console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
839
1152
  checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
840
- console.log(chalk.yellow(' Tests ARE the only coverage for library functions — step 7 only captures component screenshots.'));
1153
+ console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 7 only captures component screenshots.'));
841
1154
  console.log();
842
1155
  console.log(chalk.bold('Recursive pass:'));
843
1156
  checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
@@ -849,7 +1162,8 @@ function printStep5(root, feature) {
849
1162
  checkbox('Run all tests and verify they pass');
850
1163
  checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
851
1164
  checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
852
- checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview\``);
1165
+ checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1166
+ printDimensionGuidance(dim, dimNames);
853
1167
  console.log(chalk.dim(' The user should see the preview stay healthy as you refactor — refresh periodically, not just at the end.'));
854
1168
  console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
855
1169
  console.log();
@@ -875,19 +1189,7 @@ function printStep6(root, feature) {
875
1189
  }
876
1190
  console.log('Record all new functions/components in `.codeyam/glossary.json`.');
877
1191
  console.log();
878
- console.log(chalk.bold('Checklist:'));
879
- checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
880
- checkbox('Add an entry for each new function/component extracted in step 5');
881
- checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
882
- checkbox('For each function with a test file from step 5, set `testFile` to the relative path');
883
- console.log();
884
- console.log(chalk.bold('Entry format:'));
885
- console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
886
- console.log(chalk.dim(' "description": "Calculates total price including tax and discounts",'));
887
- console.log(chalk.dim(' "parameters": [{ "name": "items", "type": "CartItem[]" }],'));
888
- console.log(chalk.dim(' "returnType": "number", "tags": ["pricing"],'));
889
- console.log(chalk.dim(' "testFile": "app/utils/pricing.test.ts",'));
890
- console.log(chalk.dim(` "feature": "${feature}", "createdAt": "..." }`));
1192
+ printGlossaryInstructions(feature);
891
1193
  console.log();
892
1194
  console.log(chalk.dim('Focus on updating the glossary. Application code and scenarios come in later steps.'));
893
1195
  stopGate(6);
@@ -895,6 +1197,7 @@ function printStep6(root, feature) {
895
1197
  // ─── Step 7: Analyze ──────────────────────────────────────────────────
896
1198
  function printStep7(root, feature) {
897
1199
  const port = getServerPort();
1200
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
898
1201
  const prevState = readState(root);
899
1202
  const isResuming = prevState?.step === 7;
900
1203
  const now = new Date().toISOString();
@@ -914,48 +1217,11 @@ function printStep7(root, feature) {
914
1217
  console.log();
915
1218
  console.log(chalk.bold('Visual Components — Component Isolation:'));
916
1219
  checkbox('List all files with new/modified visual components from step 5');
917
- checkbox('Create isolation route dirs: `codeyam editor isolate "ComponentA ComponentB ..."`');
918
- console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
919
- console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
920
1220
  checkbox('Check existing scenarios: `codeyam editor scenarios`');
921
1221
  console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
922
1222
  console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
923
1223
  console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
924
- checkbox('For each visual component:');
925
- console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
926
- console.log(chalk.dim(' — Props/interface'));
927
- console.log(chalk.dim(' — Container width in the real app (e.g. card in a 3-col grid → max-w-sm)'));
928
- console.log(chalk.dim(' 2. Plan multiple scenarios that exercise the component like tests:'));
929
- console.log(chalk.dim(' — Default/happy path with typical data'));
930
- console.log(chalk.dim(' — Edge cases: empty/missing data, long text, maximum items, zero counts'));
931
- console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
932
- console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
933
- console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
934
- console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
935
- console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
936
- console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
937
- console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
938
- console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
939
- console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block", padding:"20px" }}>'));
940
- console.log(chalk.dim(' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
941
- console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth:"24rem", full-width component → omit maxWidth'));
942
- console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
943
- console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
944
- console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
945
- console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
946
- console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
947
- console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
948
- console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
949
- console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
950
- console.log(chalk.dim(' Optional: add "viewportWidth" and "viewportHeight" to override the project default screen size'));
951
- console.log(chalk.dim(' If omitted, captures use the project default from setup. Only override for scenarios that need a different size.'));
952
- console.log(chalk.dim(' url is a PATH (starts with /) — the proxy routes it and intercepts API calls'));
953
- console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
954
- console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
955
- console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
956
- console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
957
- console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
958
- console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
1224
+ printComponentCaptureInstructions();
959
1225
  console.log();
960
1226
  console.log(chalk.bold('Library Functions — run tests:'));
961
1227
  checkbox('Run ALL test files created in step 5');
@@ -965,17 +1231,16 @@ function printStep7(root, feature) {
965
1231
  console.log();
966
1232
  console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
967
1233
  console.log();
968
- checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions have tests');
1234
+ checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
1235
+ console.log(chalk.red.bold(' The audit is a HARD GATE — step 8 will refuse to run until the audit passes.'));
1236
+ console.log(chalk.dim(' When audit passes, the import graph is built automatically for change tracking.'));
969
1237
  console.log();
970
- console.log(chalk.bold('Build import graph (for change tracking):'));
971
- checkbox('Run `codeyam editor analyze-imports`');
972
- console.log(chalk.dim(' This populates the import graph so the system can detect impacted entities when code changes.'));
973
- console.log(chalk.dim(' It must run AFTER the audit passes so the glossary and entity data are complete.'));
974
1238
  stopGate(7);
975
1239
  }
976
1240
  // ─── Step 8: App Scenarios ────────────────────────────────────────────
977
1241
  function printStep8(root, feature) {
978
1242
  const port = getServerPort();
1243
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
979
1244
  const prevState = readState(root);
980
1245
  const isResuming = prevState?.step === 8;
981
1246
  const now = new Date().toISOString();
@@ -991,43 +1256,43 @@ function printStep8(root, feature) {
991
1256
  if (isResuming) {
992
1257
  printResumptionHeader(8);
993
1258
  }
994
- console.log('Create app-level scenarios representing different data states.');
1259
+ console.log('Create app-level scenarios with rich data that robustly demonstrates this feature.');
1260
+ console.log();
1261
+ console.log(chalk.bold('Goal: Every scenario should thoroughly exercise the feature with realistic data.'));
1262
+ console.log(chalk.dim(' Scenarios with minimal or generic data ("Test Item 1") won\'t reveal whether the feature works.'));
1263
+ console.log(chalk.dim(' Use rich, diverse seed data that puts the feature through its paces.'));
1264
+ console.log();
1265
+ console.log(chalk.bold.cyan('Make seed data work hard:'));
1266
+ console.log(chalk.cyan(' Every scenario — new or existing — should have data that EXERCISES the feature:'));
1267
+ console.log(chalk.cyan(' • Add data that uses new fields, relationships, or states the feature introduces'));
1268
+ console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
1269
+ console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
1270
+ console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
1271
+ console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
1272
+ console.log();
1273
+ console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
1274
+ console.log(chalk.cyan(' • New pages or pages with no scenarios yet → create new scenarios'));
1275
+ console.log(chalk.cyan(' • Existing scenarios on affected pages → re-register with enhanced data'));
1276
+ console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
1277
+ console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
995
1278
  console.log();
996
- console.log(chalk.bold('Checklist:'));
997
- checkbox('Check existing scenarios: `codeyam editor scenarios`');
998
- console.log(chalk.dim(' Review existing scenarios — reuse and update their data rather than'));
999
- console.log(chalk.dim(' creating duplicates. Re-register with the same name to update a scenario.'));
1000
1279
  checkbox('Ensure scenarios clearly demonstrate what changed in this session');
1001
1280
  console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
1002
1281
  console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
1003
1282
  console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
1004
- checkbox('Cover key data states for EVERY page/route (at least 2-3 scenarios per page)');
1005
- console.log(chalk.yellow(' Each page needs its own scenarios — a detail page needs different data than the catalog'));
1006
- console.log(chalk.dim(' Catalog: "Full Catalog", "Empty Catalog", "Single Category"'));
1007
- console.log(chalk.dim(' Detail: "Detail - With Reviews", "Detail - No Reviews", "Detail - High Rated"'));
1008
- console.log(chalk.dim(' Each scenario provides seed data to populate the database for that state'));
1009
- checkbox('If the project has a database + seed adapter, use seed-based scenarios:');
1010
- console.log(chalk.dim(` codeyam editor register '{"name":"Full Catalog","type":"application","url":"/","seed":{"products":[...],"categories":[...]}}'`));
1011
- console.log(chalk.dim(' Seed data is written to the DB via the seed adapter. Real app renders real data.'));
1012
- checkbox('Optional: add "viewportWidth" and "viewportHeight" to override the project default screen size');
1013
- console.log(chalk.dim(' If omitted, captures use the project default from setup. Only override for scenarios that need a different size.'));
1014
- checkbox('IMPORTANT: Always include "url" — the page path to screenshot for this scenario');
1015
- console.log(chalk.dim(' Use "/" for home page, "/drinks/1" for detail pages, etc.'));
1016
- console.log(chalk.dim(' Without url, the screenshot captures the root page regardless of the scenario.'));
1017
- console.log(chalk.yellow(' Create separate scenarios for each page that shows different data.'));
1018
- checkbox('For large seed data, write JSON to a project temp file and use @ prefix:');
1019
- console.log(chalk.dim(' Write JSON to .codeyam/tmp/scenario.json then register with:'));
1020
- console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
1021
- checkbox('For external API mocks (Stripe, weather, etc.), add externalApis:');
1022
- console.log(chalk.dim(` "externalApis":{"GET https://api.stripe.com/v1/prices":{"body":[...],"status":200}}`));
1023
- checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
1024
- checkbox('If no database, use component-style mock scenarios (unchanged):');
1025
- console.log(chalk.dim(` codeyam editor register '{"name":"Empty","description":"...","url":"/","mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
1026
- checkbox('After each registration, check the response for `clientErrors`');
1027
- console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
1028
- console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
1283
+ printAppScenarioInstructions();
1029
1284
  console.log();
1030
1285
  console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 10 if needed.'));
1286
+ console.log();
1287
+ console.log(chalk.bold.cyan("Verify your work — screenshots don't lie:"));
1288
+ console.log(chalk.cyan(' • View every captured screenshot. Does it show what the scenario name promises?'));
1289
+ console.log(chalk.cyan(' • A "Library with Articles" screenshot showing an empty page means the scenario is BROKEN.'));
1290
+ console.log(chalk.cyan(' • Is the seed data diverse — different lengths, categories, counts — or just placeholders?'));
1291
+ console.log(chalk.cyan(' • Only create scenarios for states the CURRENT code can render.'));
1292
+ console.log();
1293
+ console.log(chalk.bold.yellow('GATE: Before proceeding, run `codeyam editor scenario-coverage`'));
1294
+ console.log(chalk.yellow(' This checks which existing scenarios have stale screenshots.'));
1295
+ console.log(chalk.yellow(' Re-register every stale scenario listed until the check passes.'));
1031
1296
  stopGate(8);
1032
1297
  }
1033
1298
  // ─── Step 9: User Scenarios ───────────────────────────────────────────
@@ -1048,34 +1313,37 @@ function printStep9(root, feature) {
1048
1313
  if (isResuming) {
1049
1314
  printResumptionHeader(9);
1050
1315
  }
1051
- console.log('Create per-persona scenarios if the app has users. Skip to step 10 if no users.');
1316
+ console.log('Create per-persona variations of existing scenarios. Skip to step 10 if no users.');
1052
1317
  console.log();
1053
- console.log(chalk.bold('If the app has users:'));
1054
- checkbox('Check existing scenarios: `codeyam editor scenarios`');
1055
- console.log(chalk.dim(' Reuse existing persona scenarios — update data to reflect current changes.'));
1056
- console.log(chalk.dim(' Re-register with the same name to update. Only add new personas if needed.'));
1057
- checkbox('Ensure scenarios for each user persona (admin, regular user, new user)');
1318
+ console.log(chalk.bold('If the app has NO users/auth:'));
1319
+ console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
1320
+ console.log();
1321
+ console.log(chalk.bold('If the app has users/auth:'));
1322
+ console.log();
1323
+ console.log(chalk.bold('Goal: Create persona variations of EXISTING app scenarios.'));
1324
+ console.log(chalk.dim(' Do NOT create standalone persona scenarios. Each user-persona scenario should be'));
1325
+ console.log(chalk.dim(' a variation of an existing app scenario from step 8, with user-specific state layered on.'));
1326
+ console.log();
1327
+ console.log(chalk.bold('Checklist:'));
1328
+ checkbox('Run `codeyam editor scenarios` — list all existing app scenarios');
1329
+ checkbox('For EACH existing app scenario, create a logged-in variation:');
1330
+ console.log(chalk.dim(' Copy the scenario seed data and add "session":{"cookieValue":"sess_alice"} + user seed'));
1331
+ console.log(chalk.dim(' Name: "<Original Name> - Logged In" (e.g. "Full Catalog - Logged In")'));
1332
+ console.log(chalk.dim(' Step 8 scenarios already serve as logged-out versions (no session cookie)'));
1333
+ checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
1058
1334
  console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
1059
- checkbox('If using seed-based scenarios, register with type "user" and baseScenario:');
1060
- console.log(chalk.dim(` codeyam editor register '{"name":"Admin User","type":"user","url":"/","baseScenario":"<app-scenario-id>","seed":{"users":[{"id":1,"role":"admin"}]}}'`));
1061
- console.log(chalk.dim(' Always include "url" — the page to screenshot (e.g. "/", "/dashboard", "/drinks/1")'));
1062
- console.log(chalk.dim(" The base scenario's seed data is merged with this scenario's seed data (overlay wins)."));
1063
- checkbox('If the app uses auth or other patterns: see FEATURE_PATTERNS.md for per-persona scenario guidance');
1064
- checkbox('If using mock-based scenarios, register with mockData as before:');
1065
- console.log(chalk.dim(` codeyam editor register '{"name":"...","description":"...","mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
1335
+ checkbox('Include "dimensions" inherit from the base app scenario, or override if the persona implies a different device');
1336
+ console.log(chalk.dim(' e.g. a mobile-first user persona should use "dimensions":["Mobile"] even if the base scenario is Desktop.'));
1066
1337
  checkbox('After each registration, check the response for `clientErrors`');
1067
1338
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
1068
- console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
1069
1339
  console.log();
1070
- console.log(chalk.bold('If the app has NO users:'));
1071
- console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
1072
- console.log();
1073
- console.log(chalk.dim('Focus on creating user-persona scenarios (or skip if no users). Code fixes happen in step 10 if needed.'));
1340
+ console.log(chalk.dim('See FEATURE_PATTERNS.md and AUTH_PATTERNS.md for auth scenario guidance.'));
1074
1341
  stopGate(9);
1075
1342
  }
1076
1343
  // ─── Step 10: Verify ──────────────────────────────────────────────────
1077
1344
  function printStep10(root, feature) {
1078
1345
  const port = getServerPort();
1346
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1079
1347
  const prevState = readState(root);
1080
1348
  const isResuming = prevState?.step === 10;
1081
1349
  const now = new Date().toISOString();
@@ -1100,7 +1368,8 @@ function printStep10(root, feature) {
1100
1368
  console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",...}'`));
1101
1369
  console.log();
1102
1370
  console.log(chalk.bold('Editor scenarios (App tab) — visual + error check:'));
1103
- checkbox(`Refresh the preview: \`codeyam editor preview\``);
1371
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1372
+ printDimensionGuidance(dim, dimNames);
1104
1373
  checkbox('Click through each app-level and user-persona scenario in the preview');
1105
1374
  checkbox('For seed-based scenarios: verify data renders correctly after switching');
1106
1375
  console.log(chalk.dim(' Switch between scenarios and confirm the app reflects the seeded data each time'));
@@ -1153,6 +1422,7 @@ function printStep11(root, feature) {
1153
1422
  // ─── Step 12: Review ──────────────────────────────────────────────────
1154
1423
  function printStep12(root, feature) {
1155
1424
  const port = getServerPort();
1425
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1156
1426
  const prevState = readState(root);
1157
1427
  const isResuming = prevState?.step === 12;
1158
1428
  const now = new Date().toISOString();
@@ -1171,20 +1441,21 @@ function printStep12(root, feature) {
1171
1441
  console.log('Verify all screenshots and checks pass before presenting to the user.');
1172
1442
  console.log();
1173
1443
  console.log(chalk.bold('Checklist (do all of this silently):'));
1174
- checkbox(`Refresh the preview: \`codeyam editor preview\``);
1444
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1445
+ printDimensionGuidance(dim, dimNames);
1175
1446
  checkbox('Verify each component has screenshots in the App tab (grouped under Components)');
1176
1447
  checkbox('If any are missing, re-register them using `codeyam editor register`');
1177
1448
  checkbox(`Check for client errors: \`codeyam editor client-errors\``);
1178
1449
  checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
1179
1450
  checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
1180
1451
  checkbox('Fix or remove any broken images before continuing');
1452
+ checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
1181
1453
  checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
1182
1454
  checkbox('Do not proceed until all checks pass');
1183
1455
  stopGate(12);
1184
1456
  }
1185
1457
  // ─── Step 13: Present ─────────────────────────────────────────────────
1186
1458
  function printStep13(root, feature) {
1187
- const port = getServerPort();
1188
1459
  const prevState = readState(root);
1189
1460
  const isResuming = prevState?.step === 13;
1190
1461
  const now = new Date().toISOString();
@@ -1203,16 +1474,9 @@ function printStep13(root, feature) {
1203
1474
  console.log('Present the results to the user and get their approval.');
1204
1475
  console.log();
1205
1476
  console.log(chalk.bold('Checklist:'));
1206
- checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1207
- console.log(chalk.dim(' Each scenario has a `changeStatus` field: "new", "edited", "impacted", or null.'));
1208
- checkbox('Pick the best scenario to showcase from this working session:');
1209
- console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
1210
- console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
1211
- console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
1212
- checkbox('Switch the preview to that scenario using its `id`:');
1213
- console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>"}'`));
1214
1477
  checkbox(`Show the results panel: \`codeyam editor show-results\``);
1215
1478
  console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
1479
+ console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
1216
1480
  console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
1217
1481
  checkbox('Write a 1-2 sentence summary of what was built');
1218
1482
  checkbox('Report test count and audit status (one line)');
@@ -1224,17 +1488,7 @@ function printStep13(root, feature) {
1224
1488
  chalk.dim(' — describe changes, then re-verify'));
1225
1489
  console.log();
1226
1490
  console.log(chalk.bold('If the user chooses "Save & commit":'));
1227
- checkbox(`Hide the results panel first: \`codeyam editor hide-results\``);
1228
- checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
1229
- console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
1230
- checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
1231
- console.log(chalk.green(' Then run: ') +
1232
- chalk.bold('codeyam editor steps') +
1233
- chalk.green(' to start the next feature'));
1234
- console.log();
1235
- console.log(chalk.red.bold(' If the user reports a bug or requests a fix after committing:'));
1236
- console.log(chalk.red.bold(' You MUST still run `codeyam editor change` before making any changes.'));
1237
- console.log(chalk.red.bold(' The change workflow applies to ALL changes — post-commit fixes are not an exception.'));
1491
+ checkbox('Advance to the commit step: `codeyam editor 14`');
1238
1492
  console.log();
1239
1493
  console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
1240
1494
  checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
@@ -1244,45 +1498,775 @@ function printStep13(root, feature) {
1244
1498
  console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
1245
1499
  stopGate(13, { confirm: true });
1246
1500
  }
1247
- // ─── Command definition ───────────────────────────────────────────────
1248
- // ─── Analyze-imports subcommand ────────────────────────────────────────
1501
+ // ─── Migration Mode ──────────────────────────────────────────────────
1249
1502
  /**
1250
- * `codeyam editor analyze-imports`
1251
- *
1252
- * Runs data-structure-only analysis for all glossary entities, then outputs
1253
- * an import graph and entity data structures as JSON to stdout.
1503
+ * Print a progress tracker for the 8 migration steps.
1254
1504
  */
1255
- async function handleAnalyzeImports() {
1256
- const root = getProjectRoot();
1257
- // Read glossary
1258
- const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
1259
- if (!fs.existsSync(glossaryPath)) {
1505
+ function printMigrationProgressTracker(current, pageName, pageIndex, totalPages) {
1506
+ console.log();
1507
+ console.log(chalk.dim(` Migration: Page ${pageIndex + 1}/${totalPages} (${pageName})`));
1508
+ console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
1509
+ for (let i = 1; i <= 10; i++) {
1510
+ const label = MIGRATION_STEP_LABELS[i];
1511
+ const num = i < 10 ? ` ${i}` : `${i}`;
1512
+ const content = `${num}. ${label.padEnd(28)}`;
1513
+ if (i < current) {
1514
+ console.log(chalk.dim(' │') + chalk.green(` ✓ ${content}`) + chalk.dim('│'));
1515
+ }
1516
+ else if (i === current) {
1517
+ console.log(chalk.dim(' │') + chalk.bold.cyan(` → ${content}`) + chalk.dim('│'));
1518
+ }
1519
+ else {
1520
+ console.log(chalk.dim(` │ ○ ${content}│`));
1521
+ }
1522
+ }
1523
+ console.log(chalk.dim(' └─────────────────────────────────────┘'));
1524
+ }
1525
+ /**
1526
+ * Print a STOP gate for migration steps.
1527
+ */
1528
+ function migrationStopGate(current, pageName, pageIndex, totalPages, opts) {
1529
+ console.log();
1530
+ console.log(chalk.bold.red('━━━ STOP ━━━'));
1531
+ console.log();
1532
+ console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
1533
+ if (opts?.confirm) {
1534
+ console.log();
1535
+ console.log(chalk.yellow('Wait for user confirmation before moving on.'));
1536
+ }
1537
+ console.log();
1538
+ console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
1539
+ printMigrationProgressTracker(current, pageName, pageIndex, totalPages);
1540
+ console.log();
1541
+ console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
1542
+ console.log();
1543
+ if (current === 5) {
1544
+ // Step 5 (Discuss) can skip to step 9 if user declines decomposition
1545
+ console.log(chalk.green('If decomposing, run: ') +
1546
+ chalk.bold('codeyam editor migrate 6'));
1547
+ console.log(chalk.green('If skipping decomposition, run: ') +
1548
+ chalk.bold('codeyam editor migrate 9'));
1549
+ }
1550
+ else if (current < 10) {
1551
+ console.log(chalk.green('When done, run: ') +
1552
+ chalk.bold(`codeyam editor migrate ${current + 1}`));
1553
+ }
1554
+ else {
1555
+ console.log(chalk.green('Page complete! Run: ') +
1556
+ chalk.bold('codeyam editor migrate next') +
1557
+ chalk.green(' to start the next page'));
1558
+ }
1559
+ console.log();
1560
+ }
1561
+ /**
1562
+ * Write migration-aware editor state.
1563
+ */
1564
+ function writeMigrationStepState(root, step, pageName, pageIndex, totalPages) {
1565
+ const prevState = readState(root);
1566
+ const isResuming = prevState?.step === step && !!prevState?.migration;
1567
+ const now = new Date().toISOString();
1568
+ writeState(root, {
1569
+ feature: `Migration: ${pageName}`,
1570
+ step,
1571
+ label: MIGRATION_STEP_LABELS[step],
1572
+ startedAt: isResuming ? prevState.startedAt : now,
1573
+ featureStartedAt: prevState?.featureStartedAt || now,
1574
+ migration: {
1575
+ pageName,
1576
+ pageIndex,
1577
+ totalPages,
1578
+ },
1579
+ });
1580
+ logEvent(root, 'step', {
1581
+ step,
1582
+ label: MIGRATION_STEP_LABELS[step],
1583
+ feature: `Migration: ${pageName}`,
1584
+ migration: true,
1585
+ });
1586
+ }
1587
+ /**
1588
+ * Get the current migration page info from state, or return null.
1589
+ */
1590
+ function getCurrentMigrationPage(root) {
1591
+ const migState = readMigrationState(root);
1592
+ if (!migState || migState.pages.length === 0)
1593
+ return null;
1594
+ const page = migState.pages[migState.currentPageIndex];
1595
+ if (!page)
1596
+ return null;
1597
+ return {
1598
+ pageName: page.name,
1599
+ pageIndex: migState.currentPageIndex,
1600
+ totalPages: migState.pages.length,
1601
+ };
1602
+ }
1603
+ // ─── Migration Step 1: Survey ───────────────────────────────────────
1604
+ function printMigrateStep1(root) {
1605
+ const pageInfo = getCurrentMigrationPage(root);
1606
+ if (!pageInfo) {
1607
+ console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
1608
+ process.exit(1);
1609
+ }
1610
+ const { pageName, pageIndex, totalPages } = pageInfo;
1611
+ writeMigrationStepState(root, 1, pageName, pageIndex, totalPages);
1612
+ const migState = readMigrationState(root);
1613
+ const page = migState.pages[pageIndex];
1614
+ console.log();
1615
+ console.log(chalk.bold.cyan(`━━━ Migration Step 1: Survey — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1616
+ console.log();
1617
+ console.log(chalk.bold('Goal: Survey the page, understand its structure, and start the dev server.'));
1618
+ console.log();
1619
+ console.log(chalk.bold('Survey:'));
1620
+ checkbox(`Read the page file: \`${page.filePath}\``);
1621
+ checkbox('Follow every import — read all local components, hooks, utilities used by this page');
1622
+ checkbox('Map the data flow: where does data come from? Server actions? API routes? Props?');
1623
+ checkbox('Check `.codeyam/glossary.json` for components already extracted from previous pages');
1624
+ checkbox('Check `.codeyam/migration-state.json` sharedComponents for reusable pieces');
1625
+ checkbox('Note the page route and any dynamic segments');
1626
+ console.log();
1627
+ console.log(chalk.bold('Dev Server:'));
1628
+ checkbox('If the dev server is not running yet, figure out how to start it (check package.json scripts)');
1629
+ checkbox('FIRST: Verify dependencies are installed (check if node_modules/ exists and is non-empty)');
1630
+ console.log(chalk.yellow(' If node_modules is missing or sparse, run the package manager install command first'));
1631
+ console.log(chalk.dim(' Missing deps cause cryptic 500 errors at render time — install BEFORE starting the server'));
1632
+ checkbox('Start the dev server: `codeyam editor dev-server \'{"action":"start"}\'`');
1633
+ checkbox("Verify it's running: check for successful startup output");
1634
+ console.log();
1635
+ migrationStopGate(1, pageName, pageIndex, totalPages);
1636
+ }
1637
+ // ─── Migration Step 2: App Scenarios ────────────────────────────────
1638
+ function printMigrateStep2(root) {
1639
+ const pageInfo = getCurrentMigrationPage(root);
1640
+ if (!pageInfo) {
1641
+ console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
1642
+ process.exit(1);
1643
+ }
1644
+ const { pageName, pageIndex, totalPages } = pageInfo;
1645
+ writeMigrationStepState(root, 2, pageName, pageIndex, totalPages);
1646
+ const migState = readMigrationState(root);
1647
+ const page = migState.pages[pageIndex];
1648
+ console.log();
1649
+ console.log(chalk.bold.cyan(`━━━ Migration Step 2: App Scenarios — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1650
+ console.log();
1651
+ console.log(chalk.bold(`Goal: Register application scenarios that screenshot the ACTUAL page at "${page.route}".`));
1652
+ console.log();
1653
+ console.log(chalk.yellow(' App scenarios capture how the full page looks with different data states.'));
1654
+ console.log(chalk.yellow(` Every scenario MUST use the real page route ("${page.route}") — NOT isolation routes.`));
1655
+ console.log(chalk.yellow(' These are the primary scenarios the user will see for this page.'));
1656
+ console.log();
1657
+ console.log(chalk.bold('Seed Adapter Setup:'));
1658
+ checkbox('Check if a seed adapter exists: `ls .codeyam/seed-adapter.ts 2>/dev/null`');
1659
+ console.log(chalk.yellow(' If NO seed adapter exists, create one. The seed adapter wipes and re-seeds the database'));
1660
+ console.log(chalk.yellow(' for each scenario, enabling different data states on the real page.'));
1661
+ console.log();
1662
+ console.log(chalk.dim(' How to create a seed adapter:'));
1663
+ console.log(chalk.dim(' 1. Identify the database technology from your step 1 survey (Prisma, Supabase, Drizzle, etc.)'));
1664
+ console.log(chalk.dim(' 2. Check for a matching template: `ls .codeyam/seed-adapters/`'));
1665
+ console.log(chalk.dim(' 3. For Supabase: `cp .codeyam/seed-adapters/supabase.ts .codeyam/seed-adapter.ts`'));
1666
+ console.log(chalk.dim(' The seed adapter auto-detects Supabase credentials by scanning env var VALUES (not names).'));
1667
+ console.log(chalk.dim(' It reads .env / .env.local and finds: *.supabase.co URLs, sb_secret_* keys, and legacy JWTs.'));
1668
+ console.log(chalk.dim(' Do NOT grep for specific env var names — just register a scenario and try it.'));
1669
+ console.log(chalk.dim(' If it fails, the error message will say exactly what credentials are missing.'));
1670
+ console.log(chalk.dim(' 4. For Prisma: write a seed adapter that uses your Prisma client to delete/insert rows'));
1671
+ console.log(chalk.dim(' 5. For other databases: write a .codeyam/seed-adapter.ts that reads a JSON file (argv[2]),'));
1672
+ console.log(chalk.dim(' deletes all rows from each table, then inserts the seed rows. Format: {"table": [{...}]}'));
1673
+ console.log(chalk.dim(' 6. Test it: write a small test seed JSON and run `npx tsx .codeyam/seed-adapter.ts test.json`'));
1674
+ console.log();
1675
+ printAppScenarioInstructions(pageName, page.route);
1676
+ console.log();
1677
+ migrationStopGate(2, pageName, pageIndex, totalPages);
1678
+ }
1679
+ // ─── Migration Step 3: Component Scenarios ──────────────────────────
1680
+ function printMigrateStep3(root) {
1681
+ const pageInfo = getCurrentMigrationPage(root);
1682
+ if (!pageInfo) {
1683
+ console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
1684
+ process.exit(1);
1685
+ }
1686
+ const { pageName, pageIndex, totalPages } = pageInfo;
1687
+ writeMigrationStepState(root, 3, pageName, pageIndex, totalPages);
1688
+ console.log();
1689
+ console.log(chalk.bold.cyan(`━━━ Migration Step 3: Component Scenarios — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1690
+ console.log();
1691
+ console.log(chalk.bold('Goal: Capture individual component scenarios for major visual components on this page.'));
1692
+ console.log();
1693
+ console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
1694
+ console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
1695
+ console.log();
1696
+ printComponentCaptureInstructions();
1697
+ console.log();
1698
+ migrationStopGate(3, pageName, pageIndex, totalPages);
1699
+ }
1700
+ // ─── Migration Step 4: Preview ──────────────────────────────────────
1701
+ function printMigrateStep4(root) {
1702
+ const pageInfo = getCurrentMigrationPage(root);
1703
+ if (!pageInfo) {
1704
+ console.error(chalk.red('Error: No migration in progress.'));
1705
+ process.exit(1);
1706
+ }
1707
+ const { pageName, pageIndex, totalPages } = pageInfo;
1708
+ writeMigrationStepState(root, 4, pageName, pageIndex, totalPages);
1709
+ console.log();
1710
+ console.log(chalk.bold.cyan(`━━━ Migration Step 4: Preview — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1711
+ console.log();
1712
+ console.log(chalk.bold('Goal: Verify screenshots look correct and show results to the user.'));
1713
+ console.log();
1714
+ console.log(chalk.bold('Checklist:'));
1715
+ checkbox('Review all scenario screenshots for this page — do they look correct?');
1716
+ checkbox('Check for client errors: `codeyam editor client-errors`');
1717
+ checkbox('If any issues: fix the scenario data, re-register affected scenarios');
1718
+ checkbox('Show results: `codeyam editor show-results`');
1719
+ console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
1720
+ console.log();
1721
+ console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
1722
+ migrationStopGate(4, pageName, pageIndex, totalPages);
1723
+ }
1724
+ // ─── Migration Step 5: Discuss ──────────────────────────────────────
1725
+ function printMigrateStep5(root) {
1726
+ const pageInfo = getCurrentMigrationPage(root);
1727
+ if (!pageInfo) {
1728
+ console.error(chalk.red('Error: No migration in progress.'));
1729
+ process.exit(1);
1730
+ }
1731
+ const { pageName, pageIndex, totalPages } = pageInfo;
1732
+ writeMigrationStepState(root, 5, pageName, pageIndex, totalPages);
1733
+ console.log();
1734
+ console.log(chalk.bold.cyan(`━━━ Migration Step 5: Discuss — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1735
+ console.log();
1736
+ console.log(chalk.bold('Goal: Assess page complexity and ask the user if decomposition is worth it.'));
1737
+ console.log();
1738
+ console.log(chalk.bold('Checklist:'));
1739
+ checkbox('Hide results: `codeyam editor hide-results`');
1740
+ checkbox("Assess the page's complexity:");
1741
+ console.log(chalk.dim(' — How many inline components exist?'));
1742
+ console.log(chalk.dim(' — How much business logic is embedded in the page file?'));
1743
+ console.log(chalk.dim(' — Are there reusable pieces that other pages could share?'));
1744
+ console.log(chalk.dim(' — Would extraction improve testability or maintainability?'));
1745
+ checkbox('Present your assessment to the user');
1746
+ console.log();
1747
+ console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
1748
+ console.log(chalk.green(' Option 1 label: "Yes, decompose this page"') +
1749
+ chalk.dim(' — continue to step 6 (Decompose)'));
1750
+ console.log(chalk.yellow(' Option 2 label: "No, skip decomposition"') +
1751
+ chalk.dim(' — skip to step 9 (Journal)'));
1752
+ console.log();
1753
+ console.log(chalk.bold.yellow('Routing:'));
1754
+ console.log(chalk.yellow(' If user chooses decomposition: ') +
1755
+ chalk.bold('codeyam editor migrate 6'));
1756
+ console.log(chalk.yellow(' If user skips: ') + chalk.bold('codeyam editor migrate 9'));
1757
+ migrationStopGate(5, pageName, pageIndex, totalPages, { confirm: true });
1758
+ }
1759
+ // ─── Migration Step 6: Decompose ────────────────────────────────────
1760
+ function printMigrateStep6(root) {
1761
+ const pageInfo = getCurrentMigrationPage(root);
1762
+ if (!pageInfo) {
1763
+ console.error(chalk.red('Error: No migration in progress.'));
1764
+ process.exit(1);
1765
+ }
1766
+ const { pageName, pageIndex, totalPages } = pageInfo;
1767
+ writeMigrationStepState(root, 6, pageName, pageIndex, totalPages);
1768
+ console.log();
1769
+ console.log(chalk.bold.cyan(`━━━ Migration Step 6: Decompose — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1770
+ console.log();
1771
+ console.log(chalk.bold('Goal: Plan all extractions. Mark already-extracted shared components as REUSE.'));
1772
+ console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
1773
+ console.log();
1774
+ console.log(chalk.bold.yellow('Migration note: check glossary for reuse opportunities'));
1775
+ console.log(chalk.yellow(' If a component exists in the glossary from a previous page, mark it as REUSE in your plan.'));
1776
+ console.log(chalk.yellow(' Only add it to the extraction plan if it needs modifications for this page.'));
1777
+ console.log();
1778
+ printExtractionPlanInstructions();
1779
+ migrationStopGate(6, pageName, pageIndex, totalPages);
1780
+ }
1781
+ // ─── Migration Step 7: Extract ──────────────────────────────────────
1782
+ function printMigrateStep7(root) {
1783
+ const pageInfo = getCurrentMigrationPage(root);
1784
+ if (!pageInfo) {
1785
+ console.error(chalk.red('Error: No migration in progress.'));
1786
+ process.exit(1);
1787
+ }
1788
+ const { pageName, pageIndex, totalPages } = pageInfo;
1789
+ writeMigrationStepState(root, 7, pageName, pageIndex, totalPages);
1790
+ console.log();
1791
+ console.log(chalk.bold.cyan(`━━━ Migration Step 7: Extract — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1792
+ console.log();
1793
+ console.log(chalk.bold('Goal: Execute the extraction plan. Components first, then functions via TDD.'));
1794
+ console.log();
1795
+ console.log(chalk.bold('Checklist:'));
1796
+ checkbox('Extract visual components (no tests needed for components)');
1797
+ checkbox('Extract library functions via TDD (write failing test first, then implement)');
1798
+ checkbox('Extract hooks as needed');
1799
+ checkbox('Recursive pass: check each extracted component for further extraction');
1800
+ console.log(chalk.yellow(' Each component should be a thin shell composing sub-components.'));
1801
+ checkbox('Verify the page file is ONLY imports + component composition');
1802
+ checkbox('Run tests to confirm nothing is broken: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
1803
+ console.log();
1804
+ console.log(chalk.dim('After extraction, the page should be a thin shell. Move to step 8 to recapture.'));
1805
+ migrationStopGate(7, pageName, pageIndex, totalPages);
1806
+ }
1807
+ // ─── Migration Step 8: Recapture ────────────────────────────────────
1808
+ function printMigrateStep8(root) {
1809
+ const pageInfo = getCurrentMigrationPage(root);
1810
+ if (!pageInfo) {
1811
+ console.error(chalk.red('Error: No migration in progress.'));
1812
+ process.exit(1);
1813
+ }
1814
+ const { pageName, pageIndex, totalPages } = pageInfo;
1815
+ writeMigrationStepState(root, 8, pageName, pageIndex, totalPages);
1816
+ const migState = readMigrationState(root);
1817
+ const page = migState.pages[pageIndex];
1818
+ console.log();
1819
+ console.log(chalk.bold.cyan(`━━━ Migration Step 8: Recapture — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1820
+ console.log();
1821
+ console.log(chalk.bold('Goal: Update glossary, re-register ALL scenarios after code changes, and verify.'));
1822
+ console.log();
1823
+ console.log(chalk.bold('Glossary:'));
1824
+ printGlossaryInstructions();
1825
+ console.log(chalk.yellow(' Skip entries already in the glossary from previous page migrations.'));
1826
+ checkbox('Update sharedComponents in `.codeyam/migration-state.json` if any extracted components are shared');
1827
+ console.log();
1828
+ console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
1829
+ console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
1830
+ checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
1831
+ checkbox('Find all scenarios that use the real page route (not /codeyam-isolate/ paths)');
1832
+ checkbox('Re-register each one with the SAME name to update its screenshot');
1833
+ console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
1834
+ checkbox('After each registration, check the response for `clientErrors`');
1835
+ console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
1836
+ console.log();
1837
+ console.log(chalk.bold('Component Scenarios:'));
1838
+ console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
1839
+ printComponentCaptureInstructions();
1840
+ console.log();
1841
+ console.log(chalk.bold('Verify:'));
1842
+ checkbox('Run `codeyam editor analyze-imports` to populate import graph');
1843
+ checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
1844
+ checkbox('Run `codeyam editor audit` to check completeness');
1845
+ checkbox('Check for client errors: `codeyam editor client-errors`');
1846
+ checkbox('Fix any issues before proceeding');
1847
+ console.log();
1848
+ migrationStopGate(8, pageName, pageIndex, totalPages);
1849
+ }
1850
+ // ─── Migration Step 9: Journal ──────────────────────────────────────
1851
+ function printMigrateStep9(root) {
1852
+ const pageInfo = getCurrentMigrationPage(root);
1853
+ if (!pageInfo) {
1854
+ console.error(chalk.red('Error: No migration in progress.'));
1855
+ process.exit(1);
1856
+ }
1857
+ const { pageName, pageIndex, totalPages } = pageInfo;
1858
+ writeMigrationStepState(root, 9, pageName, pageIndex, totalPages);
1859
+ console.log();
1860
+ console.log(chalk.bold.cyan(`━━━ Migration Step 9: Journal — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1861
+ console.log();
1862
+ console.log(`Create a journal entry documenting the migration of the ${pageName} page.`);
1863
+ console.log();
1864
+ console.log(chalk.bold('Checklist:'));
1865
+ checkbox('Write a concise description of what was migrated (2-3 sentences)');
1866
+ checkbox(`Create journal: \`codeyam editor journal '{"title":"Migration: ${pageName}","type":"migration","description":"...","includeSessionScenarios":true}'\``);
1867
+ console.log();
1868
+ migrationStopGate(9, pageName, pageIndex, totalPages);
1869
+ }
1870
+ // ─── Migration Step 10: Present ─────────────────────────────────────
1871
+ function printMigrateStep10(root) {
1872
+ const pageInfo = getCurrentMigrationPage(root);
1873
+ if (!pageInfo) {
1874
+ console.error(chalk.red('Error: No migration in progress.'));
1875
+ process.exit(1);
1876
+ }
1877
+ const { pageName, pageIndex, totalPages } = pageInfo;
1878
+ writeMigrationStepState(root, 10, pageName, pageIndex, totalPages);
1879
+ console.log();
1880
+ console.log(chalk.bold.cyan(`━━━ Migration Step 10: Present — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1881
+ console.log();
1882
+ console.log("Show results and commit this page's migration.");
1883
+ console.log();
1884
+ console.log(chalk.bold('Checklist:'));
1885
+ checkbox('Show results: `codeyam editor show-results`');
1886
+ console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
1887
+ checkbox('Write a 1-2 sentence summary of what was migrated');
1888
+ console.log();
1889
+ console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
1890
+ console.log(chalk.green(' Option 1 label: "Save & commit"') +
1891
+ chalk.dim(" — commit this page's migration"));
1892
+ console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
1893
+ chalk.dim(' — make changes, then re-verify'));
1894
+ console.log();
1895
+ console.log(chalk.bold('If the user chooses "Save & commit":'));
1896
+ checkbox('Hide results: `codeyam editor hide-results`');
1897
+ checkbox(`Commit: \`codeyam editor commit '{"message":"migration: ${pageName} page\\n\\n<description>"}'\``);
1898
+ checkbox('Mark page complete: `codeyam editor migrate complete`');
1899
+ const remaining = pageInfo.totalPages - pageIndex - 1;
1900
+ if (remaining > 0) {
1901
+ checkbox('Advance to next page: `codeyam editor migrate next`');
1902
+ console.log();
1903
+ console.log(chalk.bold.green(`Page ${pageIndex + 1}/${totalPages} complete! ${remaining} page(s) remaining.`));
1904
+ }
1905
+ else {
1906
+ console.log();
1907
+ console.log(chalk.bold.green('Migration complete! All pages processed.'));
1908
+ console.log(chalk.green('The project is now fully migrated. Future sessions use the normal feature workflow.'));
1909
+ console.log(chalk.green('Start building: ') + chalk.bold('codeyam editor steps'));
1910
+ }
1911
+ console.log();
1912
+ console.log(chalk.bold('If the user chooses "Make changes":'));
1913
+ checkbox('Hide results: `codeyam editor hide-results`');
1914
+ checkbox('Ask what changes the user wants');
1915
+ checkbox(`Run: \`codeyam editor change "Migration: ${pageName}"\` — follow the change checklist`);
1916
+ console.log();
1917
+ migrationStopGate(10, pageName, pageIndex, totalPages, { confirm: true });
1918
+ }
1919
+ // ─── Migration Survey ───────────────────────────────────────────────
1920
+ function printMigrateSurvey(root) {
1921
+ console.log();
1922
+ console.log(chalk.bold.cyan('━━━ Project Migration Survey ━━━'));
1923
+ console.log();
1924
+ console.log('Survey the existing project, present a migration plan, and confirm the order with the user.');
1925
+ console.log(chalk.dim('See the "Migration Survey" section in SKILL.md for the full checklist and migration-state.json format.'));
1926
+ console.log();
1927
+ // Write editor state so hooks and printCycleOverview detect migration
1928
+ const now = new Date().toISOString();
1929
+ writeState(root, {
1930
+ feature: 'Migration: Survey',
1931
+ step: 0,
1932
+ label: 'Survey',
1933
+ startedAt: now,
1934
+ featureStartedAt: now,
1935
+ migration: {
1936
+ pageName: 'Survey',
1937
+ pageIndex: -1,
1938
+ totalPages: 0,
1939
+ },
1940
+ });
1941
+ logEvent(root, 'migration-survey', {});
1942
+ console.log(chalk.bold.red('━━━ STOP ━━━'));
1943
+ console.log();
1944
+ console.log(chalk.red('Survey the project and confirm the migration order with the user.'));
1945
+ console.log(chalk.green('After confirmation and writing migration-state.json: ') +
1946
+ chalk.bold('codeyam editor migrate 1'));
1947
+ console.log();
1948
+ }
1949
+ // ─── Migration Status ───────────────────────────────────────────────
1950
+ function printMigrationStatus(root) {
1951
+ const state = readMigrationState(root);
1952
+ if (!state) {
1953
+ console.log(chalk.dim('No migration in progress.'));
1954
+ return;
1955
+ }
1956
+ console.log();
1957
+ console.log(chalk.bold.cyan('━━━ Migration Progress ━━━'));
1958
+ console.log();
1959
+ const done = state.pages.filter((p) => p.status === 'complete').length;
1960
+ const total = state.pages.length;
1961
+ const pct = total > 0 ? Math.round((done / total) * 100) : 0;
1962
+ console.log(` Status: ${chalk.bold(state.status)} — ${done}/${total} pages (${pct}%)`);
1963
+ console.log();
1964
+ for (let i = 0; i < state.pages.length; i++) {
1965
+ const p = state.pages[i];
1966
+ let icon;
1967
+ let color;
1968
+ if (p.status === 'complete') {
1969
+ icon = '✓';
1970
+ color = chalk.green;
1971
+ }
1972
+ else if (p.status === 'in-progress') {
1973
+ icon = '→';
1974
+ color = chalk.bold.cyan;
1975
+ }
1976
+ else {
1977
+ icon = '○';
1978
+ color = chalk.dim;
1979
+ }
1980
+ const extra = p.status === 'complete'
1981
+ ? chalk.dim(` (${p.extractedComponents.length} components, ${p.extractedFunctions.length} functions, ${p.scenarioCount} scenarios)`)
1982
+ : '';
1983
+ console.log(` ${color(`${icon} ${i + 1}. ${p.name}`)} ${chalk.dim(`(${p.route})`)}${extra}`);
1984
+ }
1985
+ if (state.sharedComponents.length > 0) {
1986
+ console.log();
1987
+ console.log(chalk.bold('Shared Components:'));
1988
+ for (const sc of state.sharedComponents) {
1989
+ console.log(` ${chalk.cyan(sc.name)} ${chalk.dim(`— from ${sc.extractedFromPage}, used in: ${sc.usedInPages.join(', ')}`)}`);
1990
+ }
1991
+ }
1992
+ console.log();
1993
+ }
1994
+ // ─── Migration Command Dispatcher ───────────────────────────────────
1995
+ function handleMigrateCommand(root, subArg) {
1996
+ // No subcommand or explicit empty → run initial survey
1997
+ if (!subArg) {
1998
+ const migState = readMigrationState(root);
1999
+ if (migState?.status === 'in-progress') {
2000
+ // Already in progress — show status and resume hint
2001
+ printMigrationStatus(root);
2002
+ const page = migState.pages[migState.currentPageIndex];
2003
+ if (page) {
2004
+ const editorState = readState(root);
2005
+ const currentStep = editorState?.migration ? editorState.step : 1;
2006
+ console.log(chalk.green('Continue with: ') +
2007
+ chalk.bold(`codeyam editor migrate ${currentStep}`));
2008
+ console.log();
2009
+ }
2010
+ return;
2011
+ }
2012
+ if (migState?.status === 'complete') {
2013
+ console.log(chalk.green('Migration is complete! Use the normal feature workflow: `codeyam editor steps`'));
2014
+ return;
2015
+ }
2016
+ printMigrateSurvey(root);
2017
+ return;
2018
+ }
2019
+ // Parse subcommand
2020
+ if (subArg === 'next') {
2021
+ const result = advanceToNextPage(root);
2022
+ if (!result) {
2023
+ const migState = readMigrationState(root);
2024
+ if (migState?.status === 'complete') {
2025
+ console.log();
2026
+ console.log(chalk.bold.green('🎉 Migration complete!'));
2027
+ console.log();
2028
+ const done = migState.pages.filter((p) => p.status === 'complete');
2029
+ const totalComponents = done.reduce((sum, p) => sum + p.extractedComponents.length, 0);
2030
+ const totalFunctions = done.reduce((sum, p) => sum + p.extractedFunctions.length, 0);
2031
+ const totalScenarios = done.reduce((sum, p) => sum + p.scenarioCount, 0);
2032
+ console.log(` Pages migrated: ${done.length}`);
2033
+ console.log(` Components extracted: ${totalComponents}`);
2034
+ console.log(` Functions extracted: ${totalFunctions}`);
2035
+ console.log(` Scenarios created: ${totalScenarios}`);
2036
+ console.log(` Shared components: ${migState.sharedComponents.length}`);
2037
+ console.log();
2038
+ console.log(chalk.green('The project is fully migrated. Start building features: `codeyam editor steps`'));
2039
+ console.log();
2040
+ }
2041
+ else {
2042
+ console.log(chalk.red('No pending pages found. Run `codeyam editor migrate status` to check progress.'));
2043
+ }
2044
+ return;
2045
+ }
2046
+ // Start M1 for the next page
2047
+ printMigrateStep1(root);
2048
+ return;
2049
+ }
2050
+ if (subArg === 'status') {
2051
+ printMigrationStatus(root);
2052
+ return;
2053
+ }
2054
+ if (subArg === 'complete') {
2055
+ const migState = readMigrationState(root);
2056
+ if (!migState) {
2057
+ console.error(chalk.red('Error: No migration in progress.'));
2058
+ process.exit(1);
2059
+ }
2060
+ const page = migState.pages[migState.currentPageIndex];
2061
+ if (!page) {
2062
+ console.error(chalk.red('Error: No active migration page.'));
2063
+ process.exit(1);
2064
+ }
2065
+ completePage(root, {
2066
+ extractedComponents: page.extractedComponents,
2067
+ extractedFunctions: page.extractedFunctions,
2068
+ scenarioCount: page.scenarioCount,
2069
+ });
2070
+ console.log(chalk.green(`Page "${page.name}" marked complete.`));
2071
+ return;
2072
+ }
2073
+ // Numeric step: 1-8
2074
+ const step = parseInt(subArg, 10);
2075
+ if (isNaN(step) || step < 1 || step > 10) {
2076
+ console.error(chalk.red(`Error: Invalid migration step "${subArg}". Must be 1-10, "next", "complete", or "status".`));
2077
+ process.exit(1);
2078
+ }
2079
+ // Ensure migration is active and first page started
2080
+ const migState = readMigrationState(root);
2081
+ if (!migState) {
2082
+ console.error(chalk.red('Error: No migration state. Run `codeyam editor migrate` first.'));
2083
+ process.exit(1);
2084
+ }
2085
+ // If still in survey state and step 1 requested, start first page
2086
+ if (migState.status === 'surveyed' && step === 1) {
2087
+ migState.status = 'in-progress';
2088
+ migState.pages[0].status = 'in-progress';
2089
+ migState.pages[0].startedAt = new Date().toISOString();
2090
+ writeMigrationState(root, migState);
2091
+ }
2092
+ const stepFns = {
2093
+ 1: printMigrateStep1,
2094
+ 2: printMigrateStep2,
2095
+ 3: printMigrateStep3,
2096
+ 4: printMigrateStep4,
2097
+ 5: printMigrateStep5,
2098
+ 6: printMigrateStep6,
2099
+ 7: printMigrateStep7,
2100
+ 8: printMigrateStep8,
2101
+ 9: printMigrateStep9,
2102
+ 10: printMigrateStep10,
2103
+ };
2104
+ stepFns[step](root);
2105
+ }
2106
+ // ─── Step 14: Commit ─────────────────────────────────────────────────
2107
+ function printStep14(root, feature) {
2108
+ const prevState = readState(root);
2109
+ const isResuming = prevState?.step === 14;
2110
+ const now = new Date().toISOString();
2111
+ writeState(root, {
2112
+ feature,
2113
+ step: 14,
2114
+ label: STEP_LABELS[14],
2115
+ startedAt: isResuming ? prevState.startedAt : now,
2116
+ featureStartedAt: prevState?.featureStartedAt || now,
2117
+ });
2118
+ logEvent(root, 'step', { step: 14, label: 'Commit', feature });
2119
+ stepHeader(14, 'Commit', feature);
2120
+ if (isResuming) {
2121
+ printResumptionHeader(14);
2122
+ }
2123
+ console.log('Commit all changes for this feature.');
2124
+ console.log();
2125
+ console.log(chalk.bold('Checklist:'));
2126
+ checkbox('Ensure all screenshots are fresh: `codeyam editor recapture-stale`');
2127
+ checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
2128
+ checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
2129
+ console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
2130
+ stopGate(14);
2131
+ }
2132
+ // ─── Step 15: Finalize ───────────────────────────────────────────────
2133
+ function printStep15(root, feature) {
2134
+ const prevState = readState(root);
2135
+ const isResuming = prevState?.step === 15;
2136
+ const now = new Date().toISOString();
2137
+ writeState(root, {
2138
+ feature,
2139
+ step: 15,
2140
+ label: STEP_LABELS[15],
2141
+ startedAt: isResuming ? prevState.startedAt : now,
2142
+ featureStartedAt: prevState?.featureStartedAt || now,
2143
+ });
2144
+ logEvent(root, 'step', { step: 15, label: 'Finalize', feature });
2145
+ stepHeader(15, 'Finalize', feature);
2146
+ if (isResuming) {
2147
+ printResumptionHeader(15);
2148
+ }
2149
+ console.log('Update the journal with the commit SHA and amend the commit.');
2150
+ console.log();
2151
+ console.log(chalk.bold('Checklist:'));
2152
+ checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
2153
+ checkbox('Amend the commit to include the journal update: `git add .codeyam/journal/ && git commit --amend --no-edit`');
2154
+ console.log(chalk.dim(' The journal-update modifies journal files after the commit — amend to keep the tree clean.'));
2155
+ stopGate(15);
2156
+ }
2157
+ // ─── Step 16: Push ───────────────────────────────────────────────────
2158
+ function printStep16(root, feature) {
2159
+ const prevState = readState(root);
2160
+ const isResuming = prevState?.step === 16;
2161
+ const now = new Date().toISOString();
2162
+ writeState(root, {
2163
+ feature,
2164
+ step: 16,
2165
+ label: STEP_LABELS[16],
2166
+ startedAt: isResuming ? prevState.startedAt : now,
2167
+ featureStartedAt: prevState?.featureStartedAt || now,
2168
+ });
2169
+ logEvent(root, 'step', { step: 16, label: 'Push', feature });
2170
+ stepHeader(16, 'Push', feature);
2171
+ if (isResuming) {
2172
+ printResumptionHeader(16);
2173
+ }
2174
+ console.log('Push the commit to the remote repository.');
2175
+ console.log();
2176
+ console.log(chalk.bold('Checklist:'));
2177
+ checkbox('Check if a git remote is configured: `git remote -v`');
2178
+ checkbox("Offer to push to remote (AskUserQuestion — STOP and wait for the user's answer before proceeding):");
2179
+ 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"'));
2180
+ console.log(chalk.dim(' If the user says yes, run: `git push`'));
2181
+ 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"'));
2182
+ console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
2183
+ checkbox('After the user responds, run: `codeyam editor steps` to start the next feature');
2184
+ console.log();
2185
+ console.log(chalk.red.bold(' If the user reports a bug or requests a fix after committing:'));
2186
+ console.log(chalk.red.bold(' You MUST still run `codeyam editor change` before making any changes.'));
2187
+ console.log(chalk.red.bold(' The change workflow applies to ALL changes — post-commit fixes are not an exception.'));
2188
+ stopGate(16, { confirm: true });
2189
+ }
2190
+ // ─── Command definition ───────────────────────────────────────────────
2191
+ // ─── Analyze-imports subcommand ────────────────────────────────────────
2192
+ /**
2193
+ * `codeyam editor analyze-imports`
2194
+ *
2195
+ * Runs data-structure-only analysis for all glossary entities, then outputs
2196
+ * an import graph and entity data structures as JSON to stdout.
2197
+ */
2198
+ async function handleAnalyzeImports(options = {}) {
2199
+ const root = getProjectRoot();
2200
+ // Read glossary
2201
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
2202
+ if (!fs.existsSync(glossaryPath)) {
2203
+ if (options.silent) {
2204
+ // Internal caller — glossary doesn't exist yet, nothing to analyze
2205
+ return;
2206
+ }
1260
2207
  console.error(chalk.red('Error: .codeyam/glossary.json not found.'));
1261
2208
  console.error(chalk.dim(' Run codeyam editor 6 to create the glossary first.'));
1262
2209
  process.exit(1);
1263
2210
  }
1264
2211
  let glossaryEntries;
1265
2212
  try {
1266
- glossaryEntries = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
2213
+ const parsed = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
2214
+ glossaryEntries = sanitizeGlossaryEntries(parsed);
1267
2215
  }
1268
2216
  catch {
1269
2217
  console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
1270
2218
  process.exit(1);
1271
2219
  }
1272
- if (!Array.isArray(glossaryEntries) || glossaryEntries.length === 0) {
2220
+ if (glossaryEntries.length === 0) {
1273
2221
  console.error(chalk.red('Error: glossary.json is empty.'));
1274
2222
  process.exit(1);
1275
2223
  }
1276
2224
  const filePaths = glossaryEntries.map((e) => e.filePath);
1277
- const entityNames = glossaryEntries.map((e) => e.name);
2225
+ // Include page files so pages get analyzed as entities too.
2226
+ // This allows the import graph to map page names to their dependencies.
2227
+ // Use allFiles (not map values) to include ALL pages — multiple routes
2228
+ // can share a page name (e.g., /feedback/[id] and /feedback/new both
2229
+ // map to "Feedback") but each needs its own entity analysis.
2230
+ const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
2231
+ const { allFiles: allPageFiles } = scanPageFilePaths(root);
2232
+ for (const pageFile of allPageFiles) {
2233
+ if (!filePaths.includes(pageFile)) {
2234
+ filePaths.push(pageFile);
2235
+ }
2236
+ }
2237
+ // Include file paths from editor scenarios (both component_path and
2238
+ // page_file_path) so that all components and pages with scenarios get
2239
+ // entities — critical for non-Next.js apps where scanPageFilePaths
2240
+ // doesn't find page.tsx files.
2241
+ try {
2242
+ const { getDatabase } = await import('../../../packages/database/index.js');
2243
+ const db = getDatabase();
2244
+ const scenarioFiles = await db
2245
+ .selectFrom('editor_scenarios')
2246
+ .select(['component_path', 'page_file_path'])
2247
+ .distinct()
2248
+ .execute();
2249
+ for (const row of scenarioFiles) {
2250
+ const r = row;
2251
+ for (const fp of [r.component_path, r.page_file_path]) {
2252
+ if (fp && !filePaths.includes(fp)) {
2253
+ filePaths.push(fp);
2254
+ }
2255
+ }
2256
+ }
2257
+ }
2258
+ catch {
2259
+ // Non-fatal — scenario file paths just won't be included
2260
+ }
1278
2261
  const progress = new ProgressReporter();
1279
- // Run data-structure-only analysis for all entities
1280
- progress.start('Running import analysis for all glossary entities...');
2262
+ // Run data-structure-only analysis for all entities (glossary + pages)
2263
+ // Don't pass entityNames entities may not exist yet (fresh clone).
2264
+ // The analyzer will discover and create them from file paths.
2265
+ progress.start('Running import analysis for all entities...');
1281
2266
  try {
1282
2267
  await runAnalysisForEntities({
1283
2268
  projectRoot: root,
1284
2269
  filePaths,
1285
- entityNames,
1286
2270
  progress,
1287
2271
  onlyDataStructure: true,
1288
2272
  });
@@ -1300,7 +2284,9 @@ async function handleAnalyzeImports() {
1300
2284
  const entities = await loadEntities({});
1301
2285
  if (!entities || entities.length === 0) {
1302
2286
  progress.succeed('No entities found in database yet — skipping import analysis. This is normal on the first feature.');
1303
- console.log(JSON.stringify({ imports: {}, entities: {} }));
2287
+ if (!options.silent) {
2288
+ console.log(JSON.stringify({ imports: {}, entities: {} }));
2289
+ }
1304
2290
  return;
1305
2291
  }
1306
2292
  // Deduplicate to latest versions
@@ -1315,7 +2301,7 @@ async function handleAnalyzeImports() {
1315
2301
  latestByKey.set(key, entity);
1316
2302
  }
1317
2303
  }
1318
- const entityNameSet = new Set(entityNames);
2304
+ const entityNameSet = new Set(glossaryEntries.map((e) => e.name));
1319
2305
  const latestEntities = [...latestByKey.values()].filter((e) => entityNameSet.has(e.name));
1320
2306
  // Build import graph from importedExports metadata
1321
2307
  const imports = {};
@@ -1353,9 +2339,34 @@ async function handleAnalyzeImports() {
1353
2339
  };
1354
2340
  }
1355
2341
  progress.succeed('Done');
1356
- // Output combined JSON
1357
- const output = { imports, entities: entityData };
1358
- console.log(JSON.stringify(output, null, 2));
2342
+ // Output combined JSON (suppressed when called internally during startup)
2343
+ if (!options.silent) {
2344
+ const summary = formatApiSubcommandResult('analyze-imports', {
2345
+ imports,
2346
+ entities: entityData,
2347
+ });
2348
+ console.log(summary || JSON.stringify({ imports, entities: entityData }));
2349
+ }
2350
+ // Backfill entity_sha on scenarios registered before entities existed.
2351
+ // This fixes the "missing data" UI state when scenarios were registered
2352
+ // while analyze-imports was still running in the background.
2353
+ try {
2354
+ const { getDatabase } = await import('../../../packages/database/index.js');
2355
+ const db = getDatabase();
2356
+ const backfillResult = await backfillEntityShaOnScenarios(db, latestEntities.map((e) => ({
2357
+ sha: e.sha,
2358
+ name: e.name,
2359
+ filePath: e.filePath || '',
2360
+ isDefaultExport: e.metadata?.notExported === false &&
2361
+ e.metadata?.namedExport === false,
2362
+ })));
2363
+ if (backfillResult.updated > 0 && !options.silent) {
2364
+ console.log(chalk.green(`Linked ${backfillResult.updated} scenario(s) to their entities.`));
2365
+ }
2366
+ }
2367
+ catch {
2368
+ /* non-fatal */
2369
+ }
1359
2370
  }
1360
2371
  // ─── Validate-seed subcommand ─────────────────────────────────────────
1361
2372
  /**
@@ -1394,6 +2405,24 @@ function handleValidateSeed(jsonArg) {
1394
2405
  : 0;
1395
2406
  console.log(chalk.dim(` ${table}: ${rows} row(s)`));
1396
2407
  }
2408
+ // Check seed keys against Prisma schema if available
2409
+ const schemaPath = path.join(root, 'prisma', 'schema.prisma');
2410
+ try {
2411
+ if (fs.existsSync(schemaPath)) {
2412
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
2413
+ const warnings = validateSeedKeysAgainstPrisma(tables, schemaContent);
2414
+ if (warnings.length > 0) {
2415
+ console.log();
2416
+ console.log(chalk.yellow('Prisma model name warnings:'));
2417
+ for (const w of warnings) {
2418
+ console.log(chalk.yellow(` ⚠ ${w}`));
2419
+ }
2420
+ }
2421
+ }
2422
+ }
2423
+ catch {
2424
+ // Schema not readable — skip Prisma validation
2425
+ }
1397
2426
  }
1398
2427
  else {
1399
2428
  console.error(chalk.red('Seed data validation failed:'));
@@ -1403,6 +2432,27 @@ function handleValidateSeed(jsonArg) {
1403
2432
  process.exit(1);
1404
2433
  }
1405
2434
  }
2435
+ // ─── Delete subcommand ────────────────────────────────────────────────
2436
+ /**
2437
+ * `codeyam editor delete <scenarioId>`
2438
+ *
2439
+ * Delete a scenario by ID via the running editor server.
2440
+ */
2441
+ async function handleDelete(scenarioId) {
2442
+ if (!scenarioId) {
2443
+ console.error(chalk.red('Error: Missing scenario ID. Usage: codeyam editor delete <scenarioId>'));
2444
+ process.exit(1);
2445
+ }
2446
+ const port = getServerPort();
2447
+ const result = await deleteScenarioViaCli(scenarioId, port);
2448
+ if (result.success) {
2449
+ console.log(chalk.green(result.message));
2450
+ }
2451
+ else {
2452
+ console.error(chalk.red(result.message));
2453
+ process.exit(1);
2454
+ }
2455
+ }
1406
2456
  // ─── Isolate subcommand ───────────────────────────────────────────────
1407
2457
  /**
1408
2458
  * `codeyam editor isolate "StarRating CategoryBadge DrinkCard"`
@@ -1538,6 +2588,17 @@ function formatApiSubcommandResult(subcommand, data) {
1538
2588
  }
1539
2589
  return parts.join(' ');
1540
2590
  }
2591
+ case 'analyze-imports': {
2592
+ const importEntries = Object.entries(data.imports || {});
2593
+ const entityEntries = Object.entries(data.entities || {});
2594
+ const parts = [];
2595
+ parts.push(`entities=${entityEntries.length}`);
2596
+ parts.push(`withImports=${importEntries.length}`);
2597
+ if (importEntries.length > 0) {
2598
+ parts.push(`imports: ${importEntries.map(([name, deps]) => `${name}→[${deps.join(',')}]`).join(' ')}`);
2599
+ }
2600
+ return parts.join(' ');
2601
+ }
1541
2602
  default:
1542
2603
  return null; // journal-list, show/hide-results: keep full JSON
1543
2604
  }
@@ -1550,8 +2611,9 @@ function formatApiSubcommandResult(subcommand, data) {
1550
2611
  */
1551
2612
  async function handleRegister(jsonArg) {
1552
2613
  if (!jsonArg) {
2614
+ const { defaultName: dim } = getProjectDimensions(getProjectRoot());
1553
2615
  console.error(chalk.red('Error: JSON argument required.'));
1554
- console.error(chalk.dim(' Usage: codeyam editor register \'{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/isolated-components/DrinkCard?s=Default"}\''));
2616
+ console.error(chalk.dim(` Usage: codeyam editor register '{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/isolated-components/DrinkCard?s=Default","dimensions":["${dim}"]}'`));
1555
2617
  console.error(chalk.dim(' For large payloads: codeyam editor register @/tmp/scenario.json'));
1556
2618
  process.exit(1);
1557
2619
  }
@@ -1563,57 +2625,138 @@ async function handleRegister(jsonArg) {
1563
2625
  }
1564
2626
  process.exit(1);
1565
2627
  }
1566
- const body = parsed.body;
2628
+ // Normalize to array for uniform handling
2629
+ const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
1567
2630
  const port = getServerPort();
1568
2631
  const url = `http://localhost:${port}/api/editor-register-scenario`;
1569
- try {
1570
- const res = await fetch(url, {
1571
- method: 'POST',
1572
- headers: { 'Content-Type': 'application/json' },
1573
- body: JSON.stringify(body),
1574
- });
1575
- const data = await res.json();
1576
- // Print concise summary instead of raw JSON so Claude doesn't need python3
1577
- const parts = [];
1578
- parts.push(`success=${data.success}`);
1579
- if (data.scenario?.name)
1580
- parts.push(`name="${data.scenario.name}"`);
1581
- parts.push(`screenshot=${!!data.screenshotCaptured}`);
1582
- if (data.capturedViewport) {
1583
- parts.push(`viewport=${data.capturedViewport.width}×${data.capturedViewport.height}`);
1584
- }
1585
- if (data.clientErrors?.length > 0) {
1586
- parts.push(chalk.red(`errors=${data.clientErrors.length}`));
1587
- }
1588
- else {
1589
- parts.push(`errors=0`);
1590
- }
1591
- if (data.seedResult)
1592
- parts.push(`seed=${data.seedResult.success}`);
1593
- if (data.captureError)
1594
- parts.push(chalk.yellow(`captureError="${data.captureError}"`));
1595
- console.log(parts.join(' '));
1596
- // Surface client errors prominently so they can't be missed
1597
- if (data.clientErrors && data.clientErrors.length > 0) {
1598
- console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
1599
- for (const err of data.clientErrors) {
1600
- console.log(chalk.red(` → ${err}`));
2632
+ const isBatch = items.length > 1;
2633
+ let succeeded = 0;
2634
+ let failed = 0;
2635
+ for (let i = 0; i < items.length; i++) {
2636
+ const body = items[i];
2637
+ const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
2638
+ try {
2639
+ const res = await fetch(url, {
2640
+ method: 'POST',
2641
+ headers: { 'Content-Type': 'application/json' },
2642
+ body: JSON.stringify(body),
2643
+ });
2644
+ const data = await res.json();
2645
+ // Print concise summary instead of raw JSON so Claude doesn't need python3
2646
+ const parts = [];
2647
+ parts.push(`success=${data.success}`);
2648
+ if (data.scenario?.name)
2649
+ parts.push(`name="${data.scenario.name}"`);
2650
+ parts.push(`screenshot=${!!data.screenshotCaptured}`);
2651
+ if (data.capturedViewport) {
2652
+ parts.push(`viewport=${data.capturedViewport.width}×${data.capturedViewport.height}`);
2653
+ }
2654
+ if (data.scenario?.dimension)
2655
+ parts.push(`dimension="${data.scenario.dimension}"`);
2656
+ if (data.clientErrors?.length > 0) {
2657
+ parts.push(chalk.red(`errors=${data.clientErrors.length}`));
2658
+ }
2659
+ else {
2660
+ parts.push(`errors=0`);
2661
+ }
2662
+ if (data.seedResult)
2663
+ parts.push(`seed=${data.seedResult.success}`);
2664
+ if (data.captureError)
2665
+ parts.push(chalk.yellow(`captureError="${data.captureError}"`));
2666
+ console.log(prefix + parts.join(' '));
2667
+ // Warn when application scenario is missing seed data
2668
+ if (data.missingSeedWarning) {
2669
+ console.log(chalk.red.bold('WARNING: No seed data in this application scenario!'));
2670
+ console.log(chalk.yellow(' This scenario has type "application" but no "seed" data was provided.'));
2671
+ console.log(chalk.yellow(' The page will render with an empty database — nothing will be visible.'));
2672
+ console.log(chalk.yellow(' Re-register with "seed":{...} containing the database rows this page needs.'));
2673
+ }
2674
+ // Surface client errors prominently so they can't be missed
2675
+ if (data.clientErrors && data.clientErrors.length > 0) {
2676
+ console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
2677
+ for (const err of data.clientErrors) {
2678
+ console.log(chalk.red(` → ${err}`));
2679
+ }
2680
+ console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
2681
+ // Provide actionable debugging hints for HTTP/API errors
2682
+ const hasApiResponseError = data.clientErrors.some((e) => e.includes('API response error:'));
2683
+ const hasHttpError = data.clientErrors.some((e) => e.includes('HTTP error:'));
2684
+ if (hasHttpError || hasApiResponseError) {
2685
+ console.log(chalk.yellow(' To debug: look at the "API response error" lines above — they show the exact API endpoint'));
2686
+ console.log(chalk.yellow(' that failed and what the server returned. Then check the scenario seed data to ensure'));
2687
+ console.log(chalk.yellow(' all IDs referenced in the URL exist in the seed, and that the route is correct.'));
2688
+ }
2689
+ console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
2690
+ }
2691
+ if (!res.ok) {
2692
+ console.error(chalk.dim(JSON.stringify(data, null, 2)));
2693
+ failed++;
2694
+ }
2695
+ else {
2696
+ succeeded++;
1601
2697
  }
1602
- console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
1603
- console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
1604
2698
  }
1605
- if (!res.ok) {
1606
- console.error(chalk.dim(JSON.stringify(data, null, 2)));
1607
- process.exit(1);
2699
+ catch (error) {
2700
+ const msg = error instanceof Error ? error.message : String(error);
2701
+ console.error(prefix + chalk.red(`Error: Could not reach editor server at ${url}`));
2702
+ console.error(chalk.dim(` ${msg}`));
2703
+ console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
2704
+ failed++;
1608
2705
  }
1609
2706
  }
1610
- catch (error) {
1611
- const msg = error instanceof Error ? error.message : String(error);
1612
- console.error(chalk.red(`Error: Could not reach editor server at ${url}`));
1613
- console.error(chalk.dim(` ${msg}`));
1614
- console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
2707
+ if (isBatch) {
2708
+ console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
2709
+ }
2710
+ if (failed > 0) {
2711
+ process.exit(1);
2712
+ }
2713
+ }
2714
+ // ─── Glossary-add subcommand ──────────────────────────────────────────
2715
+ /**
2716
+ * `codeyam editor glossary-add '{"name":"...", "filePath":"...", "description":"..."}'`
2717
+ *
2718
+ * Safely adds/updates entries in .codeyam/glossary.json via the CLI,
2719
+ * avoiding hand-editing that breaks on unicode characters.
2720
+ */
2721
+ async function handleGlossaryAdd(jsonArg) {
2722
+ if (!jsonArg) {
2723
+ console.error(chalk.red('Error: JSON argument required.'));
2724
+ console.error(chalk.dim(' Usage: codeyam editor glossary-add \'{"name":"DrinkCard","filePath":"app/components/DrinkCard.tsx","description":"Displays a drink item"}\''));
2725
+ console.error(chalk.dim(' For large payloads: codeyam editor glossary-add @.codeyam/tmp/entry.json'));
2726
+ process.exit(1);
2727
+ }
2728
+ const parsed = parseRegisterArg(jsonArg);
2729
+ if (parsed.error) {
2730
+ console.error(chalk.red(`Error: ${parsed.error}`));
2731
+ process.exit(1);
2732
+ }
2733
+ // Normalize to array
2734
+ const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
2735
+ // Read existing glossary
2736
+ const root = getProjectRoot();
2737
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
2738
+ let existing = [];
2739
+ try {
2740
+ const raw = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
2741
+ existing = sanitizeGlossaryEntries(raw);
2742
+ }
2743
+ catch {
2744
+ // Glossary doesn't exist yet or can't be parsed — start fresh
2745
+ }
2746
+ // Merge
2747
+ const { validateGlossaryEntry, mergeGlossaryEntries } = await import('../utils/glossaryAdd.js');
2748
+ const result = mergeGlossaryEntries(existing, items);
2749
+ // Report validation errors
2750
+ for (const err of result.errors) {
2751
+ console.error(chalk.red(`Error at index ${err.index}: ${err.message}`));
2752
+ }
2753
+ if (result.added === 0 && result.updated === 0 && result.errors.length > 0) {
1615
2754
  process.exit(1);
1616
2755
  }
2756
+ // Write back with utf8 encoding to safely handle unicode
2757
+ fs.mkdirSync(path.dirname(glossaryPath), { recursive: true });
2758
+ fs.writeFileSync(glossaryPath, JSON.stringify(result.entries, null, 2), 'utf8');
2759
+ console.log(`added=${result.added} updated=${result.updated} total=${result.entries.length}`);
1617
2760
  }
1618
2761
  // ─── Dependents subcommand ────────────────────────────────────────────
1619
2762
  /**
@@ -1632,11 +2775,23 @@ async function handleDependents(entityName) {
1632
2775
  const progress = new ProgressReporter();
1633
2776
  progress.start('Loading entities from database...');
1634
2777
  await initializeEnvironment();
1635
- const allEntities = await loadEntities({});
2778
+ let allEntities = await loadEntities({});
1636
2779
  if (!allEntities || allEntities.length === 0) {
1637
- progress.fail('No entities found in database');
1638
- console.error(chalk.dim(' Run codeyam editor analyze-imports first to populate entity data.'));
1639
- process.exit(1);
2780
+ progress.succeed('No entities found running analyze-imports first...');
2781
+ try {
2782
+ await handleAnalyzeImports({ silent: true });
2783
+ }
2784
+ catch {
2785
+ console.error(chalk.red('Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
2786
+ process.exit(1);
2787
+ }
2788
+ // Reload entities after analysis
2789
+ const reloaded = await loadEntities({});
2790
+ if (!reloaded || reloaded.length === 0) {
2791
+ progress.fail('No entities found even after analyze-imports');
2792
+ process.exit(1);
2793
+ }
2794
+ allEntities = reloaded;
1640
2795
  }
1641
2796
  // Find the target entity by name (case-insensitive match)
1642
2797
  const targetEntities = allEntities.filter((e) => e.name.toLowerCase() === entityName.toLowerCase());
@@ -1723,15 +2878,16 @@ async function handleDependents(entityName) {
1723
2878
  * `codeyam editor change <feature>`
1724
2879
  *
1725
2880
  * Prints a condensed post-change checklist that guides Claude through
1726
- * re-verifying after user-requested modifications. This is the "change
1727
- * loop" it replaces the freeform "make changes" path with structured
1728
- * instructions that always loop back to step 13 present.
2881
+ * re-verifying after user-requested modifications. When called from
2882
+ * step 13+, this loops back to step 13 (present). When called from an
2883
+ * earlier step, it returns to that step so the normal flow continues.
1729
2884
  */
1730
2885
  function handleChange(feature) {
1731
2886
  const root = getProjectRoot();
2887
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
2888
+ const state = readState(root);
1732
2889
  if (!feature) {
1733
2890
  // Try to read feature from state
1734
- const state = readState(root);
1735
2891
  if (state?.feature) {
1736
2892
  feature = state.feature;
1737
2893
  }
@@ -1741,6 +2897,7 @@ function handleChange(feature) {
1741
2897
  process.exit(1);
1742
2898
  }
1743
2899
  }
2900
+ const currentStep = state?.step ?? 13;
1744
2901
  const port = getServerPort();
1745
2902
  console.log();
1746
2903
  console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
@@ -1748,9 +2905,19 @@ function handleChange(feature) {
1748
2905
  console.log();
1749
2906
  console.log('The user has requested changes. Follow this checklist after making them.');
1750
2907
  console.log();
2908
+ const designSystem = readDesignSystem(root);
2909
+ if (designSystem) {
2910
+ console.log(chalk.bold.magenta('Design System (active):'));
2911
+ console.log(chalk.magenta(' Use existing CSS custom property tokens when making visual changes — no hardcoded px or hex values.'));
2912
+ console.log(chalk.magenta(' Use var(--text-sm) not 14, var(--spacing-lg) not 16px, var(--text-primary) not #1A1B25.'));
2913
+ console.log();
2914
+ console.log(designSystem);
2915
+ console.log();
2916
+ }
1751
2917
  console.log(chalk.bold.cyan('Keep the preview moving:'));
1752
2918
  console.log(chalk.cyan(` Refresh after EACH individual change — not after all changes are done:`));
1753
- console.log(chalk.cyan(` codeyam editor preview`));
2919
+ console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
2920
+ printDimensionGuidance(dim, dimNames);
1754
2921
  console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
1755
2922
  console.log();
1756
2923
  console.log(chalk.bold('0. Close the results panel:'));
@@ -1758,7 +2925,7 @@ function handleChange(feature) {
1758
2925
  console.log();
1759
2926
  console.log(chalk.bold('1. Re-register affected component scenarios:'));
1760
2927
  checkbox('For each component you modified, re-register ALL its scenarios');
1761
- console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","componentName":"ComponentName","componentPath":"app/components/ComponentName.tsx","url":"/isolated-components/ComponentName?s=ScenarioName"}'`));
2928
+ 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}"]}'`));
1762
2929
  checkbox('For any NEW components: `codeyam editor isolate NewComponent` then create isolation routes and register scenarios');
1763
2930
  console.log();
1764
2931
  console.log(chalk.bold('2. Re-run affected tests:'));
@@ -1769,7 +2936,6 @@ function handleChange(feature) {
1769
2936
  console.log(chalk.dim(` codeyam editor dev-server '{"action":"restart"}'`));
1770
2937
  console.log();
1771
2938
  console.log(chalk.bold('3. Re-capture app-level scenarios:'));
1772
- checkbox('Run `codeyam editor analyze-imports` to refresh the import graph after code changes');
1773
2939
  checkbox('For each changed component, run `codeyam editor dependents ComponentName` to find affected pages');
1774
2940
  checkbox('Run `codeyam editor scenarios` to list all existing scenarios');
1775
2941
  checkbox('For EACH page listed by dependents: find its existing scenarios in the list and re-register every one');
@@ -1777,15 +2943,23 @@ function handleChange(feature) {
1777
2943
  console.log(chalk.dim(' Re-register with the SAME name to update — do NOT create new/duplicate scenarios.'));
1778
2944
  console.log(chalk.dim(' Example: if Header changed and Home, Catalog, Detail pages use it,'));
1779
2945
  console.log(chalk.dim(' re-register "Home - Default", "Catalog - Full", "Detail - WithReviews", etc.'));
2946
+ checkbox("Enrich existing scenario data to exercise the change — don't just re-register unchanged data");
2947
+ console.log(chalk.dim(' Add data that demonstrates what changed: new fields, relationships, states, content variety.'));
2948
+ checkbox('After each re-registration, view the screenshot to verify data is visible');
2949
+ console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
1780
2950
  console.log();
1781
2951
  console.log(chalk.bold('4. Verify completeness:'));
1782
- checkbox(`Refresh the preview: \`codeyam editor preview\``);
1783
- checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route"}'\``);
2952
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
2953
+ checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
2954
+ printDimensionGuidance(dim, dimNames);
2955
+ checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
2956
+ checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
1784
2957
  checkbox('Run `codeyam editor audit` — all checks must pass');
1785
2958
  checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
1786
2959
  console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
1787
2960
  console.log(chalk.dim(' If `liveErrors>0`, there are JS errors in the preview — fix them.'));
1788
2961
  checkbox('Fix any errors, then re-register affected scenarios');
2962
+ checkbox('If changes affect setup, new dependencies, or new scripts: update `README.md` and `npm run setup`');
1789
2963
  console.log();
1790
2964
  console.log(chalk.bold('5. Update the existing journal entry:'));
1791
2965
  checkbox('Get existing entry: `codeyam editor journal-list`');
@@ -1794,14 +2968,173 @@ function handleChange(feature) {
1794
2968
  console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
1795
2969
  console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
1796
2970
  console.log();
1797
- console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
1798
- console.log(chalk.red.bold(' REQUIRED: Show Results'));
1799
- console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
1800
- chalk.bold(`codeyam editor 13`));
1801
- console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
1802
- console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2971
+ // If the change was initiated from a step before 13, return to that step
2972
+ // instead of jumping to step 13. The change workflow should only loop to
2973
+ // step 13 when changes are requested FROM step 13.
2974
+ if (currentStep < 13) {
2975
+ console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2976
+ console.log(chalk.red.bold(' REQUIRED: Return to current step'));
2977
+ console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
2978
+ chalk.bold(`codeyam editor ${currentStep}`));
2979
+ console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2980
+ }
2981
+ else {
2982
+ console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2983
+ console.log(chalk.red.bold(' REQUIRED: Show Results'));
2984
+ console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
2985
+ chalk.bold(`codeyam editor 13`));
2986
+ console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
2987
+ console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2988
+ }
1803
2989
  console.log();
1804
2990
  }
2991
+ // ─── Audit gate ─────────────────────────────────────────────────────
2992
+ /**
2993
+ * Fetch the audit result from the server.
2994
+ * Returns null if the server is unreachable.
2995
+ */
2996
+ async function fetchAuditResult() {
2997
+ const port = getServerPort();
2998
+ try {
2999
+ const res = await fetch(`http://localhost:${port}/api/editor-audit`);
3000
+ if (!res.ok)
3001
+ return null;
3002
+ return await res.json();
3003
+ }
3004
+ catch {
3005
+ return null;
3006
+ }
3007
+ }
3008
+ /**
3009
+ * Silently check whether the audit passes. Returns true if allPassing,
3010
+ * false if any issues remain or the server is unreachable.
3011
+ * Used as a hard gate before steps 8+.
3012
+ *
3013
+ * Auto-runs analyze-imports if the only failure is incomplete entities,
3014
+ * then re-checks once.
3015
+ */
3016
+ async function checkAuditGate() {
3017
+ const data = await fetchAuditResult();
3018
+ if (!data)
3019
+ return true; // Server unreachable — don't block
3020
+ if (data.summary?.allPassing === true)
3021
+ return true;
3022
+ // If incomplete entities are the only issue, auto-fix with analyze-imports (once)
3023
+ const { isAutoRemediable } = await import('../utils/editorAudit.js');
3024
+ if (isAutoRemediable(data.summary, false)) {
3025
+ try {
3026
+ await handleAnalyzeImports({ silent: true });
3027
+ }
3028
+ catch {
3029
+ return false;
3030
+ }
3031
+ // Re-check after fix
3032
+ const retry = await fetchAuditResult();
3033
+ if (retry?.summary?.allPassing === true)
3034
+ return true;
3035
+ }
3036
+ // Print specific failures so Claude knows what to fix without running audit separately
3037
+ printAuditGateFailures(data);
3038
+ return false;
3039
+ }
3040
+ /**
3041
+ * Print a concise summary of audit failures for the gate block message.
3042
+ * This gives Claude immediate context about what to fix without needing
3043
+ * to run `codeyam editor audit` as a separate step.
3044
+ */
3045
+ function printAuditGateFailures(data) {
3046
+ const s = data.summary;
3047
+ if (!s)
3048
+ return;
3049
+ const issues = [];
3050
+ if (s.componentsMissing > 0) {
3051
+ issues.push(`${s.componentsMissing} component(s) missing scenarios`);
3052
+ const missing = (data.components || []).filter((c) => c.status === 'missing');
3053
+ for (const c of missing) {
3054
+ issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
3055
+ }
3056
+ }
3057
+ if (s.componentsWithErrors > 0) {
3058
+ issues.push(`${s.componentsWithErrors} component(s) with client errors (browser API or runtime errors in captured scenarios)`);
3059
+ const withErrors = (data.components || []).filter((c) => c.status === 'has_errors');
3060
+ for (const c of withErrors) {
3061
+ issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
3062
+ }
3063
+ }
3064
+ if (s.functionsMissing > 0) {
3065
+ issues.push(`${s.functionsMissing} function(s) missing test files`);
3066
+ const missing = (data.functions || []).filter((f) => f.status === 'missing');
3067
+ for (const f of missing) {
3068
+ issues.push(` → ${f.name}${f.filePath ? ` (${f.filePath})` : ''}`);
3069
+ }
3070
+ }
3071
+ if (s.functionsFailing > 0) {
3072
+ issues.push(`${s.functionsFailing} function(s) with failing tests`);
3073
+ const failing = (data.functions || []).filter((f) => f.status === 'failing');
3074
+ for (const f of failing) {
3075
+ issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3076
+ }
3077
+ }
3078
+ if (s.functionsRunnerError > 0) {
3079
+ issues.push(`${s.functionsRunnerError} function(s) with test runner errors (the runner crashed — not a test failure)`);
3080
+ const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
3081
+ for (const f of runnerErrors) {
3082
+ issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3083
+ }
3084
+ }
3085
+ if (s.functionsNameMismatch > 0) {
3086
+ issues.push(`${s.functionsNameMismatch} function(s) with test name mismatch`);
3087
+ const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
3088
+ for (const f of mismatch) {
3089
+ issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3090
+ }
3091
+ }
3092
+ if (s.missingFromGlossary > 0)
3093
+ issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
3094
+ if (s.incompleteEntities > 0) {
3095
+ const preCount = s.preExistingIncompleteEntities || 0;
3096
+ if (preCount > 0 && preCount === s.incompleteEntities) {
3097
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
3098
+ }
3099
+ else if (preCount > 0) {
3100
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis (${preCount} pre-existing — not from your changes)`);
3101
+ }
3102
+ else {
3103
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
3104
+ }
3105
+ }
3106
+ if (s.miscategorizedScenarios > 0)
3107
+ issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
3108
+ if (s.scenariosNeedingRecapture > 0)
3109
+ issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
3110
+ if (issues.length > 0) {
3111
+ console.error(chalk.yellow('\nAudit failures:'));
3112
+ for (const issue of issues) {
3113
+ console.error(chalk.yellow(` • ${issue}`));
3114
+ }
3115
+ }
3116
+ // Surface client error details inline — these are the most common cause of
3117
+ // Claude looping because the errors require code fixes, not audit re-runs.
3118
+ if (data.components) {
3119
+ const withErrors = data.components.filter((c) => c.status === 'has_errors' && c.clientErrors?.length > 0);
3120
+ if (withErrors.length > 0) {
3121
+ console.error(chalk.yellow('\nClient errors found:'));
3122
+ for (const c of withErrors) {
3123
+ console.error(chalk.red(` ${c.name}:`));
3124
+ for (const err of c.clientErrors.slice(0, 3)) {
3125
+ console.error(chalk.red(` → ${err}`));
3126
+ }
3127
+ if (c.clientErrors.length > 3) {
3128
+ console.error(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
3129
+ }
3130
+ }
3131
+ console.error(chalk.yellow('\nFix: Fix the code errors above, then re-capture the affected scenarios.'));
3132
+ console.error(chalk.yellow('If errors reference browser APIs (localStorage, sessionStorage, window, document),'));
3133
+ console.error(chalk.yellow('create a universal mock: codeyam detect-universal-mocks'));
3134
+ }
3135
+ }
3136
+ console.error(chalk.dim('\nRun `codeyam editor audit` for full details.\n'));
3137
+ }
1805
3138
  // ─── Audit subcommand ────────────────────────────────────────────────
1806
3139
  /**
1807
3140
  * `codeyam editor audit`
@@ -1811,42 +3144,74 @@ function handleChange(feature) {
1811
3144
  * have test files. Exits with code 1 if anything is missing.
1812
3145
  */
1813
3146
  async function handleAudit() {
1814
- const port = getServerPort();
1815
- const url = `http://localhost:${port}/api/editor-audit`;
1816
- let data;
1817
- try {
1818
- const res = await fetch(url);
1819
- if (!res.ok) {
1820
- console.error(chalk.red(`Error: Audit endpoint returned ${res.status}`));
1821
- process.exit(1);
1822
- }
1823
- data = await res.json();
1824
- }
1825
- catch (err) {
3147
+ let data = await fetchAuditResult();
3148
+ if (!data) {
1826
3149
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
1827
- console.error(chalk.dim(` ${err.message}`));
1828
3150
  process.exit(1);
1829
3151
  }
3152
+ // Auto-fix incomplete entities — but only once.
3153
+ // If analyze-imports runs and entities are STILL incomplete, report clearly
3154
+ // instead of retrying. The analysis may have errors (e.g., stale entity
3155
+ // references from refactoring) that can't be fixed by re-running.
3156
+ const incompleteBeforeFix = data.incompleteEntities || [];
3157
+ let autoRemediationFailed = false;
3158
+ if (incompleteBeforeFix.length > 0) {
3159
+ console.log(chalk.dim(`Running import analysis for ${incompleteBeforeFix.length} incomplete entit${incompleteBeforeFix.length !== 1 ? 'ies' : 'y'}...`));
3160
+ try {
3161
+ await handleAnalyzeImports({ silent: true });
3162
+ }
3163
+ catch {
3164
+ // Fall through — the audit will still report them as incomplete
3165
+ }
3166
+ // Re-fetch audit results after the fix
3167
+ data = await fetchAuditResult();
3168
+ if (!data) {
3169
+ console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
3170
+ process.exit(1);
3171
+ }
3172
+ // If entities are still incomplete after running analyze-imports,
3173
+ // flag it so we can show a clear message instead of looping
3174
+ const incompleteAfterFix = data.incompleteEntities || [];
3175
+ if (incompleteAfterFix.length > 0) {
3176
+ autoRemediationFailed = true;
3177
+ }
3178
+ }
1830
3179
  const { components, functions, summary } = data;
1831
3180
  console.log();
1832
3181
  console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
1833
3182
  console.log();
1834
3183
  // Components
1835
3184
  if (components.length > 0) {
3185
+ // Build name frequency map for disambiguation
3186
+ const componentNameCounts = new Map();
3187
+ for (const c of components) {
3188
+ componentNameCounts.set(c.name, (componentNameCounts.get(c.name) || 0) + 1);
3189
+ }
1836
3190
  console.log(chalk.bold('Components (scenarios):'));
1837
3191
  for (const c of components) {
1838
- const icon = c.status === 'ok' ? chalk.green('✓') : chalk.red('✗');
3192
+ const icon = c.status === 'ok'
3193
+ ? chalk.green('✓')
3194
+ : c.status === 'needs_recapture'
3195
+ ? chalk.yellow('↻')
3196
+ : chalk.red('✗');
1839
3197
  let detail;
1840
3198
  if (c.status === 'has_errors') {
1841
3199
  detail = chalk.red(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
1842
3200
  }
3201
+ else if (c.status === 'needs_recapture') {
3202
+ detail = chalk.yellow(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''}, needs recapture`);
3203
+ }
1843
3204
  else if (c.status === 'ok') {
1844
3205
  detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
1845
3206
  }
1846
3207
  else {
1847
3208
  detail = chalk.red(' — no scenarios registered');
1848
3209
  }
1849
- console.log(` ${icon} ${c.name}${detail}`);
3210
+ // Show file path for failing components always, for OK only when name is ambiguous
3211
+ const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
3212
+ const showPath = (c.status !== 'ok' && c.status !== 'needs_recapture') || isDuplicate;
3213
+ const pathSuffix = showPath && c.filePath ? chalk.dim(` (${c.filePath})`) : '';
3214
+ console.log(` ${icon} ${c.name}${pathSuffix}${detail}`);
1850
3215
  if (c.clientErrors && c.clientErrors.length > 0) {
1851
3216
  for (const err of c.clientErrors.slice(0, 3)) {
1852
3217
  console.log(chalk.red(` → ${err}`));
@@ -1854,6 +3219,14 @@ async function handleAudit() {
1854
3219
  if (c.clientErrors.length > 3) {
1855
3220
  console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
1856
3221
  }
3222
+ // Detect browser API errors and provide actionable guidance
3223
+ const browserApiPattern = /\b(localStorage|sessionStorage|window\.|document\.|navigator\.|indexedDB|matchMedia|ResizeObserver|IntersectionObserver|MutationObserver)\b/;
3224
+ const hasBrowserApiErrors = c.clientErrors.some((err) => browserApiPattern.test(err));
3225
+ if (hasBrowserApiErrors) {
3226
+ console.log(chalk.yellow(` ⚠ These errors are caused by browser APIs that don't exist during server-side analysis.`));
3227
+ console.log(chalk.yellow(` Fix: Create a universal mock to stub the missing API. Run: codeyam detect-universal-mocks`));
3228
+ console.log(chalk.yellow(` DO NOT re-run the audit or analyze-imports — the error will persist until a mock is created.`));
3229
+ }
1857
3230
  }
1858
3231
  }
1859
3232
  console.log();
@@ -1868,6 +3241,14 @@ async function handleAudit() {
1868
3241
  case 'ok':
1869
3242
  detail = chalk.dim(` (${f.testFile})`);
1870
3243
  break;
3244
+ case 'runner_error':
3245
+ detail = chalk.red(` — test runner crashed: ${f.testFile}`);
3246
+ if (f.errorMessage) {
3247
+ detail += `\n ${chalk.red(f.errorMessage)}`;
3248
+ detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
3249
+ detail += `\n ${chalk.yellow('Try running the test manually: npx vitest run ' + f.testFile)}`;
3250
+ }
3251
+ break;
1871
3252
  case 'failing':
1872
3253
  detail = chalk.red(` — tests failing: ${f.testFile}`);
1873
3254
  break;
@@ -1885,6 +3266,79 @@ async function handleAudit() {
1885
3266
  }
1886
3267
  console.log();
1887
3268
  }
3269
+ // Missing from glossary
3270
+ const missingFromGlossary = data.missingFromGlossary || [];
3271
+ if (missingFromGlossary.length > 0) {
3272
+ console.log(chalk.bold('Missing from glossary:'));
3273
+ for (const m of missingFromGlossary) {
3274
+ console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
3275
+ }
3276
+ console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
3277
+ console.log();
3278
+ }
3279
+ // Incomplete entities (scenarios without analyses)
3280
+ const incompleteEntities = data.incompleteEntities || [];
3281
+ if (incompleteEntities.length > 0) {
3282
+ console.log(chalk.bold('Incomplete entities (need import analysis):'));
3283
+ for (const e of incompleteEntities) {
3284
+ console.log(` ${chalk.red('✗')} ${e.name} — ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
3285
+ }
3286
+ if (autoRemediationFailed) {
3287
+ console.log(chalk.red(' analyze-imports was run automatically but these entities are STILL incomplete.'));
3288
+ console.log(chalk.red(' DO NOT re-run analyze-imports or the audit in a loop — the result will be the same.'));
3289
+ console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
3290
+ console.log(chalk.yellow(' • Entity code uses browser APIs (localStorage, window, document) — create a universal mock'));
3291
+ console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
3292
+ console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
3293
+ console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
3294
+ console.log(chalk.yellow(' To fix browser API issues: run `codeyam detect-universal-mocks`'));
3295
+ }
3296
+ else {
3297
+ console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
3298
+ }
3299
+ console.log();
3300
+ }
3301
+ // Miscategorized scenarios (component scenarios using real page URLs)
3302
+ const miscategorizedScenarios = data.miscategorizedScenarios || [];
3303
+ if (miscategorizedScenarios.length > 0) {
3304
+ console.log(chalk.bold('Miscategorized scenarios (component with page URL):'));
3305
+ for (const m of miscategorizedScenarios) {
3306
+ console.log(` ${chalk.red('✗')} ${m.componentName} at ${chalk.dim(m.url)} — ${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''}`);
3307
+ for (const name of m.scenarioNames) {
3308
+ console.log(chalk.dim(` "${name}"`));
3309
+ }
3310
+ }
3311
+ console.log(chalk.yellow(' Component scenarios must use isolation routes (/isolated-components/...).'));
3312
+ console.log(chalk.yellow(' Either re-register as app scenarios (remove componentName, add pageFilePath)'));
3313
+ console.log(chalk.yellow(' or re-register with an isolation URL.'));
3314
+ console.log();
3315
+ }
3316
+ // Scenarios needing recapture (entity or dependency tree changed)
3317
+ const scenariosNeedingRecapture = data.scenariosNeedingRecapture || [];
3318
+ if (scenariosNeedingRecapture.length > 0) {
3319
+ console.log(chalk.bold('Scenarios needing recapture (dependency tree changed):'));
3320
+ for (const s of scenariosNeedingRecapture) {
3321
+ const reason = s.status.status === 'impacted' && s.status.impactedBy?.length
3322
+ ? `impacted by: ${s.status.impactedBy.map((d) => `${d.name} [${d.changeType}]`).join(', ')}`
3323
+ : `${s.status.status}`;
3324
+ console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
3325
+ }
3326
+ console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
3327
+ console.log();
3328
+ }
3329
+ // Duplicate glossary names (warning, not a failure)
3330
+ const duplicateNames = data.duplicateNames || [];
3331
+ if (duplicateNames.length > 0) {
3332
+ console.log(chalk.bold('Duplicate names in glossary (confusing for audit):'));
3333
+ for (const dn of duplicateNames) {
3334
+ console.log(` ${chalk.yellow('⚠')} "${dn.name}" appears ${dn.filePaths.length} times:`);
3335
+ for (const fp of dn.filePaths) {
3336
+ console.log(` ${fp}`);
3337
+ }
3338
+ }
3339
+ console.log(chalk.yellow(' Fix: remove duplicate entries or rename them to be unique in .codeyam/glossary.json'));
3340
+ console.log();
3341
+ }
1888
3342
  // Summary
1889
3343
  const allOk = summary.allPassing;
1890
3344
  if (allOk) {
@@ -1905,15 +3359,144 @@ async function handleAudit() {
1905
3359
  if (summary.functionsFailing > 0) {
1906
3360
  parts.push(`${summary.functionsFailing} function${summary.functionsFailing !== 1 ? 's' : ''} with failing tests`);
1907
3361
  }
3362
+ if (summary.functionsRunnerError > 0) {
3363
+ parts.push(`${summary.functionsRunnerError} function${summary.functionsRunnerError !== 1 ? 's' : ''} with test runner errors (not test failures — see details above)`);
3364
+ }
1908
3365
  if (summary.functionsNameMismatch > 0) {
1909
3366
  parts.push(`${summary.functionsNameMismatch} function${summary.functionsNameMismatch !== 1 ? 's' : ''} with test name mismatch (missing top-level describe)`);
1910
3367
  }
3368
+ if (summary.missingFromGlossary > 0) {
3369
+ parts.push(`${summary.missingFromGlossary} file${summary.missingFromGlossary !== 1 ? 's' : ''} with scenarios not in glossary`);
3370
+ }
3371
+ if (summary.incompleteEntities > 0) {
3372
+ parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
3373
+ }
3374
+ if (summary.miscategorizedScenarios > 0) {
3375
+ parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
3376
+ }
3377
+ if (summary.scenariosNeedingRecapture > 0) {
3378
+ parts.push(`${summary.scenariosNeedingRecapture} scenario${summary.scenariosNeedingRecapture !== 1 ? 's' : ''} need recapture`);
3379
+ }
1911
3380
  console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
1912
3381
  }
1913
3382
  console.log();
1914
3383
  if (!allOk) {
1915
3384
  process.exit(1);
1916
3385
  }
3386
+ // Auto-run analyze-imports when audit passes — this builds the import graph
3387
+ // so the App tab can show component/function dependencies for each page.
3388
+ // Previously this was a manual step that Claude had to remember to run.
3389
+ console.log(chalk.dim('Building import graph...'));
3390
+ try {
3391
+ await handleAnalyzeImports({ silent: true });
3392
+ }
3393
+ catch {
3394
+ // Non-fatal — audit passed, import graph is a bonus
3395
+ console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
3396
+ }
3397
+ }
3398
+ // ─── Recapture-stale subcommand ────────────────────────────────────────
3399
+ /**
3400
+ * `codeyam editor recapture-stale`
3401
+ *
3402
+ * Identifies all scenarios whose entity (or dependency) has changed but
3403
+ * whose screenshot hasn't been recaptured this session, then recaptures
3404
+ * them in batch.
3405
+ */
3406
+ async function handleRecaptureStale() {
3407
+ const port = getServerPort();
3408
+ const url = `http://localhost:${port}/api/editor-recapture-stale`;
3409
+ console.log();
3410
+ console.log(chalk.bold.cyan('━━━ Recapture Stale Scenarios ━━━'));
3411
+ console.log();
3412
+ let res;
3413
+ try {
3414
+ res = await fetch(url, { method: 'POST' });
3415
+ }
3416
+ catch (err) {
3417
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3418
+ console.error(chalk.dim(` ${err.message}`));
3419
+ process.exit(1);
3420
+ }
3421
+ if (!res.ok) {
3422
+ const body = await res.json().catch(() => null);
3423
+ console.error(chalk.red(`Error: Recapture endpoint returned ${res.status}`));
3424
+ if (body?.error)
3425
+ console.error(chalk.red(` ${body.error}`));
3426
+ process.exit(1);
3427
+ }
3428
+ // Handle JSON response (early returns like "no changes" / "all up to date")
3429
+ const contentType = res.headers.get('content-type') || '';
3430
+ if (contentType.includes('application/json')) {
3431
+ const data = await res.json();
3432
+ if (data.note) {
3433
+ console.log(chalk.dim(data.note));
3434
+ }
3435
+ else if (data.total === 0) {
3436
+ console.log(chalk.green('All scenarios are up to date.'));
3437
+ }
3438
+ console.log();
3439
+ return;
3440
+ }
3441
+ // Stream NDJSON progress events
3442
+ let total = 0;
3443
+ let recapturedCount = 0;
3444
+ let failedCount = 0;
3445
+ const reader = res.body?.getReader();
3446
+ if (!reader) {
3447
+ console.error(chalk.red('Error: No response body'));
3448
+ process.exit(1);
3449
+ }
3450
+ const decoder = new TextDecoder();
3451
+ let buffer = '';
3452
+ while (true) {
3453
+ const { done, value } = await reader.read();
3454
+ if (done)
3455
+ break;
3456
+ buffer += decoder.decode(value, { stream: true });
3457
+ const lines = buffer.split('\n');
3458
+ buffer = lines.pop() || ''; // Keep incomplete last line in buffer
3459
+ for (const line of lines) {
3460
+ if (!line.trim())
3461
+ continue;
3462
+ try {
3463
+ const event = JSON.parse(line);
3464
+ switch (event.type) {
3465
+ case 'start':
3466
+ total = event.total;
3467
+ console.log(`Found ${total} stale scenario(s). Recapturing...\n`);
3468
+ break;
3469
+ case 'capturing':
3470
+ process.stdout.write(chalk.dim(` … ${event.name}`));
3471
+ break;
3472
+ case 'success':
3473
+ // Clear the "capturing" line and print success
3474
+ process.stdout.write('\r\x1b[K');
3475
+ console.log(` ${chalk.green('✓')} ${event.name}`);
3476
+ recapturedCount++;
3477
+ break;
3478
+ case 'failure':
3479
+ process.stdout.write('\r\x1b[K');
3480
+ console.log(` ${chalk.red('✗')} ${event.name} — ${chalk.dim(event.error)}`);
3481
+ failedCount++;
3482
+ break;
3483
+ case 'done':
3484
+ // Final summary
3485
+ console.log();
3486
+ const color = failedCount > 0 ? chalk.yellow : chalk.green;
3487
+ console.log(color(`Recaptured ${recapturedCount}/${total} scenario(s).`));
3488
+ console.log();
3489
+ break;
3490
+ }
3491
+ }
3492
+ catch {
3493
+ // Skip unparseable lines
3494
+ }
3495
+ }
3496
+ }
3497
+ if (failedCount > 0) {
3498
+ process.exit(1);
3499
+ }
1917
3500
  }
1918
3501
  // ─── Scenarios subcommand ─────────────────────────────────────────────
1919
3502
  async function handleScenarios() {
@@ -2001,6 +3584,103 @@ async function handleScenarios() {
2001
3584
  const total = scenarios.length;
2002
3585
  const groupCount = groups.size;
2003
3586
  console.log(chalk.dim(`${total} scenario${total !== 1 ? 's' : ''} across ${groupCount} component${groupCount !== 1 ? 's' : ''}`));
3587
+ // Show screenshot paths so Claude can verify images with the Read tool
3588
+ const withScreenshots = scenarios.filter((s) => s.screenshotPath);
3589
+ if (withScreenshots.length > 0) {
3590
+ console.log();
3591
+ console.log(chalk.bold.cyan('Screenshots:'));
3592
+ for (const s of withScreenshots) {
3593
+ console.log(chalk.dim(` ${s.name}: ${s.screenshotPath}`));
3594
+ }
3595
+ }
3596
+ console.log();
3597
+ }
3598
+ // ─── Scenario Coverage subcommand ───────────────────────────────────
3599
+ async function handleScenarioCoverage() {
3600
+ // Safety net: heal any scenarios with null entity_sha before checking coverage
3601
+ try {
3602
+ const { getDatabase } = await import('../../../packages/database/index.js');
3603
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
3604
+ const db = getDatabase();
3605
+ const backfillCount = await countScenariosNeedingEntityBackfill(db);
3606
+ if (backfillCount > 0) {
3607
+ console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
3608
+ await handleAnalyzeImports({ silent: true });
3609
+ // Run backfill after analysis
3610
+ const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
3611
+ const entities = await loadEntities({});
3612
+ if (entities && entities.length > 0) {
3613
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3614
+ sha: e.sha,
3615
+ name: e.name,
3616
+ filePath: e.filePath || '',
3617
+ isDefaultExport: e.metadata?.notExported === false &&
3618
+ e.metadata?.namedExport === false,
3619
+ })));
3620
+ }
3621
+ }
3622
+ }
3623
+ catch {
3624
+ // Non-fatal — proceed with coverage check regardless
3625
+ }
3626
+ const port = getServerPort();
3627
+ const url = `http://localhost:${port}/api/editor-scenario-coverage`;
3628
+ let data;
3629
+ try {
3630
+ const res = await fetch(url);
3631
+ if (!res.ok) {
3632
+ console.error(chalk.red(`Error: Scenario coverage endpoint returned ${res.status}`));
3633
+ process.exit(1);
3634
+ }
3635
+ data = await res.json();
3636
+ }
3637
+ catch (err) {
3638
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3639
+ console.error(chalk.dim(` ${err.message}`));
3640
+ process.exit(1);
3641
+ }
3642
+ console.log();
3643
+ console.log(chalk.bold.cyan('━━━ Scenario Coverage ━━━'));
3644
+ console.log();
3645
+ if (data.note) {
3646
+ console.log(chalk.yellow(data.note));
3647
+ console.log();
3648
+ return;
3649
+ }
3650
+ // Fresh scenarios (already recaptured this feature)
3651
+ if (data.freshScenarios.length > 0) {
3652
+ console.log(chalk.green(`✓ ${data.freshScenarios.length} scenario(s) captured this feature:`));
3653
+ for (const s of data.freshScenarios) {
3654
+ console.log(chalk.dim(` ${s.name} (${s.url || '/'})`));
3655
+ }
3656
+ console.log();
3657
+ }
3658
+ // Stale scenarios (need recapturing)
3659
+ if (data.staleScenarios.length > 0) {
3660
+ console.log(chalk.red(`✗ ${data.staleScenarios.length} scenario(s) need re-registering (stale screenshots):`));
3661
+ for (const s of data.staleScenarios) {
3662
+ console.log(chalk.yellow(` ${s.name} (${s.url || '/'}) — ${s.changeStatus} but not recaptured`));
3663
+ }
3664
+ console.log();
3665
+ console.log(chalk.yellow('Re-register each stale scenario with the SAME name to update its screenshot.'));
3666
+ console.log(chalk.yellow("Enhance the seed data to reflect this feature's changes where relevant."));
3667
+ console.log();
3668
+ }
3669
+ // Uncovered pages (changed but no scenarios at all)
3670
+ if (data.uncoveredPages.length > 0) {
3671
+ console.log(chalk.red(`✗ ${data.uncoveredPages.length} page(s) affected by changes with no scenarios:`));
3672
+ for (const p of data.uncoveredPages) {
3673
+ console.log(chalk.yellow(` ${p.entityName} — ${p.changeStatus}`));
3674
+ }
3675
+ console.log();
3676
+ }
3677
+ // Summary
3678
+ if (data.pass) {
3679
+ console.log(chalk.green.bold('PASS — all affected scenarios are fresh'));
3680
+ }
3681
+ else {
3682
+ console.log(chalk.red.bold('FAIL — re-register stale scenarios before proceeding'));
3683
+ }
2004
3684
  console.log();
2005
3685
  }
2006
3686
  // ─── Template subcommand ─────────────────────────────────────────────
@@ -2090,6 +3770,22 @@ async function handleTemplate() {
2090
3770
  // Config parse error is non-fatal
2091
3771
  }
2092
3772
  }
3773
+ // 5b. Mark the project as template-scaffolded so migration detection
3774
+ // doesn't treat it as a pre-existing project that needs migration.
3775
+ const now = new Date().toISOString();
3776
+ const existingState = readState(root);
3777
+ writeState(root, {
3778
+ feature: '',
3779
+ step: 0,
3780
+ label: '',
3781
+ startedAt: now,
3782
+ featureStartedAt: now,
3783
+ ...existingState,
3784
+ scaffolded: true,
3785
+ });
3786
+ // 5c. Write a fresh prototypeId so the proxy clears stale localStorage
3787
+ const activeScenarioPath = path.join(root, '.codeyam', 'active-scenario.json');
3788
+ fs.writeFileSync(activeScenarioPath, JSON.stringify({ prototypeId: Date.now().toString() }), 'utf-8');
2093
3789
  // 6. Trigger editor-refresh so the server picks up the new project
2094
3790
  console.log(chalk.bold('Refreshing editor...'));
2095
3791
  try {
@@ -2108,17 +3804,14 @@ async function handleTemplate() {
2108
3804
  // ─── Sync subcommand ─────────────────────────────────────────────────
2109
3805
  /**
2110
3806
  * `codeyam editor sync`
2111
- * Import scenarios from scenarios-manifest.json into the local database.
3807
+ * Import scenarios from editor-scenarios/ files into the local database.
3808
+ * Falls back to legacy scenarios-manifest.json if no _metadata files found.
2112
3809
  */
2113
3810
  async function handleSync() {
2114
3811
  const root = getProjectRoot();
2115
- const manifest = readManifest(root);
2116
- if (!manifest) {
2117
- console.log(chalk.yellow('No scenarios-manifest.json found. Nothing to sync.'));
2118
- return;
2119
- }
2120
- if (manifest.scenarios.length === 0) {
2121
- console.log(chalk.dim('Manifest is empty. Nothing to sync.'));
3812
+ const entries = scanScenarioFiles(root);
3813
+ if (entries.length === 0) {
3814
+ console.log(chalk.yellow('No scenario files with metadata found. Nothing to sync.'));
2122
3815
  return;
2123
3816
  }
2124
3817
  const configPath = path.join(root, '.codeyam', 'config.json');
@@ -2142,13 +3835,12 @@ async function handleSync() {
2142
3835
  const { project } = await requireBranchAndProject(projectSlug);
2143
3836
  const { getDatabase } = await import('../../../packages/database/index.js');
2144
3837
  const db = getDatabase();
2145
- // Fetch existing editor scenarios
2146
3838
  const existingRows = await db
2147
3839
  .selectFrom('editor_scenarios')
2148
3840
  .select(['id', 'updated_at'])
2149
3841
  .where('project_id', '=', project.id)
2150
3842
  .execute();
2151
- const result = await syncManifestToDatabase(root, project.id, existingRows, async (row) => {
3843
+ const result = await syncScenarioFilesToDatabase(root, project.id, existingRows, async (row) => {
2152
3844
  await db
2153
3845
  .insertInto('editor_scenarios')
2154
3846
  .values(row)
@@ -2173,6 +3865,34 @@ async function handleSync() {
2173
3865
  parts.push(`${result.skipped} unchanged`);
2174
3866
  console.log(chalk.green(`Synced scenarios: ${parts.join(', ')}`));
2175
3867
  }
3868
+ // Migrate legacy scenario formats after sync, then re-sync if anything was fixed
3869
+ try {
3870
+ const migrateResult = migrateScenarioFormats(root);
3871
+ if (migrateResult.fixed > 0) {
3872
+ console.log(chalk.green(`Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
3873
+ // Re-sync so the DB reflects the corrected file metadata
3874
+ const refreshedRows = await db
3875
+ .selectFrom('editor_scenarios')
3876
+ .select(['id', 'updated_at'])
3877
+ .where('project_id', '=', project.id)
3878
+ .execute();
3879
+ await syncScenarioFilesToDatabase(root, project.id, refreshedRows, async (row) => {
3880
+ await db
3881
+ .insertInto('editor_scenarios')
3882
+ .values(row)
3883
+ .execute();
3884
+ }, async (id, row) => {
3885
+ await db
3886
+ .updateTable('editor_scenarios')
3887
+ .set(row)
3888
+ .where('id', '=', id)
3889
+ .execute();
3890
+ });
3891
+ }
3892
+ }
3893
+ catch {
3894
+ // Non-fatal
3895
+ }
2176
3896
  }
2177
3897
  // ─── Verify Images subcommand ─────────────────────────────────────────
2178
3898
  async function handleVerifyImages(jsonArg) {
@@ -2305,8 +4025,11 @@ function handleEditorDebug(args) {
2305
4025
  11: printStep11,
2306
4026
  12: printStep12,
2307
4027
  13: printStep13,
4028
+ 14: printStep14,
4029
+ 15: printStep15,
4030
+ 16: printStep16,
2308
4031
  };
2309
- for (let step = 1; step <= 13; step++) {
4032
+ for (let step = 1; step <= 16; step++) {
2310
4033
  const stepId = `step-${step}`;
2311
4034
  if (!wants(stepId))
2312
4035
  continue;
@@ -2385,8 +4108,8 @@ const editorCommand = {
2385
4108
  describe: 'Editor mode guided workflow',
2386
4109
  builder: (yargs) => {
2387
4110
  const stepDescription = IS_INTERNAL_BUILD
2388
- ? 'Step number (1-13) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)'
2389
- : 'Step number (1-13) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)';
4111
+ ? 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)'
4112
+ : 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)';
2390
4113
  let builder = yargs
2391
4114
  .positional('step', {
2392
4115
  type: 'string',
@@ -2422,7 +4145,7 @@ const editorCommand = {
2422
4145
  builder = builder
2423
4146
  .option('target', {
2424
4147
  type: 'string',
2425
- describe: 'Debug target (setup, overview, overview-with-state, step-1..step-13, or comma-separated list)',
4148
+ describe: 'Debug target (setup, overview, overview-with-state, step-1..step-16, or comma-separated list)',
2426
4149
  })
2427
4150
  .option('resume', {
2428
4151
  type: 'boolean',
@@ -2438,7 +4161,9 @@ const editorCommand = {
2438
4161
  describe: 'Debug: output directory for the bundle',
2439
4162
  });
2440
4163
  }
2441
- return builder;
4164
+ // Allow extra positional args for subcommands like `isolate A B C`
4165
+ // without yargs rejecting them as unknown.
4166
+ return builder.strict(false);
2442
4167
  },
2443
4168
  handler: async (argv) => {
2444
4169
  const root = getProjectRoot();
@@ -2477,6 +4202,11 @@ const editorCommand = {
2477
4202
  await handleRegister(argv.json || '');
2478
4203
  return;
2479
4204
  }
4205
+ // Subcommand: codeyam editor glossary-add '{"name":"...", ...}'
4206
+ if (argv.step === 'glossary-add') {
4207
+ await handleGlossaryAdd(argv.json || '');
4208
+ return;
4209
+ }
2480
4210
  // Subcommand: codeyam editor analyze-imports
2481
4211
  if (argv.step === 'analyze-imports') {
2482
4212
  await handleAnalyzeImports();
@@ -2497,6 +4227,16 @@ const editorCommand = {
2497
4227
  await handleScenarios();
2498
4228
  return;
2499
4229
  }
4230
+ // Subcommand: codeyam editor scenario-coverage
4231
+ if (argv.step === 'scenario-coverage') {
4232
+ await handleScenarioCoverage();
4233
+ return;
4234
+ }
4235
+ // Subcommand: codeyam editor recapture-stale
4236
+ if (argv.step === 'recapture-stale') {
4237
+ await handleRecaptureStale();
4238
+ return;
4239
+ }
2500
4240
  // Subcommand: codeyam editor change <feature>
2501
4241
  if (argv.step === 'change') {
2502
4242
  handleChange(argv.json || '');
@@ -2512,14 +4252,16 @@ const editorCommand = {
2512
4252
  await handleValidateSeed(argv.json || '');
2513
4253
  return;
2514
4254
  }
4255
+ // Subcommand: codeyam editor delete <scenarioId>
4256
+ if (argv.step === 'delete') {
4257
+ await handleDelete(argv.json || '');
4258
+ return;
4259
+ }
2515
4260
  // Subcommand: codeyam editor isolate "StarRating CategoryBadge DrinkCard"
4261
+ // Also supports: codeyam editor isolate StarRating CategoryBadge DrinkCard
2516
4262
  if (argv.step === 'isolate') {
2517
- const names = [];
2518
- if (argv.json)
2519
- names.push(...argv.json.split(/[\s,]+/).filter(Boolean));
2520
- // Collect any extra positional args (yargs puts them in argv._)
2521
- const extras = argv._.filter((a) => typeof a === 'string' && a !== 'editor');
2522
- names.push(...extras);
4263
+ const { parseIsolateArgs } = await import('./editorIsolateArgs.js');
4264
+ const names = parseIsolateArgs(argv.json, argv._);
2523
4265
  handleIsolate(names);
2524
4266
  return;
2525
4267
  }
@@ -2542,6 +4284,11 @@ const editorCommand = {
2542
4284
  await handleEditorDebug(argv);
2543
4285
  return;
2544
4286
  }
4287
+ // Subcommand: codeyam editor migrate [subArg]
4288
+ if (argv.step === 'migrate') {
4289
+ handleMigrateCommand(root, argv.json || undefined);
4290
+ return;
4291
+ }
2545
4292
  // Subcommand: codeyam editor steps — show setup or cycle overview
2546
4293
  if (argv.step === 'steps') {
2547
4294
  if (!hasProject(root)) {
@@ -2549,9 +4296,9 @@ const editorCommand = {
2549
4296
  }
2550
4297
  else {
2551
4298
  const state = readState(root);
2552
- // Clear prompt file when feature is done (step 13) so the hook
4299
+ // Clear prompt file when feature is done (step 16) so the hook
2553
4300
  // can capture the next feature request from the user.
2554
- if (state?.step === 13) {
4301
+ if (state?.step === 16) {
2555
4302
  clearEditorUserPrompt(root);
2556
4303
  }
2557
4304
  printCycleOverview(root, state);
@@ -2559,8 +4306,8 @@ const editorCommand = {
2559
4306
  return;
2560
4307
  }
2561
4308
  const step = argv.step ? parseInt(argv.step, 10) : undefined;
2562
- if (step != null && (isNaN(step) || step < 1 || step > 13)) {
2563
- console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-13.`));
4309
+ if (step != null && (isNaN(step) || step < 1 || step > 16)) {
4310
+ console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-16.`));
2564
4311
  process.exit(1);
2565
4312
  }
2566
4313
  if (step == null) {
@@ -2593,9 +4340,45 @@ const editorCommand = {
2593
4340
  return;
2594
4341
  }
2595
4342
  const { project, branch } = await requireBranchAndProject(projectSlug);
2596
- // Auto-sync scenarios from manifest if it exists
2597
- const manifestData = readManifest(projectRoot);
2598
- if (manifestData && manifestData.scenarios.length > 0) {
4343
+ // Backfill _metadata into existing scenario files that predate the embedded metadata feature.
4344
+ // This reads metadata from the DB and writes it into files that don't have _metadata yet.
4345
+ try {
4346
+ const { getDatabase } = await import('../../../packages/database/index.js');
4347
+ const db = getDatabase();
4348
+ const dbScenarios = await db
4349
+ .selectFrom('editor_scenarios')
4350
+ .select([
4351
+ 'id',
4352
+ 'name',
4353
+ 'description',
4354
+ 'component_name',
4355
+ 'component_path',
4356
+ 'url',
4357
+ 'type',
4358
+ 'screenshot_path',
4359
+ 'viewport_width',
4360
+ 'viewport_height',
4361
+ 'dimensions',
4362
+ 'screenshot_paths',
4363
+ 'page_file_path',
4364
+ 'created_at',
4365
+ 'updated_at',
4366
+ ])
4367
+ .where('project_id', '=', project.id)
4368
+ .execute();
4369
+ if (dbScenarios.length > 0) {
4370
+ const backfilled = backfillScenarioMetadata(projectRoot, dbScenarios);
4371
+ if (backfilled > 0) {
4372
+ console.log(chalk.green(` Backfilled metadata into ${backfilled} scenario file${backfilled > 1 ? 's' : ''}`));
4373
+ }
4374
+ }
4375
+ }
4376
+ catch {
4377
+ // Non-fatal — backfill is best-effort
4378
+ }
4379
+ // Auto-sync scenario files (with _metadata) to database
4380
+ const scenarioEntries = scanScenarioFiles(projectRoot);
4381
+ if (scenarioEntries.length > 0) {
2599
4382
  try {
2600
4383
  const { getDatabase } = await import('../../../packages/database/index.js');
2601
4384
  const db = getDatabase();
@@ -2604,7 +4387,7 @@ const editorCommand = {
2604
4387
  .select(['id', 'updated_at'])
2605
4388
  .where('project_id', '=', project.id)
2606
4389
  .execute();
2607
- const syncResult = await syncManifestToDatabase(projectRoot, project.id, existingRows, async (row) => {
4390
+ const syncResult = await syncScenarioFilesToDatabase(projectRoot, project.id, existingRows, async (row) => {
2608
4391
  await db
2609
4392
  .insertInto('editor_scenarios')
2610
4393
  .values(row)
@@ -2622,13 +4405,85 @@ const editorCommand = {
2622
4405
  parts.push(`${syncResult.inserted} imported`);
2623
4406
  if (syncResult.updated > 0)
2624
4407
  parts.push(`${syncResult.updated} updated`);
2625
- console.log(chalk.green(` Synced scenarios from manifest: ${parts.join(', ')}`));
4408
+ console.log(chalk.green(` Synced scenarios from files: ${parts.join(', ')}`));
2626
4409
  }
2627
4410
  }
2628
4411
  catch {
2629
4412
  // Non-fatal — sync failure shouldn't block editor startup
2630
4413
  }
2631
4414
  }
4415
+ // Migrate legacy scenario formats: resolve null viewports, populate
4416
+ // dimensions arrays, and build screenshotPaths maps from single values.
4417
+ // If files were fixed, re-sync to update the database with corrected values.
4418
+ try {
4419
+ const migrateResult = migrateScenarioFormats(projectRoot);
4420
+ if (migrateResult.fixed > 0) {
4421
+ console.log(chalk.green(` Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
4422
+ // Re-sync so the DB reflects the fixed file metadata
4423
+ try {
4424
+ const { getDatabase } = await import('../../../packages/database/index.js');
4425
+ const db = getDatabase();
4426
+ const rows = await db
4427
+ .selectFrom('editor_scenarios')
4428
+ .select(['id', 'updated_at'])
4429
+ .where('project_id', '=', project.id)
4430
+ .execute();
4431
+ await syncScenarioFilesToDatabase(projectRoot, project.id, rows, async (row) => {
4432
+ await db
4433
+ .insertInto('editor_scenarios')
4434
+ .values(row)
4435
+ .execute();
4436
+ }, async (id, row) => {
4437
+ await db
4438
+ .updateTable('editor_scenarios')
4439
+ .set(row)
4440
+ .where('id', '=', id)
4441
+ .execute();
4442
+ });
4443
+ }
4444
+ catch {
4445
+ // Non-fatal — DB re-sync failure shouldn't block startup
4446
+ }
4447
+ }
4448
+ }
4449
+ catch {
4450
+ // Non-fatal — migration failure shouldn't block editor startup
4451
+ }
4452
+ // Auto-seed on fresh clone: if no scenario has ever been activated
4453
+ // (active-scenario.json doesn't exist), seed the application database
4454
+ // with the first application scenario that has seed data.
4455
+ const activeScenarioPath = path.join(projectRoot, '.codeyam', 'active-scenario.json');
4456
+ if (!fs.existsSync(activeScenarioPath)) {
4457
+ try {
4458
+ const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4459
+ const seedDb = getDb();
4460
+ const appScenario = await seedDb
4461
+ .selectFrom('editor_scenarios')
4462
+ .select(['id', 'name', 'type'])
4463
+ .where('project_id', '=', project.id)
4464
+ .where('type', '=', 'application')
4465
+ .orderBy('created_at', 'asc')
4466
+ .executeTakeFirst();
4467
+ if (appScenario) {
4468
+ const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
4469
+ if (fs.existsSync(seedFile)) {
4470
+ const { switchActiveScenario } = await import('../utils/editorScenarioSwitch.js');
4471
+ const seedResult = await switchActiveScenario({
4472
+ scenarioId: appScenario.id,
4473
+ scenarioName: appScenario.name || undefined,
4474
+ scenarioType: appScenario.type || undefined,
4475
+ projectRoot,
4476
+ });
4477
+ if (seedResult.seedResult?.success) {
4478
+ console.log(chalk.green(` Auto-seeded database with scenario: ${appScenario.name || appScenario.id}`));
4479
+ }
4480
+ }
4481
+ }
4482
+ }
4483
+ catch {
4484
+ // Non-fatal — auto-seed is best-effort
4485
+ }
4486
+ }
2632
4487
  // `codeyam editor` (no step) always implies editor mode.
2633
4488
  // The empty-folder heuristic is no longer needed here — running this
2634
4489
  // command IS the signal. We still detect empty folders so that
@@ -2667,29 +4522,23 @@ const editorCommand = {
2667
4522
  editorMode,
2668
4523
  });
2669
4524
  // Auto-finalize analyzer so codeyam analyze works
2670
- if (editorMode && !isAnalyzerFinalized()) {
2671
- try {
2672
- const { execSync } = await import('child_process');
2673
- const templatePath = getAnalyzerTemplatePath();
2674
- if (fs.existsSync(templatePath)) {
2675
- console.log(' Setting up simulations (first time only)...');
2676
- execSync('npm install --include=dev', {
2677
- cwd: templatePath,
2678
- stdio: 'pipe',
2679
- });
2680
- execSync('npx playwright install chromium', {
2681
- cwd: templatePath,
2682
- stdio: 'pipe',
2683
- });
2684
- execSync('npm run build', {
2685
- cwd: templatePath,
2686
- stdio: 'pipe',
2687
- });
2688
- fs.writeFileSync(path.join(templatePath, '.finalized'), new Date().toISOString());
4525
+ if (editorMode) {
4526
+ const stepLabels = {
4527
+ 'npm-install': 'Installing simulation dependencies...',
4528
+ 'playwright-install': 'Installing browser (Chromium)...',
4529
+ build: 'Building simulation engine...',
4530
+ };
4531
+ const simProgress = new ProgressReporter();
4532
+ const finalization = ensureAnalyzerFinalized({
4533
+ onProgress: (step) => simProgress.start(stepLabels[step]),
4534
+ });
4535
+ if (finalization.errors.length > 0) {
4536
+ for (const err of finalization.errors) {
4537
+ simProgress.warn(`${stepLabels[err.step].replace('...', '')} failed: ${err.message}`);
2689
4538
  }
2690
4539
  }
2691
- catch {
2692
- // Non-fatal
4540
+ else if (finalization.needed) {
4541
+ simProgress.succeed('Simulation engine ready');
2693
4542
  }
2694
4543
  }
2695
4544
  // Start background server (handles killing existing servers internally)
@@ -2700,6 +4549,150 @@ const editorCommand = {
2700
4549
  project,
2701
4550
  branch,
2702
4551
  });
4552
+ // Build import graph if glossary exists but entities are missing.
4553
+ // Runs on first startup (no entities at all) AND when page files or
4554
+ // scenario component files lack entity coverage.
4555
+ const glossaryPath = path.join(projectRoot, '.codeyam', 'glossary.json');
4556
+ if (fs.existsSync(glossaryPath)) {
4557
+ let needsAnalysis = false;
4558
+ const entities = await loadEntities({});
4559
+ if (!entities || entities.length === 0) {
4560
+ needsAnalysis = true;
4561
+ }
4562
+ else {
4563
+ const entityFilePaths = new Set(entities.map((e) => e.filePath));
4564
+ // Check if any page files are missing entities (Next.js apps)
4565
+ try {
4566
+ const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
4567
+ const { allFiles } = scanPageFilePaths(projectRoot);
4568
+ const pageFiles = allFiles.filter((f) => f.endsWith('/page.tsx') || f.endsWith('/page.js'));
4569
+ const missingPages = pageFiles.filter((f) => !entityFilePaths.has(f));
4570
+ if (missingPages.length > 0) {
4571
+ console.log(chalk.dim(` Found ${missingPages.length} page(s) without entity analysis — running import analysis...`));
4572
+ needsAnalysis = true;
4573
+ }
4574
+ }
4575
+ catch {
4576
+ // Non-fatal — page file check failed
4577
+ }
4578
+ // Check if any scenario files (component_path or page_file_path)
4579
+ // are missing entities — covers non-Next.js apps
4580
+ if (!needsAnalysis) {
4581
+ try {
4582
+ const { getDatabase } = await import('../../../packages/database/index.js');
4583
+ const db = getDatabase();
4584
+ const scenarioFiles = await db
4585
+ .selectFrom('editor_scenarios')
4586
+ .select(['component_path', 'page_file_path'])
4587
+ .where('project_id', '=', project.id)
4588
+ .distinct()
4589
+ .execute();
4590
+ const missingCount = scenarioFiles.filter((row) => {
4591
+ const cp = row.component_path;
4592
+ const pfp = row.page_file_path;
4593
+ return ((cp && !entityFilePaths.has(cp)) ||
4594
+ (pfp && !entityFilePaths.has(pfp)));
4595
+ }).length;
4596
+ if (missingCount > 0) {
4597
+ console.log(chalk.dim(` Found ${missingCount} scenario file(s) without entity analysis — running import analysis...`));
4598
+ needsAnalysis = true;
4599
+ }
4600
+ }
4601
+ catch {
4602
+ // Non-fatal
4603
+ }
4604
+ }
4605
+ // Check if any scenarios have null entity_sha with file paths — heal on startup
4606
+ if (!needsAnalysis) {
4607
+ try {
4608
+ const { getDatabase } = await import('../../../packages/database/index.js');
4609
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
4610
+ const db = getDatabase();
4611
+ const backfillCount = await countScenariosNeedingEntityBackfill(db);
4612
+ if (backfillCount > 0) {
4613
+ console.log(chalk.dim(` Found ${backfillCount} scenario(s) with unlinked entities — running import analysis...`));
4614
+ needsAnalysis = true;
4615
+ }
4616
+ }
4617
+ catch {
4618
+ // Non-fatal
4619
+ }
4620
+ }
4621
+ }
4622
+ if (needsAnalysis) {
4623
+ try {
4624
+ await handleAnalyzeImports({ silent: true });
4625
+ }
4626
+ catch {
4627
+ // Non-fatal
4628
+ }
4629
+ }
4630
+ }
4631
+ // Backfill page_file_path for application scenarios that have a URL but
4632
+ // no page_file_path. This resolves the file from the URL using Next.js
4633
+ // routing conventions, enabling syncScenarioEntityShas to link them to
4634
+ // entities. Covers fresh clones where JSON files lack pageFilePath.
4635
+ try {
4636
+ const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4637
+ const pfpDb = getDb();
4638
+ const unresolved = await pfpDb
4639
+ .selectFrom('editor_scenarios')
4640
+ .select(['id', 'url'])
4641
+ .where('project_id', '=', project.id)
4642
+ .where('component_name', 'is', null)
4643
+ .where('page_file_path', 'is', null)
4644
+ .where('url', 'is not', null)
4645
+ .execute();
4646
+ if (unresolved.length > 0) {
4647
+ const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
4648
+ const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
4649
+ const { allFiles: pfpFiles } = scanPfp(projectRoot);
4650
+ let pfpResolved = 0;
4651
+ for (const row of unresolved) {
4652
+ const r = row;
4653
+ if (!r.url)
4654
+ continue;
4655
+ const matched = matchUrlToPageFile(r.url, pfpFiles);
4656
+ if (matched) {
4657
+ await pfpDb
4658
+ .updateTable('editor_scenarios')
4659
+ .set({ page_file_path: matched })
4660
+ .where('id', '=', r.id)
4661
+ .execute();
4662
+ pfpResolved++;
4663
+ }
4664
+ }
4665
+ if (pfpResolved > 0) {
4666
+ console.log(chalk.green(` Resolved page_file_path for ${pfpResolved} scenario(s) from URL`));
4667
+ }
4668
+ }
4669
+ }
4670
+ catch {
4671
+ /* Non-fatal — page_file_path backfill from URL */
4672
+ }
4673
+ // Backfill entity_sha on scenarios that were synced before entities existed.
4674
+ // This runs independently of analyze-imports so fresh clones and file-synced
4675
+ // scenarios get linked to their entities even when auto-analyze doesn't trigger.
4676
+ try {
4677
+ const entities = await loadEntities({});
4678
+ if (entities && entities.length > 0) {
4679
+ const { getDatabase } = await import('../../../packages/database/index.js');
4680
+ const db = getDatabase();
4681
+ const result = await backfillEntityShaOnScenarios(db, entities.map((e) => ({
4682
+ sha: e.sha,
4683
+ name: e.name,
4684
+ filePath: e.filePath || '',
4685
+ isDefaultExport: e.metadata?.notExported === false &&
4686
+ e.metadata?.namedExport === false,
4687
+ })));
4688
+ if (result.updated > 0) {
4689
+ console.log(chalk.green(` Linked ${result.updated} scenario(s) to their entities`));
4690
+ }
4691
+ }
4692
+ }
4693
+ catch {
4694
+ /* Non-fatal */
4695
+ }
2703
4696
  console.log();
2704
4697
  console.log(` Dashboard: ${url}`);
2705
4698
  console.log(' Run "codeyam --help" for all commands');
@@ -2713,7 +4706,9 @@ const editorCommand = {
2713
4706
  : process.platform === 'win32'
2714
4707
  ? 'start ""'
2715
4708
  : 'xdg-open';
2716
- execSync(`${openCommand} "${url}/editor"`, { stdio: 'ignore' });
4709
+ execSync(`${openCommand} "${url}/editor"`, {
4710
+ stdio: 'ignore',
4711
+ });
2717
4712
  }
2718
4713
  catch {
2719
4714
  // Silently fail if open command doesn't work
@@ -2727,6 +4722,18 @@ const editorCommand = {
2727
4722
  return;
2728
4723
  }
2729
4724
  const state = readState(root);
4725
+ // Validate step transition — prevent skipping steps.
4726
+ // Exception: step 2 with --feature is always allowed because step 1's
4727
+ // instructions explicitly tell Claude to run `codeyam editor 2 --feature "..."`.
4728
+ // Step 1 is planning-only and may not persist state (no --feature flag).
4729
+ const skipValidation = step === 2 && argv.feature;
4730
+ if (!skipValidation) {
4731
+ const stepError = validateStepTransition(step, state?.step ?? null);
4732
+ if (stepError) {
4733
+ console.error(chalk.red(`Error: ${stepError}`));
4734
+ process.exit(1);
4735
+ }
4736
+ }
2730
4737
  switch (step) {
2731
4738
  case 1: {
2732
4739
  const feature = argv.feature || undefined;
@@ -2758,12 +4765,25 @@ const editorCommand = {
2758
4765
  case 10:
2759
4766
  case 11:
2760
4767
  case 12:
2761
- case 13: {
4768
+ case 13:
4769
+ case 14:
4770
+ case 15:
4771
+ case 16: {
2762
4772
  const feature = argv.feature || state?.feature;
2763
4773
  if (!feature) {
2764
4774
  console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
2765
4775
  process.exit(1);
2766
4776
  }
4777
+ // Hard gate: steps 8+ require audit to have passed
4778
+ if (step >= 8) {
4779
+ const auditOk = await checkAuditGate();
4780
+ if (!auditOk) {
4781
+ // checkAuditGate() already printed specific failure details above
4782
+ console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
4783
+ console.error(chalk.yellow('DO NOT re-run the audit in a loop — fix the underlying issues first.'));
4784
+ process.exit(1);
4785
+ }
4786
+ }
2767
4787
  const stepFns = {
2768
4788
  3: printStep3,
2769
4789
  4: printStep4,
@@ -2776,6 +4796,9 @@ const editorCommand = {
2776
4796
  11: printStep11,
2777
4797
  12: printStep12,
2778
4798
  13: printStep13,
4799
+ 14: printStep14,
4800
+ 15: printStep15,
4801
+ 16: printStep16,
2779
4802
  };
2780
4803
  stepFns[step](root, feature);
2781
4804
  break;