@codeyam/codeyam-cli 0.1.0-staging.c5503ac → 0.1.0-staging.c85943e

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 (719) 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 +23 -23
  4. package/analyzer-template/packages/ai/package.json +3 -3
  5. package/analyzer-template/packages/ai/src/lib/astScopes/astScopeAnalyzer.ts +34 -3
  6. package/analyzer-template/packages/ai/src/lib/completionCall.ts +114 -113
  7. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +259 -5
  8. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/coercePrimitivesToArraysBySchema.ts +62 -0
  9. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/stripNullableMarkers.ts +35 -0
  10. package/analyzer-template/packages/ai/src/lib/dataStructureChunking.ts +15 -6
  11. package/analyzer-template/packages/ai/src/lib/generateEntityScenarioData.ts +78 -2
  12. package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +96 -33
  13. package/analyzer-template/packages/analyze/src/lib/ProjectAnalyzer.ts +19 -7
  14. package/analyzer-template/packages/analyze/src/lib/asts/index.ts +7 -2
  15. package/analyzer-template/packages/analyze/src/lib/asts/nodes/getNodeType.ts +1 -0
  16. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +9 -1
  17. package/analyzer-template/packages/analyze/src/lib/files/analyze/dependencyResolver.ts +0 -6
  18. package/analyzer-template/packages/analyze/src/lib/files/analyze/findOrCreateEntity.ts +12 -0
  19. package/analyzer-template/packages/analyze/src/lib/files/scenarios/TransformationTracer.ts +65 -28
  20. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +83 -0
  21. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.ts +0 -98
  22. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +23 -4
  23. package/analyzer-template/packages/aws/package.json +10 -10
  24. package/analyzer-template/packages/database/index.ts +1 -0
  25. package/analyzer-template/packages/database/package.json +3 -3
  26. package/analyzer-template/packages/database/src/lib/kysely/db.ts +8 -0
  27. package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +164 -0
  28. package/analyzer-template/packages/database/src/lib/loadCommits.ts +31 -20
  29. package/analyzer-template/packages/database/src/lib/loadEntities.ts +0 -6
  30. package/analyzer-template/packages/database/src/lib/loadReadyToBeCapturedAnalyses.ts +0 -5
  31. package/analyzer-template/packages/database/src/lib/updateCommitMetadata.ts +94 -143
  32. package/analyzer-template/packages/database/src/lib/updateFreshAnalysisStatus.ts +58 -42
  33. package/analyzer-template/packages/database/src/lib/updateFreshAnalysisStatusWithScenarios.ts +81 -65
  34. package/analyzer-template/packages/generate/src/lib/componentScenarioPage/generateScenarioClientWrapper.ts +29 -1
  35. package/analyzer-template/packages/generate/src/lib/componentScenarioPage/getIFrameMessageListenerCode.ts +33 -5
  36. package/analyzer-template/packages/github/dist/database/index.d.ts +1 -0
  37. package/analyzer-template/packages/github/dist/database/index.d.ts.map +1 -1
  38. package/analyzer-template/packages/github/dist/database/index.js +1 -0
  39. package/analyzer-template/packages/github/dist/database/index.js.map +1 -1
  40. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.d.ts +2 -0
  41. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.d.ts.map +1 -1
  42. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.js +5 -0
  43. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.js.map +1 -1
  44. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +29 -0
  45. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts.map +1 -0
  46. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js +149 -0
  47. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -0
  48. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/scenariosTable.d.ts +5 -0
  49. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/scenariosTable.d.ts.map +1 -1
  50. package/analyzer-template/packages/github/dist/database/src/lib/loadCommits.d.ts.map +1 -1
  51. package/analyzer-template/packages/github/dist/database/src/lib/loadCommits.js +23 -13
  52. package/analyzer-template/packages/github/dist/database/src/lib/loadCommits.js.map +1 -1
  53. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts.map +1 -1
  54. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js +0 -6
  55. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js.map +1 -1
  56. package/analyzer-template/packages/github/dist/database/src/lib/loadReadyToBeCapturedAnalyses.d.ts.map +1 -1
  57. package/analyzer-template/packages/github/dist/database/src/lib/loadReadyToBeCapturedAnalyses.js +1 -4
  58. package/analyzer-template/packages/github/dist/database/src/lib/loadReadyToBeCapturedAnalyses.js.map +1 -1
  59. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts.map +1 -1
  60. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js +76 -90
  61. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js.map +1 -1
  62. package/analyzer-template/packages/github/dist/database/src/lib/updateFreshAnalysisStatus.d.ts.map +1 -1
  63. package/analyzer-template/packages/github/dist/database/src/lib/updateFreshAnalysisStatus.js +41 -30
  64. package/analyzer-template/packages/github/dist/database/src/lib/updateFreshAnalysisStatus.js.map +1 -1
  65. package/analyzer-template/packages/github/dist/database/src/lib/updateFreshAnalysisStatusWithScenarios.d.ts.map +1 -1
  66. package/analyzer-template/packages/github/dist/database/src/lib/updateFreshAnalysisStatusWithScenarios.js +68 -57
  67. package/analyzer-template/packages/github/dist/database/src/lib/updateFreshAnalysisStatusWithScenarios.js.map +1 -1
  68. package/analyzer-template/packages/github/dist/generate/src/lib/componentScenarioPage/generateScenarioClientWrapper.d.ts.map +1 -1
  69. package/analyzer-template/packages/github/dist/generate/src/lib/componentScenarioPage/generateScenarioClientWrapper.js +29 -1
  70. package/analyzer-template/packages/github/dist/generate/src/lib/componentScenarioPage/generateScenarioClientWrapper.js.map +1 -1
  71. package/analyzer-template/packages/github/dist/generate/src/lib/componentScenarioPage/getIFrameMessageListenerCode.d.ts.map +1 -1
  72. package/analyzer-template/packages/github/dist/generate/src/lib/componentScenarioPage/getIFrameMessageListenerCode.js +33 -5
  73. package/analyzer-template/packages/github/dist/generate/src/lib/componentScenarioPage/getIFrameMessageListenerCode.js.map +1 -1
  74. package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.d.ts +2 -0
  75. package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.d.ts.map +1 -1
  76. package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.js +2 -0
  77. package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.js.map +1 -1
  78. package/analyzer-template/packages/github/dist/types/src/types/ProjectMetadata.d.ts +1 -0
  79. package/analyzer-template/packages/github/dist/types/src/types/ProjectMetadata.d.ts.map +1 -1
  80. package/analyzer-template/packages/github/dist/types/src/types/Scenario.d.ts +10 -0
  81. package/analyzer-template/packages/github/dist/types/src/types/Scenario.d.ts.map +1 -1
  82. package/analyzer-template/packages/github/package.json +1 -1
  83. package/analyzer-template/packages/types/src/enums/ProjectFramework.ts +2 -0
  84. package/analyzer-template/packages/types/src/types/ProjectMetadata.ts +1 -0
  85. package/analyzer-template/packages/types/src/types/Scenario.ts +10 -0
  86. package/analyzer-template/packages/ui-components/package.json +1 -1
  87. package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.d.ts +2 -0
  88. package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.d.ts.map +1 -1
  89. package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.js +2 -0
  90. package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.js.map +1 -1
  91. package/analyzer-template/packages/utils/dist/types/src/types/ProjectMetadata.d.ts +1 -0
  92. package/analyzer-template/packages/utils/dist/types/src/types/ProjectMetadata.d.ts.map +1 -1
  93. package/analyzer-template/packages/utils/dist/types/src/types/Scenario.d.ts +10 -0
  94. package/analyzer-template/packages/utils/dist/types/src/types/Scenario.d.ts.map +1 -1
  95. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
  96. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +6 -2
  97. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  98. package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +14 -2
  99. package/analyzer-template/playwright/captureFromUrl.ts +89 -82
  100. package/analyzer-template/project/constructMockCode.ts +168 -48
  101. package/analyzer-template/project/orchestrateCapture.ts +4 -1
  102. package/analyzer-template/project/reconcileMockDataKeys.ts +19 -14
  103. package/analyzer-template/project/start.ts +3 -0
  104. package/analyzer-template/project/startScenarioCapture.ts +9 -0
  105. package/analyzer-template/project/writeClientLogRoute.ts +125 -0
  106. package/analyzer-template/project/writeMockDataTsx.ts +17 -0
  107. package/analyzer-template/project/writeScenarioComponents.ts +110 -17
  108. package/analyzer-template/tsconfig.json +13 -1
  109. package/background/src/lib/virtualized/project/constructMockCode.js +143 -39
  110. package/background/src/lib/virtualized/project/constructMockCode.js.map +1 -1
  111. package/background/src/lib/virtualized/project/orchestrateCapture.js +4 -1
  112. package/background/src/lib/virtualized/project/orchestrateCapture.js.map +1 -1
  113. package/background/src/lib/virtualized/project/reconcileMockDataKeys.js +17 -11
  114. package/background/src/lib/virtualized/project/reconcileMockDataKeys.js.map +1 -1
  115. package/background/src/lib/virtualized/project/start.js +2 -0
  116. package/background/src/lib/virtualized/project/start.js.map +1 -1
  117. package/background/src/lib/virtualized/project/startScenarioCapture.js +5 -0
  118. package/background/src/lib/virtualized/project/startScenarioCapture.js.map +1 -1
  119. package/background/src/lib/virtualized/project/writeClientLogRoute.js +110 -0
  120. package/background/src/lib/virtualized/project/writeClientLogRoute.js.map +1 -0
  121. package/background/src/lib/virtualized/project/writeMockDataTsx.js +12 -0
  122. package/background/src/lib/virtualized/project/writeMockDataTsx.js.map +1 -1
  123. package/background/src/lib/virtualized/project/writeScenarioComponents.js +83 -12
  124. package/background/src/lib/virtualized/project/writeScenarioComponents.js.map +1 -1
  125. package/codeyam-cli/scripts/apply-setup.js +208 -11
  126. package/codeyam-cli/scripts/apply-setup.js.map +1 -1
  127. package/codeyam-cli/src/__tests__/memory-scripts/filter-session.test.js +196 -0
  128. package/codeyam-cli/src/__tests__/memory-scripts/filter-session.test.js.map +1 -0
  129. package/codeyam-cli/src/__tests__/memory-scripts/read-json-field.test.js +114 -0
  130. package/codeyam-cli/src/__tests__/memory-scripts/read-json-field.test.js.map +1 -0
  131. package/codeyam-cli/src/__tests__/memory-scripts/ripgrep-fallback.test.js +149 -0
  132. package/codeyam-cli/src/__tests__/memory-scripts/ripgrep-fallback.test.js.map +1 -0
  133. package/codeyam-cli/src/cli.js +32 -25
  134. package/codeyam-cli/src/cli.js.map +1 -1
  135. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +56 -0
  136. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -0
  137. package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +101 -47
  138. package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
  139. package/codeyam-cli/src/commands/analyze.js +17 -7
  140. package/codeyam-cli/src/commands/analyze.js.map +1 -1
  141. package/codeyam-cli/src/commands/default.js +14 -2
  142. package/codeyam-cli/src/commands/default.js.map +1 -1
  143. package/codeyam-cli/src/commands/editor.js +4357 -0
  144. package/codeyam-cli/src/commands/editor.js.map +1 -0
  145. package/codeyam-cli/src/commands/init.js +108 -45
  146. package/codeyam-cli/src/commands/init.js.map +1 -1
  147. package/codeyam-cli/src/commands/memory.js +89 -75
  148. package/codeyam-cli/src/commands/memory.js.map +1 -1
  149. package/codeyam-cli/src/data/techStacks.js +77 -0
  150. package/codeyam-cli/src/data/techStacks.js.map +1 -0
  151. package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js +173 -0
  152. package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js.map +1 -0
  153. package/codeyam-cli/src/utils/__tests__/backgroundServer.test.js +46 -0
  154. package/codeyam-cli/src/utils/__tests__/backgroundServer.test.js.map +1 -0
  155. package/codeyam-cli/src/utils/__tests__/devServerState.test.js +134 -0
  156. package/codeyam-cli/src/utils/__tests__/devServerState.test.js.map +1 -0
  157. package/codeyam-cli/src/utils/__tests__/editorApi.test.js +137 -0
  158. package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -0
  159. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +1831 -0
  160. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -0
  161. package/codeyam-cli/src/utils/__tests__/editorBroadcastViewport.test.js +76 -0
  162. package/codeyam-cli/src/utils/__tests__/editorBroadcastViewport.test.js.map +1 -0
  163. package/codeyam-cli/src/utils/__tests__/editorCapture.test.js +93 -0
  164. package/codeyam-cli/src/utils/__tests__/editorCapture.test.js.map +1 -0
  165. package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js +100 -0
  166. package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js.map +1 -0
  167. package/codeyam-cli/src/utils/__tests__/editorDevServer.test.js +304 -0
  168. package/codeyam-cli/src/utils/__tests__/editorDevServer.test.js.map +1 -0
  169. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +124 -0
  170. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -0
  171. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +261 -0
  172. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -0
  173. package/codeyam-cli/src/utils/__tests__/editorImageVerifier.test.js +294 -0
  174. package/codeyam-cli/src/utils/__tests__/editorImageVerifier.test.js.map +1 -0
  175. package/codeyam-cli/src/utils/__tests__/editorJournal.test.js +542 -0
  176. package/codeyam-cli/src/utils/__tests__/editorJournal.test.js.map +1 -0
  177. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +594 -0
  178. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -0
  179. package/codeyam-cli/src/utils/__tests__/editorMigration.test.js +435 -0
  180. package/codeyam-cli/src/utils/__tests__/editorMigration.test.js.map +1 -0
  181. package/codeyam-cli/src/utils/__tests__/editorMockState.test.js +270 -0
  182. package/codeyam-cli/src/utils/__tests__/editorMockState.test.js.map +1 -0
  183. package/codeyam-cli/src/utils/__tests__/editorPreloadHelpers.test.js +217 -0
  184. package/codeyam-cli/src/utils/__tests__/editorPreloadHelpers.test.js.map +1 -0
  185. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +353 -0
  186. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -0
  187. package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +153 -0
  188. package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -0
  189. package/codeyam-cli/src/utils/__tests__/editorScenarioLookup.test.js +139 -0
  190. package/codeyam-cli/src/utils/__tests__/editorScenarioLookup.test.js.map +1 -0
  191. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +221 -0
  192. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -0
  193. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +1483 -0
  194. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -0
  195. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +280 -0
  196. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -0
  197. package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js +143 -0
  198. package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js.map +1 -0
  199. package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js +66 -0
  200. package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js.map +1 -0
  201. package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js +53 -0
  202. package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js.map +1 -0
  203. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +1857 -0
  204. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -0
  205. package/codeyam-cli/src/utils/__tests__/git.editor.test.js +134 -0
  206. package/codeyam-cli/src/utils/__tests__/git.editor.test.js.map +1 -0
  207. package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js +107 -0
  208. package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js.map +1 -0
  209. package/codeyam-cli/src/utils/__tests__/npmVersionCheck.test.js +26 -20
  210. package/codeyam-cli/src/utils/__tests__/npmVersionCheck.test.js.map +1 -1
  211. package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js +129 -0
  212. package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js.map +1 -0
  213. package/codeyam-cli/src/utils/__tests__/pathIgnoring.test.js +9 -0
  214. package/codeyam-cli/src/utils/__tests__/pathIgnoring.test.js.map +1 -1
  215. package/codeyam-cli/src/utils/__tests__/project.test.js +65 -0
  216. package/codeyam-cli/src/utils/__tests__/project.test.js.map +1 -0
  217. package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js +118 -0
  218. package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js.map +1 -0
  219. package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +227 -0
  220. package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -0
  221. package/codeyam-cli/src/utils/__tests__/scenarioMarkers.test.js +121 -0
  222. package/codeyam-cli/src/utils/__tests__/scenarioMarkers.test.js.map +1 -0
  223. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +493 -0
  224. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -0
  225. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +51 -4
  226. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
  227. package/codeyam-cli/src/utils/__tests__/templateConsistency.test.js +51 -0
  228. package/codeyam-cli/src/utils/__tests__/templateConsistency.test.js.map +1 -0
  229. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +142 -0
  230. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -0
  231. package/codeyam-cli/src/utils/analysisRunner.js +3 -1
  232. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  233. package/codeyam-cli/src/utils/analyzer.js +9 -0
  234. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  235. package/codeyam-cli/src/utils/analyzerFinalization.js +100 -0
  236. package/codeyam-cli/src/utils/analyzerFinalization.js.map +1 -0
  237. package/codeyam-cli/src/utils/backgroundServer.js +104 -12
  238. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  239. package/codeyam-cli/src/utils/buildFlags.js +4 -0
  240. package/codeyam-cli/src/utils/buildFlags.js.map +1 -0
  241. package/codeyam-cli/src/utils/database.js +37 -2
  242. package/codeyam-cli/src/utils/database.js.map +1 -1
  243. package/codeyam-cli/src/utils/devModeEvents.js +40 -0
  244. package/codeyam-cli/src/utils/devModeEvents.js.map +1 -0
  245. package/codeyam-cli/src/utils/devServerState.js +71 -0
  246. package/codeyam-cli/src/utils/devServerState.js.map +1 -0
  247. package/codeyam-cli/src/utils/editorApi.js +79 -0
  248. package/codeyam-cli/src/utils/editorApi.js.map +1 -0
  249. package/codeyam-cli/src/utils/editorAudit.js +343 -0
  250. package/codeyam-cli/src/utils/editorAudit.js.map +1 -0
  251. package/codeyam-cli/src/utils/editorBroadcastViewport.js +26 -0
  252. package/codeyam-cli/src/utils/editorBroadcastViewport.js.map +1 -0
  253. package/codeyam-cli/src/utils/editorCapture.js +102 -0
  254. package/codeyam-cli/src/utils/editorCapture.js.map +1 -0
  255. package/codeyam-cli/src/utils/editorDeleteScenario.js +67 -0
  256. package/codeyam-cli/src/utils/editorDeleteScenario.js.map +1 -0
  257. package/codeyam-cli/src/utils/editorDevServer.js +197 -0
  258. package/codeyam-cli/src/utils/editorDevServer.js.map +1 -0
  259. package/codeyam-cli/src/utils/editorEntityChangeStatus.js +44 -0
  260. package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -0
  261. package/codeyam-cli/src/utils/editorEntityHelpers.js +129 -0
  262. package/codeyam-cli/src/utils/editorEntityHelpers.js.map +1 -0
  263. package/codeyam-cli/src/utils/editorImageVerifier.js +155 -0
  264. package/codeyam-cli/src/utils/editorImageVerifier.js.map +1 -0
  265. package/codeyam-cli/src/utils/editorJournal.js +225 -0
  266. package/codeyam-cli/src/utils/editorJournal.js.map +1 -0
  267. package/codeyam-cli/src/utils/editorLoaderHelpers.js +152 -0
  268. package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -0
  269. package/codeyam-cli/src/utils/editorMigration.js +224 -0
  270. package/codeyam-cli/src/utils/editorMigration.js.map +1 -0
  271. package/codeyam-cli/src/utils/editorMockState.js +248 -0
  272. package/codeyam-cli/src/utils/editorMockState.js.map +1 -0
  273. package/codeyam-cli/src/utils/editorPreloadHelpers.js +135 -0
  274. package/codeyam-cli/src/utils/editorPreloadHelpers.js.map +1 -0
  275. package/codeyam-cli/src/utils/editorPreview.js +137 -0
  276. package/codeyam-cli/src/utils/editorPreview.js.map +1 -0
  277. package/codeyam-cli/src/utils/editorScenarioSwitch.js +112 -0
  278. package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -0
  279. package/codeyam-cli/src/utils/editorScenarios.js +548 -0
  280. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -0
  281. package/codeyam-cli/src/utils/editorSeedAdapter.js +422 -0
  282. package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -0
  283. package/codeyam-cli/src/utils/editorShouldRevalidate.js +21 -0
  284. package/codeyam-cli/src/utils/editorShouldRevalidate.js.map +1 -0
  285. package/codeyam-cli/src/utils/entityChangeStatus.js +366 -0
  286. package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -0
  287. package/codeyam-cli/src/utils/entityChangeStatus.server.js +196 -0
  288. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -0
  289. package/codeyam-cli/src/utils/fileMetadata.js +5 -0
  290. package/codeyam-cli/src/utils/fileMetadata.js.map +1 -1
  291. package/codeyam-cli/src/utils/fileWatcher.js +63 -9
  292. package/codeyam-cli/src/utils/fileWatcher.js.map +1 -1
  293. package/codeyam-cli/src/utils/git.js +103 -0
  294. package/codeyam-cli/src/utils/git.js.map +1 -1
  295. package/codeyam-cli/src/utils/install-skills.js +66 -15
  296. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  297. package/codeyam-cli/src/utils/interactiveSyncWatcher.js +126 -0
  298. package/codeyam-cli/src/utils/interactiveSyncWatcher.js.map +1 -0
  299. package/codeyam-cli/src/utils/npmVersionCheck.js +2 -2
  300. package/codeyam-cli/src/utils/npmVersionCheck.js.map +1 -1
  301. package/codeyam-cli/src/utils/parseRegisterArg.js +31 -0
  302. package/codeyam-cli/src/utils/parseRegisterArg.js.map +1 -0
  303. package/codeyam-cli/src/utils/pathIgnoring.js +19 -7
  304. package/codeyam-cli/src/utils/pathIgnoring.js.map +1 -1
  305. package/codeyam-cli/src/utils/progress.js +2 -2
  306. package/codeyam-cli/src/utils/progress.js.map +1 -1
  307. package/codeyam-cli/src/utils/project.js +15 -5
  308. package/codeyam-cli/src/utils/project.js.map +1 -1
  309. package/codeyam-cli/src/utils/queue/__tests__/heartbeat.test.js +11 -11
  310. package/codeyam-cli/src/utils/queue/__tests__/heartbeat.test.js.map +1 -1
  311. package/codeyam-cli/src/utils/queue/__tests__/manager.test.js +22 -0
  312. package/codeyam-cli/src/utils/queue/__tests__/manager.test.js.map +1 -1
  313. package/codeyam-cli/src/utils/queue/heartbeat.js +13 -5
  314. package/codeyam-cli/src/utils/queue/heartbeat.js.map +1 -1
  315. package/codeyam-cli/src/utils/queue/job.js +70 -1
  316. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  317. package/codeyam-cli/src/utils/queue/manager.js +7 -6
  318. package/codeyam-cli/src/utils/queue/manager.js.map +1 -1
  319. package/codeyam-cli/src/utils/requireSimulations.js +1 -1
  320. package/codeyam-cli/src/utils/requireSimulations.js.map +1 -1
  321. package/codeyam-cli/src/utils/routePatternMatching.js +129 -0
  322. package/codeyam-cli/src/utils/routePatternMatching.js.map +1 -0
  323. package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js +5 -6
  324. package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js.map +1 -1
  325. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/assertRules.js +1 -1
  326. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/assertRules.js.map +1 -1
  327. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/setupTempProject.js +0 -1
  328. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/setupTempProject.js.map +1 -1
  329. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js +2 -4
  330. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js.map +1 -1
  331. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js +4 -6
  332. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js.map +1 -1
  333. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js +1 -1
  334. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js.map +1 -1
  335. package/codeyam-cli/src/utils/rules/__tests__/parser.test.js +83 -0
  336. package/codeyam-cli/src/utils/rules/__tests__/parser.test.js.map +1 -0
  337. package/codeyam-cli/src/utils/rules/__tests__/pathMatcher.test.js +118 -0
  338. package/codeyam-cli/src/utils/rules/__tests__/pathMatcher.test.js.map +1 -0
  339. package/codeyam-cli/src/utils/rules/__tests__/rulePlacement.test.js +72 -0
  340. package/codeyam-cli/src/utils/rules/__tests__/rulePlacement.test.js.map +1 -0
  341. package/codeyam-cli/src/utils/rules/__tests__/sourceFiles.test.js +76 -0
  342. package/codeyam-cli/src/utils/rules/__tests__/sourceFiles.test.js.map +1 -0
  343. package/codeyam-cli/src/utils/rules/index.js +1 -0
  344. package/codeyam-cli/src/utils/rules/index.js.map +1 -1
  345. package/codeyam-cli/src/utils/rules/parser.js +14 -4
  346. package/codeyam-cli/src/utils/rules/parser.js.map +1 -1
  347. package/codeyam-cli/src/utils/rules/pathMatcher.js +34 -3
  348. package/codeyam-cli/src/utils/rules/pathMatcher.js.map +1 -1
  349. package/codeyam-cli/src/utils/rules/rulePlacement.js +65 -0
  350. package/codeyam-cli/src/utils/rules/rulePlacement.js.map +1 -0
  351. package/codeyam-cli/src/utils/rules/sourceFiles.js +43 -0
  352. package/codeyam-cli/src/utils/rules/sourceFiles.js.map +1 -0
  353. package/codeyam-cli/src/utils/scenarioCoverage.js +74 -0
  354. package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -0
  355. package/codeyam-cli/src/utils/scenarioMarkers.js +134 -0
  356. package/codeyam-cli/src/utils/scenarioMarkers.js.map +1 -0
  357. package/codeyam-cli/src/utils/scenariosManifest.js +249 -0
  358. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -0
  359. package/codeyam-cli/src/utils/serverState.js +57 -2
  360. package/codeyam-cli/src/utils/serverState.js.map +1 -1
  361. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +83 -11
  362. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
  363. package/codeyam-cli/src/utils/simulationGateMiddleware.js +166 -0
  364. package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -0
  365. package/codeyam-cli/src/utils/slugUtils.js +25 -0
  366. package/codeyam-cli/src/utils/slugUtils.js.map +1 -0
  367. package/codeyam-cli/src/utils/syncMocksMiddleware.js +7 -26
  368. package/codeyam-cli/src/utils/syncMocksMiddleware.js.map +1 -1
  369. package/codeyam-cli/src/utils/testRunner.js +158 -0
  370. package/codeyam-cli/src/utils/testRunner.js.map +1 -0
  371. package/codeyam-cli/src/utils/transcriptPruning.js +67 -0
  372. package/codeyam-cli/src/utils/transcriptPruning.js.map +1 -0
  373. package/codeyam-cli/src/utils/versionInfo.js +46 -0
  374. package/codeyam-cli/src/utils/versionInfo.js.map +1 -1
  375. package/codeyam-cli/src/utils/webappDetection.js +35 -2
  376. package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
  377. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +35 -0
  378. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -0
  379. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +40 -0
  380. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -0
  381. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +600 -0
  382. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -0
  383. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +146 -0
  384. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -0
  385. package/codeyam-cli/src/webserver/app/lib/clientErrors.js +65 -0
  386. package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -0
  387. package/codeyam-cli/src/webserver/app/lib/database.js +41 -27
  388. package/codeyam-cli/src/webserver/app/lib/database.js.map +1 -1
  389. package/codeyam-cli/src/webserver/app/lib/dbNotifier.js.map +1 -1
  390. package/codeyam-cli/src/webserver/app/lib/git.js +397 -0
  391. package/codeyam-cli/src/webserver/app/lib/git.js.map +1 -0
  392. package/codeyam-cli/src/webserver/app/types/editor.js +8 -0
  393. package/codeyam-cli/src/webserver/app/types/editor.js.map +1 -0
  394. package/codeyam-cli/src/webserver/backgroundServer.js +134 -20
  395. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  396. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-CzTDWkF2.js +1 -0
  397. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-bwuHPyTa.js → EntityItem-BFbq6iFk.js} +5 -5
  398. package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-CQgyEGV-.js +1 -0
  399. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-BH0XDim7.js → EntityTypeIcon-B6OMi58N.js} +9 -9
  400. package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-DuYodzo1.js +1 -0
  401. package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-CXo9EeCl.js +25 -0
  402. package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-DYCNb2It.js +3 -0
  403. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-BvMu2i-g.js → LoadingDots-By5zI316.js} +1 -1
  404. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-kgBTLoJD.js → LogViewer-CZgY3sxX.js} +3 -3
  405. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-BzPgx-xO.js → ReportIssueModal-CnYYwRDw.js} +4 -4
  406. package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-CDoF7ZpU.js +1 -0
  407. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-BX2Ny2Qj.js → ScenarioViewer-DrnfvaLL.js} +3 -3
  408. package/codeyam-cli/src/webserver/build/client/assets/Spinner-Df3UCi8k.js +34 -0
  409. package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-CK7-NaPZ.js +1 -0
  410. package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-DRKR9T0U.js +1 -0
  411. package/codeyam-cli/src/webserver/build/client/assets/{_index-BRx8ZGZo.js → _index-ClR-g3tY.js} +4 -4
  412. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-4S4yPfFw.js → activity.(_tab)-DTH6ydEA.js} +8 -8
  413. package/codeyam-cli/src/webserver/build/client/assets/addon-canvas-DpzMmAy5.js +1 -0
  414. package/codeyam-cli/src/webserver/build/client/assets/addon-fit-YJmn1quW.js +12 -0
  415. package/codeyam-cli/src/webserver/build/client/assets/addon-web-links-74hnHF59.js +1 -0
  416. package/codeyam-cli/src/webserver/build/client/assets/addon-webgl-DI8QOUvO.js +58 -0
  417. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-B8CYhCO9.js +22 -0
  418. package/codeyam-cli/src/webserver/build/client/assets/api.dev-mode-events-l0sNRNKZ.js +1 -0
  419. package/codeyam-cli/src/webserver/build/client/assets/api.editor-audit-l0sNRNKZ.js +1 -0
  420. package/codeyam-cli/src/webserver/build/client/assets/api.editor-capture-scenario-l0sNRNKZ.js +1 -0
  421. package/codeyam-cli/src/webserver/build/client/assets/api.editor-client-errors-l0sNRNKZ.js +1 -0
  422. package/codeyam-cli/src/webserver/build/client/assets/api.editor-commit-l0sNRNKZ.js +1 -0
  423. package/codeyam-cli/src/webserver/build/client/assets/api.editor-dev-server-l0sNRNKZ.js +1 -0
  424. package/codeyam-cli/src/webserver/build/client/assets/api.editor-entity-status-l0sNRNKZ.js +1 -0
  425. package/codeyam-cli/src/webserver/build/client/assets/api.editor-file-diff-l0sNRNKZ.js +1 -0
  426. package/codeyam-cli/src/webserver/build/client/assets/api.editor-file-l0sNRNKZ.js +1 -0
  427. package/codeyam-cli/src/webserver/build/client/assets/api.editor-journal-entry-l0sNRNKZ.js +1 -0
  428. package/codeyam-cli/src/webserver/build/client/assets/api.editor-journal-image._-l0sNRNKZ.js +1 -0
  429. package/codeyam-cli/src/webserver/build/client/assets/api.editor-journal-l0sNRNKZ.js +1 -0
  430. package/codeyam-cli/src/webserver/build/client/assets/api.editor-journal-screenshot-l0sNRNKZ.js +1 -0
  431. package/codeyam-cli/src/webserver/build/client/assets/api.editor-journal-update-l0sNRNKZ.js +1 -0
  432. package/codeyam-cli/src/webserver/build/client/assets/api.editor-load-commit-l0sNRNKZ.js +1 -0
  433. package/codeyam-cli/src/webserver/build/client/assets/api.editor-project-info-l0sNRNKZ.js +1 -0
  434. package/codeyam-cli/src/webserver/build/client/assets/api.editor-refresh-l0sNRNKZ.js +1 -0
  435. package/codeyam-cli/src/webserver/build/client/assets/api.editor-register-scenario-l0sNRNKZ.js +1 -0
  436. package/codeyam-cli/src/webserver/build/client/assets/api.editor-rename-scenario-l0sNRNKZ.js +1 -0
  437. package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-seed-state-l0sNRNKZ.js +1 -0
  438. package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-coverage-l0sNRNKZ.js +1 -0
  439. package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-data-l0sNRNKZ.js +1 -0
  440. package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-image._-l0sNRNKZ.js +1 -0
  441. package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-prompt-l0sNRNKZ.js +1 -0
  442. package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenarios-l0sNRNKZ.js +1 -0
  443. package/codeyam-cli/src/webserver/build/client/assets/api.editor-session-l0sNRNKZ.js +1 -0
  444. package/codeyam-cli/src/webserver/build/client/assets/api.editor-switch-scenario-l0sNRNKZ.js +1 -0
  445. package/codeyam-cli/src/webserver/build/client/assets/api.editor-test-results-l0sNRNKZ.js +1 -0
  446. package/codeyam-cli/src/webserver/build/client/assets/api.rule-path-l0sNRNKZ.js +1 -0
  447. package/codeyam-cli/src/webserver/build/client/assets/{book-open-D4IPYH_y.js → book-open-CLaoh4ac.js} +2 -2
  448. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-CG65viiV.js → chevron-down-BZ2DZxbW.js} +2 -2
  449. package/codeyam-cli/src/webserver/build/client/assets/{chunk-JZWAC4HX-DB3aFuEO.js → chunk-JZWAC4HX-BBXArFPl.js} +13 -21
  450. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-igfMr5DY.js → circle-check-CT4unAk-.js} +2 -2
  451. package/codeyam-cli/src/webserver/build/client/assets/{copy-Coc4o_8c.js → copy-zK0B6Nu-.js} +3 -3
  452. package/codeyam-cli/src/webserver/build/client/assets/createLucideIcon-DJB0YQJL.js +41 -0
  453. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-CkXFP_i-.js +1 -0
  454. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-DPw7NZHc.js +1 -0
  455. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DmBK1JBK.js +58 -0
  456. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DBa7T2FK.js +41 -0
  457. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-B0h9AqE6.js → entity._sha._-BqAN7hyG.js} +11 -11
  458. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-BOi8kpwd.js +6 -0
  459. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-Dg1NhIms.js +6 -0
  460. package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-CJX6kkkV.js +6 -0
  461. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-PePWg17F.js → entity._sha_.edit._scenarioId-BhVjZhKg.js} +2 -2
  462. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-I-Wo99C_.js → entry.client-_gzKltPN.js} +6 -6
  463. package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-Daa96Fr1.js +1 -0
  464. package/codeyam-cli/src/webserver/build/client/assets/files-CV_17tZS.js +1 -0
  465. package/codeyam-cli/src/webserver/build/client/assets/git-D-YXmMbR.js +1 -0
  466. package/codeyam-cli/src/webserver/build/client/assets/globals-CGrDAxj0.css +1 -0
  467. package/codeyam-cli/src/webserver/build/client/assets/index-Blo6EK8G.js +15 -0
  468. package/codeyam-cli/src/webserver/build/client/assets/{index-CUM5iXwc.js → index-BsX0F-9C.js} +1 -1
  469. package/codeyam-cli/src/webserver/build/client/assets/{index-_417gcQW.js → index-CCrgCshv.js} +1 -1
  470. package/codeyam-cli/src/webserver/build/client/assets/jsx-runtime-D_zvdyIk.js +9 -0
  471. package/codeyam-cli/src/webserver/build/client/assets/labs-Byazq8Pv.js +1 -0
  472. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-TzRHMVog.js → loader-circle-DVQ0oHR7.js} +2 -2
  473. package/codeyam-cli/src/webserver/build/client/assets/manifest-b3f77062.js +1 -0
  474. package/codeyam-cli/src/webserver/build/client/assets/memory-b-VmA2Vj.js +101 -0
  475. package/codeyam-cli/src/webserver/build/client/assets/{pause-hjzB7t2z.js → pause-DGcndCAa.js} +3 -3
  476. package/codeyam-cli/src/webserver/build/client/assets/root-D5Zi3U2Z.js +67 -0
  477. package/codeyam-cli/src/webserver/build/client/assets/{search-DcAwD_Ln.js → search-C0Uw0bcK.js} +2 -2
  478. package/codeyam-cli/src/webserver/build/client/assets/settings-OoNgHIfW.js +1 -0
  479. package/codeyam-cli/src/webserver/build/client/assets/simulations-Bcemfu8a.js +1 -0
  480. package/codeyam-cli/src/webserver/build/client/assets/{terminal-DbEAHMbA.js → terminal-BgMmG7R9.js} +3 -3
  481. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-CAD5b1o_.js → triangle-alert-Cs87hJYK.js} +2 -2
  482. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-BR3Rs7JY.js +1 -0
  483. package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-BxxP_XF9.js +2 -0
  484. package/codeyam-cli/src/webserver/build/client/assets/useReportContext-BermyNU5.js +1 -0
  485. package/codeyam-cli/src/webserver/build/client/assets/useToast-a_QN_W9_.js +1 -0
  486. package/codeyam-cli/src/webserver/build/client/assets/xterm-BqvuqXEL.js +27 -0
  487. package/codeyam-cli/src/webserver/build/client/sound-test.html +98 -0
  488. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-yTyb36j3.js +13 -0
  489. package/codeyam-cli/src/webserver/build/server/assets/index-Cr7d_IsG.js +1 -0
  490. package/codeyam-cli/src/webserver/build/server/assets/init-M_wqNAfu.js +10 -0
  491. package/codeyam-cli/src/webserver/build/server/assets/progress-CHTtrxFG.js +1 -0
  492. package/codeyam-cli/src/webserver/build/server/assets/server-build-_ybRgrlc.js +551 -0
  493. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  494. package/codeyam-cli/src/webserver/build-info.json +5 -5
  495. package/codeyam-cli/src/webserver/devServer.js +39 -5
  496. package/codeyam-cli/src/webserver/devServer.js.map +1 -1
  497. package/codeyam-cli/src/webserver/editorProxy.js +959 -0
  498. package/codeyam-cli/src/webserver/editorProxy.js.map +1 -0
  499. package/codeyam-cli/src/webserver/idleDetector.js +73 -0
  500. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -0
  501. package/codeyam-cli/src/webserver/mockStateEvents.js +28 -0
  502. package/codeyam-cli/src/webserver/mockStateEvents.js.map +1 -0
  503. package/codeyam-cli/src/webserver/public/sound-test.html +98 -0
  504. package/codeyam-cli/src/webserver/scripts/codeyam-preload.mjs +414 -0
  505. package/codeyam-cli/src/webserver/scripts/journalCapture.ts +230 -0
  506. package/codeyam-cli/src/webserver/server.js +309 -1
  507. package/codeyam-cli/src/webserver/server.js.map +1 -1
  508. package/codeyam-cli/src/webserver/terminalServer.js +831 -0
  509. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -0
  510. package/codeyam-cli/templates/chrome-extension-react/EXTENSION_SETUP.md +75 -0
  511. package/codeyam-cli/templates/chrome-extension-react/README.md +46 -0
  512. package/codeyam-cli/templates/chrome-extension-react/gitignore +15 -0
  513. package/codeyam-cli/templates/chrome-extension-react/index.html +12 -0
  514. package/codeyam-cli/templates/chrome-extension-react/package.json +27 -0
  515. package/codeyam-cli/templates/chrome-extension-react/popup.html +12 -0
  516. package/codeyam-cli/templates/chrome-extension-react/public/manifest.json +15 -0
  517. package/codeyam-cli/templates/chrome-extension-react/src/background/service-worker.ts +7 -0
  518. package/codeyam-cli/templates/chrome-extension-react/src/globals.css +6 -0
  519. package/codeyam-cli/templates/chrome-extension-react/src/lib/storage.ts +37 -0
  520. package/codeyam-cli/templates/chrome-extension-react/src/popup/App.tsx +12 -0
  521. package/codeyam-cli/templates/chrome-extension-react/src/popup/main.tsx +10 -0
  522. package/codeyam-cli/templates/chrome-extension-react/tsconfig.json +24 -0
  523. package/codeyam-cli/templates/chrome-extension-react/vite.config.ts +41 -0
  524. package/codeyam-cli/templates/codeyam-editor-claude.md +147 -0
  525. package/codeyam-cli/templates/editor-step-hook.py +321 -0
  526. package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +89 -0
  527. package/codeyam-cli/templates/expo-react-native/README.md +41 -0
  528. package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +33 -0
  529. package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +12 -0
  530. package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +12 -0
  531. package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +12 -0
  532. package/codeyam-cli/templates/expo-react-native/app.json +18 -0
  533. package/codeyam-cli/templates/expo-react-native/babel.config.js +9 -0
  534. package/codeyam-cli/templates/expo-react-native/gitignore +12 -0
  535. package/codeyam-cli/templates/expo-react-native/global.css +3 -0
  536. package/codeyam-cli/templates/expo-react-native/lib/storage.ts +32 -0
  537. package/codeyam-cli/templates/expo-react-native/metro.config.js +6 -0
  538. package/codeyam-cli/templates/expo-react-native/nativewind-env.d.ts +1 -0
  539. package/codeyam-cli/templates/expo-react-native/package.json +38 -0
  540. package/codeyam-cli/templates/expo-react-native/tailwind.config.js +10 -0
  541. package/codeyam-cli/templates/expo-react-native/tsconfig.json +10 -0
  542. package/codeyam-cli/templates/hooks/staleness-check.sh +43 -0
  543. package/codeyam-cli/templates/isolation-route/next-app.tsx.template +80 -0
  544. package/codeyam-cli/templates/isolation-route/next-pages.tsx.template +79 -0
  545. package/codeyam-cli/templates/isolation-route/vite-react.tsx.template +78 -0
  546. package/codeyam-cli/templates/msw/browser-setup.ts.template +47 -0
  547. package/codeyam-cli/templates/msw/handler-router.ts.template +47 -0
  548. package/codeyam-cli/templates/msw/server-setup.ts.template +52 -0
  549. package/codeyam-cli/templates/nextjs-prisma-sqlite/AUTH_PATTERNS.md +308 -0
  550. package/codeyam-cli/templates/nextjs-prisma-sqlite/AUTH_UPGRADE.md +304 -0
  551. package/codeyam-cli/templates/nextjs-prisma-sqlite/DATABASE.md +126 -0
  552. package/codeyam-cli/templates/nextjs-prisma-sqlite/FEATURE_PATTERNS.md +37 -0
  553. package/codeyam-cli/templates/nextjs-prisma-sqlite/README.md +53 -0
  554. package/codeyam-cli/templates/nextjs-prisma-sqlite/app/api/todos/route.ts +17 -0
  555. package/codeyam-cli/templates/nextjs-prisma-sqlite/app/codeyam-isolate/layout.tsx +12 -0
  556. package/codeyam-cli/templates/nextjs-prisma-sqlite/app/globals.css +26 -0
  557. package/codeyam-cli/templates/nextjs-prisma-sqlite/app/layout.tsx +34 -0
  558. package/codeyam-cli/templates/nextjs-prisma-sqlite/app/lib/prisma.ts +24 -0
  559. package/codeyam-cli/templates/nextjs-prisma-sqlite/app/page.tsx +10 -0
  560. package/codeyam-cli/templates/nextjs-prisma-sqlite/env +4 -0
  561. package/codeyam-cli/templates/nextjs-prisma-sqlite/eslint.config.mjs +11 -0
  562. package/codeyam-cli/templates/nextjs-prisma-sqlite/gitignore +64 -0
  563. package/codeyam-cli/templates/nextjs-prisma-sqlite/next.config.ts +14 -0
  564. package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +39 -0
  565. package/codeyam-cli/templates/nextjs-prisma-sqlite/postcss.config.mjs +7 -0
  566. package/codeyam-cli/templates/nextjs-prisma-sqlite/prisma/schema.prisma +27 -0
  567. package/codeyam-cli/templates/nextjs-prisma-sqlite/prisma/seed.ts +40 -0
  568. package/codeyam-cli/templates/nextjs-prisma-sqlite/prisma.config.ts +12 -0
  569. package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +127 -0
  570. package/codeyam-cli/templates/nextjs-prisma-sqlite/tsconfig.json +34 -0
  571. package/codeyam-cli/templates/nextjs-prisma-sqlite/vitest.config.ts +13 -0
  572. package/codeyam-cli/templates/nextjs-prisma-supabase/README.md +52 -0
  573. package/codeyam-cli/templates/nextjs-prisma-supabase/SUPABASE_SETUP.md +104 -0
  574. package/codeyam-cli/templates/nextjs-prisma-supabase/app/api/todos/route.ts +17 -0
  575. package/codeyam-cli/templates/nextjs-prisma-supabase/app/globals.css +26 -0
  576. package/codeyam-cli/templates/nextjs-prisma-supabase/app/layout.tsx +34 -0
  577. package/codeyam-cli/templates/nextjs-prisma-supabase/app/lib/prisma.ts +20 -0
  578. package/codeyam-cli/templates/nextjs-prisma-supabase/app/lib/supabase.ts +12 -0
  579. package/codeyam-cli/templates/nextjs-prisma-supabase/app/page.tsx +10 -0
  580. package/codeyam-cli/templates/nextjs-prisma-supabase/env +9 -0
  581. package/codeyam-cli/templates/nextjs-prisma-supabase/eslint.config.mjs +11 -0
  582. package/codeyam-cli/templates/nextjs-prisma-supabase/gitignore +40 -0
  583. package/codeyam-cli/templates/nextjs-prisma-supabase/next.config.ts +11 -0
  584. package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +37 -0
  585. package/codeyam-cli/templates/nextjs-prisma-supabase/postcss.config.mjs +7 -0
  586. package/codeyam-cli/templates/nextjs-prisma-supabase/prisma/schema.prisma +27 -0
  587. package/codeyam-cli/templates/nextjs-prisma-supabase/prisma/seed.ts +39 -0
  588. package/codeyam-cli/templates/nextjs-prisma-supabase/prisma.config.ts +12 -0
  589. package/codeyam-cli/templates/nextjs-prisma-supabase/tsconfig.json +34 -0
  590. package/codeyam-cli/templates/prompts/conversation-guidance.txt +44 -0
  591. package/codeyam-cli/templates/prompts/conversation-prompt.txt +28 -0
  592. package/codeyam-cli/templates/prompts/interruption-prompt.txt +31 -0
  593. package/codeyam-cli/templates/prompts/stale-rules-prompt.txt +24 -0
  594. package/codeyam-cli/templates/rule-notification-hook.py +44 -17
  595. package/codeyam-cli/templates/rule-reflection-hook.py +25 -5
  596. package/codeyam-cli/templates/rules-instructions.md +34 -88
  597. package/codeyam-cli/templates/seed-adapters/supabase.ts +282 -0
  598. package/codeyam-cli/templates/skills/codeyam-dev-mode/SKILL.md +237 -0
  599. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +211 -0
  600. package/codeyam-cli/templates/{codeyam-memory.md → skills/codeyam-memory/SKILL.md} +215 -0
  601. package/codeyam-cli/templates/skills/codeyam-memory/scripts/holistic-analysis/deprecated-prompt.md +100 -0
  602. package/codeyam-cli/templates/skills/codeyam-memory/scripts/holistic-analysis/detect-deprecated-patterns.mjs +139 -0
  603. package/codeyam-cli/templates/skills/codeyam-memory/scripts/holistic-analysis/find-exports.mjs +52 -0
  604. package/codeyam-cli/templates/skills/codeyam-memory/scripts/holistic-analysis/misleading-api-prompt.md +117 -0
  605. package/codeyam-cli/templates/skills/codeyam-memory/scripts/lib/read-json-field.mjs +61 -0
  606. package/codeyam-cli/templates/skills/codeyam-memory/scripts/lib/ripgrep-fallback.mjs +155 -0
  607. package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/analyze-prompt.md +46 -0
  608. package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/cleanup.mjs +13 -0
  609. package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/filter-session.mjs +95 -0
  610. package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/preprocess.mjs +160 -0
  611. package/codeyam-cli/templates/{codeyam-new-rule.md → skills/codeyam-new-rule/SKILL.md} +0 -2
  612. package/package.json +21 -14
  613. package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js +22 -4
  614. package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js.map +1 -1
  615. package/packages/ai/src/lib/completionCall.js +10 -7
  616. package/packages/ai/src/lib/completionCall.js.map +1 -1
  617. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +234 -3
  618. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  619. package/packages/ai/src/lib/dataStructure/helpers/coercePrimitivesToArraysBySchema.js +54 -0
  620. package/packages/ai/src/lib/dataStructure/helpers/coercePrimitivesToArraysBySchema.js.map +1 -0
  621. package/packages/ai/src/lib/dataStructure/helpers/stripNullableMarkers.js +34 -0
  622. package/packages/ai/src/lib/dataStructure/helpers/stripNullableMarkers.js.map +1 -0
  623. package/packages/ai/src/lib/dataStructureChunking.js +9 -5
  624. package/packages/ai/src/lib/dataStructureChunking.js.map +1 -1
  625. package/packages/ai/src/lib/generateEntityScenarioData.js +57 -2
  626. package/packages/ai/src/lib/generateEntityScenarioData.js.map +1 -1
  627. package/packages/ai/src/lib/generateExecutionFlows.js +81 -11
  628. package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
  629. package/packages/analyze/src/lib/ProjectAnalyzer.js +13 -4
  630. package/packages/analyze/src/lib/ProjectAnalyzer.js.map +1 -1
  631. package/packages/analyze/src/lib/asts/index.js +4 -2
  632. package/packages/analyze/src/lib/asts/index.js.map +1 -1
  633. package/packages/analyze/src/lib/asts/nodes/getNodeType.js +1 -0
  634. package/packages/analyze/src/lib/asts/nodes/getNodeType.js.map +1 -1
  635. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +8 -1
  636. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  637. package/packages/analyze/src/lib/files/analyze/dependencyResolver.js +0 -5
  638. package/packages/analyze/src/lib/files/analyze/dependencyResolver.js.map +1 -1
  639. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +9 -0
  640. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
  641. package/packages/analyze/src/lib/files/scenarios/TransformationTracer.js +54 -27
  642. package/packages/analyze/src/lib/files/scenarios/TransformationTracer.js.map +1 -1
  643. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +65 -0
  644. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  645. package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js +0 -40
  646. package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js.map +1 -1
  647. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +18 -4
  648. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  649. package/packages/database/index.js +1 -0
  650. package/packages/database/index.js.map +1 -1
  651. package/packages/database/src/lib/kysely/db.js +5 -0
  652. package/packages/database/src/lib/kysely/db.js.map +1 -1
  653. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +149 -0
  654. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -0
  655. package/packages/database/src/lib/loadCommits.js +23 -13
  656. package/packages/database/src/lib/loadCommits.js.map +1 -1
  657. package/packages/database/src/lib/loadEntities.js +0 -6
  658. package/packages/database/src/lib/loadEntities.js.map +1 -1
  659. package/packages/database/src/lib/loadReadyToBeCapturedAnalyses.js +1 -4
  660. package/packages/database/src/lib/loadReadyToBeCapturedAnalyses.js.map +1 -1
  661. package/packages/database/src/lib/updateCommitMetadata.js +76 -90
  662. package/packages/database/src/lib/updateCommitMetadata.js.map +1 -1
  663. package/packages/database/src/lib/updateFreshAnalysisStatus.js +41 -30
  664. package/packages/database/src/lib/updateFreshAnalysisStatus.js.map +1 -1
  665. package/packages/database/src/lib/updateFreshAnalysisStatusWithScenarios.js +68 -57
  666. package/packages/database/src/lib/updateFreshAnalysisStatusWithScenarios.js.map +1 -1
  667. package/packages/generate/src/lib/componentScenarioPage/generateScenarioClientWrapper.js +29 -1
  668. package/packages/generate/src/lib/componentScenarioPage/generateScenarioClientWrapper.js.map +1 -1
  669. package/packages/generate/src/lib/componentScenarioPage/getIFrameMessageListenerCode.js +33 -5
  670. package/packages/generate/src/lib/componentScenarioPage/getIFrameMessageListenerCode.js.map +1 -1
  671. package/packages/types/src/enums/ProjectFramework.js +2 -0
  672. package/packages/types/src/enums/ProjectFramework.js.map +1 -1
  673. package/packages/utils/src/lib/fs/rsyncCopy.js +6 -2
  674. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  675. package/scripts/npm-post-install.cjs +34 -0
  676. package/codeyam-cli/src/commands/detect-universal-mocks.js +0 -120
  677. package/codeyam-cli/src/commands/detect-universal-mocks.js.map +0 -1
  678. package/codeyam-cli/src/commands/list.js +0 -31
  679. package/codeyam-cli/src/commands/list.js.map +0 -1
  680. package/codeyam-cli/src/commands/webapp-info.js +0 -146
  681. package/codeyam-cli/src/commands/webapp-info.js.map +0 -1
  682. package/codeyam-cli/src/utils/universal-mocks.js +0 -152
  683. package/codeyam-cli/src/utils/universal-mocks.js.map +0 -1
  684. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-jNYXRRNI.js +0 -1
  685. package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-CvzqMxcu.js +0 -1
  686. package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-EhOseatT.js +0 -34
  687. package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-yjIHlOGa.js +0 -25
  688. package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-Cq5o8jL4.js +0 -3
  689. package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-CwZrv-Ok.js +0 -1
  690. package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-CDpEprKa.js +0 -1
  691. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-DHKuQSmR.js +0 -17
  692. package/codeyam-cli/src/webserver/build/client/assets/createLucideIcon-D1zB-pYc.js +0 -21
  693. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-JTAjQ54M.js +0 -1
  694. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-DjLxr2JB.js +0 -6
  695. package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-CtYowLOt.js +0 -6
  696. package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-9sMMAiWJ.js +0 -1
  697. package/codeyam-cli/src/webserver/build/client/assets/files-Co65J0s3.js +0 -1
  698. package/codeyam-cli/src/webserver/build/client/assets/git-BdHOxVfg.js +0 -15
  699. package/codeyam-cli/src/webserver/build/client/assets/globals-CCgBKWy4.css +0 -1
  700. package/codeyam-cli/src/webserver/build/client/assets/labs-BK0C1H1T.js +0 -1
  701. package/codeyam-cli/src/webserver/build/client/assets/manifest-7d29f0c4.js +0 -1
  702. package/codeyam-cli/src/webserver/build/client/assets/memory-CzZySbBE.js +0 -78
  703. package/codeyam-cli/src/webserver/build/client/assets/root-COPwrT8R.js +0 -62
  704. package/codeyam-cli/src/webserver/build/client/assets/settings-CclxrcPK.js +0 -1
  705. package/codeyam-cli/src/webserver/build/client/assets/simulations-DVNJVQgD.js +0 -1
  706. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-BqgrAzs3.js +0 -1
  707. package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-DAFqfEDH.js +0 -2
  708. package/codeyam-cli/src/webserver/build/client/assets/useReportContext-DZlYx2c4.js +0 -1
  709. package/codeyam-cli/src/webserver/build/client/assets/useToast-ihdMtlf6.js +0 -1
  710. package/codeyam-cli/src/webserver/build/server/assets/index-ChT1lWao.js +0 -1
  711. package/codeyam-cli/src/webserver/build/server/assets/server-build-CtNSi7yC.js +0 -259
  712. package/codeyam-cli/templates/codeyam-stop-hook.sh +0 -284
  713. package/scripts/finalize-analyzer.cjs +0 -13
  714. /package/codeyam-cli/templates/{codeyam-diagnose.md → commands/codeyam-diagnose.md} +0 -0
  715. /package/codeyam-cli/templates/{codeyam-debug.md → skills/codeyam-debug/SKILL.md} +0 -0
  716. /package/codeyam-cli/templates/{codeyam-setup.md → skills/codeyam-setup/SKILL.md} +0 -0
  717. /package/codeyam-cli/templates/{codeyam-sim.md → skills/codeyam-sim/SKILL.md} +0 -0
  718. /package/codeyam-cli/templates/{codeyam-test.md → skills/codeyam-test/SKILL.md} +0 -0
  719. /package/codeyam-cli/templates/{codeyam-verify.md → skills/codeyam-verify/SKILL.md} +0 -0
