@codeyam/codeyam-cli 0.1.11 → 0.1.13

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 (214) 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 +42 -16
  8. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +3 -1
  9. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts.map +1 -1
  10. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js +44 -16
  11. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  12. package/codeyam-cli/src/cli.js +9 -0
  13. package/codeyam-cli/src/cli.js.map +1 -1
  14. package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js +51 -0
  15. package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js.map +1 -0
  16. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +11 -0
  17. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -1
  18. package/codeyam-cli/src/commands/editor.js +1360 -201
  19. package/codeyam-cli/src/commands/editor.js.map +1 -1
  20. package/codeyam-cli/src/commands/editorIsolateArgs.js +25 -0
  21. package/codeyam-cli/src/commands/editorIsolateArgs.js.map +1 -0
  22. package/codeyam-cli/src/commands/telemetry.js +37 -0
  23. package/codeyam-cli/src/commands/telemetry.js.map +1 -0
  24. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +893 -1
  25. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  26. package/codeyam-cli/src/utils/__tests__/editorBroadcastViewport.test.js +76 -0
  27. package/codeyam-cli/src/utils/__tests__/editorBroadcastViewport.test.js.map +1 -0
  28. package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js +100 -0
  29. package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js.map +1 -0
  30. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +76 -3
  31. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -1
  32. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +261 -0
  33. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -0
  34. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +75 -1
  35. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -1
  36. package/codeyam-cli/src/utils/__tests__/editorMigration.test.js +435 -0
  37. package/codeyam-cli/src/utils/__tests__/editorMigration.test.js.map +1 -0
  38. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +441 -17
  39. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  40. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +67 -0
  41. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
  42. package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js +143 -0
  43. package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js.map +1 -0
  44. package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js +66 -0
  45. package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js.map +1 -0
  46. package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js +53 -0
  47. package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js.map +1 -0
  48. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +67 -9
  49. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  50. package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js +118 -0
  51. package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js.map +1 -0
  52. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +40 -1
  53. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
  54. package/codeyam-cli/src/utils/__tests__/telemetry.test.js +159 -0
  55. package/codeyam-cli/src/utils/__tests__/telemetry.test.js.map +1 -0
  56. package/codeyam-cli/src/utils/analysisRunner.js +3 -1
  57. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  58. package/codeyam-cli/src/utils/editorAudit.js +145 -0
  59. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  60. package/codeyam-cli/src/utils/editorBroadcastViewport.js +26 -0
  61. package/codeyam-cli/src/utils/editorBroadcastViewport.js.map +1 -0
  62. package/codeyam-cli/src/utils/editorDeleteScenario.js +67 -0
  63. package/codeyam-cli/src/utils/editorDeleteScenario.js.map +1 -0
  64. package/codeyam-cli/src/utils/editorEntityChangeStatus.js +13 -7
  65. package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -1
  66. package/codeyam-cli/src/utils/editorEntityHelpers.js +129 -0
  67. package/codeyam-cli/src/utils/editorEntityHelpers.js.map +1 -0
  68. package/codeyam-cli/src/utils/editorLoaderHelpers.js +40 -1
  69. package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -1
  70. package/codeyam-cli/src/utils/editorMigration.js +224 -0
  71. package/codeyam-cli/src/utils/editorMigration.js.map +1 -0
  72. package/codeyam-cli/src/utils/editorScenarios.js +163 -2
  73. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  74. package/codeyam-cli/src/utils/editorSeedAdapter.js +253 -4
  75. package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
  76. package/codeyam-cli/src/utils/editorShouldRevalidate.js +21 -0
  77. package/codeyam-cli/src/utils/editorShouldRevalidate.js.map +1 -0
  78. package/codeyam-cli/src/utils/entityChangeStatus.js +19 -2
  79. package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
  80. package/codeyam-cli/src/utils/entityChangeStatus.server.js +7 -3
  81. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  82. package/codeyam-cli/src/utils/fileWatcher.js +38 -0
  83. package/codeyam-cli/src/utils/fileWatcher.js.map +1 -1
  84. package/codeyam-cli/src/utils/install-skills.js +9 -0
  85. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  86. package/codeyam-cli/src/utils/routePatternMatching.js +129 -0
  87. package/codeyam-cli/src/utils/routePatternMatching.js.map +1 -0
  88. package/codeyam-cli/src/utils/scenarioCoverage.js +8 -9
  89. package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -1
  90. package/codeyam-cli/src/utils/scenariosManifest.js +18 -10
  91. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  92. package/codeyam-cli/src/utils/telemetry.js +106 -0
  93. package/codeyam-cli/src/utils/telemetry.js.map +1 -0
  94. package/codeyam-cli/src/utils/telemetryMiddleware.js +22 -0
  95. package/codeyam-cli/src/utils/telemetryMiddleware.js.map +1 -0
  96. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +35 -0
  97. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -0
  98. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +61 -0
  99. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
  100. package/codeyam-cli/src/webserver/app/types/editor.js +8 -0
  101. package/codeyam-cli/src/webserver/app/types/editor.js.map +1 -0
  102. package/codeyam-cli/src/webserver/backgroundServer.js +18 -4
  103. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  104. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-CzTDWkF2.js +1 -0
  105. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-BcgbViKV.js → EntityItem-BFbq6iFk.js} +3 -3
  106. package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-CQgyEGV-.js +1 -0
  107. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CQIG2qda.js → EntityTypeIcon-B6OMi58N.js} +1 -1
  108. package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-DuYodzo1.js +1 -0
  109. package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-CXo9EeCl.js +25 -0
  110. package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-DYCNb2It.js +3 -0
  111. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-BU_OAEMP.js → LoadingDots-By5zI316.js} +1 -1
  112. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-ceAyBX-H.js → LogViewer-CZgY3sxX.js} +3 -3
  113. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-BzHcG7SE.js → ReportIssueModal-CnYYwRDw.js} +2 -2
  114. package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-CDoF7ZpU.js +1 -0
  115. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-TSD3C211.js → ScenarioViewer-DrnfvaLL.js} +3 -3
  116. package/codeyam-cli/src/webserver/build/client/assets/Spinner-Df3UCi8k.js +34 -0
  117. package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-CK7-NaPZ.js +1 -0
  118. package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-DRKR9T0U.js +1 -0
  119. package/codeyam-cli/src/webserver/build/client/assets/{_index-DLxKhri3.js → _index-ClR-g3tY.js} +2 -2
  120. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BcY3q6nt.js → activity.(_tab)-DTH6ydEA.js} +3 -3
  121. package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-Duc5hnl7.js → addon-web-links-74hnHF59.js} +1 -1
  122. package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-Bni3iiUj.js → agent-transcripts-B8CYhCO9.js} +3 -3
  123. package/codeyam-cli/src/webserver/build/client/assets/api.editor-rename-scenario-l0sNRNKZ.js +1 -0
  124. package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-seed-state-l0sNRNKZ.js +1 -0
  125. package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-prompt-l0sNRNKZ.js +1 -0
  126. package/codeyam-cli/src/webserver/build/client/assets/{book-open-BYOypzCa.js → book-open-CLaoh4ac.js} +1 -1
  127. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-C_Pmso5S.js → chevron-down-BZ2DZxbW.js} +1 -1
  128. package/codeyam-cli/src/webserver/build/client/assets/{chunk-JZWAC4HX-C4pqxYJB.js → chunk-JZWAC4HX-BBXArFPl.js} +13 -21
  129. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-BVMi9VA5.js → circle-check-CT4unAk-.js} +1 -1
  130. package/codeyam-cli/src/webserver/build/client/assets/{copy-n2FB0_Sw.js → copy-zK0B6Nu-.js} +1 -1
  131. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-CC6AbExI.js → createLucideIcon-DJB0YQJL.js} +1 -1
  132. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-CkXFP_i-.js +1 -0
  133. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-DPw7NZHc.js +1 -0
  134. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-CjC3_6JI.js +58 -0
  135. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DBa7T2FK.js +41 -0
  136. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-DwCV5__E.js → entity._sha._-BqAN7hyG.js} +2 -2
  137. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-BOi8kpwd.js +6 -0
  138. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-Dg1NhIms.js +6 -0
  139. package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-CJX6kkkV.js +6 -0
  140. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-BMvVHNXU.js → entity._sha_.edit._scenarioId-BhVjZhKg.js} +2 -2
  141. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-DTvKq3TY.js → entry.client-_gzKltPN.js} +6 -6
  142. package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-Daa96Fr1.js +1 -0
  143. package/codeyam-cli/src/webserver/build/client/assets/files-CV_17tZS.js +1 -0
  144. package/codeyam-cli/src/webserver/build/client/assets/git-D-YXmMbR.js +1 -0
  145. package/codeyam-cli/src/webserver/build/client/assets/globals-DRvOjyO3.css +1 -0
  146. package/codeyam-cli/src/webserver/build/client/assets/{index-yHOVb4rc.js → index-Blo6EK8G.js} +1 -1
  147. package/codeyam-cli/src/webserver/build/client/assets/{index-10oVnAAH.js → index-BsX0F-9C.js} +1 -1
  148. package/codeyam-cli/src/webserver/build/client/assets/{index-BcvgDzbZ.js → index-CCrgCshv.js} +1 -1
  149. package/codeyam-cli/src/webserver/build/client/assets/jsx-runtime-D_zvdyIk.js +9 -0
  150. package/codeyam-cli/src/webserver/build/client/assets/labs-Byazq8Pv.js +1 -0
  151. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-DaAZ_H2w.js → loader-circle-DVQ0oHR7.js} +1 -1
  152. package/codeyam-cli/src/webserver/build/client/assets/manifest-75b1b319.js +1 -0
  153. package/codeyam-cli/src/webserver/build/client/assets/{memory-9gnxSZlb.js → memory-b-VmA2Vj.js} +2 -2
  154. package/codeyam-cli/src/webserver/build/client/assets/{pause-f5-1lKBt.js → pause-DGcndCAa.js} +1 -1
  155. package/codeyam-cli/src/webserver/build/client/assets/{root-DBjt6o04.js → root-F-k2uYj5.js} +15 -15
  156. package/codeyam-cli/src/webserver/build/client/assets/{search-Di64LWVb.js → search-C0Uw0bcK.js} +1 -1
  157. package/codeyam-cli/src/webserver/build/client/assets/settings-OoNgHIfW.js +1 -0
  158. package/codeyam-cli/src/webserver/build/client/assets/simulations-Bcemfu8a.js +1 -0
  159. package/codeyam-cli/src/webserver/build/client/assets/{terminal-Br7MOqts.js → terminal-BgMmG7R9.js} +1 -1
  160. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-BLdiCuG-.js → triangle-alert-Cs87hJYK.js} +1 -1
  161. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-BR3Rs7JY.js +1 -0
  162. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-C14nCb1q.js → useLastLogLine-BxxP_XF9.js} +1 -1
  163. package/codeyam-cli/src/webserver/build/client/assets/useReportContext-BermyNU5.js +1 -0
  164. package/codeyam-cli/src/webserver/build/client/assets/useToast-a_QN_W9_.js +1 -0
  165. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-lv2ooewK.js +13 -0
  166. package/codeyam-cli/src/webserver/build/server/assets/{index-DsZjKspK.js → index-Im3Smyei.js} +1 -1
  167. package/codeyam-cli/src/webserver/build/server/assets/init-BjuAFKGM.js +10 -0
  168. package/codeyam-cli/src/webserver/build/server/assets/progress-CHTtrxFG.js +1 -0
  169. package/codeyam-cli/src/webserver/build/server/assets/server-build-CNjF0B9B.js +551 -0
  170. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  171. package/codeyam-cli/src/webserver/build-info.json +5 -5
  172. package/codeyam-cli/src/webserver/editorProxy.js +112 -13
  173. package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
  174. package/codeyam-cli/src/webserver/mockStateEvents.js +28 -0
  175. package/codeyam-cli/src/webserver/mockStateEvents.js.map +1 -0
  176. package/codeyam-cli/src/webserver/server.js +41 -0
  177. package/codeyam-cli/src/webserver/server.js.map +1 -1
  178. package/codeyam-cli/src/webserver/terminalServer.js +74 -8
  179. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  180. package/codeyam-cli/templates/editor-step-hook.py +104 -20
  181. package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +42 -7
  182. package/codeyam-cli/templates/seed-adapters/supabase.ts +282 -0
  183. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +62 -0
  184. package/package.json +2 -1
  185. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +44 -16
  186. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  187. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-BPXZwM4t.js +0 -1
  188. package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-g3saevPb.js +0 -1
  189. package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-Bu6c6aDe.js +0 -1
  190. package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-DYFW3lDD.js +0 -25
  191. package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-DLeucoVX.js +0 -3
  192. package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-BED4B6sP.js +0 -1
  193. package/codeyam-cli/src/webserver/build/client/assets/Spinner-Bb5uFQ5V.js +0 -34
  194. package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-C8OKAR5x.js +0 -1
  195. package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-oAf2Kqsf.js +0 -1
  196. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-Ii3inc0_.js +0 -1
  197. package/codeyam-cli/src/webserver/build/client/assets/editor-16o0AIFV.js +0 -15
  198. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-7Uga8I59.js +0 -41
  199. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-BwKcai0j.js +0 -6
  200. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CHMiAog3.js +0 -6
  201. package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-p9hhkjJM.js +0 -6
  202. package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-cPo8LiG3.js +0 -1
  203. package/codeyam-cli/src/webserver/build/client/assets/files-BZrlFE1F.js +0 -1
  204. package/codeyam-cli/src/webserver/build/client/assets/git-DdZcvjGh.js +0 -1
  205. package/codeyam-cli/src/webserver/build/client/assets/globals-CQPR0pFR.css +0 -1
  206. package/codeyam-cli/src/webserver/build/client/assets/labs-Zk7ryIM1.js +0 -1
  207. package/codeyam-cli/src/webserver/build/client/assets/manifest-76e7b62c.js +0 -1
  208. package/codeyam-cli/src/webserver/build/client/assets/settings-0OrEMU6J.js +0 -1
  209. package/codeyam-cli/src/webserver/build/client/assets/simulations-DWT-CvLy.js +0 -1
  210. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-C-_hOl_g.js +0 -1
  211. package/codeyam-cli/src/webserver/build/client/assets/useReportContext-O-jkvSPx.js +0 -1
  212. package/codeyam-cli/src/webserver/build/client/assets/useToast-9FIWuYfK.js +0 -1
  213. package/codeyam-cli/src/webserver/build/server/assets/init-DdqKD2p4.js +0 -10
  214. package/codeyam-cli/src/webserver/build/server/assets/server-build-CKKeWtVK.js +0 -444