@@ -0,0 +1,4357 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import { fileURLToPath } from 'url';
5
+ import chalk from 'chalk';
6
+ import { runAnalysisForEntities } from "../utils/analysisRunner.js";
7
+ import { error as errorLog, ProgressReporter, withoutSpinner, } from "../utils/progress.js";
8
+ import { initializeEnvironment, requireBranchAndProject, testEnvironment, } from "../utils/database.js";
9
+ import { loadAnalyses, loadEntities, updateProjectMetadata, } from "../../../packages/database/index.js";
10
+ import { IS_INTERNAL_BUILD } from "../utils/buildFlags.js";
11
+ import { startBackgroundServer } from "../utils/backgroundServer.js";
12
+ import { installClaudeCodeSkills } from "../utils/install-skills.js";
13
+ import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
14
+ import { ensureAnalyzerFinalized, } from "../utils/analyzerFinalization.js";
15
+ import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
16
+ import { getProjectRoot as getStateProjectRoot } from "../state.js";
17
+ import initCommand from "./init.js";
18
+ import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
19
+ import { clearEditorState, clearEditorUserPrompt, validateStepTransition, backfillEntityShaOnScenarios, } from "../utils/editorScenarios.js";
20
+ import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } from "../utils/editorSeedAdapter.js";
21
+ import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
22
+ import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
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";
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = path.dirname(__filename);
28
+ const STEP_LABELS = {
29
+ 1: 'Plan',
30
+ 2: 'Prototype',
31
+ 3: 'Confirm',
32
+ 4: 'Deconstruct',
33
+ 5: 'Extract',
34
+ 6: 'Glossary',
35
+ 7: 'Analyze',
36
+ 8: 'App Scenarios',
37
+ 9: 'User Scenarios',
38
+ 10: 'Verify',
39
+ 11: 'Journal',
40
+ 12: 'Review',
41
+ 13: 'Present',
42
+ 14: 'Commit',
43
+ 15: 'Finalize',
44
+ 16: 'Push',
45
+ };
46
+ const MIGRATION_STEP_LABELS = {
47
+ 1: 'Survey',
48
+ 2: 'App Scenarios',
49
+ 3: 'Component Scenarios',
50
+ 4: 'Preview',
51
+ 5: 'Discuss',
52
+ 6: 'Decompose',
53
+ 7: 'Extract',
54
+ 8: 'Recapture',
55
+ 9: 'Journal',
56
+ 10: 'Present',
57
+ };
58
+ /**
59
+ * Append a JSONL log entry to .codeyam/logs/editor-log.jsonl
60
+ */
61
+ function logEvent(root, event, data) {
62
+ try {
63
+ const logsDir = path.join(root, '.codeyam', 'logs');
64
+ fs.mkdirSync(logsDir, { recursive: true });
65
+ const logPath = path.join(logsDir, 'editor-log.jsonl');
66
+ const entry = JSON.stringify({
67
+ ts: new Date().toISOString(),
68
+ event,
69
+ ...data,
70
+ });
71
+ fs.appendFileSync(logPath, entry + '\n', 'utf8');
72
+ }
73
+ catch {
74
+ // Logging is best-effort
75
+ }
76
+ }
77
+ /**
78
+ * Read the design system file if it exists.
79
+ */
80
+ function readDesignSystem(root) {
81
+ const designSystemPath = path.join(root, '.codeyam', 'design-system.md');
82
+ try {
83
+ return fs.readFileSync(designSystemPath, 'utf8');
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ /**
90
+ * Get the project root (where .codeyam/ lives) or cwd.
91
+ */
92
+ function getProjectRoot() {
93
+ return process.env.CODEYAM_ROOT_PATH || process.cwd();
94
+ }
95
+ /**
96
+ * Path to the editor step state file.
97
+ */
98
+ function getStatePath(root) {
99
+ return path.join(root, '.codeyam', 'editor-step.json');
100
+ }
101
+ /**
102
+ * Read the current editor state, or null if none.
103
+ */
104
+ function readState(root) {
105
+ const statePath = getStatePath(root);
106
+ try {
107
+ const content = fs.readFileSync(statePath, 'utf8');
108
+ return JSON.parse(content);
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ }
114
+ /**
115
+ * Write the editor state.
116
+ */
117
+ function writeState(root, state) {
118
+ const statePath = getStatePath(root);
119
+ const dir = path.dirname(statePath);
120
+ fs.mkdirSync(dir, { recursive: true });
121
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
122
+ }
123
+ /**
124
+ * Clear the editor state (for starting a new feature).
125
+ * Does NOT clear the user prompt file — that's handled separately
126
+ * via clearEditorUserPrompt when the previous feature commits.
127
+ */
128
+ function clearState(root) {
129
+ clearEditorState(root);
130
+ }
131
+ /**
132
+ * Check if a project has been scaffolded (package.json exists).
133
+ */
134
+ function hasProject(root) {
135
+ return fs.existsSync(path.join(root, 'package.json'));
136
+ }
137
+ /**
138
+ * Get the CodeYam server port.
139
+ * Reads from the server state file first (actual running port), then falls
140
+ * back to CODEYAM_PORT env var, then to the default 3111.
141
+ * This is critical when the server auto-selected a different port because
142
+ * the default was occupied by another project's server.
143
+ */
144
+ function getServerPort() {
145
+ try {
146
+ const root = getProjectRoot();
147
+ if (root) {
148
+ const statePath = path.join(root, '.codeyam', 'server.json');
149
+ if (fs.existsSync(statePath)) {
150
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
151
+ if (state.port)
152
+ return String(state.port);
153
+ }
154
+ }
155
+ }
156
+ catch {
157
+ // Fall through to defaults
158
+ }
159
+ return process.env.CODEYAM_PORT || '3111';
160
+ }
161
+ /**
162
+ * Read the project's default dimension name and available screen size names.
163
+ * Used by step instructions to show project-specific dimension examples
164
+ * instead of hardcoded "Desktop".
165
+ */
166
+ function getProjectDimensions(root) {
167
+ try {
168
+ const configPath = path.join(root, '.codeyam', 'config.json');
169
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
170
+ const defaultName = config.defaultScreenSize?.name ||
171
+ (config.screenSizes ? Object.keys(config.screenSizes)[0] : null) ||
172
+ 'Desktop';
173
+ const names = config.screenSizes ? Object.keys(config.screenSizes) : [];
174
+ return { defaultName, names };
175
+ }
176
+ catch {
177
+ return { defaultName: 'Desktop', names: [] };
178
+ }
179
+ }
180
+ /**
181
+ * Print dimension guidance when the project has multiple screen sizes.
182
+ * Tells Claude to pick the right dimension for the content being previewed
183
+ * instead of blindly using the default for everything.
184
+ */
185
+ function printDimensionGuidance(defaultName, names) {
186
+ if (names.length <= 1)
187
+ return;
188
+ // Find a non-default dimension to suggest as the "other" option
189
+ const otherName = names.find((n) => n !== defaultName) || names[1];
190
+ console.log(chalk.yellow(` IMPORTANT: Choose the dimension that matches what you're previewing. Available: ${names.map((n) => `"${n}"`).join(', ')}.`));
191
+ console.log(chalk.yellow(` Do NOT always use "${defaultName}". Full pages, standalone views, and desktop layouts should use "${otherName}".`));
192
+ console.log(chalk.yellow(` Think about the CONTENT — a full-page library view needs a large viewport, not a popup-sized one.`));
193
+ }
194
+ /**
195
+ * Print a checklist item.
196
+ * Inline backtick-wrapped text is highlighted in cyan for visibility.
197
+ */
198
+ function checkbox(text) {
199
+ // Highlight `backtick-wrapped` segments in cyan
200
+ const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
201
+ console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
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
+ }
343
+ /**
344
+ * Print a section header.
345
+ */
346
+ function stepHeader(step, title, feature) {
347
+ console.log();
348
+ console.log(chalk.bold.cyan(`━━━ Step ${step}: ${title} ━━━`));
349
+ if (feature) {
350
+ console.log(chalk.dim(`Feature: "${feature}"`));
351
+ }
352
+ console.log();
353
+ }
354
+ /**
355
+ * Print a colored progress tracker showing all 16 steps.
356
+ * Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
357
+ */
358
+ function printProgressTracker(current) {
359
+ console.log();
360
+ console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
361
+ for (let i = 1; i <= 16; i++) {
362
+ const label = STEP_LABELS[i];
363
+ const num = i < 10 ? ` ${i}` : `${i}`;
364
+ const content = `${num}. ${label.padEnd(28)}`;
365
+ if (i < current) {
366
+ console.log(chalk.dim(' │') + chalk.green(` ✓ ${content}`) + chalk.dim('│'));
367
+ }
368
+ else if (i === current) {
369
+ console.log(chalk.dim(' │') + chalk.bold.cyan(` → ${content}`) + chalk.dim('│'));
370
+ }
371
+ else {
372
+ console.log(chalk.dim(` │ ○ ${content}│`));
373
+ }
374
+ }
375
+ console.log(chalk.dim(' └─────────────────────────────────────┘'));
376
+ }
377
+ /**
378
+ * Print a hard STOP gate directing to the next command.
379
+ *
380
+ * Options:
381
+ * - confirm: true → step requires user confirmation before proceeding (steps 1, 3, 11)
382
+ */
383
+ function stopGate(current, opts) {
384
+ console.log();
385
+ console.log(chalk.bold.red('━━━ STOP ━━━'));
386
+ console.log();
387
+ console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
388
+ if (opts?.confirm) {
389
+ console.log();
390
+ console.log(chalk.yellow('Wait for user confirmation before moving on.'));
391
+ }
392
+ console.log();
393
+ console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
394
+ printProgressTracker(current);
395
+ console.log();
396
+ console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
397
+ console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
398
+ console.log();
399
+ if (current < 16) {
400
+ console.log(chalk.green('When done, run: ') +
401
+ chalk.bold(`codeyam editor ${current + 1}`));
402
+ }
403
+ else {
404
+ console.log(chalk.green('Feature complete! Run: ') +
405
+ chalk.bold('codeyam editor 1') +
406
+ chalk.green(' to start the next feature'));
407
+ }
408
+ console.log();
409
+ }
410
+ /**
411
+ * Print a RESUMING header with step-specific investigation instructions.
412
+ * Called when a step is re-entered (prevState.step === current step).
413
+ */
414
+ function printResumptionHeader(step) {
415
+ const port = getServerPort();
416
+ const checks = {
417
+ 1: [
418
+ 'Check if plan was already written to context file:\n `cat .codeyam/editor-mode-context.md`',
419
+ ],
420
+ 2: ['Check if project files already exist:\n `ls package.json src/`'],
421
+ 3: ['This is a confirmation step — just re-present to user'],
422
+ 4: [
423
+ 'Check if extraction plan already exists in context file:\n `cat .codeyam/editor-mode-context.md`',
424
+ ],
425
+ 5: [
426
+ 'Check if components/functions already extracted:\n `ls src/components/ src/lib/`',
427
+ ],
428
+ 6: [
429
+ 'Check if glossary already populated:\n `cat .codeyam/glossary.json`',
430
+ ],
431
+ 7: ['Check if isolation routes and registered scenarios already exist'],
432
+ 8: ['Check existing scenarios:\n `codeyam editor scenarios`'],
433
+ 9: [
434
+ 'Check existing user-persona scenarios:\n `codeyam editor scenarios`',
435
+ ],
436
+ 10: ['Re-verify is safe to repeat — just re-run the checks'],
437
+ 11: [
438
+ 'Check if a journal entry already exists for this feature:\n `codeyam editor journal-list`',
439
+ 'If an entry exists, use PATCH to update it — do NOT create a new one',
440
+ ],
441
+ 12: ['Re-verify is safe to repeat — just re-run the checks'],
442
+ 13: ['Check if commit already made:\n `git log --oneline -3`'],
443
+ 14: ['Check if commit already made:\n `git log --oneline -3`'],
444
+ 15: [
445
+ 'Check if journal was already updated with commit SHA:\n `codeyam editor journal-list`',
446
+ 'Check if commit was already amended:\n `git log --oneline -3`',
447
+ ],
448
+ 16: [
449
+ 'Check if already pushed:\n `git remote -v && git log --oneline origin/HEAD..HEAD 2>/dev/null`',
450
+ ],
451
+ };
452
+ const label = STEP_LABELS[step] || 'Unknown';
453
+ console.log(chalk.bold.yellow(`━━━ RESUMING Step ${step}: ${label} ━━━`));
454
+ console.log(chalk.yellow('This step was already started. Before repeating any actions, investigate:'));
455
+ const items = checks[step] || [];
456
+ for (const item of items) {
457
+ checkbox(item);
458
+ }
459
+ console.log();
460
+ }
461
+ function captureOutput(fn) {
462
+ const stdoutWrite = process.stdout.write.bind(process.stdout);
463
+ const stderrWrite = process.stderr.write.bind(process.stderr);
464
+ const chunks = [];
465
+ const captureWrite = (chunk, encoding, cb) => {
466
+ const text = typeof chunk === 'string'
467
+ ? chunk
468
+ : chunk instanceof Buffer
469
+ ? chunk.toString(typeof encoding === 'string' ? encoding : undefined)
470
+ : String(chunk);
471
+ chunks.push(text);
472
+ if (typeof encoding === 'function') {
473
+ encoding();
474
+ }
475
+ if (typeof cb === 'function') {
476
+ cb();
477
+ }
478
+ return true;
479
+ };
480
+ process.stdout.write = captureWrite;
481
+ process.stderr.write = captureWrite;
482
+ try {
483
+ fn();
484
+ }
485
+ finally {
486
+ process.stdout.write = stdoutWrite;
487
+ process.stderr.write = stderrWrite;
488
+ }
489
+ return chunks.join('');
490
+ }
491
+ function withTempRoot(hasProject, fn) {
492
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'codeyam-editor-debug-'));
493
+ if (hasProject) {
494
+ fs.writeFileSync(path.join(root, 'package.json'), '{"name":"codeyam-editor-debug","private":true}', 'utf8');
495
+ }
496
+ try {
497
+ return fn(root);
498
+ }
499
+ finally {
500
+ try {
501
+ fs.rmSync(root, { recursive: true, force: true });
502
+ }
503
+ catch {
504
+ // Best-effort cleanup
505
+ }
506
+ }
507
+ }
508
+ function makeState(step, feature) {
509
+ const now = new Date().toISOString();
510
+ return {
511
+ feature,
512
+ step,
513
+ label: STEP_LABELS[step],
514
+ startedAt: now,
515
+ featureStartedAt: now,
516
+ };
517
+ }
518
+ function normalizeDebugTarget(raw) {
519
+ const value = raw.trim().toLowerCase();
520
+ if (!value)
521
+ return '';
522
+ if (value === 'setup')
523
+ return 'setup';
524
+ if (value === 'overview')
525
+ return 'overview';
526
+ if (value === 'overview-with-state' || value === 'overview-state') {
527
+ return 'overview-with-state';
528
+ }
529
+ if (/^\d+$/.test(value)) {
530
+ return `step-${value}`;
531
+ }
532
+ if (/^step-?\d+$/.test(value)) {
533
+ const num = value.replace('step', '').replace('-', '');
534
+ return `step-${num}`;
535
+ }
536
+ return value;
537
+ }
538
+ function parseDebugTargets(target) {
539
+ if (!target || target.trim().toLowerCase() === 'all')
540
+ return null;
541
+ const rawTargets = target
542
+ .split(',')
543
+ .map((t) => normalizeDebugTarget(t))
544
+ .filter(Boolean);
545
+ const valid = new Set();
546
+ for (const entry of rawTargets) {
547
+ if (entry === 'setup' ||
548
+ entry === 'overview' ||
549
+ entry === 'overview-with-state') {
550
+ valid.add(entry);
551
+ continue;
552
+ }
553
+ if (entry.startsWith('step-')) {
554
+ const num = parseInt(entry.replace('step-', ''), 10);
555
+ if (!isNaN(num) && num >= 1 && num <= 16) {
556
+ valid.add(`step-${num}`);
557
+ continue;
558
+ }
559
+ }
560
+ throw new Error(`Invalid debug target: "${entry}"`);
561
+ }
562
+ return valid;
563
+ }
564
+ function writeContextSnapshot(root, outDir) {
565
+ const contextDir = path.join(outDir, 'context');
566
+ fs.mkdirSync(contextDir, { recursive: true });
567
+ const entries = [];
568
+ const skillPath = path.join(root, '.claude', 'skills', 'codeyam-editor', 'SKILL.md');
569
+ const skillFallback = path.join(__dirname, '..', '..', 'templates', 'codeyam-editor.md');
570
+ const skillSource = fs.existsSync(skillPath) ? skillPath : skillFallback;
571
+ const skillDest = path.join(contextDir, 'codeyam-editor-skill.md');
572
+ fs.copyFileSync(skillSource, skillDest);
573
+ entries.push({
574
+ label: 'codeyam-editor skill',
575
+ source: skillSource,
576
+ file: path.relative(outDir, skillDest),
577
+ status: fs.existsSync(skillPath) ? 'installed' : 'template',
578
+ });
579
+ const claudePath = path.join(root, 'CLAUDE.md');
580
+ if (fs.existsSync(claudePath)) {
581
+ const dest = path.join(contextDir, 'CLAUDE.md');
582
+ fs.copyFileSync(claudePath, dest);
583
+ entries.push({
584
+ label: 'CLAUDE.md',
585
+ source: claudePath,
586
+ file: path.relative(outDir, dest),
587
+ status: 'project',
588
+ });
589
+ }
590
+ else {
591
+ const fallback = path.join(__dirname, '..', '..', 'templates', 'codeyam-editor-claude.md');
592
+ if (fs.existsSync(fallback)) {
593
+ const dest = path.join(contextDir, 'CLAUDE.md');
594
+ fs.copyFileSync(fallback, dest);
595
+ entries.push({
596
+ label: 'CLAUDE.md',
597
+ source: fallback,
598
+ file: path.relative(outDir, dest),
599
+ status: 'template',
600
+ });
601
+ }
602
+ else {
603
+ entries.push({
604
+ label: 'CLAUDE.md',
605
+ file: path.relative(outDir, path.join(contextDir, 'CLAUDE.md')),
606
+ status: 'missing',
607
+ });
608
+ }
609
+ }
610
+ const contextPath = path.join(root, '.codeyam', 'editor-mode-context.md');
611
+ if (fs.existsSync(contextPath)) {
612
+ const dest = path.join(contextDir, 'editor-mode-context.md');
613
+ fs.copyFileSync(contextPath, dest);
614
+ entries.push({
615
+ label: 'editor-mode-context.md',
616
+ source: contextPath,
617
+ file: path.relative(outDir, dest),
618
+ status: 'project',
619
+ });
620
+ }
621
+ else {
622
+ entries.push({
623
+ label: 'editor-mode-context.md',
624
+ file: path.relative(outDir, path.join(contextDir, 'editor-mode-context.md')),
625
+ status: 'missing',
626
+ });
627
+ }
628
+ return entries;
629
+ }
630
+ // ─── Setup (no args, no project) ──────────────────────────────────────
631
+ function printSetup(root) {
632
+ const port = getServerPort();
633
+ logEvent(root, 'setup');
634
+ console.log();
635
+ console.log(chalk.bold.cyan('━━━ Editor Mode: Project Setup ━━━'));
636
+ console.log();
637
+ console.log("No project detected. Let's get started.");
638
+ console.log();
639
+ // ── Design System ────────────────────────────────────────────────
640
+ console.log(chalk.bold('Design System (ask FIRST):'));
641
+ console.log(chalk.dim(' Ask: "Do you have a design system, brand guidelines, or style preferences you\'d like me to follow?"'));
642
+ console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
643
+ console.log(chalk.yellow(' Option 1 label: "Yes, I\'ll paste my design system"'));
644
+ console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
645
+ console.log(chalk.yellow(' Option 2 label: "No, use sensible defaults"'));
646
+ console.log(chalk.dim(' → Skip'));
647
+ console.log();
648
+ console.log(chalk.bold('Checklist:'));
649
+ checkbox('Read `.codeyam/editor-mode-context.md` for session state');
650
+ checkbox('Ask the user what they want to build');
651
+ console.log();
652
+ // ── App Format Selection ───────────────────────────────────────────
653
+ console.log(chalk.bold('App Format Selection:'));
654
+ console.log(chalk.dim(' After the user describes their project, ask which app formats they want to target.'));
655
+ console.log(chalk.dim(' Use AskUserQuestion with CHECKBOXES (multiple selections allowed):'));
656
+ console.log();
657
+ for (const format of APP_FORMATS) {
658
+ console.log(chalk.yellow(` [ ] ${format.label}`) +
659
+ chalk.dim(` — ${format.description}`));
660
+ }
661
+ console.log();
662
+ // ── Tech Stack Selection ───────────────────────────────────────────
663
+ console.log(chalk.bold('Tech Stack Selection:'));
664
+ console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
665
+ console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
666
+ console.log(chalk.dim(' Show ALL matching stacks even if there is only one. Mark the recommended option.'));
667
+ console.log(chalk.dim(' Use AskUserQuestion to let the user pick one.'));
668
+ console.log();
669
+ console.log(chalk.bold(' Available Tech Stacks:'));
670
+ for (const stack of TECH_STACKS) {
671
+ const recommended = stack.recommended ? chalk.green(' (Recommended)') : '';
672
+ const formatLabels = stack.supportedFormats
673
+ .map((f) => APP_FORMATS.find((af) => af.id === f)?.label)
674
+ .join(', ');
675
+ console.log(chalk.bold.yellow(` ${stack.name}`) +
676
+ recommended +
677
+ chalk.dim(` [id: ${stack.id}]`));
678
+ console.log(chalk.dim(` ${stack.description}`));
679
+ console.log(chalk.dim(' Technologies:'));
680
+ for (const tech of stack.technologies) {
681
+ console.log(chalk.dim(` - ${tech}`));
682
+ }
683
+ console.log(chalk.dim(` Supports: ${formatLabels}`));
684
+ console.log();
685
+ }
686
+ console.log(chalk.dim(' If NO stacks match the selected formats, tell the user that support for that format is coming soon'));
687
+ console.log(chalk.dim(' and suggest they pick a supported format for now.'));
688
+ console.log();
689
+ // ── Default Screen Size Selection ──────────────────────────────────
690
+ console.log(chalk.bold('Default Screen Size:'));
691
+ console.log(chalk.dim(' After selecting a tech stack, ask the user to choose the default screen size for previews and captures.'));
692
+ console.log(chalk.dim(' Use AskUserQuestion with RADIO buttons (single selection):'));
693
+ console.log();
694
+ console.log(chalk.yellow(' ( ) Desktop') + chalk.dim(' — 1440 × 900'));
695
+ console.log(chalk.yellow(' ( ) Laptop') + chalk.dim(' — 1024 × 768'));
696
+ console.log(chalk.yellow(' ( ) Tablet') + chalk.dim(' — 768 × 1024'));
697
+ console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
698
+ console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
699
+ console.log();
700
+ console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app → Mobile, chrome-extension → Custom (400×600).'));
701
+ console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
702
+ console.log(chalk.dim(` Save the choice via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"defaultScreenSize":{"name":"Desktop","width":1440,"height":900}}'`));
703
+ console.log();
704
+ console.log(chalk.bold('Named Screen Sizes (for multi-resolution apps):'));
705
+ console.log(chalk.dim(' For mobile-responsive web apps or apps that need screenshots at multiple resolutions,'));
706
+ console.log(chalk.dim(' also save named screen sizes. Each scenario can reference a dimension name.'));
707
+ console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
708
+ console.log(chalk.dim(' -d \'{"screenSizes":{"Desktop":{"width":1440,"height":900},"Mobile":{"width":375,"height":667}}}\''));
709
+ console.log(chalk.dim(' If you discover a new viewport is needed mid-workflow, add it to screenSizes the same way and reference by name.'));
710
+ console.log(chalk.dim(' NOTE: This REPLACES all screenSizes — always include every named size, not just the new one.'));
711
+ console.log();
712
+ console.log(chalk.bold.red('━━━ STOP ━━━'));
713
+ console.log();
714
+ console.log(chalk.red('Ask the user what they want to build, then ask about app formats, then present tech stacks, then choose a default screen size. Then run:'));
715
+ console.log();
716
+ console.log(chalk.green(' ') +
717
+ chalk.bold('codeyam editor 1 --feature "Feature Name" --app-formats "FORMAT_IDS" --tech-stack "STACK_ID" --prompt "the user\'s original message"'));
718
+ console.log(chalk.dim(' Replace "Feature Name" with a short title for what the user described.'));
719
+ console.log(chalk.dim(' Replace FORMAT_IDS with the comma-separated format IDs the user selected (e.g., "chrome-extension" or "mobile-responsive-web-app").'));
720
+ console.log(chalk.dim(' Replace STACK_ID with the tech stack id shown in [brackets] above (e.g., "nextjs-prisma-sqlite" or "chrome-extension-react").'));
721
+ console.log(chalk.dim(" Pass --prompt with the user's exact original request so it appears in the journal."));
722
+ console.log(chalk.dim(' Step 1 will guide you through planning and getting user confirmation before any code is written.'));
723
+ console.log();
724
+ }
725
+ // ─── Cycle overview (no args, has project) ────────────────────────────
726
+ function printCycleOverview(root, state) {
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
+ }
764
+ console.log();
765
+ console.log(chalk.bold.cyan('━━━ Editor Mode: Feature Cycle ━━━'));
766
+ console.log();
767
+ if (state) {
768
+ console.log(chalk.yellow(`Current: Step ${state.step} (${state.label}) — "${state.feature}"`));
769
+ console.log();
770
+ console.log(chalk.green('Continue with: ') +
771
+ chalk.bold(`codeyam editor ${state.step}`));
772
+ console.log(chalk.dim('Or run ') +
773
+ chalk.bold('codeyam editor 1') +
774
+ chalk.dim(' to start a new feature'));
775
+ console.log();
776
+ console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
777
+ chalk.bold('codeyam editor change'));
778
+ console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
779
+ }
780
+ else {
781
+ console.log('Each feature follows 16 steps. You MUST run each command in order:');
782
+ console.log();
783
+ console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
784
+ console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prototype')} — Build a working prototype fast`);
785
+ console.log(` ${chalk.bold.yellow(' 3')} ${chalk.bold('Confirm')} — Confirm prototype with user`);
786
+ console.log(` ${chalk.bold.yellow(' 4')} ${chalk.bold('Deconstruct')} — Read code, plan all extractions`);
787
+ console.log(` ${chalk.bold.yellow(' 5')} ${chalk.bold('Extract')} — TDD extraction of functions + components`);
788
+ console.log(` ${chalk.bold.yellow(' 6')} ${chalk.bold('Glossary')} — Record functions in glossary`);
789
+ console.log(` ${chalk.bold.yellow(' 7')} ${chalk.bold('Analyze')} — Analyze and verify components`);
790
+ console.log(` ${chalk.bold.yellow(' 8')} ${chalk.bold('App Scenarios')} — Create app-level scenarios`);
791
+ console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('User Scenarios')} — Create user-persona scenarios`);
792
+ console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('Verify')} — Review screenshots, check for errors`);
793
+ console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('Journal')} — Create/update journal entry`);
794
+ console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Review')} — Verify screenshots and audit`);
795
+ console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Present')} — Present summary, get approval`);
796
+ console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('Commit')} — Commit all changes`);
797
+ console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
798
+ console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('Push')} — Push to remote`);
799
+ console.log();
800
+ console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
801
+ console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
802
+ }
803
+ console.log();
804
+ }
805
+ // ─── Step 1: Plan ─────────────────────────────────────────────────────
806
+ function printStep1(root, feature, options, userPrompt) {
807
+ const port = getServerPort();
808
+ const prevState = readState(root);
809
+ const isResuming = prevState?.step === 1;
810
+ if (!isResuming) {
811
+ clearState(root);
812
+ }
813
+ // Always persist state so step 2's validation sees that step 1 ran.
814
+ // The feature name may not be known yet (it's an output of planning) —
815
+ // use an empty string as placeholder; step 2 requires --feature anyway.
816
+ const now = new Date().toISOString();
817
+ writeState(root, {
818
+ feature: feature || prevState?.feature || '',
819
+ step: 1,
820
+ label: STEP_LABELS[1],
821
+ startedAt: isResuming ? prevState.startedAt : now,
822
+ featureStartedAt: isResuming ? prevState.featureStartedAt : now,
823
+ appFormats: options?.appFormats || prevState?.appFormats,
824
+ techStackId: options?.techStackId || prevState?.techStackId,
825
+ });
826
+ // Save the user's original prompt to a separate file
827
+ if (userPrompt) {
828
+ const promptPath = path.join(root, '.codeyam', 'editor-user-prompt.txt');
829
+ fs.mkdirSync(path.dirname(promptPath), { recursive: true });
830
+ fs.writeFileSync(promptPath, userPrompt, 'utf8');
831
+ }
832
+ logEvent(root, 'step', { step: 1, label: 'Plan', feature });
833
+ stepHeader(1, 'Plan', feature);
834
+ if (isResuming) {
835
+ printResumptionHeader(1);
836
+ }
837
+ console.log('Plan the feature before writing ANY code.');
838
+ console.log();
839
+ console.log(chalk.bold('Checklist:'));
840
+ checkbox('Read `.codeyam/glossary.json` for reusable functions/components');
841
+ checkbox('Ask the user what they want to build (if not already described)');
842
+ checkbox('Ask clarifying questions using `AskUserQuestion` with selectable options');
843
+ console.log(chalk.dim(' Use AskUserQuestion for EVERY clarifying question — give 2-4 concrete options the user can pick from.'));
844
+ console.log(chalk.dim(' Focus on what the USER will see and do, not on databases, APIs, or components.'));
845
+ console.log(chalk.dim(' Do NOT ask about tech stack, frameworks, libraries, or implementation details — only ask about user-facing choices.'));
846
+ console.log(chalk.dim(' Good: "What should happen when there are no results?" → options: "Show empty state message", "Show suggestions"'));
847
+ console.log(chalk.dim(' Bad: Free-form text asking "What do you think about the data model?"'));
848
+ console.log(chalk.dim(' You can ask up to 4 questions at once. Bundle related questions into a single AskUserQuestion call.'));
849
+ checkbox("Summarize what you'll build in plain language the user can verify against their vision");
850
+ checkbox('Include a brief note on what interesting data states the scenarios should demonstrate');
851
+ console.log(chalk.dim(' Think: what seed data would put this feature through its paces? Diverse content, edge cases, empty vs rich.'));
852
+ checkbox('Set the project title and description for the App tab:');
853
+ console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
854
+ console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
855
+ console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
856
+ console.log();
857
+ const designSystem = readDesignSystem(root);
858
+ if (designSystem) {
859
+ console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
860
+ console.log(chalk.dim(' Keep these design tokens in mind during planning.'));
861
+ console.log();
862
+ console.log(designSystem);
863
+ console.log();
864
+ }
865
+ console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
866
+ console.log(chalk.green(' Option 1 label: "This plan is accurate, let\'s prototype it!"') + chalk.dim(' — proceed to step 2'));
867
+ console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
868
+ chalk.dim(' — user describes changes, you revise the plan, then re-present'));
869
+ console.log();
870
+ console.log(chalk.dim('This step is for understanding user goals and getting buy-in. Code comes in Step 2.'));
871
+ console.log();
872
+ console.log(chalk.bold.red('━━━ STOP ━━━'));
873
+ console.log();
874
+ console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
875
+ console.log();
876
+ console.log(chalk.yellow('Wait for user confirmation before moving on.'));
877
+ console.log();
878
+ console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
879
+ printProgressTracker(1);
880
+ console.log();
881
+ console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
882
+ console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
883
+ console.log();
884
+ console.log(chalk.green('When done, run: ') +
885
+ chalk.bold('codeyam editor 2 --feature "Feature Name"'));
886
+ console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
887
+ console.log();
888
+ }
889
+ // ─── Step 2: Prototype ────────────────────────────────────────────────
890
+ function printStep2(root, feature) {
891
+ const port = getServerPort();
892
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
893
+ const projectExists = hasProject(root);
894
+ const prevState = readState(root);
895
+ const isResuming = prevState?.step === 2;
896
+ const now = new Date().toISOString();
897
+ writeState(root, {
898
+ feature,
899
+ step: 2,
900
+ label: STEP_LABELS[2],
901
+ startedAt: isResuming ? prevState.startedAt : now,
902
+ featureStartedAt: isResuming ? prevState.featureStartedAt : now,
903
+ appFormats: prevState?.appFormats,
904
+ techStackId: prevState?.techStackId,
905
+ });
906
+ logEvent(root, 'step', { step: 2, label: 'Prototype', feature });
907
+ stepHeader(2, 'Prototype', feature);
908
+ if (isResuming) {
909
+ printResumptionHeader(2);
910
+ }
911
+ console.log('Build fast with real data. Prioritize speed over quality.');
912
+ console.log();
913
+ // If no project exists yet, include scaffolding instructions first
914
+ if (!projectExists) {
915
+ console.log(chalk.bold('Scaffold the project:'));
916
+ checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
917
+ console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
918
+ console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
919
+ console.log();
920
+ checkbox('Define your data models in `prisma/schema.prisma`');
921
+ console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
922
+ console.log();
923
+ checkbox('Push schema and seed the database');
924
+ console.log(chalk.dim(' npm run db:push'));
925
+ console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
926
+ console.log(chalk.dim(' npm run db:seed'));
927
+ console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
928
+ console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
929
+ console.log();
930
+ console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
931
+ console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
932
+ console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
933
+ console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
934
+ console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
935
+ console.log();
936
+ console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
937
+ console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
938
+ console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
939
+ console.log();
940
+ console.log(chalk.bold('Build the feature:'));
941
+ }
942
+ console.log(chalk.bold('Checklist:'));
943
+ checkbox('Create API routes that read from the database via Prisma');
944
+ checkbox('Seed the database with demo data');
945
+ checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
946
+ console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
947
+ console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
948
+ console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
949
+ checkbox('Verify the dev server shows the changes');
950
+ checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
951
+ // Responsive design guidance when building a mobile-responsive web app
952
+ if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
953
+ console.log();
954
+ console.log(chalk.bold.magenta('Responsive Design (mobile-responsive-web-app):'));
955
+ console.log(chalk.magenta(' This app must look great on BOTH desktop AND mobile. It is NOT a mobile-only app.'));
956
+ console.log(chalk.magenta(' Design desktop-first with a full-width layout, then ensure it adapts gracefully to mobile.'));
957
+ console.log(chalk.dim(' Use Tailwind responsive prefixes (sm:, md:, lg:) for layout shifts.'));
958
+ console.log(chalk.dim(' Test at both Desktop (1280×800) and Mobile (390×844) sizes before presenting.'));
959
+ }
960
+ const designSystem = readDesignSystem(root);
961
+ if (designSystem) {
962
+ console.log();
963
+ console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
964
+ console.log();
965
+ console.log(designSystem);
966
+ console.log();
967
+ checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
968
+ console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
969
+ console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
970
+ console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
971
+ console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
972
+ console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
973
+ checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
974
+ console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
975
+ console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
976
+ console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
977
+ }
978
+ console.log();
979
+ console.log(chalk.bold.cyan('Keep the preview moving:'));
980
+ console.log(chalk.cyan(' The user is watching the preview. Refresh it after each meaningful change:'));
981
+ console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
982
+ console.log(chalk.cyan(' Refresh after: first visible page, adding each UI section, seeding data, styling.'));
983
+ console.log(chalk.cyan(' Aim for 4-8+ refreshes during prototyping — not one big reveal at the end.'));
984
+ console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
985
+ console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
986
+ printDimensionGuidance(dim, dimNames);
987
+ console.log();
988
+ console.log(chalk.bold('Verify the dev server:'));
989
+ console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
990
+ console.log(chalk.dim(' # Check page loads: curl -s -o /dev/null -w "%{http_code}" http://localhost:<dev-port>'));
991
+ console.log(chalk.dim(' # Check API routes: curl -s http://localhost:<dev-port>/api/your-route'));
992
+ console.log();
993
+ console.log(chalk.bold('Verify before proceeding:'));
994
+ console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
995
+ checkbox('Verify the page loads: curl the dev server URL and confirm HTTP 200 (not an error page)');
996
+ checkbox('Verify API routes return valid JSON: curl each route and confirm no error responses');
997
+ checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
998
+ console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
999
+ console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
1000
+ console.log(chalk.dim(' Fix or replace any broken image URLs in the seed data before proceeding.'));
1001
+ checkbox('Check the dev server terminal output for runtime errors (missing modules, failed imports)');
1002
+ checkbox('Verify the live preview renders: `codeyam editor client-errors`');
1003
+ console.log(chalk.dim(' If `hasContent=false` or `liveErrors>0`, the preview is broken — fix the issue before proceeding.'));
1004
+ console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
1005
+ console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
1006
+ console.log();
1007
+ console.log(chalk.bold('Update README and setup script:'));
1008
+ checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
1009
+ checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
1010
+ console.log(chalk.dim(' The README and setup script must stay accurate as you make changes.'));
1011
+ console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
1012
+ console.log();
1013
+ console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
1014
+ stopGate(2);
1015
+ }
1016
+ // ─── Step 3: Confirm ──────────────────────────────────────────────────
1017
+ function printStep3(root, feature) {
1018
+ const port = getServerPort();
1019
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1020
+ const prevState = readState(root);
1021
+ const isResuming = prevState?.step === 3;
1022
+ const now = new Date().toISOString();
1023
+ writeState(root, {
1024
+ feature,
1025
+ step: 3,
1026
+ label: STEP_LABELS[3],
1027
+ startedAt: isResuming ? prevState.startedAt : now,
1028
+ featureStartedAt: prevState?.featureStartedAt || now,
1029
+ });
1030
+ logEvent(root, 'step', { step: 3, label: 'Confirm', feature });
1031
+ stepHeader(3, 'Confirm', feature);
1032
+ if (isResuming) {
1033
+ printResumptionHeader(3);
1034
+ }
1035
+ console.log('Summarize what was built and get user confirmation.');
1036
+ console.log();
1037
+ console.log(chalk.bold('Before presenting — verify everything works:'));
1038
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
1039
+ checkbox('Verify API routes return valid data (curl each route)');
1040
+ console.log();
1041
+ console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
1042
+ checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
1043
+ console.log(chalk.dim(' Include ALL page paths and ALL image URLs from seed data / API responses.'));
1044
+ console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images.'));
1045
+ checkbox('Fix or remove any image that returns non-200 before continuing');
1046
+ checkbox('Check for client-side errors: `codeyam editor client-errors`');
1047
+ console.log(chalk.dim(' If there are errors, fix the underlying issue before presenting.'));
1048
+ checkbox('Verify `hasContent=true` and `liveErrors=0` — do NOT ask the user to confirm if the preview is broken');
1049
+ console.log();
1050
+ console.log(chalk.bold('Then present to the user:'));
1051
+ checkbox('Summarize what was built (routes, components, data)');
1052
+ checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
1053
+ printDimensionGuidance(dim, dimNames);
1054
+ console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
1055
+ console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
1056
+ console.log();
1057
+ console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
1058
+ checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
1059
+ checkbox('Prompt the user to try key interactions: forms, navigation, buttons, etc.');
1060
+ checkbox('Ask the user: "Does everything work as expected?"');
1061
+ console.log();
1062
+ console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
1063
+ console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 4'));
1064
+ console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
1065
+ chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 3`'));
1066
+ console.log();
1067
+ console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
1068
+ stopGate(3, { confirm: true });
1069
+ }
1070
+ // ─── Step 4: Deconstruct ──────────────────────────────────────────────
1071
+ function printStep4(root, feature) {
1072
+ const prevState = readState(root);
1073
+ const isResuming = prevState?.step === 4;
1074
+ const now = new Date().toISOString();
1075
+ writeState(root, {
1076
+ feature,
1077
+ step: 4,
1078
+ label: STEP_LABELS[4],
1079
+ startedAt: isResuming ? prevState.startedAt : now,
1080
+ featureStartedAt: prevState?.featureStartedAt || now,
1081
+ });
1082
+ logEvent(root, 'step', { step: 4, label: 'Deconstruct', feature });
1083
+ stepHeader(4, 'Deconstruct', feature);
1084
+ if (isResuming) {
1085
+ printResumptionHeader(4);
1086
+ }
1087
+ console.log(chalk.bold('Goal: pages contain ONLY components. Components contain ONLY sub-components.'));
1088
+ console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 5.'));
1089
+ console.log();
1090
+ printExtractionPlanInstructions();
1091
+ stopGate(4);
1092
+ }
1093
+ // ─── Step 5: Extract ──────────────────────────────────────────────────
1094
+ function printStep5(root, feature) {
1095
+ const port = getServerPort();
1096
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1097
+ const prevState = readState(root);
1098
+ const isResuming = prevState?.step === 5;
1099
+ const now = new Date().toISOString();
1100
+ writeState(root, {
1101
+ feature,
1102
+ step: 5,
1103
+ label: STEP_LABELS[5],
1104
+ startedAt: isResuming ? prevState.startedAt : now,
1105
+ featureStartedAt: prevState?.featureStartedAt || now,
1106
+ });
1107
+ logEvent(root, 'step', { step: 5, label: 'Extract', feature });
1108
+ stepHeader(5, 'Extract', feature);
1109
+ if (isResuming) {
1110
+ printResumptionHeader(5);
1111
+ }
1112
+ console.log('Execute your extraction plan from step 4.');
1113
+ console.log();
1114
+ console.log(chalk.bold('Components:'));
1115
+ checkbox('Extract each component from your plan into its own file');
1116
+ checkbox('Page/route files must contain ZERO direct JSX — only imported components');
1117
+ checkbox('Every component that renders multiple sections must be split into sub-components');
1118
+ console.log(chalk.dim(' No tests needed — visual verification happens in step 7'));
1119
+ console.log();
1120
+ console.log(chalk.bold('Library functions AND hooks (TDD):'));
1121
+ checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
1122
+ console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
1123
+ console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
1124
+ console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
1125
+ checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
1126
+ console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 7 only captures component screenshots.'));
1127
+ console.log();
1128
+ console.log(chalk.bold('Recursive pass:'));
1129
+ checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
1130
+ checkbox('Keep going until every file is a thin shell: just imports and composition');
1131
+ console.log(chalk.yellow(' Check: does any file contain raw <div>, <span>, <h1>, <p>, <img>, or <ul>?'));
1132
+ console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
1133
+ console.log();
1134
+ console.log(chalk.bold('Verify before proceeding:'));
1135
+ checkbox('Run all tests and verify they pass');
1136
+ checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
1137
+ checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
1138
+ checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1139
+ printDimensionGuidance(dim, dimNames);
1140
+ console.log(chalk.dim(' The user should see the preview stay healthy as you refactor — refresh periodically, not just at the end.'));
1141
+ console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
1142
+ console.log();
1143
+ console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
1144
+ stopGate(5);
1145
+ }
1146
+ // ─── Step 6: Glossary ─────────────────────────────────────────────────
1147
+ function printStep6(root, feature) {
1148
+ const prevState = readState(root);
1149
+ const isResuming = prevState?.step === 6;
1150
+ const now = new Date().toISOString();
1151
+ writeState(root, {
1152
+ feature,
1153
+ step: 6,
1154
+ label: STEP_LABELS[6],
1155
+ startedAt: isResuming ? prevState.startedAt : now,
1156
+ featureStartedAt: prevState?.featureStartedAt || now,
1157
+ });
1158
+ logEvent(root, 'step', { step: 6, label: 'Glossary', feature });
1159
+ stepHeader(6, 'Glossary', feature);
1160
+ if (isResuming) {
1161
+ printResumptionHeader(6);
1162
+ }
1163
+ console.log('Record all new functions/components in `.codeyam/glossary.json`.');
1164
+ console.log();
1165
+ printGlossaryInstructions(feature);
1166
+ console.log();
1167
+ console.log(chalk.dim('Focus on updating the glossary. Application code and scenarios come in later steps.'));
1168
+ stopGate(6);
1169
+ }
1170
+ // ─── Step 7: Analyze ──────────────────────────────────────────────────
1171
+ function printStep7(root, feature) {
1172
+ const port = getServerPort();
1173
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1174
+ const prevState = readState(root);
1175
+ const isResuming = prevState?.step === 7;
1176
+ const now = new Date().toISOString();
1177
+ writeState(root, {
1178
+ feature,
1179
+ step: 7,
1180
+ label: STEP_LABELS[7],
1181
+ startedAt: isResuming ? prevState.startedAt : now,
1182
+ featureStartedAt: prevState?.featureStartedAt || now,
1183
+ });
1184
+ logEvent(root, 'step', { step: 7, label: 'Analyze', feature });
1185
+ stepHeader(7, 'Analyze and Verify', feature);
1186
+ if (isResuming) {
1187
+ printResumptionHeader(7);
1188
+ }
1189
+ console.log('Verify visual components (via isolation routes) and library functions (via tests).');
1190
+ console.log();
1191
+ console.log(chalk.bold('Visual Components — Component Isolation:'));
1192
+ checkbox('List all files with new/modified visual components from step 5');
1193
+ checkbox('Check existing scenarios: `codeyam editor scenarios`');
1194
+ console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
1195
+ console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
1196
+ console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
1197
+ printComponentCaptureInstructions();
1198
+ console.log();
1199
+ console.log(chalk.bold('Library Functions — run tests:'));
1200
+ checkbox('Run ALL test files created in step 5');
1201
+ console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
1202
+ checkbox('Verify every test passes');
1203
+ checkbox('If any test fails, fix the source code and re-run');
1204
+ console.log();
1205
+ console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
1206
+ console.log();
1207
+ checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
1208
+ console.log(chalk.red.bold(' The audit is a HARD GATE — step 8 will refuse to run until the audit passes.'));
1209
+ console.log(chalk.dim(' When audit passes, the import graph is built automatically for change tracking.'));
1210
+ console.log();
1211
+ stopGate(7);
1212
+ }
1213
+ // ─── Step 8: App Scenarios ────────────────────────────────────────────
1214
+ function printStep8(root, feature) {
1215
+ const port = getServerPort();
1216
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1217
+ const prevState = readState(root);
1218
+ const isResuming = prevState?.step === 8;
1219
+ const now = new Date().toISOString();
1220
+ writeState(root, {
1221
+ feature,
1222
+ step: 8,
1223
+ label: STEP_LABELS[8],
1224
+ startedAt: isResuming ? prevState.startedAt : now,
1225
+ featureStartedAt: prevState?.featureStartedAt || now,
1226
+ });
1227
+ logEvent(root, 'step', { step: 8, label: 'App Scenarios', feature });
1228
+ stepHeader(8, 'App Scenarios', feature);
1229
+ if (isResuming) {
1230
+ printResumptionHeader(8);
1231
+ }
1232
+ console.log('Create app-level scenarios with rich data that robustly demonstrates this feature.');
1233
+ console.log();
1234
+ console.log(chalk.bold('Goal: Every scenario should thoroughly exercise the feature with realistic data.'));
1235
+ console.log(chalk.dim(' Scenarios with minimal or generic data ("Test Item 1") won\'t reveal whether the feature works.'));
1236
+ console.log(chalk.dim(' Use rich, diverse seed data that puts the feature through its paces.'));
1237
+ console.log();
1238
+ console.log(chalk.bold.cyan('Make seed data work hard:'));
1239
+ console.log(chalk.cyan(' Every scenario — new or existing — should have data that EXERCISES the feature:'));
1240
+ console.log(chalk.cyan(' • Add data that uses new fields, relationships, or states the feature introduces'));
1241
+ console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
1242
+ console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
1243
+ console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
1244
+ console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
1245
+ console.log();
1246
+ console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
1247
+ console.log(chalk.cyan(' • New pages or pages with no scenarios yet → create new scenarios'));
1248
+ console.log(chalk.cyan(' • Existing scenarios on affected pages → re-register with enhanced data'));
1249
+ console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
1250
+ console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
1251
+ console.log();
1252
+ checkbox('Ensure scenarios clearly demonstrate what changed in this session');
1253
+ console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
1254
+ console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
1255
+ console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
1256
+ printAppScenarioInstructions();
1257
+ console.log();
1258
+ console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 10 if needed.'));
1259
+ console.log();
1260
+ console.log(chalk.bold.cyan("Verify your work — screenshots don't lie:"));
1261
+ console.log(chalk.cyan(' • View every captured screenshot. Does it show what the scenario name promises?'));
1262
+ console.log(chalk.cyan(' • A "Library with Articles" screenshot showing an empty page means the scenario is BROKEN.'));
1263
+ console.log(chalk.cyan(' • Is the seed data diverse — different lengths, categories, counts — or just placeholders?'));
1264
+ console.log(chalk.cyan(' • Only create scenarios for states the CURRENT code can render.'));
1265
+ console.log();
1266
+ console.log(chalk.bold.yellow('GATE: Before proceeding, run `codeyam editor scenario-coverage`'));
1267
+ console.log(chalk.yellow(' This checks which existing scenarios have stale screenshots.'));
1268
+ console.log(chalk.yellow(' Re-register every stale scenario listed until the check passes.'));
1269
+ stopGate(8);
1270
+ }
1271
+ // ─── Step 9: User Scenarios ───────────────────────────────────────────
1272
+ function printStep9(root, feature) {
1273
+ const port = getServerPort();
1274
+ const prevState = readState(root);
1275
+ const isResuming = prevState?.step === 9;
1276
+ const now = new Date().toISOString();
1277
+ writeState(root, {
1278
+ feature,
1279
+ step: 9,
1280
+ label: STEP_LABELS[9],
1281
+ startedAt: isResuming ? prevState.startedAt : now,
1282
+ featureStartedAt: prevState?.featureStartedAt || now,
1283
+ });
1284
+ logEvent(root, 'step', { step: 9, label: 'User Scenarios', feature });
1285
+ stepHeader(9, 'User Scenarios', feature);
1286
+ if (isResuming) {
1287
+ printResumptionHeader(9);
1288
+ }
1289
+ console.log('Create per-persona variations of existing scenarios. Skip to step 10 if no users.');
1290
+ console.log();
1291
+ console.log(chalk.bold('If the app has NO users/auth:'));
1292
+ console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
1293
+ console.log();
1294
+ console.log(chalk.bold('If the app has users/auth:'));
1295
+ console.log();
1296
+ console.log(chalk.bold('Goal: Create persona variations of EXISTING app scenarios.'));
1297
+ console.log(chalk.dim(' Do NOT create standalone persona scenarios. Each user-persona scenario should be'));
1298
+ console.log(chalk.dim(' a variation of an existing app scenario from step 8, with user-specific state layered on.'));
1299
+ console.log();
1300
+ console.log(chalk.bold('Checklist:'));
1301
+ checkbox('Run `codeyam editor scenarios` — list all existing app scenarios');
1302
+ checkbox('For EACH existing app scenario, create a logged-in variation:');
1303
+ console.log(chalk.dim(' Copy the scenario seed data and add "session":{"cookieValue":"sess_alice"} + user seed'));
1304
+ console.log(chalk.dim(' Name: "<Original Name> - Logged In" (e.g. "Full Catalog - Logged In")'));
1305
+ console.log(chalk.dim(' Step 8 scenarios already serve as logged-out versions (no session cookie)'));
1306
+ checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
1307
+ console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
1308
+ checkbox('Include "dimensions" — inherit from the base app scenario, or override if the persona implies a different device');
1309
+ console.log(chalk.dim(' e.g. a mobile-first user persona should use "dimensions":["Mobile"] even if the base scenario is Desktop.'));
1310
+ checkbox('After each registration, check the response for `clientErrors`');
1311
+ console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
1312
+ console.log();
1313
+ console.log(chalk.dim('See FEATURE_PATTERNS.md and AUTH_PATTERNS.md for auth scenario guidance.'));
1314
+ stopGate(9);
1315
+ }
1316
+ // ─── Step 10: Verify ──────────────────────────────────────────────────
1317
+ function printStep10(root, feature) {
1318
+ const port = getServerPort();
1319
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1320
+ const prevState = readState(root);
1321
+ const isResuming = prevState?.step === 10;
1322
+ const now = new Date().toISOString();
1323
+ writeState(root, {
1324
+ feature,
1325
+ step: 10,
1326
+ label: STEP_LABELS[10],
1327
+ startedAt: isResuming ? prevState.startedAt : now,
1328
+ featureStartedAt: prevState?.featureStartedAt || now,
1329
+ });
1330
+ logEvent(root, 'step', { step: 10, label: 'Verify', feature });
1331
+ stepHeader(10, 'Verify', feature);
1332
+ if (isResuming) {
1333
+ printResumptionHeader(10);
1334
+ }
1335
+ console.log('Verify component isolation screenshots, editor scenarios, and library tests.');
1336
+ console.log();
1337
+ console.log(chalk.bold('Component isolation screenshots — verify:'));
1338
+ checkbox('Review component screenshots in the App tab (grouped under Components)');
1339
+ checkbox('If any screenshot looks wrong, fix the component code');
1340
+ console.log(chalk.yellow(' After fixing a visual component, re-register the affected scenarios:'));
1341
+ console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",...}'`));
1342
+ console.log();
1343
+ console.log(chalk.bold('Editor scenarios (App tab) — visual + error check:'));
1344
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1345
+ printDimensionGuidance(dim, dimNames);
1346
+ checkbox('Click through each app-level and user-persona scenario in the preview');
1347
+ checkbox('For seed-based scenarios: verify data renders correctly after switching');
1348
+ console.log(chalk.dim(' Switch between scenarios and confirm the app reflects the seeded data each time'));
1349
+ checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
1350
+ console.log(chalk.yellow(' If `hasErrors` is true: list each scenario with errors, fix the source code,'));
1351
+ console.log(chalk.yellow(' re-register the affected scenarios, and re-check until hasErrors is false'));
1352
+ console.log(chalk.dim(' Common errors: React errors, failed fetches, undefined references, hydration mismatches'));
1353
+ checkbox('Verify no broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
1354
+ console.log();
1355
+ console.log(chalk.bold('Library functions — test check:'));
1356
+ checkbox('Re-run all test files from step 5 to confirm they still pass');
1357
+ console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
1358
+ checkbox('If any test fails, fix the source code and re-run');
1359
+ console.log();
1360
+ console.log(chalk.dim('Focus on fixing issues. All component screenshots, scenarios, and tests must be clean before proceeding.'));
1361
+ stopGate(10);
1362
+ }
1363
+ // ─── Step 11: Journal ─────────────────────────────────────────────────
1364
+ function printStep11(root, feature) {
1365
+ const port = getServerPort();
1366
+ const prevState = readState(root);
1367
+ const isResuming = prevState?.step === 11;
1368
+ const now = new Date().toISOString();
1369
+ writeState(root, {
1370
+ feature,
1371
+ step: 11,
1372
+ label: STEP_LABELS[11],
1373
+ startedAt: isResuming ? prevState.startedAt : now,
1374
+ featureStartedAt: prevState?.featureStartedAt || now,
1375
+ });
1376
+ logEvent(root, 'step', { step: 11, label: 'Journal', feature });
1377
+ stepHeader(11, 'Journal', feature);
1378
+ if (isResuming) {
1379
+ printResumptionHeader(11);
1380
+ }
1381
+ console.log('Create or update the journal entry for this feature.');
1382
+ console.log();
1383
+ console.log(chalk.bold('Checklist:'));
1384
+ checkbox('Write a concise description of what was built (2-3 sentences)');
1385
+ checkbox(`First time at step 11 — create journal entry with ALL session screenshots:`);
1386
+ console.log(chalk.dim(` codeyam editor journal '{"title":"...","type":"feature","description":"...","includeSessionScenarios":true}'`));
1387
+ console.log(chalk.dim(' includeSessionScenarios auto-discovers component + app + user persona screenshots'));
1388
+ checkbox(`Returning to step 11 (before commit) — update the existing journal entry:`);
1389
+ console.log(chalk.dim(` codeyam editor journal-update '{"time":"<journal entry time>","description":"<updated>","includeSessionScenarios":true}'`));
1390
+ console.log(chalk.dim(' Note: PATCH only works before the entry is committed. After commit, use POST to create a new entry.'));
1391
+ console.log();
1392
+ console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step 13.'));
1393
+ stopGate(11);
1394
+ }
1395
+ // ─── Step 12: Review ──────────────────────────────────────────────────
1396
+ function printStep12(root, feature) {
1397
+ const port = getServerPort();
1398
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1399
+ const prevState = readState(root);
1400
+ const isResuming = prevState?.step === 12;
1401
+ const now = new Date().toISOString();
1402
+ writeState(root, {
1403
+ feature,
1404
+ step: 12,
1405
+ label: STEP_LABELS[12],
1406
+ startedAt: isResuming ? prevState.startedAt : now,
1407
+ featureStartedAt: prevState?.featureStartedAt || now,
1408
+ });
1409
+ logEvent(root, 'step', { step: 12, label: 'Review', feature });
1410
+ stepHeader(12, 'Review', feature);
1411
+ if (isResuming) {
1412
+ printResumptionHeader(12);
1413
+ }
1414
+ console.log('Verify all screenshots and checks pass before presenting to the user.');
1415
+ console.log();
1416
+ console.log(chalk.bold('Checklist (do all of this silently):'));
1417
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1418
+ printDimensionGuidance(dim, dimNames);
1419
+ checkbox('Verify each component has screenshots in the App tab (grouped under Components)');
1420
+ checkbox('If any are missing, re-register them using `codeyam editor register`');
1421
+ checkbox(`Check for client errors: \`codeyam editor client-errors\``);
1422
+ checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
1423
+ checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
1424
+ checkbox('Fix or remove any broken images before continuing');
1425
+ checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
1426
+ checkbox('Do not proceed until all checks pass');
1427
+ stopGate(12);
1428
+ }
1429
+ // ─── Step 13: Present ─────────────────────────────────────────────────
1430
+ function printStep13(root, feature) {
1431
+ const port = getServerPort();
1432
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1433
+ const prevState = readState(root);
1434
+ const isResuming = prevState?.step === 13;
1435
+ const now = new Date().toISOString();
1436
+ writeState(root, {
1437
+ feature,
1438
+ step: 13,
1439
+ label: STEP_LABELS[13],
1440
+ startedAt: isResuming ? prevState.startedAt : now,
1441
+ featureStartedAt: prevState?.featureStartedAt || now,
1442
+ });
1443
+ logEvent(root, 'step', { step: 13, label: 'Present', feature });
1444
+ stepHeader(13, 'Present', feature);
1445
+ if (isResuming) {
1446
+ printResumptionHeader(13);
1447
+ }
1448
+ console.log('Present the results to the user and get their approval.');
1449
+ console.log();
1450
+ console.log(chalk.bold('Checklist:'));
1451
+ checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1452
+ console.log(chalk.dim(' Each scenario has a `changeStatus` field: "new", "edited", "impacted", or null.'));
1453
+ checkbox('Pick the best scenario to showcase from this working session:');
1454
+ console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
1455
+ console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
1456
+ console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
1457
+ checkbox('Switch the preview to that scenario using its `id` and `dimension`:');
1458
+ console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1459
+ console.log(chalk.dim(' Use the dimension that matches the scenario — check its registered dimensions.'));
1460
+ printDimensionGuidance(dim, dimNames);
1461
+ checkbox(`Show the results panel: \`codeyam editor show-results\``);
1462
+ console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
1463
+ console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
1464
+ checkbox('Write a 1-2 sentence summary of what was built');
1465
+ checkbox('Report test count and audit status (one line)');
1466
+ console.log();
1467
+ console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
1468
+ console.log(chalk.green(' Option 1 label: "Save & commit"') +
1469
+ chalk.dim(' — git commit all changes and record in journal'));
1470
+ console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
1471
+ chalk.dim(' — describe changes, then re-verify'));
1472
+ console.log();
1473
+ console.log(chalk.bold('If the user chooses "Save & commit":'));
1474
+ checkbox('Advance to the commit step: `codeyam editor 14`');
1475
+ console.log();
1476
+ console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
1477
+ checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
1478
+ checkbox('Ask what changes the user wants (if not already clear)');
1479
+ checkbox(`Run: \`codeyam editor change "${feature}"\` — this gives you the change checklist`);
1480
+ checkbox('THEN make the requested changes and follow the checklist');
1481
+ console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
1482
+ stopGate(13, { confirm: true });
1483
+ }
1484
+ // ─── Migration Mode ──────────────────────────────────────────────────
1485
+ /**
1486
+ * Print a progress tracker for the 8 migration steps.
1487
+ */
1488
+ function printMigrationProgressTracker(current, pageName, pageIndex, totalPages) {
1489
+ console.log();
1490
+ console.log(chalk.dim(` Migration: Page ${pageIndex + 1}/${totalPages} (${pageName})`));
1491
+ console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
1492
+ for (let i = 1; i <= 10; i++) {
1493
+ const label = MIGRATION_STEP_LABELS[i];
1494
+ const num = i < 10 ? ` ${i}` : `${i}`;
1495
+ const content = `${num}. ${label.padEnd(28)}`;
1496
+ if (i < current) {
1497
+ console.log(chalk.dim(' │') + chalk.green(` ✓ ${content}`) + chalk.dim('│'));
1498
+ }
1499
+ else if (i === current) {
1500
+ console.log(chalk.dim(' │') + chalk.bold.cyan(` → ${content}`) + chalk.dim('│'));
1501
+ }
1502
+ else {
1503
+ console.log(chalk.dim(` │ ○ ${content}│`));
1504
+ }
1505
+ }
1506
+ console.log(chalk.dim(' └─────────────────────────────────────┘'));
1507
+ }
1508
+ /**
1509
+ * Print a STOP gate for migration steps.
1510
+ */
1511
+ function migrationStopGate(current, pageName, pageIndex, totalPages, opts) {
1512
+ console.log();
1513
+ console.log(chalk.bold.red('━━━ STOP ━━━'));
1514
+ console.log();
1515
+ console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
1516
+ if (opts?.confirm) {
1517
+ console.log();
1518
+ console.log(chalk.yellow('Wait for user confirmation before moving on.'));
1519
+ }
1520
+ console.log();
1521
+ console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
1522
+ printMigrationProgressTracker(current, pageName, pageIndex, totalPages);
1523
+ console.log();
1524
+ console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
1525
+ console.log();
1526
+ if (current === 5) {
1527
+ // Step 5 (Discuss) can skip to step 9 if user declines decomposition
1528
+ console.log(chalk.green('If decomposing, run: ') +
1529
+ chalk.bold('codeyam editor migrate 6'));
1530
+ console.log(chalk.green('If skipping decomposition, run: ') +
1531
+ chalk.bold('codeyam editor migrate 9'));
1532
+ }
1533
+ else if (current < 10) {
1534
+ console.log(chalk.green('When done, run: ') +
1535
+ chalk.bold(`codeyam editor migrate ${current + 1}`));
1536
+ }
1537
+ else {
1538
+ console.log(chalk.green('Page complete! Run: ') +
1539
+ chalk.bold('codeyam editor migrate next') +
1540
+ chalk.green(' to start the next page'));
1541
+ }
1542
+ console.log();
1543
+ }
1544
+ /**
1545
+ * Write migration-aware editor state.
1546
+ */
1547
+ function writeMigrationStepState(root, step, pageName, pageIndex, totalPages) {
1548
+ const prevState = readState(root);
1549
+ const isResuming = prevState?.step === step && !!prevState?.migration;
1550
+ const now = new Date().toISOString();
1551
+ writeState(root, {
1552
+ feature: `Migration: ${pageName}`,
1553
+ step,
1554
+ label: MIGRATION_STEP_LABELS[step],
1555
+ startedAt: isResuming ? prevState.startedAt : now,
1556
+ featureStartedAt: prevState?.featureStartedAt || now,
1557
+ migration: {
1558
+ pageName,
1559
+ pageIndex,
1560
+ totalPages,
1561
+ },
1562
+ });
1563
+ logEvent(root, 'step', {
1564
+ step,
1565
+ label: MIGRATION_STEP_LABELS[step],
1566
+ feature: `Migration: ${pageName}`,
1567
+ migration: true,
1568
+ });
1569
+ }
1570
+ /**
1571
+ * Get the current migration page info from state, or return null.
1572
+ */
1573
+ function getCurrentMigrationPage(root) {
1574
+ const migState = readMigrationState(root);
1575
+ if (!migState || migState.pages.length === 0)
1576
+ return null;
1577
+ const page = migState.pages[migState.currentPageIndex];
1578
+ if (!page)
1579
+ return null;
1580
+ return {
1581
+ pageName: page.name,
1582
+ pageIndex: migState.currentPageIndex,
1583
+ totalPages: migState.pages.length,
1584
+ };
1585
+ }
1586
+ // ─── Migration Step 1: Survey ───────────────────────────────────────
1587
+ function printMigrateStep1(root) {
1588
+ const pageInfo = getCurrentMigrationPage(root);
1589
+ if (!pageInfo) {
1590
+ console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
1591
+ process.exit(1);
1592
+ }
1593
+ const { pageName, pageIndex, totalPages } = pageInfo;
1594
+ writeMigrationStepState(root, 1, pageName, pageIndex, totalPages);
1595
+ const migState = readMigrationState(root);
1596
+ const page = migState.pages[pageIndex];
1597
+ console.log();
1598
+ console.log(chalk.bold.cyan(`━━━ Migration Step 1: Survey — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1599
+ console.log();
1600
+ console.log(chalk.bold('Goal: Survey the page, understand its structure, and start the dev server.'));
1601
+ console.log();
1602
+ console.log(chalk.bold('Survey:'));
1603
+ checkbox(`Read the page file: \`${page.filePath}\``);
1604
+ checkbox('Follow every import — read all local components, hooks, utilities used by this page');
1605
+ checkbox('Map the data flow: where does data come from? Server actions? API routes? Props?');
1606
+ checkbox('Check `.codeyam/glossary.json` for components already extracted from previous pages');
1607
+ checkbox('Check `.codeyam/migration-state.json` sharedComponents for reusable pieces');
1608
+ checkbox('Note the page route and any dynamic segments');
1609
+ console.log();
1610
+ console.log(chalk.bold('Dev Server:'));
1611
+ checkbox('If the dev server is not running yet, figure out how to start it (check package.json scripts)');
1612
+ checkbox('FIRST: Verify dependencies are installed (check if node_modules/ exists and is non-empty)');
1613
+ console.log(chalk.yellow(' If node_modules is missing or sparse, run the package manager install command first'));
1614
+ console.log(chalk.dim(' Missing deps cause cryptic 500 errors at render time — install BEFORE starting the server'));
1615
+ checkbox('Start the dev server: `codeyam editor dev-server \'{"action":"start"}\'`');
1616
+ checkbox("Verify it's running: check for successful startup output");
1617
+ console.log();
1618
+ migrationStopGate(1, pageName, pageIndex, totalPages);
1619
+ }
1620
+ // ─── Migration Step 2: App Scenarios ────────────────────────────────
1621
+ function printMigrateStep2(root) {
1622
+ const pageInfo = getCurrentMigrationPage(root);
1623
+ if (!pageInfo) {
1624
+ console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
1625
+ process.exit(1);
1626
+ }
1627
+ const { pageName, pageIndex, totalPages } = pageInfo;
1628
+ writeMigrationStepState(root, 2, pageName, pageIndex, totalPages);
1629
+ const migState = readMigrationState(root);
1630
+ const page = migState.pages[pageIndex];
1631
+ console.log();
1632
+ console.log(chalk.bold.cyan(`━━━ Migration Step 2: App Scenarios — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1633
+ console.log();
1634
+ console.log(chalk.bold(`Goal: Register application scenarios that screenshot the ACTUAL page at "${page.route}".`));
1635
+ console.log();
1636
+ console.log(chalk.yellow(' App scenarios capture how the full page looks with different data states.'));
1637
+ console.log(chalk.yellow(` Every scenario MUST use the real page route ("${page.route}") — NOT isolation routes.`));
1638
+ console.log(chalk.yellow(' These are the primary scenarios the user will see for this page.'));
1639
+ console.log();
1640
+ console.log(chalk.bold('Seed Adapter Setup:'));
1641
+ checkbox('Check if a seed adapter exists: `ls .codeyam/seed-adapter.ts 2>/dev/null`');
1642
+ console.log(chalk.yellow(' If NO seed adapter exists, create one. The seed adapter wipes and re-seeds the database'));
1643
+ console.log(chalk.yellow(' for each scenario, enabling different data states on the real page.'));
1644
+ console.log();
1645
+ console.log(chalk.dim(' How to create a seed adapter:'));
1646
+ console.log(chalk.dim(' 1. Identify the database technology from your step 1 survey (Prisma, Supabase, Drizzle, etc.)'));
1647
+ console.log(chalk.dim(' 2. Check for a matching template: `ls .codeyam/seed-adapters/`'));
1648
+ console.log(chalk.dim(' 3. For Supabase: `cp .codeyam/seed-adapters/supabase.ts .codeyam/seed-adapter.ts`'));
1649
+ console.log(chalk.dim(' The seed adapter auto-detects Supabase credentials by scanning env var VALUES (not names).'));
1650
+ console.log(chalk.dim(' It reads .env / .env.local and finds: *.supabase.co URLs, sb_secret_* keys, and legacy JWTs.'));
1651
+ console.log(chalk.dim(' Do NOT grep for specific env var names — just register a scenario and try it.'));
1652
+ console.log(chalk.dim(' If it fails, the error message will say exactly what credentials are missing.'));
1653
+ console.log(chalk.dim(' 4. For Prisma: write a seed adapter that uses your Prisma client to delete/insert rows'));
1654
+ console.log(chalk.dim(' 5. For other databases: write a .codeyam/seed-adapter.ts that reads a JSON file (argv[2]),'));
1655
+ console.log(chalk.dim(' deletes all rows from each table, then inserts the seed rows. Format: {"table": [{...}]}'));
1656
+ console.log(chalk.dim(' 6. Test it: write a small test seed JSON and run `npx tsx .codeyam/seed-adapter.ts test.json`'));
1657
+ console.log();
1658
+ printAppScenarioInstructions(pageName, page.route);
1659
+ console.log();
1660
+ migrationStopGate(2, pageName, pageIndex, totalPages);
1661
+ }
1662
+ // ─── Migration Step 3: Component Scenarios ──────────────────────────
1663
+ function printMigrateStep3(root) {
1664
+ const pageInfo = getCurrentMigrationPage(root);
1665
+ if (!pageInfo) {
1666
+ console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
1667
+ process.exit(1);
1668
+ }
1669
+ const { pageName, pageIndex, totalPages } = pageInfo;
1670
+ writeMigrationStepState(root, 3, pageName, pageIndex, totalPages);
1671
+ console.log();
1672
+ console.log(chalk.bold.cyan(`━━━ Migration Step 3: Component Scenarios — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1673
+ console.log();
1674
+ console.log(chalk.bold('Goal: Capture individual component scenarios for major visual components on this page.'));
1675
+ console.log();
1676
+ console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
1677
+ console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
1678
+ console.log();
1679
+ printComponentCaptureInstructions();
1680
+ console.log();
1681
+ migrationStopGate(3, pageName, pageIndex, totalPages);
1682
+ }
1683
+ // ─── Migration Step 4: Preview ──────────────────────────────────────
1684
+ function printMigrateStep4(root) {
1685
+ const pageInfo = getCurrentMigrationPage(root);
1686
+ if (!pageInfo) {
1687
+ console.error(chalk.red('Error: No migration in progress.'));
1688
+ process.exit(1);
1689
+ }
1690
+ const { pageName, pageIndex, totalPages } = pageInfo;
1691
+ writeMigrationStepState(root, 4, pageName, pageIndex, totalPages);
1692
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1693
+ console.log();
1694
+ console.log(chalk.bold.cyan(`━━━ Migration Step 4: Preview — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1695
+ console.log();
1696
+ console.log(chalk.bold('Goal: Verify screenshots look correct and show results to the user.'));
1697
+ console.log();
1698
+ console.log(chalk.bold('Checklist:'));
1699
+ checkbox('Review all scenario screenshots for this page — do they look correct?');
1700
+ checkbox('Check for client errors: `codeyam editor client-errors`');
1701
+ checkbox('If any issues: fix the scenario data, re-register affected scenarios');
1702
+ checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1703
+ checkbox('Pick the best scenario to showcase:');
1704
+ console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1705
+ printDimensionGuidance(dim, dimNames);
1706
+ checkbox('Show results: `codeyam editor show-results`');
1707
+ console.log();
1708
+ console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
1709
+ migrationStopGate(4, pageName, pageIndex, totalPages);
1710
+ }
1711
+ // ─── Migration Step 5: Discuss ──────────────────────────────────────
1712
+ function printMigrateStep5(root) {
1713
+ const pageInfo = getCurrentMigrationPage(root);
1714
+ if (!pageInfo) {
1715
+ console.error(chalk.red('Error: No migration in progress.'));
1716
+ process.exit(1);
1717
+ }
1718
+ const { pageName, pageIndex, totalPages } = pageInfo;
1719
+ writeMigrationStepState(root, 5, pageName, pageIndex, totalPages);
1720
+ console.log();
1721
+ console.log(chalk.bold.cyan(`━━━ Migration Step 5: Discuss — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1722
+ console.log();
1723
+ console.log(chalk.bold('Goal: Assess page complexity and ask the user if decomposition is worth it.'));
1724
+ console.log();
1725
+ console.log(chalk.bold('Checklist:'));
1726
+ checkbox('Hide results: `codeyam editor hide-results`');
1727
+ checkbox("Assess the page's complexity:");
1728
+ console.log(chalk.dim(' — How many inline components exist?'));
1729
+ console.log(chalk.dim(' — How much business logic is embedded in the page file?'));
1730
+ console.log(chalk.dim(' — Are there reusable pieces that other pages could share?'));
1731
+ console.log(chalk.dim(' — Would extraction improve testability or maintainability?'));
1732
+ checkbox('Present your assessment to the user');
1733
+ console.log();
1734
+ console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
1735
+ console.log(chalk.green(' Option 1 label: "Yes, decompose this page"') +
1736
+ chalk.dim(' — continue to step 6 (Decompose)'));
1737
+ console.log(chalk.yellow(' Option 2 label: "No, skip decomposition"') +
1738
+ chalk.dim(' — skip to step 9 (Journal)'));
1739
+ console.log();
1740
+ console.log(chalk.bold.yellow('Routing:'));
1741
+ console.log(chalk.yellow(' If user chooses decomposition: ') +
1742
+ chalk.bold('codeyam editor migrate 6'));
1743
+ console.log(chalk.yellow(' If user skips: ') + chalk.bold('codeyam editor migrate 9'));
1744
+ migrationStopGate(5, pageName, pageIndex, totalPages, { confirm: true });
1745
+ }
1746
+ // ─── Migration Step 6: Decompose ────────────────────────────────────
1747
+ function printMigrateStep6(root) {
1748
+ const pageInfo = getCurrentMigrationPage(root);
1749
+ if (!pageInfo) {
1750
+ console.error(chalk.red('Error: No migration in progress.'));
1751
+ process.exit(1);
1752
+ }
1753
+ const { pageName, pageIndex, totalPages } = pageInfo;
1754
+ writeMigrationStepState(root, 6, pageName, pageIndex, totalPages);
1755
+ console.log();
1756
+ console.log(chalk.bold.cyan(`━━━ Migration Step 6: Decompose — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1757
+ console.log();
1758
+ console.log(chalk.bold('Goal: Plan all extractions. Mark already-extracted shared components as REUSE.'));
1759
+ console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
1760
+ console.log();
1761
+ console.log(chalk.bold.yellow('Migration note: check glossary for reuse opportunities'));
1762
+ console.log(chalk.yellow(' If a component exists in the glossary from a previous page, mark it as REUSE in your plan.'));
1763
+ console.log(chalk.yellow(' Only add it to the extraction plan if it needs modifications for this page.'));
1764
+ console.log();
1765
+ printExtractionPlanInstructions();
1766
+ migrationStopGate(6, pageName, pageIndex, totalPages);
1767
+ }
1768
+ // ─── Migration Step 7: Extract ──────────────────────────────────────
1769
+ function printMigrateStep7(root) {
1770
+ const pageInfo = getCurrentMigrationPage(root);
1771
+ if (!pageInfo) {
1772
+ console.error(chalk.red('Error: No migration in progress.'));
1773
+ process.exit(1);
1774
+ }
1775
+ const { pageName, pageIndex, totalPages } = pageInfo;
1776
+ writeMigrationStepState(root, 7, pageName, pageIndex, totalPages);
1777
+ console.log();
1778
+ console.log(chalk.bold.cyan(`━━━ Migration Step 7: Extract — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1779
+ console.log();
1780
+ console.log(chalk.bold('Goal: Execute the extraction plan. Components first, then functions via TDD.'));
1781
+ console.log();
1782
+ console.log(chalk.bold('Checklist:'));
1783
+ checkbox('Extract visual components (no tests needed for components)');
1784
+ checkbox('Extract library functions via TDD (write failing test first, then implement)');
1785
+ checkbox('Extract hooks as needed');
1786
+ checkbox('Recursive pass: check each extracted component for further extraction');
1787
+ console.log(chalk.yellow(' Each component should be a thin shell composing sub-components.'));
1788
+ checkbox('Verify the page file is ONLY imports + component composition');
1789
+ checkbox('Run tests to confirm nothing is broken: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
1790
+ console.log();
1791
+ console.log(chalk.dim('After extraction, the page should be a thin shell. Move to step 8 to recapture.'));
1792
+ migrationStopGate(7, pageName, pageIndex, totalPages);
1793
+ }
1794
+ // ─── Migration Step 8: Recapture ────────────────────────────────────
1795
+ function printMigrateStep8(root) {
1796
+ const pageInfo = getCurrentMigrationPage(root);
1797
+ if (!pageInfo) {
1798
+ console.error(chalk.red('Error: No migration in progress.'));
1799
+ process.exit(1);
1800
+ }
1801
+ const { pageName, pageIndex, totalPages } = pageInfo;
1802
+ writeMigrationStepState(root, 8, pageName, pageIndex, totalPages);
1803
+ const migState = readMigrationState(root);
1804
+ const page = migState.pages[pageIndex];
1805
+ console.log();
1806
+ console.log(chalk.bold.cyan(`━━━ Migration Step 8: Recapture — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1807
+ console.log();
1808
+ console.log(chalk.bold('Goal: Update glossary, re-register ALL scenarios after code changes, and verify.'));
1809
+ console.log();
1810
+ console.log(chalk.bold('Glossary:'));
1811
+ printGlossaryInstructions();
1812
+ console.log(chalk.yellow(' Skip entries already in the glossary from previous page migrations.'));
1813
+ checkbox('Update sharedComponents in `.codeyam/migration-state.json` if any extracted components are shared');
1814
+ console.log();
1815
+ console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
1816
+ console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
1817
+ checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
1818
+ checkbox('Find all scenarios that use the real page route (not /codeyam-isolate/ paths)');
1819
+ checkbox('Re-register each one with the SAME name to update its screenshot');
1820
+ console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
1821
+ checkbox('After each registration, check the response for `clientErrors`');
1822
+ console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
1823
+ console.log();
1824
+ console.log(chalk.bold('Component Scenarios:'));
1825
+ console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
1826
+ printComponentCaptureInstructions();
1827
+ console.log();
1828
+ console.log(chalk.bold('Verify:'));
1829
+ checkbox('Run `codeyam editor analyze-imports` to populate import graph');
1830
+ checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
1831
+ checkbox('Run `codeyam editor audit` to check completeness');
1832
+ checkbox('Check for client errors: `codeyam editor client-errors`');
1833
+ checkbox('Fix any issues before proceeding');
1834
+ console.log();
1835
+ migrationStopGate(8, pageName, pageIndex, totalPages);
1836
+ }
1837
+ // ─── Migration Step 9: Journal ──────────────────────────────────────
1838
+ function printMigrateStep9(root) {
1839
+ const pageInfo = getCurrentMigrationPage(root);
1840
+ if (!pageInfo) {
1841
+ console.error(chalk.red('Error: No migration in progress.'));
1842
+ process.exit(1);
1843
+ }
1844
+ const { pageName, pageIndex, totalPages } = pageInfo;
1845
+ writeMigrationStepState(root, 9, pageName, pageIndex, totalPages);
1846
+ console.log();
1847
+ console.log(chalk.bold.cyan(`━━━ Migration Step 9: Journal — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1848
+ console.log();
1849
+ console.log(`Create a journal entry documenting the migration of the ${pageName} page.`);
1850
+ console.log();
1851
+ console.log(chalk.bold('Checklist:'));
1852
+ checkbox('Write a concise description of what was migrated (2-3 sentences)');
1853
+ checkbox(`Create journal: \`codeyam editor journal '{"title":"Migration: ${pageName}","type":"migration","description":"...","includeSessionScenarios":true}'\``);
1854
+ console.log();
1855
+ migrationStopGate(9, pageName, pageIndex, totalPages);
1856
+ }
1857
+ // ─── Migration Step 10: Present ─────────────────────────────────────
1858
+ function printMigrateStep10(root) {
1859
+ const pageInfo = getCurrentMigrationPage(root);
1860
+ if (!pageInfo) {
1861
+ console.error(chalk.red('Error: No migration in progress.'));
1862
+ process.exit(1);
1863
+ }
1864
+ const { pageName, pageIndex, totalPages } = pageInfo;
1865
+ writeMigrationStepState(root, 10, pageName, pageIndex, totalPages);
1866
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1867
+ console.log();
1868
+ console.log(chalk.bold.cyan(`━━━ Migration Step 10: Present — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1869
+ console.log();
1870
+ console.log("Show results and commit this page's migration.");
1871
+ console.log();
1872
+ console.log(chalk.bold('Checklist:'));
1873
+ checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1874
+ checkbox('Pick the best scenario to showcase:');
1875
+ console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1876
+ printDimensionGuidance(dim, dimNames);
1877
+ checkbox('Show results: `codeyam editor show-results`');
1878
+ checkbox('Write a 1-2 sentence summary of what was migrated');
1879
+ console.log();
1880
+ console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
1881
+ console.log(chalk.green(' Option 1 label: "Save & commit"') +
1882
+ chalk.dim(" — commit this page's migration"));
1883
+ console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
1884
+ chalk.dim(' — make changes, then re-verify'));
1885
+ console.log();
1886
+ console.log(chalk.bold('If the user chooses "Save & commit":'));
1887
+ checkbox('Hide results: `codeyam editor hide-results`');
1888
+ checkbox(`Commit: \`codeyam editor commit '{"message":"migration: ${pageName} page\\n\\n<description>"}'\``);
1889
+ checkbox('Mark page complete: `codeyam editor migrate complete`');
1890
+ const remaining = pageInfo.totalPages - pageIndex - 1;
1891
+ if (remaining > 0) {
1892
+ checkbox('Advance to next page: `codeyam editor migrate next`');
1893
+ console.log();
1894
+ console.log(chalk.bold.green(`Page ${pageIndex + 1}/${totalPages} complete! ${remaining} page(s) remaining.`));
1895
+ }
1896
+ else {
1897
+ console.log();
1898
+ console.log(chalk.bold.green('Migration complete! All pages processed.'));
1899
+ console.log(chalk.green('The project is now fully migrated. Future sessions use the normal feature workflow.'));
1900
+ console.log(chalk.green('Start building: ') + chalk.bold('codeyam editor steps'));
1901
+ }
1902
+ console.log();
1903
+ console.log(chalk.bold('If the user chooses "Make changes":'));
1904
+ checkbox('Hide results: `codeyam editor hide-results`');
1905
+ checkbox('Ask what changes the user wants');
1906
+ checkbox(`Run: \`codeyam editor change "Migration: ${pageName}"\` — follow the change checklist`);
1907
+ console.log();
1908
+ migrationStopGate(10, pageName, pageIndex, totalPages, { confirm: true });
1909
+ }
1910
+ // ─── Migration Survey ───────────────────────────────────────────────
1911
+ function printMigrateSurvey(root) {
1912
+ console.log();
1913
+ console.log(chalk.bold.cyan('━━━ Project Migration Survey ━━━'));
1914
+ console.log();
1915
+ console.log('Survey the existing project, present a migration plan, and confirm the order with the user.');
1916
+ console.log(chalk.dim('See the "Migration Survey" section in SKILL.md for the full checklist and migration-state.json format.'));
1917
+ console.log();
1918
+ // Write editor state so hooks and printCycleOverview detect migration
1919
+ const now = new Date().toISOString();
1920
+ writeState(root, {
1921
+ feature: 'Migration: Survey',
1922
+ step: 0,
1923
+ label: 'Survey',
1924
+ startedAt: now,
1925
+ featureStartedAt: now,
1926
+ migration: {
1927
+ pageName: 'Survey',
1928
+ pageIndex: -1,
1929
+ totalPages: 0,
1930
+ },
1931
+ });
1932
+ logEvent(root, 'migration-survey', {});
1933
+ console.log(chalk.bold.red('━━━ STOP ━━━'));
1934
+ console.log();
1935
+ console.log(chalk.red('Survey the project and confirm the migration order with the user.'));
1936
+ console.log(chalk.green('After confirmation and writing migration-state.json: ') +
1937
+ chalk.bold('codeyam editor migrate 1'));
1938
+ console.log();
1939
+ }
1940
+ // ─── Migration Status ───────────────────────────────────────────────
1941
+ function printMigrationStatus(root) {
1942
+ const state = readMigrationState(root);
1943
+ if (!state) {
1944
+ console.log(chalk.dim('No migration in progress.'));
1945
+ return;
1946
+ }
1947
+ console.log();
1948
+ console.log(chalk.bold.cyan('━━━ Migration Progress ━━━'));
1949
+ console.log();
1950
+ const done = state.pages.filter((p) => p.status === 'complete').length;
1951
+ const total = state.pages.length;
1952
+ const pct = total > 0 ? Math.round((done / total) * 100) : 0;
1953
+ console.log(` Status: ${chalk.bold(state.status)} — ${done}/${total} pages (${pct}%)`);
1954
+ console.log();
1955
+ for (let i = 0; i < state.pages.length; i++) {
1956
+ const p = state.pages[i];
1957
+ let icon;
1958
+ let color;
1959
+ if (p.status === 'complete') {
1960
+ icon = '✓';
1961
+ color = chalk.green;
1962
+ }
1963
+ else if (p.status === 'in-progress') {
1964
+ icon = '→';
1965
+ color = chalk.bold.cyan;
1966
+ }
1967
+ else {
1968
+ icon = '○';
1969
+ color = chalk.dim;
1970
+ }
1971
+ const extra = p.status === 'complete'
1972
+ ? chalk.dim(` (${p.extractedComponents.length} components, ${p.extractedFunctions.length} functions, ${p.scenarioCount} scenarios)`)
1973
+ : '';
1974
+ console.log(` ${color(`${icon} ${i + 1}. ${p.name}`)} ${chalk.dim(`(${p.route})`)}${extra}`);
1975
+ }
1976
+ if (state.sharedComponents.length > 0) {
1977
+ console.log();
1978
+ console.log(chalk.bold('Shared Components:'));
1979
+ for (const sc of state.sharedComponents) {
1980
+ console.log(` ${chalk.cyan(sc.name)} ${chalk.dim(`— from ${sc.extractedFromPage}, used in: ${sc.usedInPages.join(', ')}`)}`);
1981
+ }
1982
+ }
1983
+ console.log();
1984
+ }
1985
+ // ─── Migration Command Dispatcher ───────────────────────────────────
1986
+ function handleMigrateCommand(root, subArg) {
1987
+ // No subcommand or explicit empty → run initial survey
1988
+ if (!subArg) {
1989
+ const migState = readMigrationState(root);
1990
+ if (migState?.status === 'in-progress') {
1991
+ // Already in progress — show status and resume hint
1992
+ printMigrationStatus(root);
1993
+ const page = migState.pages[migState.currentPageIndex];
1994
+ if (page) {
1995
+ const editorState = readState(root);
1996
+ const currentStep = editorState?.migration ? editorState.step : 1;
1997
+ console.log(chalk.green('Continue with: ') +
1998
+ chalk.bold(`codeyam editor migrate ${currentStep}`));
1999
+ console.log();
2000
+ }
2001
+ return;
2002
+ }
2003
+ if (migState?.status === 'complete') {
2004
+ console.log(chalk.green('Migration is complete! Use the normal feature workflow: `codeyam editor steps`'));
2005
+ return;
2006
+ }
2007
+ printMigrateSurvey(root);
2008
+ return;
2009
+ }
2010
+ // Parse subcommand
2011
+ if (subArg === 'next') {
2012
+ const result = advanceToNextPage(root);
2013
+ if (!result) {
2014
+ const migState = readMigrationState(root);
2015
+ if (migState?.status === 'complete') {
2016
+ console.log();
2017
+ console.log(chalk.bold.green('🎉 Migration complete!'));
2018
+ console.log();
2019
+ const done = migState.pages.filter((p) => p.status === 'complete');
2020
+ const totalComponents = done.reduce((sum, p) => sum + p.extractedComponents.length, 0);
2021
+ const totalFunctions = done.reduce((sum, p) => sum + p.extractedFunctions.length, 0);
2022
+ const totalScenarios = done.reduce((sum, p) => sum + p.scenarioCount, 0);
2023
+ console.log(` Pages migrated: ${done.length}`);
2024
+ console.log(` Components extracted: ${totalComponents}`);
2025
+ console.log(` Functions extracted: ${totalFunctions}`);
2026
+ console.log(` Scenarios created: ${totalScenarios}`);
2027
+ console.log(` Shared components: ${migState.sharedComponents.length}`);
2028
+ console.log();
2029
+ console.log(chalk.green('The project is fully migrated. Start building features: `codeyam editor steps`'));
2030
+ console.log();
2031
+ }
2032
+ else {
2033
+ console.log(chalk.red('No pending pages found. Run `codeyam editor migrate status` to check progress.'));
2034
+ }
2035
+ return;
2036
+ }
2037
+ // Start M1 for the next page
2038
+ printMigrateStep1(root);
2039
+ return;
2040
+ }
2041
+ if (subArg === 'status') {
2042
+ printMigrationStatus(root);
2043
+ return;
2044
+ }
2045
+ if (subArg === 'complete') {
2046
+ const migState = readMigrationState(root);
2047
+ if (!migState) {
2048
+ console.error(chalk.red('Error: No migration in progress.'));
2049
+ process.exit(1);
2050
+ }
2051
+ const page = migState.pages[migState.currentPageIndex];
2052
+ if (!page) {
2053
+ console.error(chalk.red('Error: No active migration page.'));
2054
+ process.exit(1);
2055
+ }
2056
+ completePage(root, {
2057
+ extractedComponents: page.extractedComponents,
2058
+ extractedFunctions: page.extractedFunctions,
2059
+ scenarioCount: page.scenarioCount,
2060
+ });
2061
+ console.log(chalk.green(`Page "${page.name}" marked complete.`));
2062
+ return;
2063
+ }
2064
+ // Numeric step: 1-8
2065
+ const step = parseInt(subArg, 10);
2066
+ if (isNaN(step) || step < 1 || step > 10) {
2067
+ console.error(chalk.red(`Error: Invalid migration step "${subArg}". Must be 1-10, "next", "complete", or "status".`));
2068
+ process.exit(1);
2069
+ }
2070
+ // Ensure migration is active and first page started
2071
+ const migState = readMigrationState(root);
2072
+ if (!migState) {
2073
+ console.error(chalk.red('Error: No migration state. Run `codeyam editor migrate` first.'));
2074
+ process.exit(1);
2075
+ }
2076
+ // If still in survey state and step 1 requested, start first page
2077
+ if (migState.status === 'surveyed' && step === 1) {
2078
+ migState.status = 'in-progress';
2079
+ migState.pages[0].status = 'in-progress';
2080
+ migState.pages[0].startedAt = new Date().toISOString();
2081
+ writeMigrationState(root, migState);
2082
+ }
2083
+ const stepFns = {
2084
+ 1: printMigrateStep1,
2085
+ 2: printMigrateStep2,
2086
+ 3: printMigrateStep3,
2087
+ 4: printMigrateStep4,
2088
+ 5: printMigrateStep5,
2089
+ 6: printMigrateStep6,
2090
+ 7: printMigrateStep7,
2091
+ 8: printMigrateStep8,
2092
+ 9: printMigrateStep9,
2093
+ 10: printMigrateStep10,
2094
+ };
2095
+ stepFns[step](root);
2096
+ }
2097
+ // ─── Step 14: Commit ─────────────────────────────────────────────────
2098
+ function printStep14(root, feature) {
2099
+ const prevState = readState(root);
2100
+ const isResuming = prevState?.step === 14;
2101
+ const now = new Date().toISOString();
2102
+ writeState(root, {
2103
+ feature,
2104
+ step: 14,
2105
+ label: STEP_LABELS[14],
2106
+ startedAt: isResuming ? prevState.startedAt : now,
2107
+ featureStartedAt: prevState?.featureStartedAt || now,
2108
+ });
2109
+ logEvent(root, 'step', { step: 14, label: 'Commit', feature });
2110
+ stepHeader(14, 'Commit', feature);
2111
+ if (isResuming) {
2112
+ printResumptionHeader(14);
2113
+ }
2114
+ console.log('Commit all changes for this feature.');
2115
+ console.log();
2116
+ console.log(chalk.bold('Checklist:'));
2117
+ checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
2118
+ checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
2119
+ console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
2120
+ stopGate(14);
2121
+ }
2122
+ // ─── Step 15: Finalize ───────────────────────────────────────────────
2123
+ function printStep15(root, feature) {
2124
+ const prevState = readState(root);
2125
+ const isResuming = prevState?.step === 15;
2126
+ const now = new Date().toISOString();
2127
+ writeState(root, {
2128
+ feature,
2129
+ step: 15,
2130
+ label: STEP_LABELS[15],
2131
+ startedAt: isResuming ? prevState.startedAt : now,
2132
+ featureStartedAt: prevState?.featureStartedAt || now,
2133
+ });
2134
+ logEvent(root, 'step', { step: 15, label: 'Finalize', feature });
2135
+ stepHeader(15, 'Finalize', feature);
2136
+ if (isResuming) {
2137
+ printResumptionHeader(15);
2138
+ }
2139
+ console.log('Update the journal with the commit SHA and amend the commit.');
2140
+ console.log();
2141
+ console.log(chalk.bold('Checklist:'));
2142
+ checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
2143
+ checkbox('Amend the commit to include the journal update: `git add .codeyam/journal/ && git commit --amend --no-edit`');
2144
+ console.log(chalk.dim(' The journal-update modifies journal files after the commit — amend to keep the tree clean.'));
2145
+ stopGate(15);
2146
+ }
2147
+ // ─── Step 16: Push ───────────────────────────────────────────────────
2148
+ function printStep16(root, feature) {
2149
+ const prevState = readState(root);
2150
+ const isResuming = prevState?.step === 16;
2151
+ const now = new Date().toISOString();
2152
+ writeState(root, {
2153
+ feature,
2154
+ step: 16,
2155
+ label: STEP_LABELS[16],
2156
+ startedAt: isResuming ? prevState.startedAt : now,
2157
+ featureStartedAt: prevState?.featureStartedAt || now,
2158
+ });
2159
+ logEvent(root, 'step', { step: 16, label: 'Push', feature });
2160
+ stepHeader(16, 'Push', feature);
2161
+ if (isResuming) {
2162
+ printResumptionHeader(16);
2163
+ }
2164
+ console.log('Push the commit to the remote repository.');
2165
+ console.log();
2166
+ console.log(chalk.bold('Checklist:'));
2167
+ checkbox('Check if a git remote is configured: `git remote -v`');
2168
+ checkbox("Offer to push to remote (AskUserQuestion — STOP and wait for the user's answer before proceeding):");
2169
+ console.log(chalk.dim(' If a remote exists, ask (AskUserQuestion): "Would you like me to push this commit to the remote?" with options "Yes, push" and "No, skip"'));
2170
+ console.log(chalk.dim(' If the user says yes, run: `git push`'));
2171
+ console.log(chalk.dim(' If NO remote exists, ask (AskUserQuestion): "This project doesn\'t have a git remote yet. Would you like help setting one up? I can walk you through creating a GitHub repository." with options "Yes, set up remote" and "No, skip"'));
2172
+ console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
2173
+ checkbox('After the user responds, run: `codeyam editor steps` to start the next feature');
2174
+ console.log();
2175
+ console.log(chalk.red.bold(' If the user reports a bug or requests a fix after committing:'));
2176
+ console.log(chalk.red.bold(' You MUST still run `codeyam editor change` before making any changes.'));
2177
+ console.log(chalk.red.bold(' The change workflow applies to ALL changes — post-commit fixes are not an exception.'));
2178
+ stopGate(16, { confirm: true });
2179
+ }
2180
+ // ─── Command definition ───────────────────────────────────────────────
2181
+ // ─── Analyze-imports subcommand ────────────────────────────────────────
2182
+ /**
2183
+ * `codeyam editor analyze-imports`
2184
+ *
2185
+ * Runs data-structure-only analysis for all glossary entities, then outputs
2186
+ * an import graph and entity data structures as JSON to stdout.
2187
+ */
2188
+ async function handleAnalyzeImports(options = {}) {
2189
+ const root = getProjectRoot();
2190
+ // Read glossary
2191
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
2192
+ if (!fs.existsSync(glossaryPath)) {
2193
+ if (options.silent) {
2194
+ // Internal caller — glossary doesn't exist yet, nothing to analyze
2195
+ return;
2196
+ }
2197
+ console.error(chalk.red('Error: .codeyam/glossary.json not found.'));
2198
+ console.error(chalk.dim(' Run codeyam editor 6 to create the glossary first.'));
2199
+ process.exit(1);
2200
+ }
2201
+ let glossaryEntries;
2202
+ try {
2203
+ const parsed = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
2204
+ glossaryEntries = sanitizeGlossaryEntries(parsed);
2205
+ }
2206
+ catch {
2207
+ console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
2208
+ process.exit(1);
2209
+ }
2210
+ if (glossaryEntries.length === 0) {
2211
+ console.error(chalk.red('Error: glossary.json is empty.'));
2212
+ process.exit(1);
2213
+ }
2214
+ const filePaths = glossaryEntries.map((e) => e.filePath);
2215
+ // Include page files so pages get analyzed as entities too.
2216
+ // This allows the import graph to map page names to their dependencies.
2217
+ // Use allFiles (not map values) to include ALL pages — multiple routes
2218
+ // can share a page name (e.g., /feedback/[id] and /feedback/new both
2219
+ // map to "Feedback") but each needs its own entity analysis.
2220
+ const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
2221
+ const { allFiles: allPageFiles } = scanPageFilePaths(root);
2222
+ for (const pageFile of allPageFiles) {
2223
+ if (!filePaths.includes(pageFile)) {
2224
+ filePaths.push(pageFile);
2225
+ }
2226
+ }
2227
+ // Include file paths from editor scenarios (both component_path and
2228
+ // page_file_path) so that all components and pages with scenarios get
2229
+ // entities — critical for non-Next.js apps where scanPageFilePaths
2230
+ // doesn't find page.tsx files.
2231
+ try {
2232
+ const { getDatabase } = await import('../../../packages/database/index.js');
2233
+ const db = getDatabase();
2234
+ const scenarioFiles = await db
2235
+ .selectFrom('editor_scenarios')
2236
+ .select(['component_path', 'page_file_path'])
2237
+ .distinct()
2238
+ .execute();
2239
+ for (const row of scenarioFiles) {
2240
+ const r = row;
2241
+ for (const fp of [r.component_path, r.page_file_path]) {
2242
+ if (fp && !filePaths.includes(fp)) {
2243
+ filePaths.push(fp);
2244
+ }
2245
+ }
2246
+ }
2247
+ }
2248
+ catch {
2249
+ // Non-fatal — scenario file paths just won't be included
2250
+ }
2251
+ const progress = new ProgressReporter();
2252
+ // Run data-structure-only analysis for all entities (glossary + pages)
2253
+ // Don't pass entityNames — entities may not exist yet (fresh clone).
2254
+ // The analyzer will discover and create them from file paths.
2255
+ progress.start('Running import analysis for all entities...');
2256
+ try {
2257
+ await runAnalysisForEntities({
2258
+ projectRoot: root,
2259
+ filePaths,
2260
+ progress,
2261
+ onlyDataStructure: true,
2262
+ });
2263
+ }
2264
+ catch (err) {
2265
+ progress.fail('Analysis failed');
2266
+ const msg = err instanceof Error ? err.message : String(err);
2267
+ console.error(chalk.red(`Error: ${msg}`));
2268
+ process.exit(1);
2269
+ }
2270
+ progress.succeed('Analysis complete');
2271
+ // Load entities WITH metadata from database
2272
+ progress.start('Loading entity metadata...');
2273
+ await initializeEnvironment();
2274
+ const entities = await loadEntities({});
2275
+ if (!entities || entities.length === 0) {
2276
+ progress.succeed('No entities found in database yet — skipping import analysis. This is normal on the first feature.');
2277
+ if (!options.silent) {
2278
+ console.log(JSON.stringify({ imports: {}, entities: {} }));
2279
+ }
2280
+ return;
2281
+ }
2282
+ // Deduplicate to latest versions
2283
+ const latestByKey = new Map();
2284
+ for (const entity of entities) {
2285
+ const key = `${entity.name}::${entity.filePath}`;
2286
+ const existing = latestByKey.get(key);
2287
+ if (!existing ||
2288
+ (entity.createdAt &&
2289
+ existing.createdAt &&
2290
+ entity.createdAt > existing.createdAt)) {
2291
+ latestByKey.set(key, entity);
2292
+ }
2293
+ }
2294
+ const entityNameSet = new Set(glossaryEntries.map((e) => e.name));
2295
+ const latestEntities = [...latestByKey.values()].filter((e) => entityNameSet.has(e.name));
2296
+ // Build import graph from importedExports metadata
2297
+ const imports = {};
2298
+ const entityData = {};
2299
+ // Also load analyses for data structures
2300
+ const entityShas = latestEntities.map((e) => e.sha);
2301
+ const analyses = await loadAnalyses({ entityShas });
2302
+ const analysisMap = new Map();
2303
+ if (analyses) {
2304
+ for (const a of analyses) {
2305
+ if (!analysisMap.has(a.entitySha) ||
2306
+ (a.createdAt &&
2307
+ (!analysisMap.get(a.entitySha).createdAt ||
2308
+ a.createdAt > analysisMap.get(a.entitySha).createdAt))) {
2309
+ analysisMap.set(a.entitySha, a);
2310
+ }
2311
+ }
2312
+ }
2313
+ for (const entity of latestEntities) {
2314
+ const importedExports = entity.metadata?.importedExports || [];
2315
+ const importedNames = importedExports
2316
+ .map((ie) => ie.name)
2317
+ .filter((name) => entityNameSet.has(name));
2318
+ if (importedNames.length > 0) {
2319
+ imports[entity.name] = importedNames;
2320
+ }
2321
+ // Collect entity data with analysis data structures
2322
+ const analysis = analysisMap.get(entity.sha);
2323
+ const analysisMetadata = analysis?.metadata;
2324
+ entityData[entity.name] = {
2325
+ filePath: entity.filePath || '',
2326
+ entityType: entity.entityType || 'visual',
2327
+ dataForMocks: analysisMetadata?.scenariosDataStructure?.dataForMocks,
2328
+ dependencySchemas: analysisMetadata?.mergedDataStructure?.dependencySchemas,
2329
+ };
2330
+ }
2331
+ progress.succeed('Done');
2332
+ // Output combined JSON (suppressed when called internally during startup)
2333
+ if (!options.silent) {
2334
+ const summary = formatApiSubcommandResult('analyze-imports', {
2335
+ imports,
2336
+ entities: entityData,
2337
+ });
2338
+ console.log(summary || JSON.stringify({ imports, entities: entityData }));
2339
+ }
2340
+ // Backfill entity_sha on scenarios registered before entities existed.
2341
+ // This fixes the "missing data" UI state when scenarios were registered
2342
+ // while analyze-imports was still running in the background.
2343
+ try {
2344
+ const { getDatabase } = await import('../../../packages/database/index.js');
2345
+ const db = getDatabase();
2346
+ const backfillResult = await backfillEntityShaOnScenarios(db, latestEntities.map((e) => ({
2347
+ sha: e.sha,
2348
+ name: e.name,
2349
+ filePath: e.filePath || '',
2350
+ })));
2351
+ if (backfillResult.updated > 0 && !options.silent) {
2352
+ console.log(chalk.green(`Linked ${backfillResult.updated} scenario(s) to their entities.`));
2353
+ }
2354
+ }
2355
+ catch {
2356
+ /* non-fatal */
2357
+ }
2358
+ }
2359
+ // ─── Validate-seed subcommand ─────────────────────────────────────────
2360
+ /**
2361
+ * Validate seed data structure and check for a seed adapter.
2362
+ * Usage: codeyam editor validate-seed '{"products":[...]}'
2363
+ */
2364
+ function handleValidateSeed(jsonArg) {
2365
+ const root = process.cwd();
2366
+ // Check for seed adapter
2367
+ const adapterPath = detectSeedAdapter(root);
2368
+ if (adapterPath) {
2369
+ console.log(chalk.green(`Seed adapter found: ${adapterPath}`));
2370
+ }
2371
+ else {
2372
+ console.log(chalk.yellow('No seed adapter found. Create .codeyam/seed-adapter.ts to use seed-based scenarios.'));
2373
+ }
2374
+ if (!jsonArg) {
2375
+ return;
2376
+ }
2377
+ let data;
2378
+ try {
2379
+ data = JSON.parse(jsonArg);
2380
+ }
2381
+ catch {
2382
+ console.error(chalk.red('Error: Invalid JSON.'));
2383
+ process.exit(1);
2384
+ }
2385
+ const result = validateSeedData(data);
2386
+ if (result.valid) {
2387
+ const tables = Object.keys(data);
2388
+ const totalRows = tables.reduce((sum, t) => sum + (Array.isArray(data[t]) ? data[t].length : 0), 0);
2389
+ console.log(chalk.green(`Seed data valid: ${tables.length} table(s), ${totalRows} total row(s)`));
2390
+ for (const table of tables) {
2391
+ const rows = Array.isArray(data[table])
2392
+ ? data[table].length
2393
+ : 0;
2394
+ console.log(chalk.dim(` ${table}: ${rows} row(s)`));
2395
+ }
2396
+ // Check seed keys against Prisma schema if available
2397
+ const schemaPath = path.join(root, 'prisma', 'schema.prisma');
2398
+ try {
2399
+ if (fs.existsSync(schemaPath)) {
2400
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
2401
+ const warnings = validateSeedKeysAgainstPrisma(tables, schemaContent);
2402
+ if (warnings.length > 0) {
2403
+ console.log();
2404
+ console.log(chalk.yellow('Prisma model name warnings:'));
2405
+ for (const w of warnings) {
2406
+ console.log(chalk.yellow(` ⚠ ${w}`));
2407
+ }
2408
+ }
2409
+ }
2410
+ }
2411
+ catch {
2412
+ // Schema not readable — skip Prisma validation
2413
+ }
2414
+ }
2415
+ else {
2416
+ console.error(chalk.red('Seed data validation failed:'));
2417
+ for (const err of result.errors) {
2418
+ console.error(chalk.red(` - ${err}`));
2419
+ }
2420
+ process.exit(1);
2421
+ }
2422
+ }
2423
+ // ─── Delete subcommand ────────────────────────────────────────────────
2424
+ /**
2425
+ * `codeyam editor delete <scenarioId>`
2426
+ *
2427
+ * Delete a scenario by ID via the running editor server.
2428
+ */
2429
+ async function handleDelete(scenarioId) {
2430
+ if (!scenarioId) {
2431
+ console.error(chalk.red('Error: Missing scenario ID. Usage: codeyam editor delete <scenarioId>'));
2432
+ process.exit(1);
2433
+ }
2434
+ const port = getServerPort();
2435
+ const result = await deleteScenarioViaCli(scenarioId, port);
2436
+ if (result.success) {
2437
+ console.log(chalk.green(result.message));
2438
+ }
2439
+ else {
2440
+ console.error(chalk.red(result.message));
2441
+ process.exit(1);
2442
+ }
2443
+ }
2444
+ // ─── Isolate subcommand ───────────────────────────────────────────────
2445
+ /**
2446
+ * `codeyam editor isolate "StarRating CategoryBadge DrinkCard"`
2447
+ *
2448
+ * Creates isolation route directories and the layout guard file.
2449
+ * This avoids brace-expansion permission prompts in the embedded terminal.
2450
+ */
2451
+ function handleIsolate(componentNames) {
2452
+ const root = process.cwd();
2453
+ if (componentNames.length === 0) {
2454
+ console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
2455
+ process.exit(1);
2456
+ }
2457
+ const isolateDir = path.join(root, 'app', 'isolated-components');
2458
+ // Create layout.tsx with production guard if missing
2459
+ const layoutPath = path.join(isolateDir, 'layout.tsx');
2460
+ if (!fs.existsSync(layoutPath)) {
2461
+ fs.mkdirSync(isolateDir, { recursive: true });
2462
+ fs.writeFileSync(layoutPath, [
2463
+ 'import { notFound } from "next/navigation";',
2464
+ '',
2465
+ 'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
2466
+ ' if (process.env.NODE_ENV === "production") notFound();',
2467
+ ' return <>{children}</>;',
2468
+ '}',
2469
+ '',
2470
+ ].join('\n'), 'utf8');
2471
+ console.log(chalk.green(`Created layout guard: app/isolated-components/layout.tsx`));
2472
+ }
2473
+ // Create a directory for each component
2474
+ const created = [];
2475
+ const existed = [];
2476
+ for (const name of componentNames) {
2477
+ const dir = path.join(isolateDir, name);
2478
+ if (fs.existsSync(dir)) {
2479
+ existed.push(name);
2480
+ }
2481
+ else {
2482
+ fs.mkdirSync(dir, { recursive: true });
2483
+ created.push(name);
2484
+ }
2485
+ }
2486
+ if (created.length > 0) {
2487
+ console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
2488
+ }
2489
+ if (existed.length > 0) {
2490
+ console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
2491
+ }
2492
+ }
2493
+ // ─── Register subcommand ──────────────────────────────────────────────
2494
+ /**
2495
+ * Format API subcommand results as concise one-line summaries.
2496
+ * Prevents Claude from piping output to python3 just to extract key fields.
2497
+ * Returns null for subcommands that should keep raw JSON output.
2498
+ */
2499
+ function formatApiSubcommandResult(subcommand, data) {
2500
+ if (!data || typeof data !== 'object')
2501
+ return null;
2502
+ switch (subcommand) {
2503
+ case 'journal': {
2504
+ const parts = [`success=${data.success}`];
2505
+ if (data.entry?.time)
2506
+ parts.push(`time="${data.entry.time}"`);
2507
+ if (data.entry?.title)
2508
+ parts.push(`title="${data.entry.title}"`);
2509
+ if (data.scenarioScreenshotsFound != null) {
2510
+ parts.push(`screenshots=${data.scenarioScreenshotsFound}`);
2511
+ }
2512
+ return parts.join(' ');
2513
+ }
2514
+ case 'journal-update': {
2515
+ const parts = [`success=${data.success}`];
2516
+ if (data.entry?.time)
2517
+ parts.push(`time="${data.entry.time}"`);
2518
+ if (data.scenarioScreenshotsFound != null) {
2519
+ parts.push(`screenshots=${data.scenarioScreenshotsFound}`);
2520
+ }
2521
+ return parts.join(' ');
2522
+ }
2523
+ case 'commit': {
2524
+ const parts = [`success=${data.success}`];
2525
+ if (data.sha)
2526
+ parts.push(`sha=${data.sha}`);
2527
+ if (data.message)
2528
+ parts.push(`message="${data.message.split('\n')[0]}"`);
2529
+ return parts.join(' ');
2530
+ }
2531
+ case 'preview': {
2532
+ const parts = [
2533
+ `success=${!!data.ok || data.success !== false}`,
2534
+ ];
2535
+ if (data.path)
2536
+ parts.push(`path="${data.path}"`);
2537
+ if (data.scenarioId)
2538
+ parts.push(`scenarioId="${data.scenarioId}"`);
2539
+ if (data.sessionsNotified != null)
2540
+ parts.push(`notified=${data.sessionsNotified}`);
2541
+ return parts.join(' ');
2542
+ }
2543
+ case 'dev-server': {
2544
+ if (data.status) {
2545
+ const parts = [`status=${data.status}`, `pid=${data.pid || 'none'}`];
2546
+ if (data.url)
2547
+ parts.push(`url=${data.url}`);
2548
+ if (data.proxyUrl)
2549
+ parts.push(`proxyUrl=${data.proxyUrl}`);
2550
+ if (data.errorMessage)
2551
+ parts.push(`error: ${data.errorMessage}`);
2552
+ return parts.join(' ');
2553
+ }
2554
+ return null;
2555
+ }
2556
+ case 'client-errors': {
2557
+ const parts = [`hasErrors=${data.hasErrors}`];
2558
+ parts.push(`totalErrors=${data.totalErrors ?? 0}`);
2559
+ if (data.livePreview) {
2560
+ const lp = data.livePreview;
2561
+ parts.push(`livePreview=${lp.loaded ? 'loaded' : 'not-loaded'}`);
2562
+ if (lp.loaded) {
2563
+ parts.push(`hasContent=${lp.hasContent}`);
2564
+ }
2565
+ const liveErrors = lp.errors?.length ?? 0;
2566
+ parts.push(`liveErrors=${liveErrors}`);
2567
+ if (liveErrors > 0) {
2568
+ // Show first few error messages for context
2569
+ for (const err of lp.errors.slice(0, 3)) {
2570
+ parts.push(` error: ${err.message}`);
2571
+ }
2572
+ }
2573
+ }
2574
+ else {
2575
+ parts.push('livePreview=not-loaded');
2576
+ }
2577
+ return parts.join(' ');
2578
+ }
2579
+ case 'analyze-imports': {
2580
+ const importEntries = Object.entries(data.imports || {});
2581
+ const entityEntries = Object.entries(data.entities || {});
2582
+ const parts = [];
2583
+ parts.push(`entities=${entityEntries.length}`);
2584
+ parts.push(`withImports=${importEntries.length}`);
2585
+ if (importEntries.length > 0) {
2586
+ parts.push(`imports: ${importEntries.map(([name, deps]) => `${name}→[${deps.join(',')}]`).join(' ')}`);
2587
+ }
2588
+ return parts.join(' ');
2589
+ }
2590
+ default:
2591
+ return null; // journal-list, show/hide-results: keep full JSON
2592
+ }
2593
+ }
2594
+ /**
2595
+ * `codeyam editor register '{"name":"...","componentName":"...",...}'`
2596
+ *
2597
+ * Thin CLI wrapper around POST /api/editor-register-scenario.
2598
+ * Auto-approved via `Bash(codeyam:*)` — no manual approval needed.
2599
+ */
2600
+ async function handleRegister(jsonArg) {
2601
+ if (!jsonArg) {
2602
+ const { defaultName: dim } = getProjectDimensions(getProjectRoot());
2603
+ console.error(chalk.red('Error: JSON argument required.'));
2604
+ console.error(chalk.dim(` Usage: codeyam editor register '{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/isolated-components/DrinkCard?s=Default","dimensions":["${dim}"]}'`));
2605
+ console.error(chalk.dim(' For large payloads: codeyam editor register @/tmp/scenario.json'));
2606
+ process.exit(1);
2607
+ }
2608
+ const parsed = parseRegisterArg(jsonArg);
2609
+ if (parsed.error) {
2610
+ console.error(chalk.red(`Error: ${parsed.error}`));
2611
+ if (parsed.error === 'Invalid JSON') {
2612
+ console.error(chalk.dim(` Received: ${jsonArg.slice(0, 200)}`));
2613
+ }
2614
+ process.exit(1);
2615
+ }
2616
+ // Normalize to array for uniform handling
2617
+ const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
2618
+ const port = getServerPort();
2619
+ const url = `http://localhost:${port}/api/editor-register-scenario`;
2620
+ const isBatch = items.length > 1;
2621
+ let succeeded = 0;
2622
+ let failed = 0;
2623
+ for (let i = 0; i < items.length; i++) {
2624
+ const body = items[i];
2625
+ const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
2626
+ try {
2627
+ const res = await fetch(url, {
2628
+ method: 'POST',
2629
+ headers: { 'Content-Type': 'application/json' },
2630
+ body: JSON.stringify(body),
2631
+ });
2632
+ const data = await res.json();
2633
+ // Print concise summary instead of raw JSON so Claude doesn't need python3
2634
+ const parts = [];
2635
+ parts.push(`success=${data.success}`);
2636
+ if (data.scenario?.name)
2637
+ parts.push(`name="${data.scenario.name}"`);
2638
+ parts.push(`screenshot=${!!data.screenshotCaptured}`);
2639
+ if (data.capturedViewport) {
2640
+ parts.push(`viewport=${data.capturedViewport.width}×${data.capturedViewport.height}`);
2641
+ }
2642
+ if (data.scenario?.dimension)
2643
+ parts.push(`dimension="${data.scenario.dimension}"`);
2644
+ if (data.clientErrors?.length > 0) {
2645
+ parts.push(chalk.red(`errors=${data.clientErrors.length}`));
2646
+ }
2647
+ else {
2648
+ parts.push(`errors=0`);
2649
+ }
2650
+ if (data.seedResult)
2651
+ parts.push(`seed=${data.seedResult.success}`);
2652
+ if (data.captureError)
2653
+ parts.push(chalk.yellow(`captureError="${data.captureError}"`));
2654
+ console.log(prefix + parts.join(' '));
2655
+ // Surface client errors prominently so they can't be missed
2656
+ if (data.clientErrors && data.clientErrors.length > 0) {
2657
+ console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
2658
+ for (const err of data.clientErrors) {
2659
+ console.log(chalk.red(` → ${err}`));
2660
+ }
2661
+ console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
2662
+ console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
2663
+ }
2664
+ if (!res.ok) {
2665
+ console.error(chalk.dim(JSON.stringify(data, null, 2)));
2666
+ failed++;
2667
+ }
2668
+ else {
2669
+ succeeded++;
2670
+ }
2671
+ }
2672
+ catch (error) {
2673
+ const msg = error instanceof Error ? error.message : String(error);
2674
+ console.error(prefix + chalk.red(`Error: Could not reach editor server at ${url}`));
2675
+ console.error(chalk.dim(` ${msg}`));
2676
+ console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
2677
+ failed++;
2678
+ }
2679
+ }
2680
+ if (isBatch) {
2681
+ console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
2682
+ }
2683
+ if (failed > 0) {
2684
+ process.exit(1);
2685
+ }
2686
+ }
2687
+ // ─── Dependents subcommand ────────────────────────────────────────────
2688
+ /**
2689
+ * `codeyam editor dependents <EntityName>`
2690
+ *
2691
+ * Walks the importedBy reverse dependency tree to find all ancestors of a
2692
+ * given entity — i.e., every component or page that transitively imports it.
2693
+ * Used after code changes to determine what needs recapturing.
2694
+ */
2695
+ async function handleDependents(entityName) {
2696
+ if (!entityName) {
2697
+ console.error(chalk.red('Error: Entity name required.'));
2698
+ console.error(chalk.dim(' Usage: codeyam editor dependents DrinkCard'));
2699
+ process.exit(1);
2700
+ }
2701
+ const progress = new ProgressReporter();
2702
+ progress.start('Loading entities from database...');
2703
+ await initializeEnvironment();
2704
+ let allEntities = await loadEntities({});
2705
+ if (!allEntities || allEntities.length === 0) {
2706
+ progress.succeed('No entities found — running analyze-imports first...');
2707
+ try {
2708
+ await handleAnalyzeImports({ silent: true });
2709
+ }
2710
+ catch {
2711
+ console.error(chalk.red('Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
2712
+ process.exit(1);
2713
+ }
2714
+ // Reload entities after analysis
2715
+ const reloaded = await loadEntities({});
2716
+ if (!reloaded || reloaded.length === 0) {
2717
+ progress.fail('No entities found even after analyze-imports');
2718
+ process.exit(1);
2719
+ }
2720
+ allEntities = reloaded;
2721
+ }
2722
+ // Find the target entity by name (case-insensitive match)
2723
+ const targetEntities = allEntities.filter((e) => e.name.toLowerCase() === entityName.toLowerCase());
2724
+ if (targetEntities.length === 0) {
2725
+ progress.fail(`Entity "${entityName}" not found in database`);
2726
+ const names = [...new Set(allEntities.map((e) => e.name))].sort();
2727
+ console.error(chalk.dim(` Available entities: ${names.join(', ')}`));
2728
+ process.exit(1);
2729
+ }
2730
+ progress.succeed('Entities loaded');
2731
+ // Build lookup: entityName -> Set<entityName> of entities that import it.
2732
+ // Each entity's metadata.importedBy is the pre-computed reverse graph:
2733
+ // { [filePath]: { [importerName]: { shas: string[] } } }
2734
+ const importedByName = new Map();
2735
+ for (const entity of allEntities) {
2736
+ const importedBy = entity.metadata?.importedBy;
2737
+ if (!importedBy || typeof importedBy !== 'object')
2738
+ continue;
2739
+ const importers = new Set();
2740
+ for (const filePath of Object.keys(importedBy)) {
2741
+ for (const importerName of Object.keys(importedBy[filePath])) {
2742
+ importers.add(importerName);
2743
+ }
2744
+ }
2745
+ if (importers.size > 0) {
2746
+ importedByName.set(entity.name, importers);
2747
+ }
2748
+ }
2749
+ // BFS walk up the dependency tree from the changed entity
2750
+ const visited = new Set();
2751
+ const result = [];
2752
+ const queue = [
2753
+ { name: entityName, depth: 0 },
2754
+ ];
2755
+ visited.add(entityName.toLowerCase());
2756
+ // Collect entity types for display
2757
+ const entityTypes = new Map();
2758
+ for (const e of allEntities) {
2759
+ if (!entityTypes.has(e.name)) {
2760
+ entityTypes.set(e.name, e.entityType || 'unknown');
2761
+ }
2762
+ }
2763
+ while (queue.length > 0) {
2764
+ const { name, depth } = queue.shift();
2765
+ result.push({
2766
+ name,
2767
+ entityType: entityTypes.get(name) || 'unknown',
2768
+ depth,
2769
+ });
2770
+ // Find all entities that import this one
2771
+ const importers = importedByName.get(name);
2772
+ if (!importers)
2773
+ continue;
2774
+ for (const importerName of importers) {
2775
+ if (visited.has(importerName.toLowerCase()))
2776
+ continue;
2777
+ visited.add(importerName.toLowerCase());
2778
+ queue.push({ name: importerName, depth: depth + 1 });
2779
+ }
2780
+ }
2781
+ // Output
2782
+ if (result.length <= 1) {
2783
+ console.log(chalk.yellow(`No dependents found for "${entityName}".`));
2784
+ console.log(chalk.dim(' This entity is not imported by any other known entities.'));
2785
+ console.log(chalk.dim(' Run codeyam editor analyze-imports to populate import data.'));
2786
+ return;
2787
+ }
2788
+ console.log(chalk.bold(`Dependency tree for ${entityName} (${result.length} entities):`));
2789
+ console.log();
2790
+ for (const entry of result) {
2791
+ const indent = ' '.repeat(entry.depth);
2792
+ const prefix = entry.depth === 0 ? chalk.cyan('●') : chalk.dim('├──');
2793
+ const label = entry.depth === 0
2794
+ ? chalk.cyan(`${entry.name} (changed)`)
2795
+ : chalk.white(entry.name);
2796
+ const type = chalk.dim(`[${entry.entityType}]`);
2797
+ console.log(`${indent}${prefix} ${label} ${type}`);
2798
+ }
2799
+ console.log();
2800
+ console.log(chalk.dim('All listed entities need their scenarios recaptured after changes.'));
2801
+ }
2802
+ // ─── Change subcommand ───────────────────────────────────────────────
2803
+ /**
2804
+ * `codeyam editor change <feature>`
2805
+ *
2806
+ * Prints a condensed post-change checklist that guides Claude through
2807
+ * re-verifying after user-requested modifications. When called from
2808
+ * step 13+, this loops back to step 13 (present). When called from an
2809
+ * earlier step, it returns to that step so the normal flow continues.
2810
+ */
2811
+ function handleChange(feature) {
2812
+ const root = getProjectRoot();
2813
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
2814
+ const state = readState(root);
2815
+ if (!feature) {
2816
+ // Try to read feature from state
2817
+ if (state?.feature) {
2818
+ feature = state.feature;
2819
+ }
2820
+ else {
2821
+ console.error(chalk.red('Error: Feature name required.'));
2822
+ console.error(chalk.dim(' Usage: codeyam editor change "My Feature"'));
2823
+ process.exit(1);
2824
+ }
2825
+ }
2826
+ const currentStep = state?.step ?? 13;
2827
+ const port = getServerPort();
2828
+ console.log();
2829
+ console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
2830
+ console.log(chalk.dim(`Feature: ${feature}`));
2831
+ console.log();
2832
+ console.log('The user has requested changes. Follow this checklist after making them.');
2833
+ console.log();
2834
+ const designSystem = readDesignSystem(root);
2835
+ if (designSystem) {
2836
+ console.log(chalk.bold.magenta('Design System (active):'));
2837
+ console.log(chalk.magenta(' Use existing CSS custom property tokens when making visual changes — no hardcoded px or hex values.'));
2838
+ console.log(chalk.magenta(' Use var(--text-sm) not 14, var(--spacing-lg) not 16px, var(--text-primary) not #1A1B25.'));
2839
+ console.log();
2840
+ console.log(designSystem);
2841
+ console.log();
2842
+ }
2843
+ console.log(chalk.bold.cyan('Keep the preview moving:'));
2844
+ console.log(chalk.cyan(` Refresh after EACH individual change — not after all changes are done:`));
2845
+ console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
2846
+ printDimensionGuidance(dim, dimNames);
2847
+ console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
2848
+ console.log();
2849
+ console.log(chalk.bold('0. Close the results panel:'));
2850
+ checkbox(`Hide results: \`codeyam editor hide-results\``);
2851
+ console.log();
2852
+ console.log(chalk.bold('1. Re-register affected component scenarios:'));
2853
+ checkbox('For each component you modified, re-register ALL its scenarios');
2854
+ console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","componentName":"ComponentName","componentPath":"app/components/ComponentName.tsx","url":"/isolated-components/ComponentName?s=ScenarioName","dimensions":["${dim}"]}'`));
2855
+ checkbox('For any NEW components: `codeyam editor isolate NewComponent` then create isolation routes and register scenarios');
2856
+ console.log();
2857
+ console.log(chalk.bold('2. Re-run affected tests:'));
2858
+ checkbox('Re-run test files for any functions you modified');
2859
+ console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
2860
+ checkbox('If any test fails, fix the source code and re-run');
2861
+ checkbox('If you re-seeded the database, restart the dev server to pick up fresh data');
2862
+ console.log(chalk.dim(` codeyam editor dev-server '{"action":"restart"}'`));
2863
+ console.log();
2864
+ console.log(chalk.bold('3. Re-capture app-level scenarios:'));
2865
+ checkbox('For each changed component, run `codeyam editor dependents ComponentName` to find affected pages');
2866
+ checkbox('Run `codeyam editor scenarios` to list all existing scenarios');
2867
+ checkbox('For EACH page listed by dependents: find its existing scenarios in the list and re-register every one');
2868
+ console.log(chalk.dim(' Shared components (headers, navbars, layouts) affect MANY pages — re-register all of them.'));
2869
+ console.log(chalk.dim(' Re-register with the SAME name to update — do NOT create new/duplicate scenarios.'));
2870
+ console.log(chalk.dim(' Example: if Header changed and Home, Catalog, Detail pages use it,'));
2871
+ console.log(chalk.dim(' re-register "Home - Default", "Catalog - Full", "Detail - WithReviews", etc.'));
2872
+ checkbox("Enrich existing scenario data to exercise the change — don't just re-register unchanged data");
2873
+ console.log(chalk.dim(' Add data that demonstrates what changed: new fields, relationships, states, content variety.'));
2874
+ checkbox('After each re-registration, view the screenshot to verify data is visible');
2875
+ console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
2876
+ console.log();
2877
+ console.log(chalk.bold('4. Verify completeness:'));
2878
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
2879
+ checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
2880
+ printDimensionGuidance(dim, dimNames);
2881
+ checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
2882
+ checkbox('Run `codeyam editor audit` — all checks must pass');
2883
+ checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
2884
+ console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
2885
+ console.log(chalk.dim(' If `liveErrors>0`, there are JS errors in the preview — fix them.'));
2886
+ checkbox('Fix any errors, then re-register affected scenarios');
2887
+ checkbox('If changes affect setup, new dependencies, or new scripts: update `README.md` and `npm run setup`');
2888
+ console.log();
2889
+ console.log(chalk.bold('5. Update the existing journal entry:'));
2890
+ checkbox('Get existing entry: `codeyam editor journal-list`');
2891
+ checkbox('Find the uncommitted entry (commitSha is null) from this feature session');
2892
+ checkbox(`Update it: \`codeyam editor journal-update '{"time":"<entry time>","description":"<updated description including changes>","includeSessionScenarios":true}'\``);
2893
+ console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
2894
+ console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
2895
+ console.log();
2896
+ // If the change was initiated from a step before 13, return to that step
2897
+ // instead of jumping to step 13. The change workflow should only loop to
2898
+ // step 13 when changes are requested FROM step 13.
2899
+ if (currentStep < 13) {
2900
+ console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2901
+ console.log(chalk.red.bold(' REQUIRED: Return to current step'));
2902
+ console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
2903
+ chalk.bold(`codeyam editor ${currentStep}`));
2904
+ console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2905
+ }
2906
+ else {
2907
+ console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2908
+ console.log(chalk.red.bold(' REQUIRED: Show Results'));
2909
+ console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
2910
+ chalk.bold(`codeyam editor 13`));
2911
+ console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
2912
+ console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2913
+ }
2914
+ console.log();
2915
+ }
2916
+ // ─── Audit gate ─────────────────────────────────────────────────────
2917
+ /**
2918
+ * Fetch the audit result from the server.
2919
+ * Returns null if the server is unreachable.
2920
+ */
2921
+ async function fetchAuditResult() {
2922
+ const port = getServerPort();
2923
+ try {
2924
+ const res = await fetch(`http://localhost:${port}/api/editor-audit`);
2925
+ if (!res.ok)
2926
+ return null;
2927
+ return await res.json();
2928
+ }
2929
+ catch {
2930
+ return null;
2931
+ }
2932
+ }
2933
+ /**
2934
+ * Silently check whether the audit passes. Returns true if allPassing,
2935
+ * false if any issues remain or the server is unreachable.
2936
+ * Used as a hard gate before steps 8+.
2937
+ *
2938
+ * Auto-runs analyze-imports if the only failure is incomplete entities,
2939
+ * then re-checks once.
2940
+ */
2941
+ async function checkAuditGate() {
2942
+ const data = await fetchAuditResult();
2943
+ if (!data)
2944
+ return true; // Server unreachable — don't block
2945
+ if (data.summary?.allPassing === true)
2946
+ return true;
2947
+ // If incomplete entities are the only issue, auto-fix with analyze-imports
2948
+ const { isOnlyIncompleteEntities } = await import('../utils/editorAudit.js');
2949
+ if (data.summary?.incompleteEntities > 0 &&
2950
+ isOnlyIncompleteEntities(data.summary)) {
2951
+ try {
2952
+ await handleAnalyzeImports({ silent: true });
2953
+ }
2954
+ catch {
2955
+ return false;
2956
+ }
2957
+ // Re-check after fix
2958
+ const retry = await fetchAuditResult();
2959
+ return retry?.summary?.allPassing === true;
2960
+ }
2961
+ return false;
2962
+ }
2963
+ // ─── Audit subcommand ────────────────────────────────────────────────
2964
+ /**
2965
+ * `codeyam editor audit`
2966
+ *
2967
+ * Fetches the /api/editor-audit endpoint and prints a checklist showing
2968
+ * which glossary components have registered scenarios and which functions
2969
+ * have test files. Exits with code 1 if anything is missing.
2970
+ */
2971
+ async function handleAudit() {
2972
+ let data = await fetchAuditResult();
2973
+ if (!data) {
2974
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
2975
+ process.exit(1);
2976
+ }
2977
+ // Auto-fix incomplete entities before displaying results.
2978
+ // If entities have scenarios but no analyses, analyze-imports can fix this
2979
+ // without any manual intervention — run it transparently.
2980
+ const incompleteBeforeFix = data.incompleteEntities || [];
2981
+ if (incompleteBeforeFix.length > 0) {
2982
+ console.log(chalk.dim(`Running import analysis for ${incompleteBeforeFix.length} incomplete entit${incompleteBeforeFix.length !== 1 ? 'ies' : 'y'}...`));
2983
+ try {
2984
+ await handleAnalyzeImports({ silent: true });
2985
+ }
2986
+ catch {
2987
+ // Fall through — the audit will still report them as incomplete
2988
+ }
2989
+ // Re-fetch audit results after the fix
2990
+ data = await fetchAuditResult();
2991
+ if (!data) {
2992
+ console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
2993
+ process.exit(1);
2994
+ }
2995
+ }
2996
+ const { components, functions, summary } = data;
2997
+ console.log();
2998
+ console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
2999
+ console.log();
3000
+ // Components
3001
+ if (components.length > 0) {
3002
+ console.log(chalk.bold('Components (scenarios):'));
3003
+ for (const c of components) {
3004
+ const icon = c.status === 'ok' ? chalk.green('✓') : chalk.red('✗');
3005
+ let detail;
3006
+ if (c.status === 'has_errors') {
3007
+ detail = chalk.red(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
3008
+ }
3009
+ else if (c.status === 'ok') {
3010
+ detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
3011
+ }
3012
+ else {
3013
+ detail = chalk.red(' — no scenarios registered');
3014
+ }
3015
+ console.log(` ${icon} ${c.name}${detail}`);
3016
+ if (c.clientErrors && c.clientErrors.length > 0) {
3017
+ for (const err of c.clientErrors.slice(0, 3)) {
3018
+ console.log(chalk.red(` → ${err}`));
3019
+ }
3020
+ if (c.clientErrors.length > 3) {
3021
+ console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
3022
+ }
3023
+ }
3024
+ }
3025
+ console.log();
3026
+ }
3027
+ // Functions
3028
+ if (functions.length > 0) {
3029
+ console.log(chalk.bold('Functions (test files):'));
3030
+ for (const f of functions) {
3031
+ const icon = f.status === 'ok' ? chalk.green('✓') : chalk.red('✗');
3032
+ let detail;
3033
+ switch (f.status) {
3034
+ case 'ok':
3035
+ detail = chalk.dim(` (${f.testFile})`);
3036
+ break;
3037
+ case 'failing':
3038
+ detail = chalk.red(` — tests failing: ${f.testFile}`);
3039
+ break;
3040
+ case 'name_mismatch':
3041
+ detail = chalk.red(` — tests pass but missing top-level describe("${f.name}", ...) — tests won't display in UI`);
3042
+ break;
3043
+ case 'missing':
3044
+ default:
3045
+ detail = f.testFile
3046
+ ? chalk.red(` — test file missing: ${f.testFile}`)
3047
+ : chalk.red(' — no test file specified');
3048
+ break;
3049
+ }
3050
+ console.log(` ${icon} ${f.name}${detail}`);
3051
+ }
3052
+ console.log();
3053
+ }
3054
+ // Missing from glossary
3055
+ const missingFromGlossary = data.missingFromGlossary || [];
3056
+ if (missingFromGlossary.length > 0) {
3057
+ console.log(chalk.bold('Missing from glossary:'));
3058
+ for (const m of missingFromGlossary) {
3059
+ console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
3060
+ }
3061
+ console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
3062
+ console.log();
3063
+ }
3064
+ // Incomplete entities (scenarios without analyses)
3065
+ const incompleteEntities = data.incompleteEntities || [];
3066
+ if (incompleteEntities.length > 0) {
3067
+ console.log(chalk.bold('Incomplete entities (need import analysis):'));
3068
+ for (const e of incompleteEntities) {
3069
+ console.log(` ${chalk.red('✗')} ${e.name} — ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
3070
+ }
3071
+ console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
3072
+ console.log();
3073
+ }
3074
+ // Miscategorized scenarios (component scenarios using real page URLs)
3075
+ const miscategorizedScenarios = data.miscategorizedScenarios || [];
3076
+ if (miscategorizedScenarios.length > 0) {
3077
+ console.log(chalk.bold('Miscategorized scenarios (component with page URL):'));
3078
+ for (const m of miscategorizedScenarios) {
3079
+ console.log(` ${chalk.red('✗')} ${m.componentName} at ${chalk.dim(m.url)} — ${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''}`);
3080
+ for (const name of m.scenarioNames) {
3081
+ console.log(chalk.dim(` "${name}"`));
3082
+ }
3083
+ }
3084
+ console.log(chalk.yellow(' Component scenarios must use isolation routes (/isolated-components/...).'));
3085
+ console.log(chalk.yellow(' Either re-register as app scenarios (remove componentName, add pageFilePath)'));
3086
+ console.log(chalk.yellow(' or re-register with an isolation URL.'));
3087
+ console.log();
3088
+ }
3089
+ // Summary
3090
+ const allOk = summary.allPassing;
3091
+ if (allOk) {
3092
+ console.log(chalk.green.bold('All checks passed!') +
3093
+ chalk.dim(` (${summary.totalComponents} component${summary.totalComponents !== 1 ? 's' : ''}, ${summary.totalFunctions} function${summary.totalFunctions !== 1 ? 's' : ''})`));
3094
+ }
3095
+ else {
3096
+ const parts = [];
3097
+ if (summary.componentsMissing > 0) {
3098
+ parts.push(`${summary.componentsMissing} component${summary.componentsMissing !== 1 ? 's' : ''} missing scenarios`);
3099
+ }
3100
+ if (summary.componentsWithErrors > 0) {
3101
+ parts.push(`${summary.componentsWithErrors} component${summary.componentsWithErrors !== 1 ? 's' : ''} with client errors`);
3102
+ }
3103
+ if (summary.functionsMissing > 0) {
3104
+ parts.push(`${summary.functionsMissing} function${summary.functionsMissing !== 1 ? 's' : ''} missing tests`);
3105
+ }
3106
+ if (summary.functionsFailing > 0) {
3107
+ parts.push(`${summary.functionsFailing} function${summary.functionsFailing !== 1 ? 's' : ''} with failing tests`);
3108
+ }
3109
+ if (summary.functionsNameMismatch > 0) {
3110
+ parts.push(`${summary.functionsNameMismatch} function${summary.functionsNameMismatch !== 1 ? 's' : ''} with test name mismatch (missing top-level describe)`);
3111
+ }
3112
+ if (summary.missingFromGlossary > 0) {
3113
+ parts.push(`${summary.missingFromGlossary} file${summary.missingFromGlossary !== 1 ? 's' : ''} with scenarios not in glossary`);
3114
+ }
3115
+ if (summary.incompleteEntities > 0) {
3116
+ parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
3117
+ }
3118
+ if (summary.miscategorizedScenarios > 0) {
3119
+ parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
3120
+ }
3121
+ console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
3122
+ }
3123
+ console.log();
3124
+ if (!allOk) {
3125
+ process.exit(1);
3126
+ }
3127
+ // Auto-run analyze-imports when audit passes — this builds the import graph
3128
+ // so the App tab can show component/function dependencies for each page.
3129
+ // Previously this was a manual step that Claude had to remember to run.
3130
+ console.log(chalk.dim('Building import graph...'));
3131
+ try {
3132
+ await handleAnalyzeImports({ silent: true });
3133
+ }
3134
+ catch {
3135
+ // Non-fatal — audit passed, import graph is a bonus
3136
+ console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
3137
+ }
3138
+ }
3139
+ // ─── Scenarios subcommand ─────────────────────────────────────────────
3140
+ async function handleScenarios() {
3141
+ const port = getServerPort();
3142
+ const url = `http://localhost:${port}/api/editor-scenarios`;
3143
+ let data;
3144
+ try {
3145
+ const res = await fetch(url);
3146
+ if (!res.ok) {
3147
+ console.error(chalk.red(`Error: Scenarios endpoint returned ${res.status}`));
3148
+ process.exit(1);
3149
+ }
3150
+ data = await res.json();
3151
+ }
3152
+ catch (err) {
3153
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3154
+ console.error(chalk.dim(` ${err.message}`));
3155
+ process.exit(1);
3156
+ }
3157
+ const scenarios = data.scenarios;
3158
+ if (scenarios.length === 0) {
3159
+ console.log();
3160
+ console.log(chalk.yellow('No scenarios found for this feature session.'));
3161
+ console.log();
3162
+ return;
3163
+ }
3164
+ // Group by componentName (null → "Uncategorized")
3165
+ const groups = new Map();
3166
+ for (const s of scenarios) {
3167
+ const key = s.componentName || 'Uncategorized';
3168
+ if (!groups.has(key))
3169
+ groups.set(key, []);
3170
+ groups.get(key).push(s);
3171
+ }
3172
+ // Strip component prefix from names (e.g. "DrinkCard - Default" → "Default")
3173
+ function stripPrefix(name, componentName) {
3174
+ if (!componentName)
3175
+ return name;
3176
+ const prefix = `${componentName} - `;
3177
+ return name.startsWith(prefix) ? name.slice(prefix.length) : name;
3178
+ }
3179
+ // Calculate column widths
3180
+ let maxCompLen = 'Component'.length;
3181
+ let maxScenLen = 'Scenario'.length;
3182
+ for (const [comp, items] of groups) {
3183
+ maxCompLen = Math.max(maxCompLen, comp.length);
3184
+ for (const s of items) {
3185
+ const display = stripPrefix(s.name, s.componentName);
3186
+ maxScenLen = Math.max(maxScenLen, display.length);
3187
+ }
3188
+ }
3189
+ const pad = (str, len) => str + ' '.repeat(Math.max(0, len - str.length));
3190
+ // Build OSC 8 hyperlink (display length = visible text only)
3191
+ const osc8 = (url, text) => `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`;
3192
+ // Print table
3193
+ console.log();
3194
+ console.log(chalk.bold.cyan('━━━ Scenario Table ━━━'));
3195
+ console.log();
3196
+ const divComp = '─'.repeat(maxCompLen + 2);
3197
+ const divScen = '─'.repeat(maxScenLen + 2);
3198
+ // Header
3199
+ console.log(`┌${divComp}┬${divScen}┐`);
3200
+ console.log(`│ ${chalk.bold(pad('Component', maxCompLen))} │ ${chalk.bold(pad('Scenario', maxScenLen))} │`);
3201
+ console.log(`├${divComp}┼${divScen}┤`);
3202
+ // Rows
3203
+ let groupIndex = 0;
3204
+ for (const [comp, items] of groups) {
3205
+ if (groupIndex > 0) {
3206
+ console.log(`├${divComp}┼${divScen}┤`);
3207
+ }
3208
+ for (let i = 0; i < items.length; i++) {
3209
+ const s = items[i];
3210
+ const compCell = i === 0 ? chalk.bold(pad(comp, maxCompLen)) : pad('', maxCompLen);
3211
+ const display = stripPrefix(s.name, s.componentName);
3212
+ const linked = osc8(s.link, display);
3213
+ // linked has invisible escape chars; pad based on display length
3214
+ const scenPad = ' '.repeat(Math.max(0, maxScenLen - display.length));
3215
+ console.log(`│ ${compCell} │ ${linked}${scenPad} │`);
3216
+ }
3217
+ groupIndex++;
3218
+ }
3219
+ // Footer
3220
+ console.log(`└${divComp}┴${divScen}┘`);
3221
+ console.log();
3222
+ const total = scenarios.length;
3223
+ const groupCount = groups.size;
3224
+ console.log(chalk.dim(`${total} scenario${total !== 1 ? 's' : ''} across ${groupCount} component${groupCount !== 1 ? 's' : ''}`));
3225
+ // Show screenshot paths so Claude can verify images with the Read tool
3226
+ const withScreenshots = scenarios.filter((s) => s.screenshotPath);
3227
+ if (withScreenshots.length > 0) {
3228
+ console.log();
3229
+ console.log(chalk.bold.cyan('Screenshots:'));
3230
+ for (const s of withScreenshots) {
3231
+ console.log(chalk.dim(` ${s.name}: ${s.screenshotPath}`));
3232
+ }
3233
+ }
3234
+ console.log();
3235
+ }
3236
+ // ─── Scenario Coverage subcommand ───────────────────────────────────
3237
+ async function handleScenarioCoverage() {
3238
+ // Safety net: heal any scenarios with null entity_sha before checking coverage
3239
+ try {
3240
+ const { getDatabase } = await import('../../../packages/database/index.js');
3241
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
3242
+ const db = getDatabase();
3243
+ const backfillCount = await countScenariosNeedingEntityBackfill(db);
3244
+ if (backfillCount > 0) {
3245
+ console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
3246
+ await handleAnalyzeImports({ silent: true });
3247
+ // Run backfill after analysis
3248
+ const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
3249
+ const entities = await loadEntities({});
3250
+ if (entities && entities.length > 0) {
3251
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3252
+ sha: e.sha,
3253
+ name: e.name,
3254
+ filePath: e.filePath || '',
3255
+ })));
3256
+ }
3257
+ }
3258
+ }
3259
+ catch {
3260
+ // Non-fatal — proceed with coverage check regardless
3261
+ }
3262
+ const port = getServerPort();
3263
+ const url = `http://localhost:${port}/api/editor-scenario-coverage`;
3264
+ let data;
3265
+ try {
3266
+ const res = await fetch(url);
3267
+ if (!res.ok) {
3268
+ console.error(chalk.red(`Error: Scenario coverage endpoint returned ${res.status}`));
3269
+ process.exit(1);
3270
+ }
3271
+ data = await res.json();
3272
+ }
3273
+ catch (err) {
3274
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3275
+ console.error(chalk.dim(` ${err.message}`));
3276
+ process.exit(1);
3277
+ }
3278
+ console.log();
3279
+ console.log(chalk.bold.cyan('━━━ Scenario Coverage ━━━'));
3280
+ console.log();
3281
+ if (data.note) {
3282
+ console.log(chalk.yellow(data.note));
3283
+ console.log();
3284
+ return;
3285
+ }
3286
+ // Fresh scenarios (already recaptured this feature)
3287
+ if (data.freshScenarios.length > 0) {
3288
+ console.log(chalk.green(`✓ ${data.freshScenarios.length} scenario(s) captured this feature:`));
3289
+ for (const s of data.freshScenarios) {
3290
+ console.log(chalk.dim(` ${s.name} (${s.url || '/'})`));
3291
+ }
3292
+ console.log();
3293
+ }
3294
+ // Stale scenarios (need recapturing)
3295
+ if (data.staleScenarios.length > 0) {
3296
+ console.log(chalk.red(`✗ ${data.staleScenarios.length} scenario(s) need re-registering (stale screenshots):`));
3297
+ for (const s of data.staleScenarios) {
3298
+ console.log(chalk.yellow(` ${s.name} (${s.url || '/'}) — ${s.changeStatus} but not recaptured`));
3299
+ }
3300
+ console.log();
3301
+ console.log(chalk.yellow('Re-register each stale scenario with the SAME name to update its screenshot.'));
3302
+ console.log(chalk.yellow("Enhance the seed data to reflect this feature's changes where relevant."));
3303
+ console.log();
3304
+ }
3305
+ // Uncovered pages (changed but no scenarios at all)
3306
+ if (data.uncoveredPages.length > 0) {
3307
+ console.log(chalk.red(`✗ ${data.uncoveredPages.length} page(s) affected by changes with no scenarios:`));
3308
+ for (const p of data.uncoveredPages) {
3309
+ console.log(chalk.yellow(` ${p.entityName} — ${p.changeStatus}`));
3310
+ }
3311
+ console.log();
3312
+ }
3313
+ // Summary
3314
+ if (data.pass) {
3315
+ console.log(chalk.green.bold('PASS — all affected scenarios are fresh'));
3316
+ }
3317
+ else {
3318
+ console.log(chalk.red.bold('FAIL — re-register stale scenarios before proceeding'));
3319
+ }
3320
+ console.log();
3321
+ }
3322
+ // ─── Template subcommand ─────────────────────────────────────────────
3323
+ async function handleTemplate() {
3324
+ const root = getProjectRoot();
3325
+ const port = getServerPort();
3326
+ if (hasProject(root)) {
3327
+ console.log(chalk.yellow('Project already exists (package.json found). Skipping.'));
3328
+ return;
3329
+ }
3330
+ // Determine which template to use from editor state
3331
+ const state = readState(root);
3332
+ const techStackId = state?.techStackId;
3333
+ const stack = techStackId
3334
+ ? TECH_STACKS.find((s) => s.id === techStackId)
3335
+ : undefined;
3336
+ const templateName = stack?.templateDir || 'nextjs-prisma-sqlite';
3337
+ const templateDir = path.join(__dirname, '..', '..', 'templates', templateName);
3338
+ if (!fs.existsSync(templateDir)) {
3339
+ console.error(chalk.red('Error: Template directory not found at ' + templateDir));
3340
+ process.exit(1);
3341
+ }
3342
+ const { execSync } = await import('child_process');
3343
+ // 1. Copy template files
3344
+ console.log(chalk.bold(`Copying ${stack?.name || templateName} template files...`));
3345
+ execSync(`cp -r ${templateDir}/* .`, { cwd: root, stdio: 'inherit' });
3346
+ // Copy dotfiles that are stored without the dot prefix in templates
3347
+ const gitignorePath = path.join(templateDir, 'gitignore');
3348
+ if (fs.existsSync(gitignorePath)) {
3349
+ execSync(`cp ${templateDir}/gitignore .gitignore`, {
3350
+ cwd: root,
3351
+ stdio: 'inherit',
3352
+ });
3353
+ }
3354
+ const envPath = path.join(templateDir, 'env');
3355
+ if (fs.existsSync(envPath)) {
3356
+ execSync(`cp ${templateDir}/env .env`, { cwd: root, stdio: 'inherit' });
3357
+ }
3358
+ // Copy seed adapter into .codeyam/ (stored outside .codeyam/ in the template
3359
+ // to avoid gitignore issues — .codeyam/ is selectively ignored in user projects)
3360
+ const seedAdapterSrc = path.join(templateDir, 'seed-adapter.ts');
3361
+ if (fs.existsSync(seedAdapterSrc)) {
3362
+ const codeyamDir = path.join(root, '.codeyam');
3363
+ if (!fs.existsSync(codeyamDir)) {
3364
+ fs.mkdirSync(codeyamDir, { recursive: true });
3365
+ }
3366
+ fs.copyFileSync(seedAdapterSrc, path.join(codeyamDir, 'seed-adapter.ts'));
3367
+ }
3368
+ console.log(chalk.green(' Template files copied.'));
3369
+ // 2. Install dependencies
3370
+ console.log(chalk.bold('Installing dependencies...'));
3371
+ execSync('npm install', { cwd: root, stdio: 'inherit' });
3372
+ console.log(chalk.green(' Dependencies installed.'));
3373
+ // 3. Initialize git
3374
+ const hasGit = fs.existsSync(path.join(root, '.git'));
3375
+ if (!hasGit) {
3376
+ console.log(chalk.bold('Initializing git...'));
3377
+ execSync('git init && git add -A && git commit -m "Initial commit"', {
3378
+ cwd: root,
3379
+ stdio: 'inherit',
3380
+ });
3381
+ console.log(chalk.green(' Git initialized.'));
3382
+ }
3383
+ // 4. Run codeyam init
3384
+ console.log(chalk.bold('Running codeyam init...'));
3385
+ await initCommand.handler({
3386
+ force: true,
3387
+ 'keep-server': true,
3388
+ autoInit: false,
3389
+ $0: '',
3390
+ _: [],
3391
+ });
3392
+ console.log(chalk.green(' CodeYam initialized.'));
3393
+ // 5. Verify config has startCommand
3394
+ const configPath = path.join(root, '.codeyam', 'config.json');
3395
+ if (fs.existsSync(configPath)) {
3396
+ try {
3397
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
3398
+ const webapps = config.webapps || [];
3399
+ const hasStartCommand = webapps.some((w) => w.startCommand);
3400
+ if (!hasStartCommand) {
3401
+ console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
3402
+ console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
3403
+ }
3404
+ }
3405
+ catch {
3406
+ // Config parse error is non-fatal
3407
+ }
3408
+ }
3409
+ // 5b. Mark the project as template-scaffolded so migration detection
3410
+ // doesn't treat it as a pre-existing project that needs migration.
3411
+ const now = new Date().toISOString();
3412
+ const existingState = readState(root);
3413
+ writeState(root, {
3414
+ feature: '',
3415
+ step: 0,
3416
+ label: '',
3417
+ startedAt: now,
3418
+ featureStartedAt: now,
3419
+ ...existingState,
3420
+ scaffolded: true,
3421
+ });
3422
+ // 5c. Write a fresh prototypeId so the proxy clears stale localStorage
3423
+ const activeScenarioPath = path.join(root, '.codeyam', 'active-scenario.json');
3424
+ fs.writeFileSync(activeScenarioPath, JSON.stringify({ prototypeId: Date.now().toString() }), 'utf-8');
3425
+ // 6. Trigger editor-refresh so the server picks up the new project
3426
+ console.log(chalk.bold('Refreshing editor...'));
3427
+ try {
3428
+ await fetch(`http://localhost:${port}/api/editor-refresh`, {
3429
+ method: 'POST',
3430
+ });
3431
+ console.log(chalk.green(' Editor refreshed.'));
3432
+ }
3433
+ catch {
3434
+ console.log(chalk.yellow(' Could not reach the CodeYam server for refresh. You may need to restart it.'));
3435
+ }
3436
+ console.log();
3437
+ console.log(chalk.green.bold('Project scaffolded and ready!'));
3438
+ console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
3439
+ }
3440
+ // ─── Sync subcommand ─────────────────────────────────────────────────
3441
+ /**
3442
+ * `codeyam editor sync`
3443
+ * Import scenarios from editor-scenarios/ files into the local database.
3444
+ * Falls back to legacy scenarios-manifest.json if no _metadata files found.
3445
+ */
3446
+ async function handleSync() {
3447
+ const root = getProjectRoot();
3448
+ const entries = scanScenarioFiles(root);
3449
+ if (entries.length === 0) {
3450
+ console.log(chalk.yellow('No scenario files with metadata found. Nothing to sync.'));
3451
+ return;
3452
+ }
3453
+ const configPath = path.join(root, '.codeyam', 'config.json');
3454
+ let projectSlug = null;
3455
+ try {
3456
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
3457
+ projectSlug = config.projectSlug || null;
3458
+ }
3459
+ catch {
3460
+ // fall through
3461
+ }
3462
+ if (!projectSlug) {
3463
+ console.error(chalk.red('Error: No project slug found. Run codeyam init first.'));
3464
+ process.exit(1);
3465
+ }
3466
+ const connectionOk = await withoutSpinner(() => testEnvironment());
3467
+ if (!connectionOk) {
3468
+ errorLog('Environment validation failed');
3469
+ return;
3470
+ }
3471
+ const { project } = await requireBranchAndProject(projectSlug);
3472
+ const { getDatabase } = await import('../../../packages/database/index.js');
3473
+ const db = getDatabase();
3474
+ const existingRows = await db
3475
+ .selectFrom('editor_scenarios')
3476
+ .select(['id', 'updated_at'])
3477
+ .where('project_id', '=', project.id)
3478
+ .execute();
3479
+ const result = await syncScenarioFilesToDatabase(root, project.id, existingRows, async (row) => {
3480
+ await db
3481
+ .insertInto('editor_scenarios')
3482
+ .values(row)
3483
+ .execute();
3484
+ }, async (id, row) => {
3485
+ await db
3486
+ .updateTable('editor_scenarios')
3487
+ .set(row)
3488
+ .where('id', '=', id)
3489
+ .execute();
3490
+ });
3491
+ if (result.inserted === 0 && result.updated === 0) {
3492
+ console.log(chalk.dim('All scenarios up to date.'));
3493
+ }
3494
+ else {
3495
+ const parts = [];
3496
+ if (result.inserted > 0)
3497
+ parts.push(`${result.inserted} imported`);
3498
+ if (result.updated > 0)
3499
+ parts.push(`${result.updated} updated`);
3500
+ if (result.skipped > 0)
3501
+ parts.push(`${result.skipped} unchanged`);
3502
+ console.log(chalk.green(`Synced scenarios: ${parts.join(', ')}`));
3503
+ }
3504
+ // Migrate legacy scenario formats after sync, then re-sync if anything was fixed
3505
+ try {
3506
+ const migrateResult = migrateScenarioFormats(root);
3507
+ if (migrateResult.fixed > 0) {
3508
+ console.log(chalk.green(`Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
3509
+ // Re-sync so the DB reflects the corrected file metadata
3510
+ const refreshedRows = await db
3511
+ .selectFrom('editor_scenarios')
3512
+ .select(['id', 'updated_at'])
3513
+ .where('project_id', '=', project.id)
3514
+ .execute();
3515
+ await syncScenarioFilesToDatabase(root, project.id, refreshedRows, async (row) => {
3516
+ await db
3517
+ .insertInto('editor_scenarios')
3518
+ .values(row)
3519
+ .execute();
3520
+ }, async (id, row) => {
3521
+ await db
3522
+ .updateTable('editor_scenarios')
3523
+ .set(row)
3524
+ .where('id', '=', id)
3525
+ .execute();
3526
+ });
3527
+ }
3528
+ }
3529
+ catch {
3530
+ // Non-fatal
3531
+ }
3532
+ }
3533
+ // ─── Verify Images subcommand ─────────────────────────────────────────
3534
+ async function handleVerifyImages(jsonArg) {
3535
+ const port = getServerPort();
3536
+ const { parseVerifyImagesInput, extractImageUrls, resolveImageUrl, verifyImageUrls, buildVerifyImagesReport, extractImageUrlsFromScenarioFiles, } = await import('../utils/editorImageVerifier.js');
3537
+ let paths;
3538
+ let explicitImageUrls;
3539
+ try {
3540
+ const input = parseVerifyImagesInput(jsonArg);
3541
+ paths = input.paths;
3542
+ explicitImageUrls = input.imageUrls;
3543
+ }
3544
+ catch {
3545
+ console.error(chalk.red('Error: Invalid JSON argument'));
3546
+ process.exit(1);
3547
+ }
3548
+ // Get dev server URL
3549
+ let devServerUrl;
3550
+ try {
3551
+ const res = await fetch(`http://localhost:${port}/api/editor-dev-server`);
3552
+ const data = await res.json();
3553
+ devServerUrl = data.url || `http://localhost:${data.port || 3000}`;
3554
+ }
3555
+ catch {
3556
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3557
+ process.exit(1);
3558
+ }
3559
+ const allUrls = new Set();
3560
+ let pagesChecked = 0;
3561
+ for (const pagePath of paths) {
3562
+ const pageUrl = `${devServerUrl}${pagePath}`;
3563
+ try {
3564
+ const res = await fetch(pageUrl);
3565
+ if (!res.ok) {
3566
+ console.error(chalk.red(` ✗ ${pagePath} — HTTP ${res.status}`));
3567
+ continue;
3568
+ }
3569
+ const html = await res.text();
3570
+ const imageUrls = extractImageUrls(html);
3571
+ for (const url of imageUrls) {
3572
+ allUrls.add(resolveImageUrl(url, devServerUrl));
3573
+ }
3574
+ pagesChecked++;
3575
+ }
3576
+ catch (err) {
3577
+ console.error(chalk.red(` ✗ ${pagePath} — ${err.message}`));
3578
+ }
3579
+ }
3580
+ // Add explicitly-provided image URLs (Claude passes these directly)
3581
+ for (const url of explicitImageUrls) {
3582
+ allUrls.add(resolveImageUrl(url, devServerUrl));
3583
+ }
3584
+ // Also scan scenario mock data files for image URLs (client-rendered pages)
3585
+ const scenariosDir = path.join(getProjectRoot(), '.codeyam', 'editor-scenarios');
3586
+ const { urls: scenarioUrls, filesScanned: scenarioFilesScanned } = extractImageUrlsFromScenarioFiles(scenariosDir);
3587
+ for (const url of scenarioUrls) {
3588
+ allUrls.add(resolveImageUrl(url, devServerUrl));
3589
+ }
3590
+ const urlList = [...allUrls];
3591
+ const results = urlList.length > 0 ? await verifyImageUrls(urlList) : [];
3592
+ const report = buildVerifyImagesReport({
3593
+ pagesChecked,
3594
+ imageUrls: urlList,
3595
+ results,
3596
+ scenarioFilesScanned,
3597
+ });
3598
+ console.log(JSON.stringify(report, null, 2));
3599
+ if (report.failures.length > 0) {
3600
+ process.exit(1);
3601
+ }
3602
+ }
3603
+ // ─── Debug subcommand ────────────────────────────────────────────────
3604
+ function handleEditorDebug(args) {
3605
+ const root = getProjectRoot();
3606
+ const feature = args.feature || 'Sample Feature';
3607
+ const includeContext = args.includeContext !== false;
3608
+ let targets;
3609
+ try {
3610
+ targets = parseDebugTargets(args.target);
3611
+ }
3612
+ catch (err) {
3613
+ const msg = err instanceof Error ? err.message : String(err);
3614
+ console.error(chalk.red(`Error: ${msg}`));
3615
+ process.exit(1);
3616
+ }
3617
+ const includeResume = typeof args.resume === 'boolean' ? args.resume : true;
3618
+ const includeNormal = typeof args.resume === 'boolean' ? !args.resume : true;
3619
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
3620
+ const outputDir = args.output
3621
+ ? path.resolve(args.output)
3622
+ : path.join(root, '.codeyam', 'logs', 'editor-debug', timestamp);
3623
+ fs.mkdirSync(outputDir, { recursive: true });
3624
+ const contextEntries = includeContext
3625
+ ? writeContextSnapshot(root, outputDir)
3626
+ : [];
3627
+ const scenarios = [];
3628
+ const wants = (id) => (targets ? targets.has(id) : true);
3629
+ if (wants('setup')) {
3630
+ scenarios.push({
3631
+ id: 'setup',
3632
+ title: 'Setup (no project)',
3633
+ render: () => withTempRoot(false, (tempRoot) => captureOutput(() => printSetup(tempRoot))),
3634
+ });
3635
+ }
3636
+ if (wants('overview')) {
3637
+ scenarios.push({
3638
+ id: 'overview',
3639
+ title: 'Cycle overview (project, no state)',
3640
+ render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, null))),
3641
+ });
3642
+ }
3643
+ if (wants('overview-with-state')) {
3644
+ scenarios.push({
3645
+ id: 'overview-with-state',
3646
+ title: 'Cycle overview (project, with state)',
3647
+ render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(4, feature)))),
3648
+ });
3649
+ }
3650
+ const stepFns = {
3651
+ 1: (r, f) => printStep1(r, f),
3652
+ 2: printStep2,
3653
+ 3: printStep3,
3654
+ 4: printStep4,
3655
+ 5: printStep5,
3656
+ 6: printStep6,
3657
+ 7: printStep7,
3658
+ 8: printStep8,
3659
+ 9: printStep9,
3660
+ 10: printStep10,
3661
+ 11: printStep11,
3662
+ 12: printStep12,
3663
+ 13: printStep13,
3664
+ 14: printStep14,
3665
+ 15: printStep15,
3666
+ 16: printStep16,
3667
+ };
3668
+ for (let step = 1; step <= 16; step++) {
3669
+ const stepId = `step-${step}`;
3670
+ if (!wants(stepId))
3671
+ continue;
3672
+ if (includeNormal) {
3673
+ scenarios.push({
3674
+ id: stepId,
3675
+ title: `Step ${step} (${STEP_LABELS[step]})`,
3676
+ render: () => withTempRoot(true, (tempRoot) => captureOutput(() => stepFns[step](tempRoot, feature))),
3677
+ });
3678
+ if (step === 1 && !args.feature) {
3679
+ scenarios.push({
3680
+ id: 'step-1-no-feature',
3681
+ title: 'Step 1 (Plan) — no feature provided',
3682
+ render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printStep1(tempRoot, undefined))),
3683
+ });
3684
+ }
3685
+ if (step === 2) {
3686
+ scenarios.push({
3687
+ id: 'step-2-scaffold',
3688
+ title: 'Step 2 (Prototype) — scaffold flow (no project)',
3689
+ render: () => withTempRoot(false, (tempRoot) => captureOutput(() => printStep2(tempRoot, feature))),
3690
+ });
3691
+ }
3692
+ }
3693
+ if (includeResume) {
3694
+ scenarios.push({
3695
+ id: `${stepId}-resume`,
3696
+ title: `Step ${step} (${STEP_LABELS[step]}) — resuming`,
3697
+ render: () => withTempRoot(true, (tempRoot) => {
3698
+ writeState(tempRoot, makeState(step, feature));
3699
+ return captureOutput(() => stepFns[step](tempRoot, feature));
3700
+ }),
3701
+ });
3702
+ }
3703
+ }
3704
+ const indexLines = [
3705
+ '# CodeYam Editor Debug Bundle',
3706
+ '',
3707
+ `Generated: ${new Date().toISOString()}`,
3708
+ `Feature: "${feature}"`,
3709
+ `Resume variants: ${includeResume ? 'included' : 'skipped'}`,
3710
+ 'Note: Output files preserve ANSI color codes (as printed to the terminal).',
3711
+ '',
3712
+ ];
3713
+ if (includeContext) {
3714
+ indexLines.push('## Context snapshot');
3715
+ if (contextEntries.length === 0) {
3716
+ indexLines.push('- (none)');
3717
+ }
3718
+ else {
3719
+ for (const entry of contextEntries) {
3720
+ const source = entry.source ? ` ← ${entry.source}` : '';
3721
+ indexLines.push(`- ${entry.label}: ${entry.file} (${entry.status})${source}`);
3722
+ }
3723
+ }
3724
+ indexLines.push('');
3725
+ }
3726
+ indexLines.push('## Scenarios');
3727
+ for (const scenario of scenarios) {
3728
+ const fileName = `${scenario.id}.txt`;
3729
+ const output = scenario.render();
3730
+ fs.writeFileSync(path.join(outputDir, fileName), output, 'utf8');
3731
+ indexLines.push(`- ${scenario.title}: ${fileName}`);
3732
+ }
3733
+ fs.writeFileSync(path.join(outputDir, 'index.md'), indexLines.join('\n'), 'utf8');
3734
+ console.log();
3735
+ console.log(chalk.bold.cyan('━━━ Editor Debug Bundle ━━━'));
3736
+ console.log();
3737
+ console.log(chalk.green('Wrote debug bundle to: ') + chalk.bold(outputDir));
3738
+ console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
3739
+ console.log();
3740
+ }
3741
+ // ─── Command definition ───────────────────────────────────────────────
3742
+ const editorCommand = {
3743
+ command: 'editor [step] [json]',
3744
+ describe: 'Editor mode guided workflow',
3745
+ builder: (yargs) => {
3746
+ const stepDescription = IS_INTERNAL_BUILD
3747
+ ? 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)'
3748
+ : 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)';
3749
+ let builder = yargs
3750
+ .positional('step', {
3751
+ type: 'string',
3752
+ describe: stepDescription,
3753
+ })
3754
+ .positional('json', {
3755
+ type: 'string',
3756
+ describe: 'JSON argument for register subcommand',
3757
+ })
3758
+ .option('feature', {
3759
+ type: 'string',
3760
+ describe: 'Feature name (required for step 2)',
3761
+ })
3762
+ .option('app-formats', {
3763
+ type: 'string',
3764
+ describe: 'Comma-separated app formats (mobile-responsive-web-app, desktop-app, mobile-app)',
3765
+ })
3766
+ .option('tech-stack', {
3767
+ type: 'string',
3768
+ describe: 'Selected tech stack ID (e.g., nextjs-prisma-sqlite)',
3769
+ })
3770
+ .option('prompt', {
3771
+ type: 'string',
3772
+ describe: "User's original prompt (captured for journal/results)",
3773
+ })
3774
+ .option('port', {
3775
+ type: 'number',
3776
+ alias: 'p',
3777
+ describe: 'Port to run the web server on',
3778
+ default: 3111,
3779
+ });
3780
+ if (IS_INTERNAL_BUILD) {
3781
+ builder = builder
3782
+ .option('target', {
3783
+ type: 'string',
3784
+ describe: 'Debug target (setup, overview, overview-with-state, step-1..step-16, or comma-separated list)',
3785
+ })
3786
+ .option('resume', {
3787
+ type: 'boolean',
3788
+ describe: 'Debug: only render resume variants (use --no-resume for only normal)',
3789
+ })
3790
+ .option('include-context', {
3791
+ type: 'boolean',
3792
+ default: true,
3793
+ describe: 'Debug: include CLAUDE.md, skill, and editor-mode-context snapshots',
3794
+ })
3795
+ .option('output', {
3796
+ type: 'string',
3797
+ describe: 'Debug: output directory for the bundle',
3798
+ });
3799
+ }
3800
+ return builder;
3801
+ },
3802
+ handler: async (argv) => {
3803
+ const root = getProjectRoot();
3804
+ // API subcommands: preview, show-results, hide-results, commit,
3805
+ // journal, journal-update, dev-server, client-errors
3806
+ if (argv.step && EDITOR_API_SUBCOMMANDS.includes(argv.step)) {
3807
+ const port = getServerPort();
3808
+ try {
3809
+ const request = buildEditorApiRequest(argv.step, argv.json || undefined);
3810
+ const result = await callEditorApi(request, port);
3811
+ if (!result.ok) {
3812
+ console.error(chalk.red(`Error: ${request.endpoint} returned ${result.status}`));
3813
+ if (result.data)
3814
+ console.error(JSON.stringify(result.data, null, 2));
3815
+ process.exit(1);
3816
+ }
3817
+ if (result.data != null) {
3818
+ const summary = formatApiSubcommandResult(argv.step, result.data);
3819
+ if (summary) {
3820
+ console.log(summary);
3821
+ }
3822
+ else {
3823
+ console.log(JSON.stringify(result.data, null, 2));
3824
+ }
3825
+ }
3826
+ }
3827
+ catch (err) {
3828
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3829
+ console.error(chalk.dim(` ${err.message}`));
3830
+ process.exit(1);
3831
+ }
3832
+ return;
3833
+ }
3834
+ // Subcommand: codeyam editor register '{"name":"..."}'
3835
+ if (argv.step === 'register') {
3836
+ await handleRegister(argv.json || '');
3837
+ return;
3838
+ }
3839
+ // Subcommand: codeyam editor analyze-imports
3840
+ if (argv.step === 'analyze-imports') {
3841
+ await handleAnalyzeImports();
3842
+ return;
3843
+ }
3844
+ // Subcommand: codeyam editor dependents <EntityName>
3845
+ if (argv.step === 'dependents') {
3846
+ await handleDependents(argv.json || '');
3847
+ return;
3848
+ }
3849
+ // Subcommand: codeyam editor audit
3850
+ if (argv.step === 'audit') {
3851
+ await handleAudit();
3852
+ return;
3853
+ }
3854
+ // Subcommand: codeyam editor scenarios
3855
+ if (argv.step === 'scenarios') {
3856
+ await handleScenarios();
3857
+ return;
3858
+ }
3859
+ // Subcommand: codeyam editor scenario-coverage
3860
+ if (argv.step === 'scenario-coverage') {
3861
+ await handleScenarioCoverage();
3862
+ return;
3863
+ }
3864
+ // Subcommand: codeyam editor change <feature>
3865
+ if (argv.step === 'change') {
3866
+ handleChange(argv.json || '');
3867
+ return;
3868
+ }
3869
+ // Subcommand: codeyam editor verify-images '{"paths":["/","/drinks/1"]}'
3870
+ if (argv.step === 'verify-images') {
3871
+ await handleVerifyImages(argv.json || '');
3872
+ return;
3873
+ }
3874
+ // Subcommand: codeyam editor validate-seed '{"products":[...]}'
3875
+ if (argv.step === 'validate-seed') {
3876
+ await handleValidateSeed(argv.json || '');
3877
+ return;
3878
+ }
3879
+ // Subcommand: codeyam editor delete <scenarioId>
3880
+ if (argv.step === 'delete') {
3881
+ await handleDelete(argv.json || '');
3882
+ return;
3883
+ }
3884
+ // Subcommand: codeyam editor isolate "StarRating CategoryBadge DrinkCard"
3885
+ if (argv.step === 'isolate') {
3886
+ const names = [];
3887
+ if (argv.json)
3888
+ names.push(...argv.json.split(/[\s,]+/).filter(Boolean));
3889
+ // Collect any extra positional args (yargs puts them in argv._)
3890
+ const extras = argv._.filter((a) => typeof a === 'string' && a !== 'editor');
3891
+ names.push(...extras);
3892
+ handleIsolate(names);
3893
+ return;
3894
+ }
3895
+ // Subcommand: codeyam editor template — scaffold project from template
3896
+ if (argv.step === 'template') {
3897
+ await handleTemplate();
3898
+ return;
3899
+ }
3900
+ // Subcommand: codeyam editor sync — import scenarios from manifest
3901
+ if (argv.step === 'sync') {
3902
+ await handleSync();
3903
+ return;
3904
+ }
3905
+ // Subcommand: codeyam editor debug [--target ...]
3906
+ if (argv.step === 'debug') {
3907
+ if (!IS_INTERNAL_BUILD) {
3908
+ console.error(chalk.red('Error: "codeyam editor debug" is internal-only.'));
3909
+ process.exit(1);
3910
+ }
3911
+ await handleEditorDebug(argv);
3912
+ return;
3913
+ }
3914
+ // Subcommand: codeyam editor migrate [subArg]
3915
+ if (argv.step === 'migrate') {
3916
+ handleMigrateCommand(root, argv.json || undefined);
3917
+ return;
3918
+ }
3919
+ // Subcommand: codeyam editor steps — show setup or cycle overview
3920
+ if (argv.step === 'steps') {
3921
+ if (!hasProject(root)) {
3922
+ printSetup(root);
3923
+ }
3924
+ else {
3925
+ const state = readState(root);
3926
+ // Clear prompt file when feature is done (step 16) so the hook
3927
+ // can capture the next feature request from the user.
3928
+ if (state?.step === 16) {
3929
+ clearEditorUserPrompt(root);
3930
+ }
3931
+ printCycleOverview(root, state);
3932
+ }
3933
+ return;
3934
+ }
3935
+ const step = argv.step ? parseInt(argv.step, 10) : undefined;
3936
+ if (step != null && (isNaN(step) || step < 1 || step > 16)) {
3937
+ console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-16.`));
3938
+ process.exit(1);
3939
+ }
3940
+ if (step == null) {
3941
+ // No step specified: launch editor server + open browser
3942
+ // Auto-init if needed
3943
+ let projectRoot = getStateProjectRoot();
3944
+ if (!projectRoot) {
3945
+ await initCommand.handler({
3946
+ force: false,
3947
+ autoInit: true,
3948
+ $0: '',
3949
+ _: [],
3950
+ });
3951
+ projectRoot = getStateProjectRoot();
3952
+ if (!projectRoot) {
3953
+ return;
3954
+ }
3955
+ }
3956
+ const configPath = path.join(projectRoot, '.codeyam', 'config.json');
3957
+ try {
3958
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
3959
+ const { projectSlug } = config;
3960
+ if (!projectSlug) {
3961
+ errorLog('Missing project slug. Try reinitializing with: codeyam init --force');
3962
+ return;
3963
+ }
3964
+ const connectionOk = await withoutSpinner(() => testEnvironment());
3965
+ if (!connectionOk) {
3966
+ errorLog('Environment validation failed');
3967
+ return;
3968
+ }
3969
+ const { project, branch } = await requireBranchAndProject(projectSlug);
3970
+ // Backfill _metadata into existing scenario files that predate the embedded metadata feature.
3971
+ // This reads metadata from the DB and writes it into files that don't have _metadata yet.
3972
+ try {
3973
+ const { getDatabase } = await import('../../../packages/database/index.js');
3974
+ const db = getDatabase();
3975
+ const dbScenarios = await db
3976
+ .selectFrom('editor_scenarios')
3977
+ .select([
3978
+ 'id',
3979
+ 'name',
3980
+ 'description',
3981
+ 'component_name',
3982
+ 'component_path',
3983
+ 'url',
3984
+ 'type',
3985
+ 'screenshot_path',
3986
+ 'viewport_width',
3987
+ 'viewport_height',
3988
+ 'dimensions',
3989
+ 'screenshot_paths',
3990
+ 'page_file_path',
3991
+ 'created_at',
3992
+ 'updated_at',
3993
+ ])
3994
+ .where('project_id', '=', project.id)
3995
+ .execute();
3996
+ if (dbScenarios.length > 0) {
3997
+ const backfilled = backfillScenarioMetadata(projectRoot, dbScenarios);
3998
+ if (backfilled > 0) {
3999
+ console.log(chalk.green(` Backfilled metadata into ${backfilled} scenario file${backfilled > 1 ? 's' : ''}`));
4000
+ }
4001
+ }
4002
+ }
4003
+ catch {
4004
+ // Non-fatal — backfill is best-effort
4005
+ }
4006
+ // Auto-sync scenario files (with _metadata) to database
4007
+ const scenarioEntries = scanScenarioFiles(projectRoot);
4008
+ if (scenarioEntries.length > 0) {
4009
+ try {
4010
+ const { getDatabase } = await import('../../../packages/database/index.js');
4011
+ const db = getDatabase();
4012
+ const existingRows = await db
4013
+ .selectFrom('editor_scenarios')
4014
+ .select(['id', 'updated_at'])
4015
+ .where('project_id', '=', project.id)
4016
+ .execute();
4017
+ const syncResult = await syncScenarioFilesToDatabase(projectRoot, project.id, existingRows, async (row) => {
4018
+ await db
4019
+ .insertInto('editor_scenarios')
4020
+ .values(row)
4021
+ .execute();
4022
+ }, async (id, row) => {
4023
+ await db
4024
+ .updateTable('editor_scenarios')
4025
+ .set(row)
4026
+ .where('id', '=', id)
4027
+ .execute();
4028
+ });
4029
+ if (syncResult.inserted > 0 || syncResult.updated > 0) {
4030
+ const parts = [];
4031
+ if (syncResult.inserted > 0)
4032
+ parts.push(`${syncResult.inserted} imported`);
4033
+ if (syncResult.updated > 0)
4034
+ parts.push(`${syncResult.updated} updated`);
4035
+ console.log(chalk.green(` Synced scenarios from files: ${parts.join(', ')}`));
4036
+ }
4037
+ }
4038
+ catch {
4039
+ // Non-fatal — sync failure shouldn't block editor startup
4040
+ }
4041
+ }
4042
+ // Migrate legacy scenario formats: resolve null viewports, populate
4043
+ // dimensions arrays, and build screenshotPaths maps from single values.
4044
+ // If files were fixed, re-sync to update the database with corrected values.
4045
+ try {
4046
+ const migrateResult = migrateScenarioFormats(projectRoot);
4047
+ if (migrateResult.fixed > 0) {
4048
+ console.log(chalk.green(` Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
4049
+ // Re-sync so the DB reflects the fixed file metadata
4050
+ try {
4051
+ const { getDatabase } = await import('../../../packages/database/index.js');
4052
+ const db = getDatabase();
4053
+ const rows = await db
4054
+ .selectFrom('editor_scenarios')
4055
+ .select(['id', 'updated_at'])
4056
+ .where('project_id', '=', project.id)
4057
+ .execute();
4058
+ await syncScenarioFilesToDatabase(projectRoot, project.id, rows, async (row) => {
4059
+ await db
4060
+ .insertInto('editor_scenarios')
4061
+ .values(row)
4062
+ .execute();
4063
+ }, async (id, row) => {
4064
+ await db
4065
+ .updateTable('editor_scenarios')
4066
+ .set(row)
4067
+ .where('id', '=', id)
4068
+ .execute();
4069
+ });
4070
+ }
4071
+ catch {
4072
+ // Non-fatal — DB re-sync failure shouldn't block startup
4073
+ }
4074
+ }
4075
+ }
4076
+ catch {
4077
+ // Non-fatal — migration failure shouldn't block editor startup
4078
+ }
4079
+ // `codeyam editor` (no step) always implies editor mode.
4080
+ // The empty-folder heuristic is no longer needed here — running this
4081
+ // command IS the signal. We still detect empty folders so that
4082
+ // the very first `codeyam editor` in a bare directory works before
4083
+ // metadata has been persisted.
4084
+ const editorMode = true;
4085
+ if (editorMode && !project.metadata?.labs?.simulations) {
4086
+ try {
4087
+ await updateProjectMetadata({
4088
+ projectSlug,
4089
+ metadataUpdate: {
4090
+ editorMode: true,
4091
+ labs: { simulations: true },
4092
+ },
4093
+ });
4094
+ const refreshed = await requireBranchAndProject(projectSlug);
4095
+ Object.assign(project, refreshed.project);
4096
+ }
4097
+ catch {
4098
+ // Non-fatal
4099
+ }
4100
+ }
4101
+ // Install skills/hooks
4102
+ const simulationsEnabled = project.metadata?.labs?.simulations ?? false;
4103
+ const skillMode = simulationsEnabled
4104
+ ? 'full'
4105
+ : editorMode
4106
+ ? 'editor'
4107
+ : 'memory';
4108
+ await installClaudeCodeSkills(projectRoot, {
4109
+ mode: skillMode,
4110
+ editorMode,
4111
+ });
4112
+ setupClaudeCodeSettings(projectRoot, {
4113
+ mode: skillMode,
4114
+ editorMode,
4115
+ });
4116
+ // Auto-finalize analyzer so codeyam analyze works
4117
+ if (editorMode) {
4118
+ const stepLabels = {
4119
+ 'npm-install': 'Installing simulation dependencies...',
4120
+ 'playwright-install': 'Installing browser (Chromium)...',
4121
+ build: 'Building simulation engine...',
4122
+ };
4123
+ const simProgress = new ProgressReporter();
4124
+ const finalization = ensureAnalyzerFinalized({
4125
+ onProgress: (step) => simProgress.start(stepLabels[step]),
4126
+ });
4127
+ if (finalization.errors.length > 0) {
4128
+ for (const err of finalization.errors) {
4129
+ simProgress.warn(`${stepLabels[err.step].replace('...', '')} failed: ${err.message}`);
4130
+ }
4131
+ }
4132
+ else if (finalization.needed) {
4133
+ simProgress.succeed('Simulation engine ready');
4134
+ }
4135
+ }
4136
+ // Start background server (handles killing existing servers internally)
4137
+ const editorPort = argv.port || 3111;
4138
+ const { url } = await startBackgroundServer({
4139
+ port: editorPort,
4140
+ rootPath: projectRoot,
4141
+ project,
4142
+ branch,
4143
+ });
4144
+ // Build import graph if glossary exists but entities are missing.
4145
+ // Runs on first startup (no entities at all) AND when page files or
4146
+ // scenario component files lack entity coverage.
4147
+ const glossaryPath = path.join(projectRoot, '.codeyam', 'glossary.json');
4148
+ if (fs.existsSync(glossaryPath)) {
4149
+ let needsAnalysis = false;
4150
+ const entities = await loadEntities({});
4151
+ if (!entities || entities.length === 0) {
4152
+ needsAnalysis = true;
4153
+ }
4154
+ else {
4155
+ const entityFilePaths = new Set(entities.map((e) => e.filePath));
4156
+ // Check if any page files are missing entities (Next.js apps)
4157
+ try {
4158
+ const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
4159
+ const { allFiles } = scanPageFilePaths(projectRoot);
4160
+ const pageFiles = allFiles.filter((f) => f.endsWith('/page.tsx') || f.endsWith('/page.js'));
4161
+ const missingPages = pageFiles.filter((f) => !entityFilePaths.has(f));
4162
+ if (missingPages.length > 0) {
4163
+ console.log(chalk.dim(` Found ${missingPages.length} page(s) without entity analysis — running import analysis...`));
4164
+ needsAnalysis = true;
4165
+ }
4166
+ }
4167
+ catch {
4168
+ // Non-fatal — page file check failed
4169
+ }
4170
+ // Check if any scenario files (component_path or page_file_path)
4171
+ // are missing entities — covers non-Next.js apps
4172
+ if (!needsAnalysis) {
4173
+ try {
4174
+ const { getDatabase } = await import('../../../packages/database/index.js');
4175
+ const db = getDatabase();
4176
+ const scenarioFiles = await db
4177
+ .selectFrom('editor_scenarios')
4178
+ .select(['component_path', 'page_file_path'])
4179
+ .where('project_id', '=', project.id)
4180
+ .distinct()
4181
+ .execute();
4182
+ const missingCount = scenarioFiles.filter((row) => {
4183
+ const cp = row.component_path;
4184
+ const pfp = row.page_file_path;
4185
+ return ((cp && !entityFilePaths.has(cp)) ||
4186
+ (pfp && !entityFilePaths.has(pfp)));
4187
+ }).length;
4188
+ if (missingCount > 0) {
4189
+ console.log(chalk.dim(` Found ${missingCount} scenario file(s) without entity analysis — running import analysis...`));
4190
+ needsAnalysis = true;
4191
+ }
4192
+ }
4193
+ catch {
4194
+ // Non-fatal
4195
+ }
4196
+ }
4197
+ // Check if any scenarios have null entity_sha with file paths — heal on startup
4198
+ if (!needsAnalysis) {
4199
+ try {
4200
+ const { getDatabase } = await import('../../../packages/database/index.js');
4201
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
4202
+ const db = getDatabase();
4203
+ const backfillCount = await countScenariosNeedingEntityBackfill(db);
4204
+ if (backfillCount > 0) {
4205
+ console.log(chalk.dim(` Found ${backfillCount} scenario(s) with unlinked entities — running import analysis...`));
4206
+ needsAnalysis = true;
4207
+ }
4208
+ }
4209
+ catch {
4210
+ // Non-fatal
4211
+ }
4212
+ }
4213
+ }
4214
+ if (needsAnalysis) {
4215
+ try {
4216
+ await handleAnalyzeImports({ silent: true });
4217
+ }
4218
+ catch {
4219
+ // Non-fatal
4220
+ }
4221
+ }
4222
+ }
4223
+ // Backfill entity_sha on scenarios that were synced before entities existed.
4224
+ // This runs independently of analyze-imports so fresh clones and file-synced
4225
+ // scenarios get linked to their entities even when auto-analyze doesn't trigger.
4226
+ try {
4227
+ const entities = await loadEntities({});
4228
+ if (entities && entities.length > 0) {
4229
+ const { getDatabase } = await import('../../../packages/database/index.js');
4230
+ const db = getDatabase();
4231
+ const result = await backfillEntityShaOnScenarios(db, entities.map((e) => ({
4232
+ sha: e.sha,
4233
+ name: e.name,
4234
+ filePath: e.filePath || '',
4235
+ })));
4236
+ if (result.updated > 0) {
4237
+ console.log(chalk.green(` Linked ${result.updated} scenario(s) to their entities`));
4238
+ }
4239
+ }
4240
+ }
4241
+ catch {
4242
+ /* Non-fatal */
4243
+ }
4244
+ console.log();
4245
+ console.log(` Dashboard: ${url}`);
4246
+ console.log(' Run "codeyam --help" for all commands');
4247
+ console.log(chalk.bold(' Run "codeyam editor" to launch the editor at any time'));
4248
+ console.log();
4249
+ // Open browser to /editor
4250
+ try {
4251
+ const { execSync } = await import('child_process');
4252
+ const openCommand = process.platform === 'darwin'
4253
+ ? 'open'
4254
+ : process.platform === 'win32'
4255
+ ? 'start ""'
4256
+ : 'xdg-open';
4257
+ execSync(`${openCommand} "${url}/editor"`, {
4258
+ stdio: 'ignore',
4259
+ });
4260
+ }
4261
+ catch {
4262
+ // Silently fail if open command doesn't work
4263
+ }
4264
+ }
4265
+ catch (err) {
4266
+ errorLog('Failed to start CodeYam editor server');
4267
+ errorLog(err);
4268
+ process.exit(1);
4269
+ }
4270
+ return;
4271
+ }
4272
+ const state = readState(root);
4273
+ // Validate step transition — prevent skipping steps.
4274
+ // Exception: step 2 with --feature is always allowed because step 1's
4275
+ // instructions explicitly tell Claude to run `codeyam editor 2 --feature "..."`.
4276
+ // Step 1 is planning-only and may not persist state (no --feature flag).
4277
+ const skipValidation = step === 2 && argv.feature;
4278
+ if (!skipValidation) {
4279
+ const stepError = validateStepTransition(step, state?.step ?? null);
4280
+ if (stepError) {
4281
+ console.error(chalk.red(`Error: ${stepError}`));
4282
+ process.exit(1);
4283
+ }
4284
+ }
4285
+ switch (step) {
4286
+ case 1: {
4287
+ const feature = argv.feature || undefined;
4288
+ const appFormats = argv['app-formats']
4289
+ ? argv['app-formats'].split(',').map((f) => f.trim())
4290
+ : undefined;
4291
+ const techStackId = argv['tech-stack'] || undefined;
4292
+ const prompt = argv.prompt || undefined;
4293
+ printStep1(root, feature, { appFormats, techStackId }, prompt);
4294
+ break;
4295
+ }
4296
+ case 2: {
4297
+ const feature = argv.feature || state?.feature;
4298
+ if (!feature) {
4299
+ console.error(chalk.red('Error: --feature "Feature Name" is required for step 2.'));
4300
+ console.error(chalk.dim(' Usage: codeyam editor 2 --feature "Drink Rating System"'));
4301
+ process.exit(1);
4302
+ }
4303
+ printStep2(root, feature);
4304
+ break;
4305
+ }
4306
+ case 3:
4307
+ case 4:
4308
+ case 5:
4309
+ case 6:
4310
+ case 7:
4311
+ case 8:
4312
+ case 9:
4313
+ case 10:
4314
+ case 11:
4315
+ case 12:
4316
+ case 13:
4317
+ case 14:
4318
+ case 15:
4319
+ case 16: {
4320
+ const feature = argv.feature || state?.feature;
4321
+ if (!feature) {
4322
+ console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
4323
+ process.exit(1);
4324
+ }
4325
+ // Hard gate: steps 8+ require audit to have passed
4326
+ if (step >= 8) {
4327
+ const auditOk = await checkAuditGate();
4328
+ if (!auditOk) {
4329
+ console.error(chalk.red.bold('BLOCKED: The audit has not passed. Run `codeyam editor audit` and fix all issues before proceeding.'));
4330
+ console.error(chalk.yellow('Every function and hook must have a test file. Every component must have scenarios.'));
4331
+ process.exit(1);
4332
+ }
4333
+ }
4334
+ const stepFns = {
4335
+ 3: printStep3,
4336
+ 4: printStep4,
4337
+ 5: printStep5,
4338
+ 6: printStep6,
4339
+ 7: printStep7,
4340
+ 8: printStep8,
4341
+ 9: printStep9,
4342
+ 10: printStep10,
4343
+ 11: printStep11,
4344
+ 12: printStep12,
4345
+ 13: printStep13,
4346
+ 14: printStep14,
4347
+ 15: printStep15,
4348
+ 16: printStep16,
4349
+ };
4350
+ stepFns[step](root, feature);
4351
+ break;
4352
+ }
4353
+ }
4354
+ },
4355
+ };
4356
+ export default editorCommand;
4357
+ //# sourceMappingURL=editor.js.map