@@ -16,10 +16,13 @@ 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
18
  import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
19
- import { clearEditorState, clearEditorUserPrompt, validateStepTransition, } from "../utils/editorScenarios.js";
20
- import { validateSeedData, detectSeedAdapter, } from "../utils/editorSeedAdapter.js";
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 = {
@@ -40,6 +43,18 @@ const STEP_LABELS = {
40
43
  15: 'Finalize',
41
44
  16: 'Push',
42
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',
57
+ };
43
58
  /**
44
59
  * Append a JSONL log entry to .codeyam/logs/editor-log.jsonl
45
60
  */
@@ -185,6 +200,146 @@ function checkbox(text) {
185
200
  const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
186
201
  console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
187
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
+ checkbox('Check existing scenarios: `codeyam editor scenarios`');
209
+ console.log(chalk.dim(' Review existing scenarios — reuse and update their data rather than'));
210
+ console.log(chalk.dim(' creating duplicates. Re-register with the same name to update a scenario.'));
211
+ console.log();
212
+ console.log(chalk.bold.yellow('App scenarios vs component scenarios — these are DIFFERENT:'));
213
+ console.log(chalk.yellow(' App scenarios: show the FULL PAGE with seeded data at a real route (/, /library, etc.)'));
214
+ console.log(chalk.yellow(' Component scenarios: show ONE COMPONENT in isolation at /isolated-components/...'));
215
+ console.log(chalk.yellow(' This step is about APP scenarios. Do NOT set "componentName" — that makes it a component scenario.'));
216
+ console.log();
217
+ checkbox('Identify every page/route in the app and ensure each has app-level scenarios');
218
+ console.log(chalk.dim(" Check the app's router/entry points for all distinct pages"));
219
+ console.log(chalk.yellow(' Every page needs at least 2-3 app scenarios — not just the main page'));
220
+ if (pageName) {
221
+ console.log(chalk.dim(` Example: "${pageName} - Full Data", "${pageName} - Empty State"`));
222
+ }
223
+ else {
224
+ console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
225
+ }
226
+ console.log();
227
+ checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
228
+ console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
229
+ console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
230
+ console.log(chalk.yellow(' Required fields: "type":"application", "url" (real route), "pageFilePath" (source file)'));
231
+ console.log(chalk.yellow(' Do NOT include "componentName" — that would make it a component scenario'));
232
+ console.log();
233
+ checkbox('Choose the right data approach for scenarios:');
234
+ console.log(chalk.dim(' With seed adapter: add "seed":{...} to populate the database for each state'));
235
+ console.log(chalk.dim(' Without seed adapter: add "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
236
+ console.log(chalk.dim(' For external APIs (Stripe, weather, etc.): add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
237
+ checkbox('For large seed/mock data, write JSON to a temp file and use @ prefix:');
238
+ console.log(chalk.dim(' Write JSON to .codeyam/tmp/scenario.json then register with:'));
239
+ console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
240
+ checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
241
+ checkbox('After each registration, check the response for `clientErrors`');
242
+ console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
243
+ console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
244
+ }
245
+ /**
246
+ * Shared glossary instructions used by editor Step 6 and migration Step 8.
247
+ * Prints format, required fields, and example.
248
+ */
249
+ function printGlossaryInstructions(feature) {
250
+ console.log(chalk.bold.yellow('IMPORTANT: glossary.json is a plain JSON array — NOT an object wrapper.'));
251
+ checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
252
+ checkbox('Add an entry for each new function/component extracted');
253
+ checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
254
+ checkbox('For each function with a test file, set `testFile` to the relative path');
255
+ console.log();
256
+ console.log(chalk.bold('File format:'));
257
+ console.log(chalk.dim(' ['));
258
+ console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
259
+ console.log(chalk.dim(' "description": "Calculates total price including tax and discounts",'));
260
+ console.log(chalk.dim(' "parameters": [{ "name": "items", "type": "CartItem[]" }],'));
261
+ console.log(chalk.dim(' "returnType": "number", "tags": ["pricing"],'));
262
+ console.log(chalk.dim(' "testFile": "app/utils/pricing.test.ts",'));
263
+ console.log(chalk.dim(` "feature": "${feature || '...'}", "createdAt": "..." }`));
264
+ console.log(chalk.dim(' ]'));
265
+ }
266
+ /**
267
+ * Shared extraction plan instructions used by editor Step 4 and migration Step 6.
268
+ * Prints THE RULE, extractable categories, plan format requirements.
269
+ */
270
+ function printExtractionPlanInstructions() {
271
+ console.log(chalk.bold.red('THE RULE: No direct JSX in page files.'));
272
+ console.log(chalk.yellow(' After extraction, a page/route file should import and compose components — nothing else.'));
273
+ console.log(chalk.yellow(' Every distinct visual section in the page is its own component.'));
274
+ console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
275
+ console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
276
+ console.log();
277
+ console.log(chalk.bold('Checklist:'));
278
+ checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
279
+ checkbox('Read EVERY file used by this page/feature');
280
+ console.log(chalk.yellow(' For EACH file, identify EVERY extractable piece:'));
281
+ console.log(chalk.yellow(' Components: headers, nav, loading states, empty states, error states,'));
282
+ console.log(chalk.yellow(' badges/pills, image containers, card sections, form fields, modals,'));
283
+ console.log(chalk.yellow(' titles, descriptions, rating displays, status indicators, buttons'));
284
+ console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
285
+ console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
286
+ console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
287
+ console.log();
288
+ checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
289
+ console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
290
+ console.log(chalk.yellow(' No raw <div>, <span>, <h1>, <p>, <img>, or <ul> in page files.'));
291
+ console.log(chalk.yellow(' Every visual section in every component is itself a component.'));
292
+ console.log();
293
+ console.log(chalk.yellow(' For each item in the plan, note:'));
294
+ console.log(chalk.yellow(' — What it is (component, function, hook)'));
295
+ console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
296
+ console.log(chalk.yellow(' — Where it will go (new file path)'));
297
+ console.log();
298
+ console.log(chalk.dim('Present the numbered plan, then proceed to step 5 to execute it.'));
299
+ }
300
+ /**
301
+ * Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
302
+ * Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
303
+ */
304
+ function printComponentCaptureInstructions() {
305
+ checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
306
+ console.log(chalk.dim(' This creates app/codeyam-isolate/layout.tsx (with production notFound() guard) and'));
307
+ console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
308
+ checkbox('For each visual component:');
309
+ console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
310
+ console.log(chalk.dim(' — Props/interface'));
311
+ console.log(chalk.dim(' — Container width in the real app (e.g. card in a 3-col grid → max-w-sm)'));
312
+ console.log(chalk.dim(' 2. Plan multiple scenarios that exercise the component like tests:'));
313
+ console.log(chalk.dim(' — Default/happy path with typical data'));
314
+ console.log(chalk.dim(' — Edge cases: empty/missing data, long text, maximum items, zero counts'));
315
+ console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
316
+ console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
317
+ console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
318
+ console.log(chalk.dim(' Remix: app/routes/codeyam-isolate.ComponentName.tsx → /codeyam-isolate/ComponentName'));
319
+ console.log(chalk.dim(' Next.js: app/codeyam-isolate/ComponentName/page.tsx → /codeyam-isolate/ComponentName'));
320
+ console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
321
+ console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
322
+ console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
323
+ console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block", padding:"20px" }}>'));
324
+ console.log(chalk.dim(' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
325
+ console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth:"24rem", full-width component → omit maxWidth'));
326
+ console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
327
+ console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
328
+ console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
329
+ console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
330
+ console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
331
+ console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
332
+ console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
333
+ console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
334
+ console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/codeyam-isolate/... or /isolated-components/...).'));
335
+ console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
336
+ console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
337
+ console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
338
+ console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
339
+ console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
340
+ console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
341
+ console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
342
+ }
188
343
  /**
189
344
  * Print a section header.
190
345
  */
@@ -570,6 +725,42 @@ function printSetup(root) {
570
725
  // ─── Cycle overview (no args, has project) ────────────────────────────
571
726
  function printCycleOverview(root, state) {
572
727
  logEvent(root, 'overview', state ? { feature: state.feature, step: state.step } : {});
728
+ // Detect active migration (uses both editor-step.json and migration-state.json)
729
+ const migState = readMigrationState(root);
730
+ const resumeInfo = getMigrationResumeInfo(state
731
+ ? { step: state.step, label: state.label, migration: state.migration }
732
+ : null, migState);
733
+ if (resumeInfo.mode === 'survey') {
734
+ console.log();
735
+ console.log(chalk.bold.cyan('━━━ Editor Mode: Project Migration ━━━'));
736
+ console.log();
737
+ console.log(chalk.yellow('Migration survey in progress — Claude needs to explore the project.'));
738
+ console.log();
739
+ console.log(chalk.green('Continue with: ') + chalk.bold(resumeInfo.resumeCommand));
740
+ console.log();
741
+ return;
742
+ }
743
+ if (resumeInfo.mode === 'page-in-progress') {
744
+ console.log();
745
+ console.log(chalk.bold.cyan('━━━ Editor Mode: Project Migration ━━━'));
746
+ console.log();
747
+ console.log(chalk.yellow(`Migration: Page ${resumeInfo.pageIndex + 1}/${resumeInfo.totalPages} (${resumeInfo.pageName})` +
748
+ (resumeInfo.step
749
+ ? `, Step ${resumeInfo.step} (${resumeInfo.stepLabel})`
750
+ : '')));
751
+ console.log();
752
+ console.log(chalk.green('Continue with: ') + chalk.bold(resumeInfo.resumeCommand));
753
+ console.log();
754
+ return;
755
+ }
756
+ if (resumeInfo.mode === 'complete') {
757
+ console.log();
758
+ console.log(chalk.bold.cyan('━━━ Editor Mode: Feature Cycle ━━━'));
759
+ console.log();
760
+ console.log(chalk.green('Migration complete! Start building features:'));
761
+ console.log();
762
+ // Fall through to normal cycle display
763
+ }
573
764
  console.log();
574
765
  console.log(chalk.bold.cyan('━━━ Editor Mode: Feature Cycle ━━━'));
575
766
  console.log();
@@ -748,13 +939,29 @@ function printStep2(root, feature) {
748
939
  console.log();
749
940
  console.log(chalk.bold('Build the feature:'));
750
941
  }
942
+ else {
943
+ console.log(chalk.bold('Prepare the database for this feature:'));
944
+ checkbox('List existing scenarios: `codeyam editor scenarios`');
945
+ console.log(chalk.dim(' Review existing scenarios to find seed data that best demonstrates the feature.'));
946
+ console.log(chalk.dim(" Don't create throwaway seed data — use what's already been curated."));
947
+ checkbox('Pick the scenario with seed data most relevant to this feature');
948
+ console.log(chalk.dim(" Think: which existing data state best exercises the feature you're building?"));
949
+ console.log(chalk.dim(' A scenario with diverse, realistic data is usually the best starting point.'));
950
+ checkbox('Load that scenario to seed the database and preview it:');
951
+ console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
952
+ console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
953
+ console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
954
+ console.log();
955
+ }
751
956
  console.log(chalk.bold('Checklist:'));
752
957
  checkbox('Create API routes that read from the database via Prisma');
753
- checkbox('Seed the database with demo data');
754
- checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
755
- console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
756
- console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
757
- console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
958
+ if (!projectExists) {
959
+ checkbox('Seed the database with demo data');
960
+ checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
961
+ console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
962
+ console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
963
+ console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
964
+ }
758
965
  checkbox('Verify the dev server shows the changes');
759
966
  checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
760
967
  // Responsive design guidance when building a mobile-responsive web app
@@ -896,34 +1103,7 @@ function printStep4(root, feature) {
896
1103
  console.log(chalk.bold('Goal: pages contain ONLY components. Components contain ONLY sub-components.'));
897
1104
  console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 5.'));
898
1105
  console.log();
899
- console.log(chalk.bold.red('THE RULE: No direct JSX in page files.'));
900
- console.log(chalk.yellow(' After extraction, a page/route file should import and compose components — nothing else.'));
901
- console.log(chalk.yellow(' Every distinct visual section in the page is its own component.'));
902
- console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
903
- console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
904
- console.log();
905
- console.log(chalk.bold('Checklist:'));
906
- checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
907
- checkbox('Read EVERY file created or modified in this session');
908
- console.log(chalk.yellow(' For EACH file, identify EVERY extractable piece:'));
909
- console.log(chalk.yellow(' Components: headers, nav, loading states, empty states, error states,'));
910
- console.log(chalk.yellow(' badges/pills, image containers, card sections, form fields, modals,'));
911
- console.log(chalk.yellow(' titles, descriptions, rating displays, status indicators, buttons'));
912
- console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
913
- console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
914
- console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
915
- console.log();
916
- checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
917
- console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
918
- console.log(chalk.yellow(' No raw <div>, <span>, <h1>, <p>, <img>, or <ul> in page files.'));
919
- console.log(chalk.yellow(' Every visual section in every component is itself a component.'));
920
- console.log();
921
- console.log(chalk.yellow(' For each item in the plan, note:'));
922
- console.log(chalk.yellow(' — What it is (component, function, hook)'));
923
- console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
924
- console.log(chalk.yellow(' — Where it will go (new file path)'));
925
- console.log();
926
- console.log(chalk.dim('Present the numbered plan, then proceed to step 5 to execute it.'));
1106
+ printExtractionPlanInstructions();
927
1107
  stopGate(4);
928
1108
  }
929
1109
  // ─── Step 5: Extract ──────────────────────────────────────────────────
@@ -998,20 +1178,7 @@ function printStep6(root, feature) {
998
1178
  }
999
1179
  console.log('Record all new functions/components in `.codeyam/glossary.json`.');
1000
1180
  console.log();
1001
- console.log(chalk.bold('Checklist:'));
1002
- checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
1003
- checkbox('Add an entry for each new function/component extracted in step 5');
1004
- checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
1005
- checkbox('Every non-component entry MUST have `testFile` set — hooks and functions all need tests');
1006
- console.log(chalk.yellow(' If a function/hook has no test yet, go back and write one (TDD). The audit will block you.'));
1007
- console.log();
1008
- console.log(chalk.bold('Entry format:'));
1009
- console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
1010
- console.log(chalk.dim(' "description": "Calculates total price including tax and discounts",'));
1011
- console.log(chalk.dim(' "parameters": [{ "name": "items", "type": "CartItem[]" }],'));
1012
- console.log(chalk.dim(' "returnType": "number", "tags": ["pricing"],'));
1013
- console.log(chalk.dim(' "testFile": "app/utils/pricing.test.ts",'));
1014
- console.log(chalk.dim(` "feature": "${feature}", "createdAt": "..." }`));
1181
+ printGlossaryInstructions(feature);
1015
1182
  console.log();
1016
1183
  console.log(chalk.dim('Focus on updating the glossary. Application code and scenarios come in later steps.'));
1017
1184
  stopGate(6);
@@ -1039,56 +1206,11 @@ function printStep7(root, feature) {
1039
1206
  console.log();
1040
1207
  console.log(chalk.bold('Visual Components — Component Isolation:'));
1041
1208
  checkbox('List all files with new/modified visual components from step 5');
1042
- checkbox('Create isolation route dirs: `codeyam editor isolate "ComponentA ComponentB ..."`');
1043
- console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
1044
- console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
1045
1209
  checkbox('Check existing scenarios: `codeyam editor scenarios`');
1046
1210
  console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
1047
1211
  console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
1048
1212
  console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
1049
- checkbox('For each visual component:');
1050
- console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
1051
- console.log(chalk.dim(' — Props/interface'));
1052
- console.log(chalk.dim(' — Container width in the real app (e.g. card in a 3-col grid → max-w-sm)'));
1053
- console.log(chalk.dim(' 2. Plan multiple scenarios that exercise the component like tests:'));
1054
- console.log(chalk.dim(' — Default/happy path with typical data'));
1055
- console.log(chalk.dim(' — Edge cases: empty/missing data, long text, maximum items, zero counts'));
1056
- console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
1057
- console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
1058
- console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
1059
- console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
1060
- console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
1061
- console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
1062
- console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
1063
- console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
1064
- console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block", padding:"20px" }}>'));
1065
- console.log(chalk.dim(' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
1066
- console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth:"24rem", full-width component → omit maxWidth'));
1067
- console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
1068
- console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
1069
- console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
1070
- console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
1071
- console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
1072
- console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
1073
- console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
1074
- console.log(chalk.dim(` "dimensions":["${dim}"],`));
1075
- console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
1076
- console.log(chalk.yellow(' ALWAYS include "dimensions" — use the named screen size that matches the component\'s context.'));
1077
- console.log(chalk.dim(dimNames.length > 0
1078
- ? ` Available dimensions: ${dimNames.map((n) => `"${n}"`).join(', ')}. Default: "${dim}".`
1079
- : ` Use "${dim}" for most components. Names must match a key in screenSizes from setup.`));
1080
- console.log(chalk.dim(' Names must match a key in screenSizes from setup. If you need a new size, add it first:'));
1081
- console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
1082
- console.log(chalk.dim(` -d '{"screenSizes":{...existing..., "New Name":{"width":W,"height":H}}}' (replaces all — include every size)`));
1083
- console.log(chalk.dim(' url is a PATH (starts with /) — the proxy routes it and intercepts API calls'));
1084
- console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
1085
- console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
1086
- console.log(chalk.dim(' For large JSON: use your Write tool to create .codeyam/tmp/scenario.json,'));
1087
- console.log(chalk.dim(' then: codeyam editor register @.codeyam/tmp/scenario.json'));
1088
- console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
1089
- console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
1090
- console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
1091
- console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
1213
+ printComponentCaptureInstructions();
1092
1214
  console.log();
1093
1215
  console.log(chalk.bold('Library Functions — run tests:'));
1094
1216
  checkbox('Run ALL test files created in step 5');
@@ -1143,51 +1265,11 @@ function printStep8(root, feature) {
1143
1265
  console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
1144
1266
  console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
1145
1267
  console.log();
1146
- console.log(chalk.bold('Checklist:'));
1147
- checkbox('Run `codeyam editor scenarios` review all existing scenarios');
1148
- checkbox("Create new scenarios for any pages that don't have scenarios yet");
1149
- console.log(chalk.yellow(' Each page needs its own scenarios a detail page needs different data than the catalog'));
1150
- checkbox('Re-register every existing scenario whose page was affected by this feature');
1151
- console.log(chalk.dim(' Use the SAME name to update. Enhance seed data to showcase this feature where relevant.'));
1152
- console.log(chalk.yellow(" Don't just re-register unchanged data — enrich it so the feature is thoroughly demonstrated."));
1153
- checkbox('ALWAYS include "dimensions" — pick the viewport that best demonstrates the page');
1154
- console.log(chalk.yellow(' Every app scenario MUST have a "dimensions" field referencing a named screen size from setup.'));
1155
- console.log(chalk.dim(dimNames.length > 0
1156
- ? ` Available dimensions: ${dimNames.map((n) => `"${n}"`).join(', ')}. Default: "${dim}".`
1157
- : ` Use "${dim}" for most scenarios. Names must match a key in screenSizes from setup.`));
1158
- if (dimNames.length > 1) {
1159
- const otherDim = dimNames.find((n) => n !== dim) || dimNames[1];
1160
- console.log(chalk.yellow(` Think about what fits each scenario: full pages/standalone views → "${otherDim}", popups/widgets → "${dim}".`));
1161
- }
1162
- console.log(chalk.dim(' For responsive apps, include multiple dimensions: "dimensions":["Desktop","Mobile"] captures both viewports.'));
1163
- console.log(chalk.dim(' If you need a new named size mid-workflow, add it via editor-project-info API (include ALL existing sizes — the API replaces, not merges).'));
1164
- checkbox('If the project has a database + seed adapter, use seed-based scenarios:');
1165
- console.log(chalk.dim(` codeyam editor register '{"name":"Full Catalog","type":"application","url":"/","dimensions":["${dim}"],"seed":{"products":[...],"categories":[...]}}'`));
1166
- console.log(chalk.dim(' Seed data is written to the DB via the seed adapter. Real app renders real data.'));
1167
- checkbox('IMPORTANT: Always include "url" — the page path to screenshot for this scenario');
1168
- console.log(chalk.dim(' Use "/" for home page, "/drinks/1" for detail pages, etc.'));
1169
- console.log(chalk.dim(' Without url, the screenshot captures the root page regardless of the scenario.'));
1170
- console.log(chalk.yellow(' Create separate scenarios for each page that shows different data.'));
1171
- checkbox('For large seed data, use your Write tool to create .codeyam/tmp/scenario.json, then:');
1172
- console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
1173
- console.log(chalk.yellow(' IMPORTANT: Use the Write tool — NOT cat/heredoc — to avoid permission prompts'));
1174
- checkbox('For external API mocks (Stripe, weather, etc.), add externalApis:');
1175
- console.log(chalk.dim(` "externalApis":{"GET https://api.stripe.com/v1/prices":{"body":[...],"status":200}}`));
1176
- checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
1177
- checkbox('If the app uses localStorage/sessionStorage instead of a database, use localStorage scenarios:');
1178
- console.log(chalk.dim(` codeyam editor register '{"name":"Full Library","type":"application","url":"/","dimensions":["${dim}"],"localStorage":{"articles":[...],"collections":[...]}}'`));
1179
- console.log(chalk.dim(' The proxy injects data into localStorage before the app loads. Fully interactive — the app can read and write normally.'));
1180
- console.log(chalk.dim(' For an empty state, register with an empty localStorage or omit it entirely.'));
1181
- checkbox('If no database and no localStorage, use component-style mock scenarios:');
1182
- console.log(chalk.dim(` codeyam editor register '{"name":"Empty","description":"...","url":"/","dimensions":["${dim}"],"mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
1183
- checkbox('After each registration, check the response for `clientErrors`');
1184
- console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
1185
- console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
1186
- checkbox('CRITICAL: After EACH registration, view the captured screenshot to verify it');
1187
- console.log(chalk.yellow(' Read the screenshot file path from the registration response and view it.'));
1188
- console.log(chalk.yellow(' Does the screenshot actually SHOW the data you put in? If you seeded 5 articles'));
1189
- console.log(chalk.yellow(' but the screenshot shows an empty page, the scenario is broken — fix the code or data.'));
1190
- console.log(chalk.yellow(' Do NOT create scenarios with data the app cannot yet render.'));
1268
+ checkbox('Ensure scenarios clearly demonstrate what changed in this session');
1269
+ console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
1270
+ console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
1271
+ console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
1272
+ printAppScenarioInstructions();
1191
1273
  console.log();
1192
1274
  console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 10 if needed.'));
1193
1275
  console.log();
@@ -1379,41 +1461,654 @@ function printStep13(root, feature) {
1379
1461
  if (isResuming) {
1380
1462
  printResumptionHeader(13);
1381
1463
  }
1382
- console.log('Present the results to the user and get their approval.');
1464
+ console.log('Present the results to the user and get their approval.');
1465
+ console.log();
1466
+ console.log(chalk.bold('Checklist:'));
1467
+ checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1468
+ console.log(chalk.dim(' Each scenario has a `changeStatus` field: "new", "edited", "impacted", or null.'));
1469
+ checkbox('Pick the best scenario to showcase from this working session:');
1470
+ console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
1471
+ console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
1472
+ console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
1473
+ checkbox('Switch the preview to that scenario using its `id` and `dimension`:');
1474
+ console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1475
+ console.log(chalk.dim(' Use the dimension that matches the scenario — check its registered dimensions.'));
1476
+ printDimensionGuidance(dim, dimNames);
1477
+ checkbox(`Show the results panel: \`codeyam editor show-results\``);
1478
+ console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
1479
+ console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
1480
+ checkbox('Write a 1-2 sentence summary of what was built');
1481
+ checkbox('Report test count and audit status (one line)');
1482
+ console.log();
1483
+ console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
1484
+ console.log(chalk.green(' Option 1 label: "Save & commit"') +
1485
+ chalk.dim(' — git commit all changes and record in journal'));
1486
+ console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
1487
+ chalk.dim(' — describe changes, then re-verify'));
1488
+ console.log();
1489
+ console.log(chalk.bold('If the user chooses "Save & commit":'));
1490
+ checkbox('Advance to the commit step: `codeyam editor 14`');
1491
+ console.log();
1492
+ console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
1493
+ checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
1494
+ checkbox('Ask what changes the user wants (if not already clear)');
1495
+ checkbox(`Run: \`codeyam editor change "${feature}"\` — this gives you the change checklist`);
1496
+ checkbox('THEN make the requested changes and follow the checklist');
1497
+ console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
1498
+ stopGate(13, { confirm: true });
1499
+ }
1500
+ // ─── Migration Mode ──────────────────────────────────────────────────
1501
+ /**
1502
+ * Print a progress tracker for the 8 migration steps.
1503
+ */
1504
+ function printMigrationProgressTracker(current, pageName, pageIndex, totalPages) {
1505
+ console.log();
1506
+ console.log(chalk.dim(` Migration: Page ${pageIndex + 1}/${totalPages} (${pageName})`));
1507
+ console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
1508
+ for (let i = 1; i <= 10; i++) {
1509
+ const label = MIGRATION_STEP_LABELS[i];
1510
+ const num = i < 10 ? ` ${i}` : `${i}`;
1511
+ const content = `${num}. ${label.padEnd(28)}`;
1512
+ if (i < current) {
1513
+ console.log(chalk.dim(' │') + chalk.green(` ✓ ${content}`) + chalk.dim('│'));
1514
+ }
1515
+ else if (i === current) {
1516
+ console.log(chalk.dim(' │') + chalk.bold.cyan(` → ${content}`) + chalk.dim('│'));
1517
+ }
1518
+ else {
1519
+ console.log(chalk.dim(` │ ○ ${content}│`));
1520
+ }
1521
+ }
1522
+ console.log(chalk.dim(' └─────────────────────────────────────┘'));
1523
+ }
1524
+ /**
1525
+ * Print a STOP gate for migration steps.
1526
+ */
1527
+ function migrationStopGate(current, pageName, pageIndex, totalPages, opts) {
1528
+ console.log();
1529
+ console.log(chalk.bold.red('━━━ STOP ━━━'));
1530
+ console.log();
1531
+ console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
1532
+ if (opts?.confirm) {
1533
+ console.log();
1534
+ console.log(chalk.yellow('Wait for user confirmation before moving on.'));
1535
+ }
1536
+ console.log();
1537
+ console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
1538
+ printMigrationProgressTracker(current, pageName, pageIndex, totalPages);
1539
+ console.log();
1540
+ console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
1541
+ console.log();
1542
+ if (current === 5) {
1543
+ // Step 5 (Discuss) can skip to step 9 if user declines decomposition
1544
+ console.log(chalk.green('If decomposing, run: ') +
1545
+ chalk.bold('codeyam editor migrate 6'));
1546
+ console.log(chalk.green('If skipping decomposition, run: ') +
1547
+ chalk.bold('codeyam editor migrate 9'));
1548
+ }
1549
+ else if (current < 10) {
1550
+ console.log(chalk.green('When done, run: ') +
1551
+ chalk.bold(`codeyam editor migrate ${current + 1}`));
1552
+ }
1553
+ else {
1554
+ console.log(chalk.green('Page complete! Run: ') +
1555
+ chalk.bold('codeyam editor migrate next') +
1556
+ chalk.green(' to start the next page'));
1557
+ }
1558
+ console.log();
1559
+ }
1560
+ /**
1561
+ * Write migration-aware editor state.
1562
+ */
1563
+ function writeMigrationStepState(root, step, pageName, pageIndex, totalPages) {
1564
+ const prevState = readState(root);
1565
+ const isResuming = prevState?.step === step && !!prevState?.migration;
1566
+ const now = new Date().toISOString();
1567
+ writeState(root, {
1568
+ feature: `Migration: ${pageName}`,
1569
+ step,
1570
+ label: MIGRATION_STEP_LABELS[step],
1571
+ startedAt: isResuming ? prevState.startedAt : now,
1572
+ featureStartedAt: prevState?.featureStartedAt || now,
1573
+ migration: {
1574
+ pageName,
1575
+ pageIndex,
1576
+ totalPages,
1577
+ },
1578
+ });
1579
+ logEvent(root, 'step', {
1580
+ step,
1581
+ label: MIGRATION_STEP_LABELS[step],
1582
+ feature: `Migration: ${pageName}`,
1583
+ migration: true,
1584
+ });
1585
+ }
1586
+ /**
1587
+ * Get the current migration page info from state, or return null.
1588
+ */
1589
+ function getCurrentMigrationPage(root) {
1590
+ const migState = readMigrationState(root);
1591
+ if (!migState || migState.pages.length === 0)
1592
+ return null;
1593
+ const page = migState.pages[migState.currentPageIndex];
1594
+ if (!page)
1595
+ return null;
1596
+ return {
1597
+ pageName: page.name,
1598
+ pageIndex: migState.currentPageIndex,
1599
+ totalPages: migState.pages.length,
1600
+ };
1601
+ }
1602
+ // ─── Migration Step 1: Survey ───────────────────────────────────────
1603
+ function printMigrateStep1(root) {
1604
+ const pageInfo = getCurrentMigrationPage(root);
1605
+ if (!pageInfo) {
1606
+ console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
1607
+ process.exit(1);
1608
+ }
1609
+ const { pageName, pageIndex, totalPages } = pageInfo;
1610
+ writeMigrationStepState(root, 1, pageName, pageIndex, totalPages);
1611
+ const migState = readMigrationState(root);
1612
+ const page = migState.pages[pageIndex];
1613
+ console.log();
1614
+ console.log(chalk.bold.cyan(`━━━ Migration Step 1: Survey — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1615
+ console.log();
1616
+ console.log(chalk.bold('Goal: Survey the page, understand its structure, and start the dev server.'));
1617
+ console.log();
1618
+ console.log(chalk.bold('Survey:'));
1619
+ checkbox(`Read the page file: \`${page.filePath}\``);
1620
+ checkbox('Follow every import — read all local components, hooks, utilities used by this page');
1621
+ checkbox('Map the data flow: where does data come from? Server actions? API routes? Props?');
1622
+ checkbox('Check `.codeyam/glossary.json` for components already extracted from previous pages');
1623
+ checkbox('Check `.codeyam/migration-state.json` sharedComponents for reusable pieces');
1624
+ checkbox('Note the page route and any dynamic segments');
1625
+ console.log();
1626
+ console.log(chalk.bold('Dev Server:'));
1627
+ checkbox('If the dev server is not running yet, figure out how to start it (check package.json scripts)');
1628
+ checkbox('FIRST: Verify dependencies are installed (check if node_modules/ exists and is non-empty)');
1629
+ console.log(chalk.yellow(' If node_modules is missing or sparse, run the package manager install command first'));
1630
+ console.log(chalk.dim(' Missing deps cause cryptic 500 errors at render time — install BEFORE starting the server'));
1631
+ checkbox('Start the dev server: `codeyam editor dev-server \'{"action":"start"}\'`');
1632
+ checkbox("Verify it's running: check for successful startup output");
1633
+ console.log();
1634
+ migrationStopGate(1, pageName, pageIndex, totalPages);
1635
+ }
1636
+ // ─── Migration Step 2: App Scenarios ────────────────────────────────
1637
+ function printMigrateStep2(root) {
1638
+ const pageInfo = getCurrentMigrationPage(root);
1639
+ if (!pageInfo) {
1640
+ console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
1641
+ process.exit(1);
1642
+ }
1643
+ const { pageName, pageIndex, totalPages } = pageInfo;
1644
+ writeMigrationStepState(root, 2, pageName, pageIndex, totalPages);
1645
+ const migState = readMigrationState(root);
1646
+ const page = migState.pages[pageIndex];
1647
+ console.log();
1648
+ console.log(chalk.bold.cyan(`━━━ Migration Step 2: App Scenarios — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1649
+ console.log();
1650
+ console.log(chalk.bold(`Goal: Register application scenarios that screenshot the ACTUAL page at "${page.route}".`));
1651
+ console.log();
1652
+ console.log(chalk.yellow(' App scenarios capture how the full page looks with different data states.'));
1653
+ console.log(chalk.yellow(` Every scenario MUST use the real page route ("${page.route}") — NOT isolation routes.`));
1654
+ console.log(chalk.yellow(' These are the primary scenarios the user will see for this page.'));
1655
+ console.log();
1656
+ console.log(chalk.bold('Seed Adapter Setup:'));
1657
+ checkbox('Check if a seed adapter exists: `ls .codeyam/seed-adapter.ts 2>/dev/null`');
1658
+ console.log(chalk.yellow(' If NO seed adapter exists, create one. The seed adapter wipes and re-seeds the database'));
1659
+ console.log(chalk.yellow(' for each scenario, enabling different data states on the real page.'));
1660
+ console.log();
1661
+ console.log(chalk.dim(' How to create a seed adapter:'));
1662
+ console.log(chalk.dim(' 1. Identify the database technology from your step 1 survey (Prisma, Supabase, Drizzle, etc.)'));
1663
+ console.log(chalk.dim(' 2. Check for a matching template: `ls .codeyam/seed-adapters/`'));
1664
+ console.log(chalk.dim(' 3. For Supabase: `cp .codeyam/seed-adapters/supabase.ts .codeyam/seed-adapter.ts`'));
1665
+ console.log(chalk.dim(' The seed adapter auto-detects Supabase credentials by scanning env var VALUES (not names).'));
1666
+ console.log(chalk.dim(' It reads .env / .env.local and finds: *.supabase.co URLs, sb_secret_* keys, and legacy JWTs.'));
1667
+ console.log(chalk.dim(' Do NOT grep for specific env var names — just register a scenario and try it.'));
1668
+ console.log(chalk.dim(' If it fails, the error message will say exactly what credentials are missing.'));
1669
+ console.log(chalk.dim(' 4. For Prisma: write a seed adapter that uses your Prisma client to delete/insert rows'));
1670
+ console.log(chalk.dim(' 5. For other databases: write a .codeyam/seed-adapter.ts that reads a JSON file (argv[2]),'));
1671
+ console.log(chalk.dim(' deletes all rows from each table, then inserts the seed rows. Format: {"table": [{...}]}'));
1672
+ console.log(chalk.dim(' 6. Test it: write a small test seed JSON and run `npx tsx .codeyam/seed-adapter.ts test.json`'));
1673
+ console.log();
1674
+ printAppScenarioInstructions(pageName, page.route);
1675
+ console.log();
1676
+ migrationStopGate(2, pageName, pageIndex, totalPages);
1677
+ }
1678
+ // ─── Migration Step 3: Component Scenarios ──────────────────────────
1679
+ function printMigrateStep3(root) {
1680
+ const pageInfo = getCurrentMigrationPage(root);
1681
+ if (!pageInfo) {
1682
+ console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
1683
+ process.exit(1);
1684
+ }
1685
+ const { pageName, pageIndex, totalPages } = pageInfo;
1686
+ writeMigrationStepState(root, 3, pageName, pageIndex, totalPages);
1687
+ console.log();
1688
+ console.log(chalk.bold.cyan(`━━━ Migration Step 3: Component Scenarios — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1689
+ console.log();
1690
+ console.log(chalk.bold('Goal: Capture individual component scenarios for major visual components on this page.'));
1691
+ console.log();
1692
+ console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
1693
+ console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
1694
+ console.log();
1695
+ printComponentCaptureInstructions();
1696
+ console.log();
1697
+ migrationStopGate(3, pageName, pageIndex, totalPages);
1698
+ }
1699
+ // ─── Migration Step 4: Preview ──────────────────────────────────────
1700
+ function printMigrateStep4(root) {
1701
+ const pageInfo = getCurrentMigrationPage(root);
1702
+ if (!pageInfo) {
1703
+ console.error(chalk.red('Error: No migration in progress.'));
1704
+ process.exit(1);
1705
+ }
1706
+ const { pageName, pageIndex, totalPages } = pageInfo;
1707
+ writeMigrationStepState(root, 4, pageName, pageIndex, totalPages);
1708
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
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('Fetch all scenarios: `codeyam editor scenarios`');
1719
+ checkbox('Pick the best scenario to showcase:');
1720
+ console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1721
+ printDimensionGuidance(dim, dimNames);
1722
+ checkbox('Show results: `codeyam editor show-results`');
1723
+ console.log();
1724
+ console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
1725
+ migrationStopGate(4, pageName, pageIndex, totalPages);
1726
+ }
1727
+ // ─── Migration Step 5: Discuss ──────────────────────────────────────
1728
+ function printMigrateStep5(root) {
1729
+ const pageInfo = getCurrentMigrationPage(root);
1730
+ if (!pageInfo) {
1731
+ console.error(chalk.red('Error: No migration in progress.'));
1732
+ process.exit(1);
1733
+ }
1734
+ const { pageName, pageIndex, totalPages } = pageInfo;
1735
+ writeMigrationStepState(root, 5, pageName, pageIndex, totalPages);
1736
+ console.log();
1737
+ console.log(chalk.bold.cyan(`━━━ Migration Step 5: Discuss — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1738
+ console.log();
1739
+ console.log(chalk.bold('Goal: Assess page complexity and ask the user if decomposition is worth it.'));
1740
+ console.log();
1741
+ console.log(chalk.bold('Checklist:'));
1742
+ checkbox('Hide results: `codeyam editor hide-results`');
1743
+ checkbox("Assess the page's complexity:");
1744
+ console.log(chalk.dim(' — How many inline components exist?'));
1745
+ console.log(chalk.dim(' — How much business logic is embedded in the page file?'));
1746
+ console.log(chalk.dim(' — Are there reusable pieces that other pages could share?'));
1747
+ console.log(chalk.dim(' — Would extraction improve testability or maintainability?'));
1748
+ checkbox('Present your assessment to the user');
1749
+ console.log();
1750
+ console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
1751
+ console.log(chalk.green(' Option 1 label: "Yes, decompose this page"') +
1752
+ chalk.dim(' — continue to step 6 (Decompose)'));
1753
+ console.log(chalk.yellow(' Option 2 label: "No, skip decomposition"') +
1754
+ chalk.dim(' — skip to step 9 (Journal)'));
1755
+ console.log();
1756
+ console.log(chalk.bold.yellow('Routing:'));
1757
+ console.log(chalk.yellow(' If user chooses decomposition: ') +
1758
+ chalk.bold('codeyam editor migrate 6'));
1759
+ console.log(chalk.yellow(' If user skips: ') + chalk.bold('codeyam editor migrate 9'));
1760
+ migrationStopGate(5, pageName, pageIndex, totalPages, { confirm: true });
1761
+ }
1762
+ // ─── Migration Step 6: Decompose ────────────────────────────────────
1763
+ function printMigrateStep6(root) {
1764
+ const pageInfo = getCurrentMigrationPage(root);
1765
+ if (!pageInfo) {
1766
+ console.error(chalk.red('Error: No migration in progress.'));
1767
+ process.exit(1);
1768
+ }
1769
+ const { pageName, pageIndex, totalPages } = pageInfo;
1770
+ writeMigrationStepState(root, 6, pageName, pageIndex, totalPages);
1771
+ console.log();
1772
+ console.log(chalk.bold.cyan(`━━━ Migration Step 6: Decompose — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1773
+ console.log();
1774
+ console.log(chalk.bold('Goal: Plan all extractions. Mark already-extracted shared components as REUSE.'));
1775
+ console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
1776
+ console.log();
1777
+ console.log(chalk.bold.yellow('Migration note: check glossary for reuse opportunities'));
1778
+ console.log(chalk.yellow(' If a component exists in the glossary from a previous page, mark it as REUSE in your plan.'));
1779
+ console.log(chalk.yellow(' Only add it to the extraction plan if it needs modifications for this page.'));
1780
+ console.log();
1781
+ printExtractionPlanInstructions();
1782
+ migrationStopGate(6, pageName, pageIndex, totalPages);
1783
+ }
1784
+ // ─── Migration Step 7: Extract ──────────────────────────────────────
1785
+ function printMigrateStep7(root) {
1786
+ const pageInfo = getCurrentMigrationPage(root);
1787
+ if (!pageInfo) {
1788
+ console.error(chalk.red('Error: No migration in progress.'));
1789
+ process.exit(1);
1790
+ }
1791
+ const { pageName, pageIndex, totalPages } = pageInfo;
1792
+ writeMigrationStepState(root, 7, pageName, pageIndex, totalPages);
1793
+ console.log();
1794
+ console.log(chalk.bold.cyan(`━━━ Migration Step 7: Extract — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1795
+ console.log();
1796
+ console.log(chalk.bold('Goal: Execute the extraction plan. Components first, then functions via TDD.'));
1797
+ console.log();
1798
+ console.log(chalk.bold('Checklist:'));
1799
+ checkbox('Extract visual components (no tests needed for components)');
1800
+ checkbox('Extract library functions via TDD (write failing test first, then implement)');
1801
+ checkbox('Extract hooks as needed');
1802
+ checkbox('Recursive pass: check each extracted component for further extraction');
1803
+ console.log(chalk.yellow(' Each component should be a thin shell composing sub-components.'));
1804
+ checkbox('Verify the page file is ONLY imports + component composition');
1805
+ checkbox('Run tests to confirm nothing is broken: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
1806
+ console.log();
1807
+ console.log(chalk.dim('After extraction, the page should be a thin shell. Move to step 8 to recapture.'));
1808
+ migrationStopGate(7, pageName, pageIndex, totalPages);
1809
+ }
1810
+ // ─── Migration Step 8: Recapture ────────────────────────────────────
1811
+ function printMigrateStep8(root) {
1812
+ const pageInfo = getCurrentMigrationPage(root);
1813
+ if (!pageInfo) {
1814
+ console.error(chalk.red('Error: No migration in progress.'));
1815
+ process.exit(1);
1816
+ }
1817
+ const { pageName, pageIndex, totalPages } = pageInfo;
1818
+ writeMigrationStepState(root, 8, pageName, pageIndex, totalPages);
1819
+ const migState = readMigrationState(root);
1820
+ const page = migState.pages[pageIndex];
1821
+ console.log();
1822
+ console.log(chalk.bold.cyan(`━━━ Migration Step 8: Recapture — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1823
+ console.log();
1824
+ console.log(chalk.bold('Goal: Update glossary, re-register ALL scenarios after code changes, and verify.'));
1825
+ console.log();
1826
+ console.log(chalk.bold('Glossary:'));
1827
+ printGlossaryInstructions();
1828
+ console.log(chalk.yellow(' Skip entries already in the glossary from previous page migrations.'));
1829
+ checkbox('Update sharedComponents in `.codeyam/migration-state.json` if any extracted components are shared');
1830
+ console.log();
1831
+ console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
1832
+ console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
1833
+ checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
1834
+ checkbox('Find all scenarios that use the real page route (not /codeyam-isolate/ paths)');
1835
+ checkbox('Re-register each one with the SAME name to update its screenshot');
1836
+ console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
1837
+ checkbox('After each registration, check the response for `clientErrors`');
1838
+ console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
1839
+ console.log();
1840
+ console.log(chalk.bold('Component Scenarios:'));
1841
+ console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
1842
+ printComponentCaptureInstructions();
1843
+ console.log();
1844
+ console.log(chalk.bold('Verify:'));
1845
+ checkbox('Run `codeyam editor analyze-imports` to populate import graph');
1846
+ checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
1847
+ checkbox('Run `codeyam editor audit` to check completeness');
1848
+ checkbox('Check for client errors: `codeyam editor client-errors`');
1849
+ checkbox('Fix any issues before proceeding');
1850
+ console.log();
1851
+ migrationStopGate(8, pageName, pageIndex, totalPages);
1852
+ }
1853
+ // ─── Migration Step 9: Journal ──────────────────────────────────────
1854
+ function printMigrateStep9(root) {
1855
+ const pageInfo = getCurrentMigrationPage(root);
1856
+ if (!pageInfo) {
1857
+ console.error(chalk.red('Error: No migration in progress.'));
1858
+ process.exit(1);
1859
+ }
1860
+ const { pageName, pageIndex, totalPages } = pageInfo;
1861
+ writeMigrationStepState(root, 9, pageName, pageIndex, totalPages);
1862
+ console.log();
1863
+ console.log(chalk.bold.cyan(`━━━ Migration Step 9: Journal — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1864
+ console.log();
1865
+ console.log(`Create a journal entry documenting the migration of the ${pageName} page.`);
1866
+ console.log();
1867
+ console.log(chalk.bold('Checklist:'));
1868
+ checkbox('Write a concise description of what was migrated (2-3 sentences)');
1869
+ checkbox(`Create journal: \`codeyam editor journal '{"title":"Migration: ${pageName}","type":"migration","description":"...","includeSessionScenarios":true}'\``);
1870
+ console.log();
1871
+ migrationStopGate(9, pageName, pageIndex, totalPages);
1872
+ }
1873
+ // ─── Migration Step 10: Present ─────────────────────────────────────
1874
+ function printMigrateStep10(root) {
1875
+ const pageInfo = getCurrentMigrationPage(root);
1876
+ if (!pageInfo) {
1877
+ console.error(chalk.red('Error: No migration in progress.'));
1878
+ process.exit(1);
1879
+ }
1880
+ const { pageName, pageIndex, totalPages } = pageInfo;
1881
+ writeMigrationStepState(root, 10, pageName, pageIndex, totalPages);
1882
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1883
+ console.log();
1884
+ console.log(chalk.bold.cyan(`━━━ Migration Step 10: Present — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1885
+ console.log();
1886
+ console.log("Show results and commit this page's migration.");
1383
1887
  console.log();
1384
1888
  console.log(chalk.bold('Checklist:'));
1385
1889
  checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1386
- console.log(chalk.dim(' Each scenario has a `changeStatus` field: "new", "edited", "impacted", or null.'));
1387
- checkbox('Pick the best scenario to showcase from this working session:');
1388
- console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
1389
- console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
1390
- console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
1391
- checkbox('Switch the preview to that scenario using its `id` and `dimension`:');
1890
+ checkbox('Pick the best scenario to showcase:');
1392
1891
  console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1393
- console.log(chalk.dim(' Use the dimension that matches the scenario — check its registered dimensions.'));
1394
1892
  printDimensionGuidance(dim, dimNames);
1395
- checkbox(`Show the results panel: \`codeyam editor show-results\``);
1396
- console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
1397
- console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
1398
- checkbox('Write a 1-2 sentence summary of what was built');
1399
- checkbox('Report test count and audit status (one line)');
1893
+ checkbox('Show results: `codeyam editor show-results`');
1894
+ checkbox('Write a 1-2 sentence summary of what was migrated');
1400
1895
  console.log();
1401
- console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
1896
+ console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
1402
1897
  console.log(chalk.green(' Option 1 label: "Save & commit"') +
1403
- chalk.dim('git commit all changes and record in journal'));
1898
+ chalk.dim(" — commit this page's migration"));
1404
1899
  console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
1405
- chalk.dim(' — describe changes, then re-verify'));
1900
+ chalk.dim(' — make changes, then re-verify'));
1406
1901
  console.log();
1407
1902
  console.log(chalk.bold('If the user chooses "Save & commit":'));
1408
- checkbox('Advance to the commit step: `codeyam editor 14`');
1903
+ checkbox('Hide results: `codeyam editor hide-results`');
1904
+ checkbox(`Commit: \`codeyam editor commit '{"message":"migration: ${pageName} page\\n\\n<description>"}'\``);
1905
+ checkbox('Mark page complete: `codeyam editor migrate complete`');
1906
+ const remaining = pageInfo.totalPages - pageIndex - 1;
1907
+ if (remaining > 0) {
1908
+ checkbox('Advance to next page: `codeyam editor migrate next`');
1909
+ console.log();
1910
+ console.log(chalk.bold.green(`Page ${pageIndex + 1}/${totalPages} complete! ${remaining} page(s) remaining.`));
1911
+ }
1912
+ else {
1913
+ console.log();
1914
+ console.log(chalk.bold.green('Migration complete! All pages processed.'));
1915
+ console.log(chalk.green('The project is now fully migrated. Future sessions use the normal feature workflow.'));
1916
+ console.log(chalk.green('Start building: ') + chalk.bold('codeyam editor steps'));
1917
+ }
1409
1918
  console.log();
1410
- console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
1411
- checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
1412
- checkbox('Ask what changes the user wants (if not already clear)');
1413
- checkbox(`Run: \`codeyam editor change "${feature}"\` — this gives you the change checklist`);
1414
- checkbox('THEN make the requested changes and follow the checklist');
1415
- console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
1416
- stopGate(13, { confirm: true });
1919
+ console.log(chalk.bold('If the user chooses "Make changes":'));
1920
+ checkbox('Hide results: `codeyam editor hide-results`');
1921
+ checkbox('Ask what changes the user wants');
1922
+ checkbox(`Run: \`codeyam editor change "Migration: ${pageName}"\` — follow the change checklist`);
1923
+ console.log();
1924
+ migrationStopGate(10, pageName, pageIndex, totalPages, { confirm: true });
1925
+ }
1926
+ // ─── Migration Survey ───────────────────────────────────────────────
1927
+ function printMigrateSurvey(root) {
1928
+ console.log();
1929
+ console.log(chalk.bold.cyan('━━━ Project Migration Survey ━━━'));
1930
+ console.log();
1931
+ console.log('Survey the existing project, present a migration plan, and confirm the order with the user.');
1932
+ console.log(chalk.dim('See the "Migration Survey" section in SKILL.md for the full checklist and migration-state.json format.'));
1933
+ console.log();
1934
+ // Write editor state so hooks and printCycleOverview detect migration
1935
+ const now = new Date().toISOString();
1936
+ writeState(root, {
1937
+ feature: 'Migration: Survey',
1938
+ step: 0,
1939
+ label: 'Survey',
1940
+ startedAt: now,
1941
+ featureStartedAt: now,
1942
+ migration: {
1943
+ pageName: 'Survey',
1944
+ pageIndex: -1,
1945
+ totalPages: 0,
1946
+ },
1947
+ });
1948
+ logEvent(root, 'migration-survey', {});
1949
+ console.log(chalk.bold.red('━━━ STOP ━━━'));
1950
+ console.log();
1951
+ console.log(chalk.red('Survey the project and confirm the migration order with the user.'));
1952
+ console.log(chalk.green('After confirmation and writing migration-state.json: ') +
1953
+ chalk.bold('codeyam editor migrate 1'));
1954
+ console.log();
1955
+ }
1956
+ // ─── Migration Status ───────────────────────────────────────────────
1957
+ function printMigrationStatus(root) {
1958
+ const state = readMigrationState(root);
1959
+ if (!state) {
1960
+ console.log(chalk.dim('No migration in progress.'));
1961
+ return;
1962
+ }
1963
+ console.log();
1964
+ console.log(chalk.bold.cyan('━━━ Migration Progress ━━━'));
1965
+ console.log();
1966
+ const done = state.pages.filter((p) => p.status === 'complete').length;
1967
+ const total = state.pages.length;
1968
+ const pct = total > 0 ? Math.round((done / total) * 100) : 0;
1969
+ console.log(` Status: ${chalk.bold(state.status)} — ${done}/${total} pages (${pct}%)`);
1970
+ console.log();
1971
+ for (let i = 0; i < state.pages.length; i++) {
1972
+ const p = state.pages[i];
1973
+ let icon;
1974
+ let color;
1975
+ if (p.status === 'complete') {
1976
+ icon = '✓';
1977
+ color = chalk.green;
1978
+ }
1979
+ else if (p.status === 'in-progress') {
1980
+ icon = '→';
1981
+ color = chalk.bold.cyan;
1982
+ }
1983
+ else {
1984
+ icon = '○';
1985
+ color = chalk.dim;
1986
+ }
1987
+ const extra = p.status === 'complete'
1988
+ ? chalk.dim(` (${p.extractedComponents.length} components, ${p.extractedFunctions.length} functions, ${p.scenarioCount} scenarios)`)
1989
+ : '';
1990
+ console.log(` ${color(`${icon} ${i + 1}. ${p.name}`)} ${chalk.dim(`(${p.route})`)}${extra}`);
1991
+ }
1992
+ if (state.sharedComponents.length > 0) {
1993
+ console.log();
1994
+ console.log(chalk.bold('Shared Components:'));
1995
+ for (const sc of state.sharedComponents) {
1996
+ console.log(` ${chalk.cyan(sc.name)} ${chalk.dim(`— from ${sc.extractedFromPage}, used in: ${sc.usedInPages.join(', ')}`)}`);
1997
+ }
1998
+ }
1999
+ console.log();
2000
+ }
2001
+ // ─── Migration Command Dispatcher ───────────────────────────────────
2002
+ function handleMigrateCommand(root, subArg) {
2003
+ // No subcommand or explicit empty → run initial survey
2004
+ if (!subArg) {
2005
+ const migState = readMigrationState(root);
2006
+ if (migState?.status === 'in-progress') {
2007
+ // Already in progress — show status and resume hint
2008
+ printMigrationStatus(root);
2009
+ const page = migState.pages[migState.currentPageIndex];
2010
+ if (page) {
2011
+ const editorState = readState(root);
2012
+ const currentStep = editorState?.migration ? editorState.step : 1;
2013
+ console.log(chalk.green('Continue with: ') +
2014
+ chalk.bold(`codeyam editor migrate ${currentStep}`));
2015
+ console.log();
2016
+ }
2017
+ return;
2018
+ }
2019
+ if (migState?.status === 'complete') {
2020
+ console.log(chalk.green('Migration is complete! Use the normal feature workflow: `codeyam editor steps`'));
2021
+ return;
2022
+ }
2023
+ printMigrateSurvey(root);
2024
+ return;
2025
+ }
2026
+ // Parse subcommand
2027
+ if (subArg === 'next') {
2028
+ const result = advanceToNextPage(root);
2029
+ if (!result) {
2030
+ const migState = readMigrationState(root);
2031
+ if (migState?.status === 'complete') {
2032
+ console.log();
2033
+ console.log(chalk.bold.green('🎉 Migration complete!'));
2034
+ console.log();
2035
+ const done = migState.pages.filter((p) => p.status === 'complete');
2036
+ const totalComponents = done.reduce((sum, p) => sum + p.extractedComponents.length, 0);
2037
+ const totalFunctions = done.reduce((sum, p) => sum + p.extractedFunctions.length, 0);
2038
+ const totalScenarios = done.reduce((sum, p) => sum + p.scenarioCount, 0);
2039
+ console.log(` Pages migrated: ${done.length}`);
2040
+ console.log(` Components extracted: ${totalComponents}`);
2041
+ console.log(` Functions extracted: ${totalFunctions}`);
2042
+ console.log(` Scenarios created: ${totalScenarios}`);
2043
+ console.log(` Shared components: ${migState.sharedComponents.length}`);
2044
+ console.log();
2045
+ console.log(chalk.green('The project is fully migrated. Start building features: `codeyam editor steps`'));
2046
+ console.log();
2047
+ }
2048
+ else {
2049
+ console.log(chalk.red('No pending pages found. Run `codeyam editor migrate status` to check progress.'));
2050
+ }
2051
+ return;
2052
+ }
2053
+ // Start M1 for the next page
2054
+ printMigrateStep1(root);
2055
+ return;
2056
+ }
2057
+ if (subArg === 'status') {
2058
+ printMigrationStatus(root);
2059
+ return;
2060
+ }
2061
+ if (subArg === 'complete') {
2062
+ const migState = readMigrationState(root);
2063
+ if (!migState) {
2064
+ console.error(chalk.red('Error: No migration in progress.'));
2065
+ process.exit(1);
2066
+ }
2067
+ const page = migState.pages[migState.currentPageIndex];
2068
+ if (!page) {
2069
+ console.error(chalk.red('Error: No active migration page.'));
2070
+ process.exit(1);
2071
+ }
2072
+ completePage(root, {
2073
+ extractedComponents: page.extractedComponents,
2074
+ extractedFunctions: page.extractedFunctions,
2075
+ scenarioCount: page.scenarioCount,
2076
+ });
2077
+ console.log(chalk.green(`Page "${page.name}" marked complete.`));
2078
+ return;
2079
+ }
2080
+ // Numeric step: 1-8
2081
+ const step = parseInt(subArg, 10);
2082
+ if (isNaN(step) || step < 1 || step > 10) {
2083
+ console.error(chalk.red(`Error: Invalid migration step "${subArg}". Must be 1-10, "next", "complete", or "status".`));
2084
+ process.exit(1);
2085
+ }
2086
+ // Ensure migration is active and first page started
2087
+ const migState = readMigrationState(root);
2088
+ if (!migState) {
2089
+ console.error(chalk.red('Error: No migration state. Run `codeyam editor migrate` first.'));
2090
+ process.exit(1);
2091
+ }
2092
+ // If still in survey state and step 1 requested, start first page
2093
+ if (migState.status === 'surveyed' && step === 1) {
2094
+ migState.status = 'in-progress';
2095
+ migState.pages[0].status = 'in-progress';
2096
+ migState.pages[0].startedAt = new Date().toISOString();
2097
+ writeMigrationState(root, migState);
2098
+ }
2099
+ const stepFns = {
2100
+ 1: printMigrateStep1,
2101
+ 2: printMigrateStep2,
2102
+ 3: printMigrateStep3,
2103
+ 4: printMigrateStep4,
2104
+ 5: printMigrateStep5,
2105
+ 6: printMigrateStep6,
2106
+ 7: printMigrateStep7,
2107
+ 8: printMigrateStep8,
2108
+ 9: printMigrateStep9,
2109
+ 10: printMigrateStep10,
2110
+ };
2111
+ stepFns[step](root);
1417
2112
  }
1418
2113
  // ─── Step 14: Commit ─────────────────────────────────────────────────
1419
2114
  function printStep14(root, feature) {
@@ -1521,26 +2216,54 @@ async function handleAnalyzeImports(options = {}) {
1521
2216
  }
1522
2217
  let glossaryEntries;
1523
2218
  try {
1524
- glossaryEntries = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
2219
+ const parsed = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
2220
+ glossaryEntries = sanitizeGlossaryEntries(parsed);
1525
2221
  }
1526
2222
  catch {
1527
2223
  console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
1528
2224
  process.exit(1);
1529
2225
  }
1530
- if (!Array.isArray(glossaryEntries) || glossaryEntries.length === 0) {
2226
+ if (glossaryEntries.length === 0) {
1531
2227
  console.error(chalk.red('Error: glossary.json is empty.'));
1532
2228
  process.exit(1);
1533
2229
  }
1534
2230
  const filePaths = glossaryEntries.map((e) => e.filePath);
1535
2231
  // Include page files so pages get analyzed as entities too.
1536
2232
  // This allows the import graph to map page names to their dependencies.
2233
+ // Use allFiles (not map values) to include ALL pages — multiple routes
2234
+ // can share a page name (e.g., /feedback/[id] and /feedback/new both
2235
+ // map to "Feedback") but each needs its own entity analysis.
1537
2236
  const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
1538
- const pageFilePaths = scanPageFilePaths(root);
1539
- for (const pageFile of Object.values(pageFilePaths)) {
2237
+ const { allFiles: allPageFiles } = scanPageFilePaths(root);
2238
+ for (const pageFile of allPageFiles) {
1540
2239
  if (!filePaths.includes(pageFile)) {
1541
2240
  filePaths.push(pageFile);
1542
2241
  }
1543
2242
  }
2243
+ // Include file paths from editor scenarios (both component_path and
2244
+ // page_file_path) so that all components and pages with scenarios get
2245
+ // entities — critical for non-Next.js apps where scanPageFilePaths
2246
+ // doesn't find page.tsx files.
2247
+ try {
2248
+ const { getDatabase } = await import('../../../packages/database/index.js');
2249
+ const db = getDatabase();
2250
+ const scenarioFiles = await db
2251
+ .selectFrom('editor_scenarios')
2252
+ .select(['component_path', 'page_file_path'])
2253
+ .distinct()
2254
+ .execute();
2255
+ for (const row of scenarioFiles) {
2256
+ const r = row;
2257
+ for (const fp of [r.component_path, r.page_file_path]) {
2258
+ if (fp && !filePaths.includes(fp)) {
2259
+ filePaths.push(fp);
2260
+ }
2261
+ }
2262
+ }
2263
+ }
2264
+ catch {
2265
+ // Non-fatal — scenario file paths just won't be included
2266
+ }
1544
2267
  const progress = new ProgressReporter();
1545
2268
  // Run data-structure-only analysis for all entities (glossary + pages)
1546
2269
  // Don't pass entityNames — entities may not exist yet (fresh clone).
@@ -1630,6 +2353,24 @@ async function handleAnalyzeImports(options = {}) {
1630
2353
  });
1631
2354
  console.log(summary || JSON.stringify({ imports, entities: entityData }));
1632
2355
  }
2356
+ // Backfill entity_sha on scenarios registered before entities existed.
2357
+ // This fixes the "missing data" UI state when scenarios were registered
2358
+ // while analyze-imports was still running in the background.
2359
+ try {
2360
+ const { getDatabase } = await import('../../../packages/database/index.js');
2361
+ const db = getDatabase();
2362
+ const backfillResult = await backfillEntityShaOnScenarios(db, latestEntities.map((e) => ({
2363
+ sha: e.sha,
2364
+ name: e.name,
2365
+ filePath: e.filePath || '',
2366
+ })));
2367
+ if (backfillResult.updated > 0 && !options.silent) {
2368
+ console.log(chalk.green(`Linked ${backfillResult.updated} scenario(s) to their entities.`));
2369
+ }
2370
+ }
2371
+ catch {
2372
+ /* non-fatal */
2373
+ }
1633
2374
  }
1634
2375
  // ─── Validate-seed subcommand ─────────────────────────────────────────
1635
2376
  /**
@@ -1668,6 +2409,24 @@ function handleValidateSeed(jsonArg) {
1668
2409
  : 0;
1669
2410
  console.log(chalk.dim(` ${table}: ${rows} row(s)`));
1670
2411
  }
2412
+ // Check seed keys against Prisma schema if available
2413
+ const schemaPath = path.join(root, 'prisma', 'schema.prisma');
2414
+ try {
2415
+ if (fs.existsSync(schemaPath)) {
2416
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
2417
+ const warnings = validateSeedKeysAgainstPrisma(tables, schemaContent);
2418
+ if (warnings.length > 0) {
2419
+ console.log();
2420
+ console.log(chalk.yellow('Prisma model name warnings:'));
2421
+ for (const w of warnings) {
2422
+ console.log(chalk.yellow(` ⚠ ${w}`));
2423
+ }
2424
+ }
2425
+ }
2426
+ }
2427
+ catch {
2428
+ // Schema not readable — skip Prisma validation
2429
+ }
1671
2430
  }
1672
2431
  else {
1673
2432
  console.error(chalk.red('Seed data validation failed:'));
@@ -1677,6 +2436,27 @@ function handleValidateSeed(jsonArg) {
1677
2436
  process.exit(1);
1678
2437
  }
1679
2438
  }
2439
+ // ─── Delete subcommand ────────────────────────────────────────────────
2440
+ /**
2441
+ * `codeyam editor delete <scenarioId>`
2442
+ *
2443
+ * Delete a scenario by ID via the running editor server.
2444
+ */
2445
+ async function handleDelete(scenarioId) {
2446
+ if (!scenarioId) {
2447
+ console.error(chalk.red('Error: Missing scenario ID. Usage: codeyam editor delete <scenarioId>'));
2448
+ process.exit(1);
2449
+ }
2450
+ const port = getServerPort();
2451
+ const result = await deleteScenarioViaCli(scenarioId, port);
2452
+ if (result.success) {
2453
+ console.log(chalk.green(result.message));
2454
+ }
2455
+ else {
2456
+ console.error(chalk.red(result.message));
2457
+ process.exit(1);
2458
+ }
2459
+ }
1680
2460
  // ─── Isolate subcommand ───────────────────────────────────────────────
1681
2461
  /**
1682
2462
  * `codeyam editor isolate "StarRating CategoryBadge DrinkCard"`
@@ -2151,23 +2931,106 @@ function handleChange(feature) {
2151
2931
  }
2152
2932
  // ─── Audit gate ─────────────────────────────────────────────────────
2153
2933
  /**
2154
- * Silently check whether the audit passes. Returns true if allPassing,
2155
- * false if any issues remain or the server is unreachable.
2156
- * Used as a hard gate before steps 8+.
2934
+ * Fetch the audit result from the server.
2935
+ * Returns null if the server is unreachable.
2157
2936
  */
2158
- async function checkAuditGate() {
2937
+ async function fetchAuditResult() {
2159
2938
  const port = getServerPort();
2160
2939
  try {
2161
2940
  const res = await fetch(`http://localhost:${port}/api/editor-audit`);
2162
2941
  if (!res.ok)
2163
- return false;
2164
- const data = await res.json();
2165
- return data?.summary?.allPassing === true;
2942
+ return null;
2943
+ return await res.json();
2166
2944
  }
2167
2945
  catch {
2168
- // Server not running or unreachable — can't verify, so don't block
2946
+ return null;
2947
+ }
2948
+ }
2949
+ /**
2950
+ * Silently check whether the audit passes. Returns true if allPassing,
2951
+ * false if any issues remain or the server is unreachable.
2952
+ * Used as a hard gate before steps 8+.
2953
+ *
2954
+ * Auto-runs analyze-imports if the only failure is incomplete entities,
2955
+ * then re-checks once.
2956
+ */
2957
+ async function checkAuditGate() {
2958
+ const data = await fetchAuditResult();
2959
+ if (!data)
2960
+ return true; // Server unreachable — don't block
2961
+ if (data.summary?.allPassing === true)
2169
2962
  return true;
2963
+ // If incomplete entities are the only issue, auto-fix with analyze-imports (once)
2964
+ const { isAutoRemediable } = await import('../utils/editorAudit.js');
2965
+ if (isAutoRemediable(data.summary, false)) {
2966
+ try {
2967
+ await handleAnalyzeImports({ silent: true });
2968
+ }
2969
+ catch {
2970
+ return false;
2971
+ }
2972
+ // Re-check after fix
2973
+ const retry = await fetchAuditResult();
2974
+ if (retry?.summary?.allPassing === true)
2975
+ return true;
2976
+ }
2977
+ // Print specific failures so Claude knows what to fix without running audit separately
2978
+ printAuditGateFailures(data);
2979
+ return false;
2980
+ }
2981
+ /**
2982
+ * Print a concise summary of audit failures for the gate block message.
2983
+ * This gives Claude immediate context about what to fix without needing
2984
+ * to run `codeyam editor audit` as a separate step.
2985
+ */
2986
+ function printAuditGateFailures(data) {
2987
+ const s = data.summary;
2988
+ if (!s)
2989
+ return;
2990
+ const issues = [];
2991
+ if (s.componentsMissing > 0)
2992
+ issues.push(`${s.componentsMissing} component(s) missing scenarios`);
2993
+ if (s.componentsWithErrors > 0)
2994
+ issues.push(`${s.componentsWithErrors} component(s) with client errors (browser API or runtime errors in captured scenarios)`);
2995
+ if (s.functionsMissing > 0)
2996
+ issues.push(`${s.functionsMissing} function(s) missing test files`);
2997
+ if (s.functionsFailing > 0)
2998
+ issues.push(`${s.functionsFailing} function(s) with failing tests`);
2999
+ if (s.functionsNameMismatch > 0)
3000
+ issues.push(`${s.functionsNameMismatch} function(s) with test name mismatch`);
3001
+ if (s.missingFromGlossary > 0)
3002
+ issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
3003
+ if (s.incompleteEntities > 0)
3004
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
3005
+ if (s.miscategorizedScenarios > 0)
3006
+ issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
3007
+ if (issues.length > 0) {
3008
+ console.error(chalk.yellow('\nAudit failures:'));
3009
+ for (const issue of issues) {
3010
+ console.error(chalk.yellow(` • ${issue}`));
3011
+ }
3012
+ }
3013
+ // Surface client error details inline — these are the most common cause of
3014
+ // Claude looping because the errors require code fixes, not audit re-runs.
3015
+ if (data.components) {
3016
+ const withErrors = data.components.filter((c) => c.status === 'has_errors' && c.clientErrors?.length > 0);
3017
+ if (withErrors.length > 0) {
3018
+ console.error(chalk.yellow('\nClient errors found:'));
3019
+ for (const c of withErrors) {
3020
+ console.error(chalk.red(` ${c.name}:`));
3021
+ for (const err of c.clientErrors.slice(0, 3)) {
3022
+ console.error(chalk.red(` → ${err}`));
3023
+ }
3024
+ if (c.clientErrors.length > 3) {
3025
+ console.error(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
3026
+ }
3027
+ }
3028
+ console.error(chalk.yellow('\nFix: Fix the code errors above, then re-capture the affected scenarios.'));
3029
+ console.error(chalk.yellow('If errors reference browser APIs (localStorage, sessionStorage, window, document),'));
3030
+ console.error(chalk.yellow('create a universal mock: codeyam detect-universal-mocks'));
3031
+ }
2170
3032
  }
3033
+ console.error(chalk.dim('\nRun `codeyam editor audit` for full details.\n'));
2171
3034
  }
2172
3035
  // ─── Audit subcommand ────────────────────────────────────────────────
2173
3036
  /**
@@ -2178,22 +3041,38 @@ async function checkAuditGate() {
2178
3041
  * have test files. Exits with code 1 if anything is missing.
2179
3042
  */
2180
3043
  async function handleAudit() {
2181
- const port = getServerPort();
2182
- const url = `http://localhost:${port}/api/editor-audit`;
2183
- let data;
2184
- try {
2185
- const res = await fetch(url);
2186
- if (!res.ok) {
2187
- console.error(chalk.red(`Error: Audit endpoint returned ${res.status}`));
2188
- process.exit(1);
2189
- }
2190
- data = await res.json();
2191
- }
2192
- catch (err) {
3044
+ let data = await fetchAuditResult();
3045
+ if (!data) {
2193
3046
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
2194
- console.error(chalk.dim(` ${err.message}`));
2195
3047
  process.exit(1);
2196
3048
  }
3049
+ // Auto-fix incomplete entities — but only once.
3050
+ // If analyze-imports runs and entities are STILL incomplete, report clearly
3051
+ // instead of retrying. The analysis may have errors (e.g., stale entity
3052
+ // references from refactoring) that can't be fixed by re-running.
3053
+ const incompleteBeforeFix = data.incompleteEntities || [];
3054
+ let autoRemediationFailed = false;
3055
+ if (incompleteBeforeFix.length > 0) {
3056
+ console.log(chalk.dim(`Running import analysis for ${incompleteBeforeFix.length} incomplete entit${incompleteBeforeFix.length !== 1 ? 'ies' : 'y'}...`));
3057
+ try {
3058
+ await handleAnalyzeImports({ silent: true });
3059
+ }
3060
+ catch {
3061
+ // Fall through — the audit will still report them as incomplete
3062
+ }
3063
+ // Re-fetch audit results after the fix
3064
+ data = await fetchAuditResult();
3065
+ if (!data) {
3066
+ console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
3067
+ process.exit(1);
3068
+ }
3069
+ // If entities are still incomplete after running analyze-imports,
3070
+ // flag it so we can show a clear message instead of looping
3071
+ const incompleteAfterFix = data.incompleteEntities || [];
3072
+ if (incompleteAfterFix.length > 0) {
3073
+ autoRemediationFailed = true;
3074
+ }
3075
+ }
2197
3076
  const { components, functions, summary } = data;
2198
3077
  console.log();
2199
3078
  console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
@@ -2221,6 +3100,14 @@ async function handleAudit() {
2221
3100
  if (c.clientErrors.length > 3) {
2222
3101
  console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
2223
3102
  }
3103
+ // Detect browser API errors and provide actionable guidance
3104
+ const browserApiPattern = /\b(localStorage|sessionStorage|window\.|document\.|navigator\.|indexedDB|matchMedia|ResizeObserver|IntersectionObserver|MutationObserver)\b/;
3105
+ const hasBrowserApiErrors = c.clientErrors.some((err) => browserApiPattern.test(err));
3106
+ if (hasBrowserApiErrors) {
3107
+ console.log(chalk.yellow(` ⚠ These errors are caused by browser APIs that don't exist during server-side analysis.`));
3108
+ console.log(chalk.yellow(` Fix: Create a universal mock to stub the missing API. Run: codeyam detect-universal-mocks`));
3109
+ console.log(chalk.yellow(` DO NOT re-run the audit or analyze-imports — the error will persist until a mock is created.`));
3110
+ }
2224
3111
  }
2225
3112
  }
2226
3113
  console.log();
@@ -2252,6 +3139,53 @@ async function handleAudit() {
2252
3139
  }
2253
3140
  console.log();
2254
3141
  }
3142
+ // Missing from glossary
3143
+ const missingFromGlossary = data.missingFromGlossary || [];
3144
+ if (missingFromGlossary.length > 0) {
3145
+ console.log(chalk.bold('Missing from glossary:'));
3146
+ for (const m of missingFromGlossary) {
3147
+ console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
3148
+ }
3149
+ console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
3150
+ console.log();
3151
+ }
3152
+ // Incomplete entities (scenarios without analyses)
3153
+ const incompleteEntities = data.incompleteEntities || [];
3154
+ if (incompleteEntities.length > 0) {
3155
+ console.log(chalk.bold('Incomplete entities (need import analysis):'));
3156
+ for (const e of incompleteEntities) {
3157
+ console.log(` ${chalk.red('✗')} ${e.name} — ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
3158
+ }
3159
+ if (autoRemediationFailed) {
3160
+ console.log(chalk.red(' analyze-imports was run automatically but these entities are STILL incomplete.'));
3161
+ console.log(chalk.red(' DO NOT re-run analyze-imports or the audit in a loop — the result will be the same.'));
3162
+ console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
3163
+ console.log(chalk.yellow(' • Entity code uses browser APIs (localStorage, window, document) — create a universal mock'));
3164
+ console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
3165
+ console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
3166
+ console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
3167
+ console.log(chalk.yellow(' To fix browser API issues: run `codeyam detect-universal-mocks`'));
3168
+ }
3169
+ else {
3170
+ console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
3171
+ }
3172
+ console.log();
3173
+ }
3174
+ // Miscategorized scenarios (component scenarios using real page URLs)
3175
+ const miscategorizedScenarios = data.miscategorizedScenarios || [];
3176
+ if (miscategorizedScenarios.length > 0) {
3177
+ console.log(chalk.bold('Miscategorized scenarios (component with page URL):'));
3178
+ for (const m of miscategorizedScenarios) {
3179
+ console.log(` ${chalk.red('✗')} ${m.componentName} at ${chalk.dim(m.url)} — ${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''}`);
3180
+ for (const name of m.scenarioNames) {
3181
+ console.log(chalk.dim(` "${name}"`));
3182
+ }
3183
+ }
3184
+ console.log(chalk.yellow(' Component scenarios must use isolation routes (/isolated-components/...).'));
3185
+ console.log(chalk.yellow(' Either re-register as app scenarios (remove componentName, add pageFilePath)'));
3186
+ console.log(chalk.yellow(' or re-register with an isolation URL.'));
3187
+ console.log();
3188
+ }
2255
3189
  // Summary
2256
3190
  const allOk = summary.allPassing;
2257
3191
  if (allOk) {
@@ -2275,6 +3209,15 @@ async function handleAudit() {
2275
3209
  if (summary.functionsNameMismatch > 0) {
2276
3210
  parts.push(`${summary.functionsNameMismatch} function${summary.functionsNameMismatch !== 1 ? 's' : ''} with test name mismatch (missing top-level describe)`);
2277
3211
  }
3212
+ if (summary.missingFromGlossary > 0) {
3213
+ parts.push(`${summary.missingFromGlossary} file${summary.missingFromGlossary !== 1 ? 's' : ''} with scenarios not in glossary`);
3214
+ }
3215
+ if (summary.incompleteEntities > 0) {
3216
+ parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
3217
+ }
3218
+ if (summary.miscategorizedScenarios > 0) {
3219
+ parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
3220
+ }
2278
3221
  console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
2279
3222
  }
2280
3223
  console.log();
@@ -2392,6 +3335,30 @@ async function handleScenarios() {
2392
3335
  }
2393
3336
  // ─── Scenario Coverage subcommand ───────────────────────────────────
2394
3337
  async function handleScenarioCoverage() {
3338
+ // Safety net: heal any scenarios with null entity_sha before checking coverage
3339
+ try {
3340
+ const { getDatabase } = await import('../../../packages/database/index.js');
3341
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
3342
+ const db = getDatabase();
3343
+ const backfillCount = await countScenariosNeedingEntityBackfill(db);
3344
+ if (backfillCount > 0) {
3345
+ console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
3346
+ await handleAnalyzeImports({ silent: true });
3347
+ // Run backfill after analysis
3348
+ const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
3349
+ const entities = await loadEntities({});
3350
+ if (entities && entities.length > 0) {
3351
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3352
+ sha: e.sha,
3353
+ name: e.name,
3354
+ filePath: e.filePath || '',
3355
+ })));
3356
+ }
3357
+ }
3358
+ }
3359
+ catch {
3360
+ // Non-fatal — proceed with coverage check regardless
3361
+ }
2395
3362
  const port = getServerPort();
2396
3363
  const url = `http://localhost:${port}/api/editor-scenario-coverage`;
2397
3364
  let data;
@@ -2539,7 +3506,20 @@ async function handleTemplate() {
2539
3506
  // Config parse error is non-fatal
2540
3507
  }
2541
3508
  }
2542
- // 5b. Write a fresh prototypeId so the proxy clears stale localStorage
3509
+ // 5b. Mark the project as template-scaffolded so migration detection
3510
+ // doesn't treat it as a pre-existing project that needs migration.
3511
+ const now = new Date().toISOString();
3512
+ const existingState = readState(root);
3513
+ writeState(root, {
3514
+ feature: '',
3515
+ step: 0,
3516
+ label: '',
3517
+ startedAt: now,
3518
+ featureStartedAt: now,
3519
+ ...existingState,
3520
+ scaffolded: true,
3521
+ });
3522
+ // 5c. Write a fresh prototypeId so the proxy clears stale localStorage
2543
3523
  const activeScenarioPath = path.join(root, '.codeyam', 'active-scenario.json');
2544
3524
  fs.writeFileSync(activeScenarioPath, JSON.stringify({ prototypeId: Date.now().toString() }), 'utf-8');
2545
3525
  // 6. Trigger editor-refresh so the server picks up the new project
@@ -2917,7 +3897,9 @@ const editorCommand = {
2917
3897
  describe: 'Debug: output directory for the bundle',
2918
3898
  });
2919
3899
  }
2920
- return builder;
3900
+ // Allow extra positional args for subcommands like `isolate A B C`
3901
+ // without yargs rejecting them as unknown.
3902
+ return builder.strict(false);
2921
3903
  },
2922
3904
  handler: async (argv) => {
2923
3905
  const root = getProjectRoot();
@@ -2996,14 +3978,16 @@ const editorCommand = {
2996
3978
  await handleValidateSeed(argv.json || '');
2997
3979
  return;
2998
3980
  }
3981
+ // Subcommand: codeyam editor delete <scenarioId>
3982
+ if (argv.step === 'delete') {
3983
+ await handleDelete(argv.json || '');
3984
+ return;
3985
+ }
2999
3986
  // Subcommand: codeyam editor isolate "StarRating CategoryBadge DrinkCard"
3987
+ // Also supports: codeyam editor isolate StarRating CategoryBadge DrinkCard
3000
3988
  if (argv.step === 'isolate') {
3001
- const names = [];
3002
- if (argv.json)
3003
- names.push(...argv.json.split(/[\s,]+/).filter(Boolean));
3004
- // Collect any extra positional args (yargs puts them in argv._)
3005
- const extras = argv._.filter((a) => typeof a === 'string' && a !== 'editor');
3006
- names.push(...extras);
3989
+ const { parseIsolateArgs } = await import('./editorIsolateArgs.js');
3990
+ const names = parseIsolateArgs(argv.json, argv._);
3007
3991
  handleIsolate(names);
3008
3992
  return;
3009
3993
  }
@@ -3026,6 +4010,11 @@ const editorCommand = {
3026
4010
  await handleEditorDebug(argv);
3027
4011
  return;
3028
4012
  }
4013
+ // Subcommand: codeyam editor migrate [subArg]
4014
+ if (argv.step === 'migrate') {
4015
+ handleMigrateCommand(root, argv.json || undefined);
4016
+ return;
4017
+ }
3029
4018
  // Subcommand: codeyam editor steps — show setup or cycle overview
3030
4019
  if (argv.step === 'steps') {
3031
4020
  if (!hasProject(root)) {
@@ -3095,6 +4084,9 @@ const editorCommand = {
3095
4084
  'screenshot_path',
3096
4085
  'viewport_width',
3097
4086
  'viewport_height',
4087
+ 'dimensions',
4088
+ 'screenshot_paths',
4089
+ 'page_file_path',
3098
4090
  'created_at',
3099
4091
  'updated_at',
3100
4092
  ])
@@ -3183,6 +4175,41 @@ const editorCommand = {
3183
4175
  catch {
3184
4176
  // Non-fatal — migration failure shouldn't block editor startup
3185
4177
  }
4178
+ // Auto-seed on fresh clone: if no scenario has ever been activated
4179
+ // (active-scenario.json doesn't exist), seed the application database
4180
+ // with the first application scenario that has seed data.
4181
+ const activeScenarioPath = path.join(projectRoot, '.codeyam', 'active-scenario.json');
4182
+ if (!fs.existsSync(activeScenarioPath)) {
4183
+ try {
4184
+ const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4185
+ const seedDb = getDb();
4186
+ const appScenario = await seedDb
4187
+ .selectFrom('editor_scenarios')
4188
+ .select(['id', 'name', 'type'])
4189
+ .where('project_id', '=', project.id)
4190
+ .where('type', '=', 'application')
4191
+ .orderBy('created_at', 'asc')
4192
+ .executeTakeFirst();
4193
+ if (appScenario) {
4194
+ const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
4195
+ if (fs.existsSync(seedFile)) {
4196
+ const { switchActiveScenario } = await import('../utils/editorScenarioSwitch.js');
4197
+ const seedResult = await switchActiveScenario({
4198
+ scenarioId: appScenario.id,
4199
+ scenarioName: appScenario.name || undefined,
4200
+ scenarioType: appScenario.type || undefined,
4201
+ projectRoot,
4202
+ });
4203
+ if (seedResult.seedResult?.success) {
4204
+ console.log(chalk.green(` Auto-seeded database with scenario: ${appScenario.name || appScenario.id}`));
4205
+ }
4206
+ }
4207
+ }
4208
+ }
4209
+ catch {
4210
+ // Non-fatal — auto-seed is best-effort
4211
+ }
4212
+ }
3186
4213
  // `codeyam editor` (no step) always implies editor mode.
3187
4214
  // The empty-folder heuristic is no longer needed here — running this
3188
4215
  // command IS the signal. We still detect empty folders so that
@@ -3248,11 +4275,77 @@ const editorCommand = {
3248
4275
  project,
3249
4276
  branch,
3250
4277
  });
3251
- // Build import graph on first startup if glossary exists but no entities yet
4278
+ // Build import graph if glossary exists but entities are missing.
4279
+ // Runs on first startup (no entities at all) AND when page files or
4280
+ // scenario component files lack entity coverage.
3252
4281
  const glossaryPath = path.join(projectRoot, '.codeyam', 'glossary.json');
3253
4282
  if (fs.existsSync(glossaryPath)) {
4283
+ let needsAnalysis = false;
3254
4284
  const entities = await loadEntities({});
3255
4285
  if (!entities || entities.length === 0) {
4286
+ needsAnalysis = true;
4287
+ }
4288
+ else {
4289
+ const entityFilePaths = new Set(entities.map((e) => e.filePath));
4290
+ // Check if any page files are missing entities (Next.js apps)
4291
+ try {
4292
+ const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
4293
+ const { allFiles } = scanPageFilePaths(projectRoot);
4294
+ const pageFiles = allFiles.filter((f) => f.endsWith('/page.tsx') || f.endsWith('/page.js'));
4295
+ const missingPages = pageFiles.filter((f) => !entityFilePaths.has(f));
4296
+ if (missingPages.length > 0) {
4297
+ console.log(chalk.dim(` Found ${missingPages.length} page(s) without entity analysis — running import analysis...`));
4298
+ needsAnalysis = true;
4299
+ }
4300
+ }
4301
+ catch {
4302
+ // Non-fatal — page file check failed
4303
+ }
4304
+ // Check if any scenario files (component_path or page_file_path)
4305
+ // are missing entities — covers non-Next.js apps
4306
+ if (!needsAnalysis) {
4307
+ try {
4308
+ const { getDatabase } = await import('../../../packages/database/index.js');
4309
+ const db = getDatabase();
4310
+ const scenarioFiles = await db
4311
+ .selectFrom('editor_scenarios')
4312
+ .select(['component_path', 'page_file_path'])
4313
+ .where('project_id', '=', project.id)
4314
+ .distinct()
4315
+ .execute();
4316
+ const missingCount = scenarioFiles.filter((row) => {
4317
+ const cp = row.component_path;
4318
+ const pfp = row.page_file_path;
4319
+ return ((cp && !entityFilePaths.has(cp)) ||
4320
+ (pfp && !entityFilePaths.has(pfp)));
4321
+ }).length;
4322
+ if (missingCount > 0) {
4323
+ console.log(chalk.dim(` Found ${missingCount} scenario file(s) without entity analysis — running import analysis...`));
4324
+ needsAnalysis = true;
4325
+ }
4326
+ }
4327
+ catch {
4328
+ // Non-fatal
4329
+ }
4330
+ }
4331
+ // Check if any scenarios have null entity_sha with file paths — heal on startup
4332
+ if (!needsAnalysis) {
4333
+ try {
4334
+ const { getDatabase } = await import('../../../packages/database/index.js');
4335
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
4336
+ const db = getDatabase();
4337
+ const backfillCount = await countScenariosNeedingEntityBackfill(db);
4338
+ if (backfillCount > 0) {
4339
+ console.log(chalk.dim(` Found ${backfillCount} scenario(s) with unlinked entities — running import analysis...`));
4340
+ needsAnalysis = true;
4341
+ }
4342
+ }
4343
+ catch {
4344
+ // Non-fatal
4345
+ }
4346
+ }
4347
+ }
4348
+ if (needsAnalysis) {
3256
4349
  try {
3257
4350
  await handleAnalyzeImports({ silent: true });
3258
4351
  }
@@ -3261,6 +4354,69 @@ const editorCommand = {
3261
4354
  }
3262
4355
  }
3263
4356
  }
4357
+ // Backfill page_file_path for application scenarios that have a URL but
4358
+ // no page_file_path. This resolves the file from the URL using Next.js
4359
+ // routing conventions, enabling syncScenarioEntityShas to link them to
4360
+ // entities. Covers fresh clones where JSON files lack pageFilePath.
4361
+ try {
4362
+ const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4363
+ const pfpDb = getDb();
4364
+ const unresolved = await pfpDb
4365
+ .selectFrom('editor_scenarios')
4366
+ .select(['id', 'url'])
4367
+ .where('project_id', '=', project.id)
4368
+ .where('component_name', 'is', null)
4369
+ .where('page_file_path', 'is', null)
4370
+ .where('url', 'is not', null)
4371
+ .execute();
4372
+ if (unresolved.length > 0) {
4373
+ const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
4374
+ const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
4375
+ const { allFiles: pfpFiles } = scanPfp(projectRoot);
4376
+ let pfpResolved = 0;
4377
+ for (const row of unresolved) {
4378
+ const r = row;
4379
+ if (!r.url)
4380
+ continue;
4381
+ const matched = matchUrlToPageFile(r.url, pfpFiles);
4382
+ if (matched) {
4383
+ await pfpDb
4384
+ .updateTable('editor_scenarios')
4385
+ .set({ page_file_path: matched })
4386
+ .where('id', '=', r.id)
4387
+ .execute();
4388
+ pfpResolved++;
4389
+ }
4390
+ }
4391
+ if (pfpResolved > 0) {
4392
+ console.log(chalk.green(` Resolved page_file_path for ${pfpResolved} scenario(s) from URL`));
4393
+ }
4394
+ }
4395
+ }
4396
+ catch {
4397
+ /* Non-fatal — page_file_path backfill from URL */
4398
+ }
4399
+ // Backfill entity_sha on scenarios that were synced before entities existed.
4400
+ // This runs independently of analyze-imports so fresh clones and file-synced
4401
+ // scenarios get linked to their entities even when auto-analyze doesn't trigger.
4402
+ try {
4403
+ const entities = await loadEntities({});
4404
+ if (entities && entities.length > 0) {
4405
+ const { getDatabase } = await import('../../../packages/database/index.js');
4406
+ const db = getDatabase();
4407
+ const result = await backfillEntityShaOnScenarios(db, entities.map((e) => ({
4408
+ sha: e.sha,
4409
+ name: e.name,
4410
+ filePath: e.filePath || '',
4411
+ })));
4412
+ if (result.updated > 0) {
4413
+ console.log(chalk.green(` Linked ${result.updated} scenario(s) to their entities`));
4414
+ }
4415
+ }
4416
+ }
4417
+ catch {
4418
+ /* Non-fatal */
4419
+ }
3264
4420
  console.log();
3265
4421
  console.log(` Dashboard: ${url}`);
3266
4422
  console.log(' Run "codeyam --help" for all commands');
@@ -3274,7 +4430,9 @@ const editorCommand = {
3274
4430
  : process.platform === 'win32'
3275
4431
  ? 'start ""'
3276
4432
  : 'xdg-open';
3277
- execSync(`${openCommand} "${url}/editor"`, { stdio: 'ignore' });
4433
+ execSync(`${openCommand} "${url}/editor"`, {
4434
+ stdio: 'ignore',
4435
+ });
3278
4436
  }
3279
4437
  catch {
3280
4438
  // Silently fail if open command doesn't work
@@ -3344,8 +4502,9 @@ const editorCommand = {
3344
4502
  if (step >= 8) {
3345
4503
  const auditOk = await checkAuditGate();
3346
4504
  if (!auditOk) {
3347
- console.error(chalk.red.bold('BLOCKED: The audit has not passed. Run `codeyam editor audit` and fix all issues before proceeding.'));
3348
- console.error(chalk.yellow('Every function and hook must have a test file. Every component must have scenarios.'));
4505
+ // checkAuditGate() already printed specific failure details above
4506
+ console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
4507
+ console.error(chalk.yellow('DO NOT re-run the audit in a loop — fix the underlying issues first.'));
3349
4508
  process.exit(1);
3350
4509
  }
3351
4510
  }