@codeyam/codeyam-cli 0.1.0-staging.b8b17a5 → 0.1.0-staging.ba3f279

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 (331) hide show
  1. package/analyzer-template/.build-info.json +7 -7
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +2 -2
  4. package/analyzer-template/packages/ai/src/lib/astScopes/methodSemantics.ts +135 -0
  5. package/analyzer-template/packages/ai/src/lib/astScopes/nodeToSource.ts +19 -0
  6. package/analyzer-template/packages/ai/src/lib/astScopes/paths.ts +11 -4
  7. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +36 -9
  8. package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.ts +10 -3
  9. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +16 -6
  10. package/analyzer-template/packages/analyze/index.ts +4 -1
  11. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +28 -2
  12. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +5 -36
  13. package/analyzer-template/packages/analyze/src/lib/files/analyze/findOrCreateEntity.ts +10 -6
  14. package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +9 -12
  15. package/analyzer-template/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.ts +21 -0
  16. package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +82 -10
  17. package/analyzer-template/packages/analyze/src/lib/files/analyzeChange.ts +4 -0
  18. package/analyzer-template/packages/analyze/src/lib/files/analyzeInitial.ts +4 -0
  19. package/analyzer-template/packages/analyze/src/lib/files/analyzeNextRoute.ts +8 -3
  20. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +239 -58
  21. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +1684 -1462
  22. package/analyzer-template/packages/aws/package.json +1 -1
  23. package/analyzer-template/packages/database/package.json +1 -1
  24. package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +25 -15
  25. package/analyzer-template/packages/database/src/lib/loadEntity.ts +19 -8
  26. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.d.ts.map +1 -1
  27. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js +7 -1
  28. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js.map +1 -1
  29. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts +4 -1
  30. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts.map +1 -1
  31. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js +5 -5
  32. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js.map +1 -1
  33. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts +3 -1
  34. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
  35. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +22 -1
  36. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  37. package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +27 -0
  38. package/analyzer-template/project/analyzeFileEntities.ts +26 -0
  39. package/analyzer-template/project/runMultiScenarioServer.ts +26 -3
  40. package/background/src/lib/virtualized/project/analyzeFileEntities.js +22 -0
  41. package/background/src/lib/virtualized/project/analyzeFileEntities.js.map +1 -1
  42. package/background/src/lib/virtualized/project/runMultiScenarioServer.js +23 -3
  43. package/background/src/lib/virtualized/project/runMultiScenarioServer.js.map +1 -1
  44. package/codeyam-cli/src/cli.js +15 -0
  45. package/codeyam-cli/src/cli.js.map +1 -1
  46. package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js +47 -0
  47. package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js.map +1 -0
  48. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js +71 -0
  49. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
  50. package/codeyam-cli/src/commands/__tests__/editor.designSystem.test.js +30 -0
  51. package/codeyam-cli/src/commands/__tests__/editor.designSystem.test.js.map +1 -0
  52. package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js +55 -0
  53. package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js.map +1 -0
  54. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +9 -9
  55. package/codeyam-cli/src/commands/editor.js +1906 -468
  56. package/codeyam-cli/src/commands/editor.js.map +1 -1
  57. package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js +23 -0
  58. package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js.map +1 -0
  59. package/codeyam-cli/src/commands/init.js +1 -0
  60. package/codeyam-cli/src/commands/init.js.map +1 -1
  61. package/codeyam-cli/src/data/designSystems.js +27 -0
  62. package/codeyam-cli/src/data/designSystems.js.map +1 -0
  63. package/codeyam-cli/src/data/techStacks.js +1 -1
  64. package/codeyam-cli/src/utils/__tests__/editorApi.test.js +44 -0
  65. package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -1
  66. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +2114 -70
  67. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  68. package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js +137 -0
  69. package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js.map +1 -0
  70. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +66 -0
  71. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -1
  72. package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js +67 -0
  73. package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js.map +1 -0
  74. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
  75. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
  76. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +190 -0
  77. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -1
  78. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +249 -1
  79. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  80. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +134 -1
  81. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
  82. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +266 -2
  83. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  84. package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js +177 -0
  85. package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js.map +1 -0
  86. package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js +16 -1
  87. package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js.map +1 -1
  88. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
  89. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
  90. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js +127 -0
  91. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js.map +1 -0
  92. package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +57 -0
  93. package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -1
  94. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +180 -1
  95. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
  96. package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js +84 -0
  97. package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js.map +1 -0
  98. package/codeyam-cli/src/utils/__tests__/testRunner.test.js +216 -0
  99. package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
  100. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +6 -0
  101. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -1
  102. package/codeyam-cli/src/utils/analysisRunner.js +36 -7
  103. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  104. package/codeyam-cli/src/utils/analyzer.js +11 -1
  105. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  106. package/codeyam-cli/src/utils/backgroundServer.js +1 -1
  107. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  108. package/codeyam-cli/src/utils/editorApi.js +16 -0
  109. package/codeyam-cli/src/utils/editorApi.js.map +1 -1
  110. package/codeyam-cli/src/utils/editorAudit.js +499 -43
  111. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  112. package/codeyam-cli/src/utils/editorGuard.js +36 -0
  113. package/codeyam-cli/src/utils/editorGuard.js.map +1 -0
  114. package/codeyam-cli/src/utils/editorPreview.js +5 -3
  115. package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
  116. package/codeyam-cli/src/utils/editorRecapture.js +109 -0
  117. package/codeyam-cli/src/utils/editorRecapture.js.map +1 -0
  118. package/codeyam-cli/src/utils/editorScenarioSwitch.js +39 -2
  119. package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -1
  120. package/codeyam-cli/src/utils/editorScenarios.js +131 -7
  121. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  122. package/codeyam-cli/src/utils/editorSeedAdapter.js +69 -16
  123. package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
  124. package/codeyam-cli/src/utils/entityChangeStatus.js +31 -3
  125. package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
  126. package/codeyam-cli/src/utils/entityChangeStatus.server.js +31 -0
  127. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  128. package/codeyam-cli/src/utils/glossaryAdd.js +74 -0
  129. package/codeyam-cli/src/utils/glossaryAdd.js.map +1 -0
  130. package/codeyam-cli/src/utils/install-skills.js +5 -0
  131. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  132. package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
  133. package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
  134. package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js +159 -0
  135. package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js.map +1 -0
  136. package/codeyam-cli/src/utils/queue/job.js +35 -6
  137. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  138. package/codeyam-cli/src/utils/registerScenarioResult.js +52 -0
  139. package/codeyam-cli/src/utils/registerScenarioResult.js.map +1 -0
  140. package/codeyam-cli/src/utils/scenarioCoverage.js +4 -1
  141. package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -1
  142. package/codeyam-cli/src/utils/scenariosManifest.js +66 -2
  143. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  144. package/codeyam-cli/src/utils/screenshotHash.js +26 -0
  145. package/codeyam-cli/src/utils/screenshotHash.js.map +1 -0
  146. package/codeyam-cli/src/utils/simulationGateMiddleware.js +9 -0
  147. package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -1
  148. package/codeyam-cli/src/utils/testResultCache.js +53 -0
  149. package/codeyam-cli/src/utils/testResultCache.js.map +1 -0
  150. package/codeyam-cli/src/utils/testResultCache.server.js +81 -0
  151. package/codeyam-cli/src/utils/testResultCache.server.js.map +1 -0
  152. package/codeyam-cli/src/utils/testResultCache.server.test.js +187 -0
  153. package/codeyam-cli/src/utils/testResultCache.server.test.js.map +1 -0
  154. package/codeyam-cli/src/utils/testResultCache.test.js +230 -0
  155. package/codeyam-cli/src/utils/testResultCache.test.js.map +1 -0
  156. package/codeyam-cli/src/utils/testRunner.js +193 -1
  157. package/codeyam-cli/src/utils/testRunner.js.map +1 -1
  158. package/codeyam-cli/src/utils/webappDetection.js +4 -2
  159. package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
  160. package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js +98 -0
  161. package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js.map +1 -0
  162. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +68 -1
  163. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -1
  164. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +30 -11
  165. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
  166. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +190 -21
  167. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
  168. package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js +135 -0
  169. package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js.map +1 -0
  170. package/codeyam-cli/src/webserver/app/lib/clientErrors.js +22 -1
  171. package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
  172. package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js +34 -0
  173. package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js.map +1 -0
  174. package/codeyam-cli/src/webserver/backgroundServer.js +42 -57
  175. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  176. package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-CzTDWkF2.js → CopyButton-CLe80MMu.js} +1 -1
  177. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-BFbq6iFk.js → EntityItem-Crt_KN_U.js} +1 -1
  178. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-B6OMi58N.js → EntityTypeIcon-CD7lGABo.js} +1 -1
  179. package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-DuYodzo1.js → InlineSpinner-CgTNOhnu.js} +1 -1
  180. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-CXo9EeCl.js → InteractivePreview-DtYTSPL2.js} +2 -2
  181. package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-DYCNb2It.js → LibraryFunctionPreview-D3s1MFkb.js} +1 -1
  182. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-CZgY3sxX.js → LogViewer-CM5zg40N.js} +1 -1
  183. package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-CQENLSrF.js +36 -0
  184. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-CnYYwRDw.js → ReportIssueModal-C2PLkej3.js} +1 -1
  185. package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-CDoF7ZpU.js → SafeScreenshot-DanvyBPb.js} +1 -1
  186. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-DrnfvaLL.js → ScenarioViewer-CefgqbCr.js} +1 -1
  187. package/codeyam-cli/src/webserver/build/client/assets/Spinner-Bc8BG-Lw.js +34 -0
  188. package/codeyam-cli/src/webserver/build/client/assets/{ViewportInspectBar-DRKR9T0U.js → ViewportInspectBar-BA_Ry-rs.js} +1 -1
  189. package/codeyam-cli/src/webserver/build/client/assets/{_index-ClR-g3tY.js → _index-C1YkzTAV.js} +1 -1
  190. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-DTH6ydEA.js → activity.(_tab)-yH46LLUz.js} +1 -1
  191. package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-74hnHF59.js → addon-web-links-CHx25PAe.js} +1 -1
  192. package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-B8CYhCO9.js → agent-transcripts-Bg3e7q4S.js} +1 -1
  193. package/codeyam-cli/src/webserver/build/client/assets/api.editor-recapture-stale-l0sNRNKZ.js +1 -0
  194. package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-scenario-data-l0sNRNKZ.js +1 -0
  195. package/codeyam-cli/src/webserver/build/client/assets/api.editor-schema-l0sNRNKZ.js +1 -0
  196. package/codeyam-cli/src/webserver/build/client/assets/api.editor-verify-routes-l0sNRNKZ.js +1 -0
  197. package/codeyam-cli/src/webserver/build/client/assets/api.interactive-switch-scenario-l0sNRNKZ.js +1 -0
  198. package/codeyam-cli/src/webserver/build/client/assets/{book-open-CLaoh4ac.js → book-open-CL-lMgHh.js} +1 -1
  199. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-BZ2DZxbW.js → chevron-down-GmAjGS9-.js} +1 -1
  200. package/codeyam-cli/src/webserver/build/client/assets/{chunk-JZWAC4HX-BBXArFPl.js → chunk-JZWAC4HX-BAdwhyCx.js} +11 -11
  201. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-CT4unAk-.js → circle-check-DFcQkN5j.js} +1 -1
  202. package/codeyam-cli/src/webserver/build/client/assets/{copy-zK0B6Nu-.js → copy-C6iF61Xs.js} +1 -1
  203. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-DJB0YQJL.js → createLucideIcon-4ImjHTVC.js} +1 -1
  204. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-Coe5NhbS.js +1 -0
  205. package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CCKUIm0S.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
  206. package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-CkXFP_i-.js → dev.empty-CRepiabR.js} +1 -1
  207. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-Gbk_i5Js.js +1 -0
  208. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-CRxPi2BB.js +96 -0
  209. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-CluPkvXJ.js +41 -0
  210. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BqAN7hyG.js → entity._sha._-DYJRGiDI.js} +13 -12
  211. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-D1eikpe1.js → entity._sha.scenarios._scenarioId.dev-wdiwx5-Z.js} +1 -1
  212. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-Dg1NhIms.js → entity._sha.scenarios._scenarioId.fullscreen-BrkN-40Y.js} +1 -1
  213. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-CJX6kkkV.js → entity._sha_.create-scenario-DxfhekTZ.js} +1 -1
  214. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-BhVjZhKg.js → entity._sha_.edit._scenarioId-CRXJWmpB.js} +1 -1
  215. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-_gzKltPN.js → entry.client-SuW9syRS.js} +1 -1
  216. package/codeyam-cli/src/webserver/build/client/assets/{files-CV_17tZS.js → files-D-xGrg29.js} +1 -1
  217. package/codeyam-cli/src/webserver/build/client/assets/{git-D-YXmMbR.js → git-Bq_fbXP5.js} +1 -1
  218. package/codeyam-cli/src/webserver/build/client/assets/globals-BsGHu8WX.css +1 -0
  219. package/codeyam-cli/src/webserver/build/client/assets/{index-CCrgCshv.js → index-Bp1l4hSv.js} +1 -1
  220. package/codeyam-cli/src/webserver/build/client/assets/{index-BsX0F-9C.js → index-CWV9XZiG.js} +1 -1
  221. package/codeyam-cli/src/webserver/build/client/assets/{index-Blo6EK8G.js → index-DE3jI_dv.js} +1 -1
  222. package/codeyam-cli/src/webserver/build/client/assets/{labs-Byazq8Pv.js → labs-B_IX45ih.js} +1 -1
  223. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-DVQ0oHR7.js → loader-circle-De-7qQ2u.js} +1 -1
  224. package/codeyam-cli/src/webserver/build/client/assets/manifest-9032538f.js +1 -0
  225. package/codeyam-cli/src/webserver/build/client/assets/{memory-b-VmA2Vj.js → memory-Cx2xEx7s.js} +1 -1
  226. package/codeyam-cli/src/webserver/build/client/assets/{pause-DGcndCAa.js → pause-CFxEKL1u.js} +1 -1
  227. package/codeyam-cli/src/webserver/build/client/assets/root-dKFRTYcy.js +80 -0
  228. package/codeyam-cli/src/webserver/build/client/assets/{search-C0Uw0bcK.js → search-BdBb5aqc.js} +1 -1
  229. package/codeyam-cli/src/webserver/build/client/assets/{settings-OoNgHIfW.js → settings-DdE-Untf.js} +1 -1
  230. package/codeyam-cli/src/webserver/build/client/assets/{simulations-Bcemfu8a.js → simulations-DSCdE99u.js} +1 -1
  231. package/codeyam-cli/src/webserver/build/client/assets/{terminal-BgMmG7R9.js → terminal-CrplD4b1.js} +1 -1
  232. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-Cs87hJYK.js → triangle-alert-DqJ0j69l.js} +1 -1
  233. package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-BR3Rs7JY.js → useCustomSizes-DhXHbEjP.js} +1 -1
  234. package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-D9QZKaLJ.js +2 -0
  235. package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-BermyNU5.js → useReportContext-Cy5Qg_UR.js} +1 -1
  236. package/codeyam-cli/src/webserver/build/client/assets/{useToast-a_QN_W9_.js → useToast-5HR2j9ZE.js} +1 -1
  237. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-OLsM110H.js +16 -0
  238. package/codeyam-cli/src/webserver/build/server/assets/{index-CHymws6l.js → index-WHdB6WTN.js} +1 -1
  239. package/codeyam-cli/src/webserver/build/server/assets/{init-D3HkMDbI.js → init-DbSiZoE6.js} +2 -2
  240. package/codeyam-cli/src/webserver/build/server/assets/server-build-DZbLY6O_.js +690 -0
  241. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  242. package/codeyam-cli/src/webserver/build-info.json +5 -5
  243. package/codeyam-cli/src/webserver/editorProxy.js +55 -3
  244. package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
  245. package/codeyam-cli/src/webserver/idleDetector.js +65 -8
  246. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
  247. package/codeyam-cli/src/webserver/scripts/journalCapture.ts +53 -0
  248. package/codeyam-cli/src/webserver/server.js +52 -14
  249. package/codeyam-cli/src/webserver/server.js.map +1 -1
  250. package/codeyam-cli/src/webserver/terminalServer.js +153 -32
  251. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  252. package/codeyam-cli/templates/__tests__/editor-step-hook.prompt-capture.test.ts +118 -0
  253. package/codeyam-cli/templates/codeyam-editor-claude.md +2 -0
  254. package/codeyam-cli/templates/codeyam-editor-reference.md +216 -0
  255. package/codeyam-cli/templates/design-systems/clean-dashboard-design-system.md +255 -0
  256. package/codeyam-cli/templates/design-systems/editorial-design-system.md +267 -0
  257. package/codeyam-cli/templates/design-systems/mono-brutalist-design-system.md +256 -0
  258. package/codeyam-cli/templates/design-systems/neo-brutalist-design-system.md +294 -0
  259. package/codeyam-cli/templates/editor-step-hook.py +93 -46
  260. package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +204 -5
  261. package/codeyam-cli/templates/expo-react-native/__tests__/.gitkeep +0 -0
  262. package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +6 -3
  263. package/codeyam-cli/templates/expo-react-native/app/index.tsx +36 -0
  264. package/codeyam-cli/templates/expo-react-native/app.json +11 -0
  265. package/codeyam-cli/templates/expo-react-native/babel.config.js +1 -0
  266. package/codeyam-cli/templates/expo-react-native/gitignore +2 -0
  267. package/codeyam-cli/templates/expo-react-native/global.css +7 -0
  268. package/codeyam-cli/templates/expo-react-native/lib/theme.ts +73 -0
  269. package/codeyam-cli/templates/expo-react-native/package.json +32 -16
  270. package/codeyam-cli/templates/expo-react-native/patches/expo-modules-autolinking+3.0.24.patch +29 -0
  271. package/codeyam-cli/templates/isolation-route/expo-router.tsx.template +54 -0
  272. package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +47 -34
  273. package/codeyam-cli/templates/seed-adapters/supabase.ts +101 -9
  274. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +34 -1
  275. package/package.json +1 -1
  276. package/packages/ai/src/lib/astScopes/methodSemantics.js +99 -0
  277. package/packages/ai/src/lib/astScopes/methodSemantics.js.map +1 -1
  278. package/packages/ai/src/lib/astScopes/nodeToSource.js +16 -0
  279. package/packages/ai/src/lib/astScopes/nodeToSource.js.map +1 -1
  280. package/packages/ai/src/lib/astScopes/paths.js +12 -3
  281. package/packages/ai/src/lib/astScopes/paths.js.map +1 -1
  282. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +27 -10
  283. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  284. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
  285. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
  286. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
  287. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  288. package/packages/analyze/index.js +1 -1
  289. package/packages/analyze/index.js.map +1 -1
  290. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
  291. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  292. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
  293. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  294. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +3 -2
  295. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
  296. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +9 -7
  297. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
  298. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
  299. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
  300. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
  301. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
  302. package/packages/analyze/src/lib/files/analyzeChange.js +1 -0
  303. package/packages/analyze/src/lib/files/analyzeChange.js.map +1 -1
  304. package/packages/analyze/src/lib/files/analyzeInitial.js +1 -0
  305. package/packages/analyze/src/lib/files/analyzeInitial.js.map +1 -1
  306. package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
  307. package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
  308. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +120 -28
  309. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  310. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +1368 -1193
  311. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  312. package/packages/database/src/lib/loadAnalysis.js +7 -1
  313. package/packages/database/src/lib/loadAnalysis.js.map +1 -1
  314. package/packages/database/src/lib/loadEntity.js +5 -5
  315. package/packages/database/src/lib/loadEntity.js.map +1 -1
  316. package/packages/utils/src/lib/fs/rsyncCopy.js +22 -1
  317. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  318. package/codeyam-cli/src/webserver/build/client/assets/Spinner-Df3UCi8k.js +0 -34
  319. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DcX-ZS3p.js +0 -1
  320. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-DPw7NZHc.js +0 -1
  321. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DYqG1D_d.js +0 -58
  322. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DggyRwOr.js +0 -41
  323. package/codeyam-cli/src/webserver/build/client/assets/globals-DRvOjyO3.css +0 -1
  324. package/codeyam-cli/src/webserver/build/client/assets/manifest-f4212c17.js +0 -1
  325. package/codeyam-cli/src/webserver/build/client/assets/root-F-k2uYj5.js +0 -67
  326. package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-BxxP_XF9.js +0 -2
  327. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-if8kM_1Q.js +0 -13
  328. package/codeyam-cli/src/webserver/build/server/assets/server-build-DTCzJQiH.js +0 -551
  329. package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +0 -33
  330. package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +0 -12
  331. package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +0 -12
@@ -13,35 +13,39 @@ import { installClaudeCodeSkills } from "../utils/install-skills.js";
13
13
  import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
14
14
  import { ensureAnalyzerFinalized, } from "../utils/analyzerFinalization.js";
15
15
  import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
16
+ import { DESIGN_SYSTEMS } from "../data/designSystems.js";
16
17
  import { getProjectRoot as getStateProjectRoot } from "../state.js";
17
18
  import initCommand from "./init.js";
18
19
  import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
19
- import { clearEditorState, clearEditorUserPrompt, validateStepTransition, backfillEntityShaOnScenarios, } from "../utils/editorScenarios.js";
20
+ import { clearEditorState, validateStepTransition, backfillEntityShaOnScenarios, } from "../utils/editorScenarios.js";
20
21
  import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } from "../utils/editorSeedAdapter.js";
21
22
  import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
22
23
  import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
23
24
  import { parseRegisterArg } from "../utils/parseRegisterArg.js";
25
+ import { classifyRegistrationResult } from "../utils/registerScenarioResult.js";
24
26
  import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
25
27
  import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
26
28
  const __filename = fileURLToPath(import.meta.url);
27
29
  const __dirname = path.dirname(__filename);
28
30
  const STEP_LABELS = {
29
31
  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',
32
+ 2: 'Prepare',
33
+ 3: 'Prototype',
34
+ 4: 'Verify Prototype',
35
+ 5: 'Confirm',
36
+ 6: 'Deconstruct',
37
+ 7: 'Extract',
38
+ 8: 'Glossary',
39
+ 9: 'Analyze',
40
+ 10: 'App Scenarios',
41
+ 11: 'User Scenarios',
42
+ 12: 'Verify',
43
+ 13: 'Journal',
44
+ 14: 'Review',
45
+ 15: 'Present',
46
+ 16: 'Commit',
47
+ 17: 'Finalize',
48
+ 18: 'Push',
45
49
  };
46
50
  const MIGRATION_STEP_LABELS = {
47
51
  1: 'Survey',
@@ -119,11 +123,19 @@ function writeState(root, state) {
119
123
  const dir = path.dirname(statePath);
120
124
  fs.mkdirSync(dir, { recursive: true });
121
125
  fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
126
+ // Write task tracking file — marks that a task is expected for this step.
127
+ // The step hook sets taskCreated=true when it sees a TaskCreate tool call.
128
+ // Steps 1 and below don't require tasks (feature not named yet).
129
+ if (state.step && state.step >= 2) {
130
+ const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
131
+ fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: false }, null, 2), 'utf8');
132
+ }
122
133
  }
123
134
  /**
124
135
  * 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.
136
+ * Does NOT clear the user prompt file — the prompt is cleared
137
+ * by the journal API after recording, and the hook captures
138
+ * a fresh prompt for the next feature on UserPromptSubmit.
127
139
  */
128
140
  function clearState(root) {
129
141
  clearEditorState(root);
@@ -177,6 +189,44 @@ function getProjectDimensions(root) {
177
189
  return { defaultName: 'Desktop', names: [] };
178
190
  }
179
191
  }
192
+ function getTechStackContext(root) {
193
+ const state = readState(root);
194
+ const techStackId = state?.techStackId || '';
195
+ const stack = TECH_STACKS.find((s) => s.id === techStackId);
196
+ const isExpo = techStackId === 'expo-react-native';
197
+ const isChromeExt = techStackId === 'chrome-extension-react';
198
+ const isNextjs = techStackId.startsWith('nextjs-') || (!isExpo && !isChromeExt);
199
+ return {
200
+ id: techStackId || 'nextjs-prisma-sqlite',
201
+ isExpo,
202
+ isNextjs,
203
+ isChromeExt,
204
+ hasDatabase: isNextjs,
205
+ testRunner: isNextjs ? 'vitest' : 'jest',
206
+ testRunCommand: isNextjs ? 'npx vitest run' : 'npx jest',
207
+ storageType: isExpo
208
+ ? 'asyncStorage'
209
+ : isChromeExt
210
+ ? 'chromeStorage'
211
+ : 'prisma',
212
+ routerImport: isExpo
213
+ ? 'expo-router'
214
+ : isChromeExt
215
+ ? 'react-router-dom'
216
+ : 'next/navigation',
217
+ componentPrimitives: isExpo
218
+ ? '<View>, <Text>, <ScrollView>'
219
+ : '<div>, <span>, <h1>',
220
+ rawPrimitivesList: isExpo
221
+ ? '<View>, <Text>, <Image>, <ScrollView>, <FlatList>'
222
+ : '<div>, <span>, <h1>, <p>, <img>, <ul>',
223
+ patternsFile: isExpo
224
+ ? 'MOBILE_SETUP.md'
225
+ : isChromeExt
226
+ ? 'EXTENSION_SETUP.md'
227
+ : 'FEATURE_PATTERNS.md',
228
+ };
229
+ }
180
230
  /**
181
231
  * Print dimension guidance when the project has multiple screen sizes.
182
232
  * Tells Claude to pick the right dimension for the content being previewed
@@ -200,14 +250,45 @@ function checkbox(text) {
200
250
  const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
201
251
  console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
202
252
  }
253
+ /**
254
+ * Instructions for creating/updating .codeyam/data-structure.json.
255
+ * Only prints creation instructions if the file doesn't exist yet.
256
+ * If it exists, reminds Claude to update it if data models changed.
257
+ */
258
+ function printDataStructureInstructions() {
259
+ const root = getProjectRoot();
260
+ const dsPath = path.join(root, '.codeyam', 'data-structure.json');
261
+ const exists = fs.existsSync(dsPath);
262
+ if (!exists) {
263
+ console.log(chalk.bold('Create the data structure config:'));
264
+ checkbox('Create `.codeyam/data-structure.json` describing all data sources');
265
+ console.log(chalk.dim(" This file tells the editor what data the app uses and how it's stored."));
266
+ console.log(chalk.dim(' The file is a JSON array. Each entry describes one data source:'));
267
+ console.log(chalk.dim(' { "name": "Drink", "description": "...", "category": "datastore", "order": 1,'));
268
+ console.log(chalk.dim(' "fields": [{ "name": "id", "type": "number", "isId": true, "required": true }, ...] }'));
269
+ console.log();
270
+ console.log(chalk.dim(' category: "datastore" for persisted data (DB, localStorage)'));
271
+ console.log(chalk.dim(' "mock-api" for mocked external API responses'));
272
+ console.log(chalk.dim(' order: 1 = primary data store, 2 = secondary, etc.'));
273
+ console.log(chalk.dim(' Do NOT include types only used as component props.'));
274
+ }
275
+ else {
276
+ checkbox('If this feature changes data models, update `.codeyam/data-structure.json`');
277
+ console.log(chalk.dim(' Add new tables, update fields, or change descriptions to match.'));
278
+ }
279
+ console.log();
280
+ }
203
281
  /**
204
282
  * Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
205
283
  * Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
206
284
  */
207
285
  function printAppScenarioInstructions(pageName, route) {
286
+ const root = process.cwd();
287
+ const hasSeedAdapter = !!detectSeedAdapter(root);
208
288
  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.'));
289
+ console.log(chalk.dim(' Review existing scenarios — enhance their seed data to exercise new features.'));
290
+ console.log(chalk.dim(' A rich scenario that exercises 5 features is better than 5 thin scenarios with one feature each.'));
291
+ console.log(chalk.dim(' Rename scenarios if their data scope has grown beyond the original name.'));
211
292
  console.log();
212
293
  console.log(chalk.bold.yellow('App scenarios vs component scenarios — these are DIFFERENT:'));
213
294
  console.log(chalk.yellow(' App scenarios: show the FULL PAGE with seeded data at a real route (/, /library, etc.)'));
@@ -224,21 +305,42 @@ function printAppScenarioInstructions(pageName, route) {
224
305
  console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
225
306
  }
226
307
  console.log();
308
+ checkbox('Ensure every page file used in app scenarios has a glossary entry with its filePath:');
309
+ console.log(chalk.dim(' The register command validates pageFilePath against the glossary — add missing pages now.'));
310
+ console.log(chalk.dim(' Example: {"name":"CounterScreen","filePath":"app/(tabs)/index.tsx","type":"page","description":"Main counter page"}'));
311
+ console.log();
227
312
  checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
228
313
  console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
229
314
  console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
230
315
  console.log(chalk.yellow(' Required fields: "type":"application", "url" (real route), "pageFilePath" (source file)'));
231
316
  console.log(chalk.yellow(' Do NOT include "componentName" — that would make it a component scenario'));
232
317
  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'));
318
+ if (hasSeedAdapter) {
319
+ checkbox(chalk.bold.red('REQUIRED: Every app scenario MUST include "seed" data without it the page will be empty!'));
320
+ console.log(chalk.yellow(' A seed adapter exists — you MUST include "seed":{...} in every app scenario registration.'));
321
+ console.log(chalk.yellow(' An app scenario without seed data will render an empty page (no database rows = nothing to show).'));
322
+ console.log(chalk.dim(' "seed" maps table names to arrays of rows: "seed":{"user":[{"id":1,"name":"Alice"}],"article":[...]}'));
323
+ console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
324
+ }
325
+ else {
326
+ const appCtx = getTechStackContext(root);
327
+ checkbox('Include data in every app scenario — without it the page will be empty:');
328
+ if (appCtx.isExpo) {
329
+ console.log(chalk.dim(' Use "localStorage":{"items":"[...]"} to pre-populate AsyncStorage (values are JSON strings)'));
330
+ console.log(chalk.dim(" AsyncStorage uses localStorage on web — CodeYam's injection works automatically."));
331
+ }
332
+ console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
333
+ console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
334
+ }
335
+ console.log();
336
+ console.log(chalk.bold('Register ALL scenarios at once (bulk registration):'));
337
+ console.log(chalk.dim(' Write an array of scenario objects to a temp file:'));
338
+ console.log(chalk.dim(' [{"name":"...","type":"application","url":"/","seed":{...}}, {"name":"...","url":"/other",...}]'));
339
+ console.log(chalk.dim(' Then register all at once: codeyam editor register @.codeyam/tmp/scenarios.json'));
340
+ console.log(chalk.yellow(' Bulk registration is preferred — faster and avoids repeated capture overhead.'));
341
+ console.log();
240
342
  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`');
343
+ checkbox('After registration, check the response for `clientErrors`');
242
344
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
243
345
  console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
244
346
  }
@@ -295,16 +397,24 @@ function printExtractionPlanInstructions() {
295
397
  console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
296
398
  console.log(chalk.yellow(' — Where it will go (new file path)'));
297
399
  console.log();
298
- console.log(chalk.dim('Present the numbered plan, then proceed to step 5 to execute it.'));
400
+ console.log(chalk.dim('Present the numbered plan, then proceed to step 7 to execute it.'));
299
401
  }
300
402
  /**
301
403
  * Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
302
404
  * Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
303
405
  */
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.'));
406
+ function printComponentCaptureInstructions(root) {
407
+ const ctx = root ? getTechStackContext(root) : undefined;
408
+ const isExpo = ctx?.isExpo ?? false;
409
+ checkbox('Set up isolation routes: `codeyam editor isolate ComponentA ComponentB ...`');
410
+ if (isExpo) {
411
+ console.log(chalk.dim(' This creates app/isolated-components/_layout.tsx (with __DEV__ guard).'));
412
+ console.log(chalk.dim(' You then create a flat .tsx file per component (NOT subdirectories — Expo Router uses flat files).'));
413
+ }
414
+ else {
415
+ console.log(chalk.dim(` This creates app/isolated-components/layout.tsx (with notFound() guard) and`));
416
+ console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
417
+ }
308
418
  checkbox('For each visual component:');
309
419
  console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
310
420
  console.log(chalk.dim(' — Props/interface'));
@@ -315,23 +425,37 @@ function printComponentCaptureInstructions() {
315
425
  console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
316
426
  console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
317
427
  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'));
428
+ if (isExpo) {
429
+ console.log(chalk.dim(' Expo: app/isolated-components/ComponentName.tsx → /isolated-components/ComponentName'));
430
+ console.log(chalk.dim(' Use useLocalSearchParams() from expo-router to read ?s=ScenarioName.'));
431
+ }
432
+ else {
433
+ console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
434
+ console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
435
+ }
320
436
  console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
321
437
  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'));
438
+ if (isExpo) {
439
+ console.log(chalk.dim(' Wrap the component in a capture container with nativeID="codeyam-capture":'));
440
+ console.log(chalk.dim(' <View nativeID="codeyam-capture" style={{ display:"flex" }}>'));
441
+ }
442
+ else {
443
+ console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
444
+ console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
445
+ }
446
+ console.log(chalk.dim(isExpo
447
+ ? ' <View style={{ width:"100%", maxWidth:... }}> ← match the app\'s container width'
448
+ : ' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
449
+ console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth: 384, full-width component → omit maxWidth'));
326
450
  console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
327
451
  console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
328
452
  console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
329
453
  console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
330
454
  console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
331
455
  console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
332
- console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
456
+ console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
333
457
  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/...).'));
458
+ console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/isolated-components/...).'));
335
459
  console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
336
460
  console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
337
461
  console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
@@ -339,6 +463,10 @@ function printComponentCaptureInstructions() {
339
463
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
340
464
  console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
341
465
  console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
466
+ console.log();
467
+ console.log(chalk.bold(' Bulk registration: write all component scenarios to a temp file as an array:'));
468
+ console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
469
+ console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
342
470
  }
343
471
  /**
344
472
  * Print a section header.
@@ -352,13 +480,13 @@ function stepHeader(step, title, feature) {
352
480
  console.log();
353
481
  }
354
482
  /**
355
- * Print a colored progress tracker showing all 16 steps.
483
+ * Print a colored progress tracker showing all 18 steps.
356
484
  * Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
357
485
  */
358
486
  function printProgressTracker(current) {
359
487
  console.log();
360
488
  console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
361
- for (let i = 1; i <= 16; i++) {
489
+ for (let i = 1; i <= 18; i++) {
362
490
  const label = STEP_LABELS[i];
363
491
  const num = i < 10 ? ` ${i}` : `${i}`;
364
492
  const content = `${num}. ${label.padEnd(28)}`;
@@ -379,9 +507,33 @@ function printProgressTracker(current) {
379
507
  *
380
508
  * Options:
381
509
  * - confirm: true → step requires user confirmation before proceeding (steps 1, 3, 11)
510
+ * - feature: string → feature name for task directive
382
511
  */
383
512
  function stopGate(current, opts) {
384
- console.log();
513
+ const totalSteps = Object.keys(STEP_LABELS).length;
514
+ const currentLabel = STEP_LABELS[current] || `Step ${current}`;
515
+ const nextLabel = current < totalSteps ? STEP_LABELS[current + 1] : undefined;
516
+ console.log();
517
+ // ━━━ TASK ━━━ — deterministic task lifecycle directive
518
+ // Skip step 1 (no feature name yet — task starts at step 2)
519
+ if (current >= 2) {
520
+ console.log(chalk.bold.cyan('━━━ TASK ━━━'));
521
+ console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
522
+ console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
523
+ console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
524
+ console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
525
+ console.log();
526
+ let taskTitle;
527
+ if (current < totalSteps) {
528
+ taskTitle = `Complete codeyam editor step ${current}: '${currentLabel}' and move on to step ${current + 1}: '${nextLabel}'`;
529
+ }
530
+ else {
531
+ taskTitle =
532
+ 'Ask user what to build next and restart codeyam editor workflow';
533
+ }
534
+ console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
535
+ console.log();
536
+ }
385
537
  console.log(chalk.bold.red('━━━ STOP ━━━'));
386
538
  console.log();
387
539
  console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
@@ -390,20 +542,12 @@ function stopGate(current, opts) {
390
542
  console.log(chalk.yellow('Wait for user confirmation before moving on.'));
391
543
  }
392
544
  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) {
545
+ if (current < totalSteps) {
400
546
  console.log(chalk.green('When done, run: ') +
401
547
  chalk.bold(`codeyam editor ${current + 1}`));
402
548
  }
403
549
  else {
404
- console.log(chalk.green('Feature complete! Run: ') +
405
- chalk.bold('codeyam editor 1') +
406
- chalk.green(' to start the next feature'));
550
+ console.log(chalk.green('Feature complete! Ask the user what to build next, then run: ') + chalk.bold('codeyam editor 1'));
407
551
  }
408
552
  console.log();
409
553
  }
@@ -418,34 +562,36 @@ function printResumptionHeader(step) {
418
562
  'Check if plan was already written to context file:\n `cat .codeyam/editor-mode-context.md`',
419
563
  ],
420
564
  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: [
565
+ 3: ['Re-check is safe — just re-run the step'],
566
+ 4: ['Re-verify is safe to repeat — just re-run the checks'],
567
+ 5: ['This is a confirmation step — just re-present to user'],
568
+ 6: [
423
569
  'Check if extraction plan already exists in context file:\n `cat .codeyam/editor-mode-context.md`',
424
570
  ],
425
- 5: [
571
+ 7: [
426
572
  'Check if components/functions already extracted:\n `ls src/components/ src/lib/`',
427
573
  ],
428
- 6: [
574
+ 8: [
429
575
  'Check if glossary already populated:\n `cat .codeyam/glossary.json`',
430
576
  ],
431
- 7: ['Check if isolation routes and registered scenarios already exist'],
432
- 8: ['Check existing scenarios:\n `codeyam editor scenarios`'],
433
- 9: [
577
+ 9: ['Check if isolation routes and registered scenarios already exist'],
578
+ 10: ['Check existing scenarios:\n `codeyam editor scenarios`'],
579
+ 11: [
434
580
  'Check existing user-persona scenarios:\n `codeyam editor scenarios`',
435
581
  ],
436
- 10: ['Re-verify is safe to repeat — just re-run the checks'],
437
- 11: [
582
+ 12: ['Re-verify is safe to repeat — just re-run the checks'],
583
+ 13: [
438
584
  'Check if a journal entry already exists for this feature:\n `codeyam editor journal-list`',
439
585
  'If an entry exists, use PATCH to update it — do NOT create a new one',
440
586
  ],
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: [
587
+ 14: ['Re-verify is safe to repeat — just re-run the checks'],
588
+ 15: ['Check if commit already made:\n `git log --oneline -3`'],
589
+ 16: ['Check if commit already made:\n `git log --oneline -3`'],
590
+ 17: [
445
591
  'Check if journal was already updated with commit SHA:\n `codeyam editor journal-list`',
446
592
  'Check if commit was already amended:\n `git log --oneline -3`',
447
593
  ],
448
- 16: [
594
+ 18: [
449
595
  'Check if already pushed:\n `git remote -v && git log --oneline origin/HEAD..HEAD 2>/dev/null`',
450
596
  ],
451
597
  };
@@ -552,7 +698,7 @@ function parseDebugTargets(target) {
552
698
  }
553
699
  if (entry.startsWith('step-')) {
554
700
  const num = parseInt(entry.replace('step-', ''), 10);
555
- if (!isNaN(num) && num >= 1 && num <= 16) {
701
+ if (!isNaN(num) && num >= 1 && num <= 18) {
556
702
  valid.add(`step-${num}`);
557
703
  continue;
558
704
  }
@@ -638,11 +784,17 @@ function printSetup(root) {
638
784
  console.log();
639
785
  // ── Design System ────────────────────────────────────────────────
640
786
  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?"'));
787
+ console.log(chalk.dim(' Ask: "What visual style do you want? Pick a built-in design system or bring your own."'));
642
788
  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"'));
789
+ console.log();
790
+ for (const ds of DESIGN_SYSTEMS) {
791
+ console.log(chalk.yellow(` Option label: "${ds.name}"`) +
792
+ chalk.dim(` — ${ds.description}`));
793
+ console.log(chalk.dim(` → Run: codeyam editor design-system ${ds.id}`));
794
+ }
795
+ console.log(chalk.yellow(' Option label: "I\'ll paste my own design system"'));
644
796
  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"'));
797
+ console.log(chalk.yellow(' Option label: "Skip use sensible defaults"'));
646
798
  console.log(chalk.dim(' → Skip'));
647
799
  console.log();
648
800
  console.log(chalk.bold('Checklist:'));
@@ -663,8 +815,8 @@ function printSetup(root) {
663
815
  console.log(chalk.bold('Tech Stack Selection:'));
664
816
  console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
665
817
  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.'));
818
+ console.log(chalk.dim(' If only ONE stack matches, confirm it directly (e.g. "Expo + React Native is the only stack for mobile apps — using that.").'));
819
+ console.log(chalk.dim(' If MULTIPLE stacks match, use AskUserQuestion to let the user pick one.'));
668
820
  console.log();
669
821
  console.log(chalk.bold(' Available Tech Stacks:'));
670
822
  for (const stack of TECH_STACKS) {
@@ -697,7 +849,7 @@ function printSetup(root) {
697
849
  console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
698
850
  console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
699
851
  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).'));
852
+ console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app → iPhone 16 (393×852), chrome-extension → Custom (400×600).'));
701
853
  console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
702
854
  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
855
  console.log();
@@ -769,37 +921,39 @@ function printCycleOverview(root, state) {
769
921
  console.log();
770
922
  console.log(chalk.green('Continue with: ') +
771
923
  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'));
924
+ console.log(chalk.dim('Or ask the user what to build next, then run `codeyam editor 1` yourself (never expose this command to the user)'));
775
925
  console.log();
776
926
  console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
777
927
  chalk.bold('codeyam editor change'));
778
928
  console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
779
929
  }
780
930
  else {
781
- console.log('Each feature follows 16 steps. You MUST run each command in order:');
931
+ console.log('Each feature follows 18 steps. You MUST run each command in order:');
782
932
  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`);
933
+ console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
934
+ console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prepare')} Set up project and dependencies`);
935
+ console.log(` ${chalk.bold.yellow(' 3')} ${chalk.bold('Prototype')} Build a working prototype fast`);
936
+ console.log(` ${chalk.bold.yellow(' 4')} ${chalk.bold('Verify Prototype')} Verify prototype works correctly`);
937
+ console.log(` ${chalk.bold.yellow(' 5')} ${chalk.bold('Confirm')} Confirm prototype with user`);
938
+ console.log(` ${chalk.bold.yellow(' 6')} ${chalk.bold('Deconstruct')} — Read code, plan all extractions`);
939
+ console.log(` ${chalk.bold.yellow(' 7')} ${chalk.bold('Extract')} TDD extraction of functions + components`);
940
+ console.log(` ${chalk.bold.yellow(' 8')} ${chalk.bold('Glossary')} Record functions in glossary`);
941
+ console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('Analyze')} Analyze and verify components`);
942
+ console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('App Scenarios')} Create app-level scenarios`);
943
+ console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('User Scenarios')} — Create user-persona scenarios`);
944
+ console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Verify')} Review screenshots, check for errors`);
945
+ console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Journal')} Create/update journal entry`);
946
+ console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('Review')} Verify screenshots and audit`);
947
+ console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('Present')} Present summary, get approval`);
948
+ console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('Commit')} Commit all changes`);
949
+ console.log(` ${chalk.bold.yellow('17')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
950
+ console.log(` ${chalk.bold.yellow('18')} ${chalk.bold('Push')} — Push to remote`);
799
951
  console.log();
800
- console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
952
+ console.log(chalk.green('Start now: ') +
953
+ 'Ask the user what they want to build, then run `codeyam editor 1` (never expose this command to the user)');
801
954
  console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
802
955
  }
956
+ console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
803
957
  console.log();
804
958
  }
805
959
  // ─── Step 1: Plan ─────────────────────────────────────────────────────
@@ -875,18 +1029,12 @@ function printStep1(root, feature, options, userPrompt) {
875
1029
  console.log();
876
1030
  console.log(chalk.yellow('Wait for user confirmation before moving on.'));
877
1031
  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
1032
  console.log(chalk.green('When done, run: ') +
885
1033
  chalk.bold('codeyam editor 2 --feature "Feature Name"'));
886
1034
  console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
887
1035
  console.log();
888
1036
  }
889
- // ─── Step 2: Prototype ────────────────────────────────────────────────
1037
+ // ─── Step 2: Prepare ──────────────────────────────────────────────────
890
1038
  function printStep2(root, feature) {
891
1039
  const port = getServerPort();
892
1040
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
@@ -903,41 +1051,87 @@ function printStep2(root, feature) {
903
1051
  appFormats: prevState?.appFormats,
904
1052
  techStackId: prevState?.techStackId,
905
1053
  });
906
- logEvent(root, 'step', { step: 2, label: 'Prototype', feature });
907
- stepHeader(2, 'Prototype', feature);
1054
+ logEvent(root, 'step', { step: 2, label: 'Prepare', feature });
1055
+ stepHeader(2, 'Prepare', feature);
908
1056
  if (isResuming) {
909
1057
  printResumptionHeader(2);
910
1058
  }
911
- console.log('Build fast with real data. Prioritize speed over quality.');
1059
+ console.log('Get the project ready to build.');
912
1060
  console.log();
1061
+ const ctx = getTechStackContext(root);
913
1062
  // If no project exists yet, include scaffolding instructions first
914
1063
  if (!projectExists) {
915
1064
  console.log(chalk.bold('Scaffold the project:'));
916
1065
  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,'));
1066
+ if (ctx.isExpo) {
1067
+ console.log(chalk.dim(' This copies the Expo + React Native template, runs npm install,'));
1068
+ }
1069
+ else if (ctx.isChromeExt) {
1070
+ console.log(chalk.dim(' This copies the Chrome Extension + React template, runs npm install,'));
1071
+ }
1072
+ else {
1073
+ console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
1074
+ }
918
1075
  console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
919
1076
  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:'));
1077
+ if (ctx.isExpo) {
1078
+ // Expo: no database, use AsyncStorage + theme
1079
+ checkbox('Define your data types in `lib/types.ts`');
1080
+ console.log(chalk.dim(" Create TypeScript interfaces for your app's data models"));
1081
+ console.log();
1082
+ checkbox('Set up initial data using the storage helper in `lib/storage.ts`');
1083
+ console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
1084
+ console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "First item" }]);'));
1085
+ console.log();
1086
+ console.log(chalk.dim(' Read MOBILE_SETUP.md for data storage, navigation, and testing patterns.'));
1087
+ console.log();
1088
+ checkbox('Ask the user what to name the app (use AskUserQuestion), then update app.json');
1089
+ console.log(chalk.dim(' Update name, slug, scheme, ios.bundleIdentifier (com.codeyam.<slug>), and android.package'));
1090
+ console.log(chalk.dim(' Also set the projectTitle via: curl -s -X POST http://localhost:' +
1091
+ port +
1092
+ '/api/editor-project-info -H "Content-Type: application/json" -d \'{"projectTitle":"<name>"}\''));
1093
+ console.log();
1094
+ checkbox('Generate an app icon using `sharp`');
1095
+ console.log(chalk.dim(' Create `scripts/generate-icon.mjs` and `mkdir -p assets`'));
1096
+ console.log(chalk.yellow(' ICON DESIGN GOAL: The icon must look distinctive on a home screen full of other app icons.'));
1097
+ console.log(chalk.yellow(' Use multiple bold colors from the design system and complex, overlapping shapes.'));
1098
+ console.log(chalk.yellow(' NO simple/minimal icons — no single glyph on a flat background, no plain text, no basic circles.'));
1099
+ console.log(chalk.yellow(' Make it colorful, layered, and dense. Use gradients, multiple contrasting fills, and depth.'));
1100
+ console.log(chalk.dim(' Use sharp to render SVG to 1024x1024 PNG. iOS: flatten onto background (no alpha channel).'));
1101
+ console.log(chalk.dim(' Generate: icon.png, adaptive-icon.png, favicon.png (48x48). Run `node scripts/generate-icon.mjs`'));
1102
+ console.log();
1103
+ }
1104
+ else if (ctx.isChromeExt) {
1105
+ // Chrome Extension: use chrome.storage
1106
+ checkbox('Set up data storage using chrome.storage (with localStorage fallback for dev)');
1107
+ console.log();
1108
+ console.log(chalk.dim(' Read EXTENSION_SETUP.md for storage, messaging, and manifest patterns.'));
1109
+ console.log();
1110
+ }
1111
+ else {
1112
+ // Next.js: Prisma + database
1113
+ checkbox('Define your data models in `prisma/schema.prisma`');
1114
+ console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
1115
+ console.log();
1116
+ checkbox('Push schema and seed the database');
1117
+ console.log(chalk.dim(' npm run db:push'));
1118
+ console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
1119
+ console.log(chalk.dim(' npm run db:seed'));
1120
+ console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
1121
+ console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
1122
+ console.log();
1123
+ console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
1124
+ console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
1125
+ console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
1126
+ console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
1127
+ console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
1128
+ console.log();
1129
+ console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
1130
+ console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
1131
+ console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
1132
+ console.log();
1133
+ }
1134
+ printDataStructureInstructions();
941
1135
  }
942
1136
  else {
943
1137
  console.log(chalk.bold('Prepare the database for this feature:'));
@@ -952,18 +1146,68 @@ function printStep2(root, feature) {
952
1146
  console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
953
1147
  console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
954
1148
  console.log();
1149
+ printDataStructureInstructions();
1150
+ }
1151
+ stopGate(2);
1152
+ }
1153
+ // ─── Step 3: Prototype ────────────────────────────────────────────────
1154
+ function printStep3(root, feature) {
1155
+ const port = getServerPort();
1156
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1157
+ const projectExists = hasProject(root);
1158
+ const prevState = readState(root);
1159
+ const isResuming = prevState?.step === 3;
1160
+ const now = new Date().toISOString();
1161
+ writeState(root, {
1162
+ feature,
1163
+ step: 3,
1164
+ label: STEP_LABELS[3],
1165
+ startedAt: isResuming ? prevState.startedAt : now,
1166
+ featureStartedAt: prevState?.featureStartedAt || now,
1167
+ appFormats: prevState?.appFormats,
1168
+ techStackId: prevState?.techStackId,
1169
+ });
1170
+ logEvent(root, 'step', { step: 3, label: 'Prototype', feature });
1171
+ stepHeader(3, 'Prototype', feature);
1172
+ if (isResuming) {
1173
+ printResumptionHeader(3);
955
1174
  }
1175
+ const ctx = getTechStackContext(root);
1176
+ console.log('Build fast with real data. Prioritize speed over quality.');
1177
+ console.log();
956
1178
  console.log(chalk.bold('Checklist:'));
957
- checkbox('Create API routes that read from the database via Prisma');
958
- if (!projectExists) {
959
- checkbox('Seed the database with demo data');
960
- checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
961
- console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
962
- console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
963
- console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
1179
+ if (ctx.isExpo) {
1180
+ checkbox('Build screens that read from AsyncStorage via `lib/storage.ts` or fetch from APIs');
1181
+ if (!projectExists) {
1182
+ checkbox('Populate initial data in AsyncStorage for development');
1183
+ console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
1184
+ console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "Buy groceries" }]);'));
1185
+ console.log();
1186
+ console.log(chalk.bold.cyan('Make data visually rich:'));
1187
+ console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1188
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1189
+ console.log(chalk.cyan(' • Rich data makes the prototype look real and surfaces layout issues early'));
1190
+ }
1191
+ }
1192
+ else {
1193
+ checkbox('Create API routes that read from the database via Prisma');
1194
+ if (!projectExists) {
1195
+ checkbox('Seed the database with demo data');
1196
+ checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
1197
+ console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
1198
+ console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
1199
+ console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
1200
+ console.log();
1201
+ console.log(chalk.bold.cyan('Make seed data visually rich:'));
1202
+ console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
1203
+ console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
1204
+ console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1205
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1206
+ console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
1207
+ }
964
1208
  }
965
1209
  checkbox('Verify the dev server shows the changes');
966
- checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
1210
+ checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
967
1211
  // Responsive design guidance when building a mobile-responsive web app
968
1212
  if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
969
1213
  console.log();
@@ -980,16 +1224,33 @@ function printStep2(root, feature) {
980
1224
  console.log();
981
1225
  console.log(designSystem);
982
1226
  console.log();
983
- checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
984
- console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
985
- console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
986
- console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
987
- console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
988
- console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
989
- checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
990
- console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
991
- console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
992
- console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
1227
+ if (ctx.isExpo) {
1228
+ checkbox('Define ALL design tokens in `lib/theme.ts` — this is the single source of truth');
1229
+ console.log(chalk.dim(' Colors: theme.colors.bgSurface, theme.colors.textPrimary, etc.'));
1230
+ console.log(chalk.dim(' Typography: theme.fontSize.sm, theme.fontSize.lg, theme.fontFamily.mono'));
1231
+ console.log(chalk.dim(' Spacing: theme.spacing.sm, theme.spacing.md, theme.spacing.lg, etc.'));
1232
+ console.log(chalk.dim(' Border radius: theme.borderRadius.sm, theme.borderRadius.lg, etc.'));
1233
+ checkbox('Import theme in every component — ZERO hardcoded color strings or pixel values');
1234
+ console.log(chalk.dim(' Bad: color: "#333", fontSize: 14, padding: 12'));
1235
+ console.log(chalk.dim(' Good: color: theme.colors.textPrimary, fontSize: theme.fontSize.sm, padding: theme.spacing.md'));
1236
+ console.log(chalk.dim(' Do NOT use CSS custom properties (var(--token)) they do not work in React Native.'));
1237
+ checkbox('Buttons: put backgroundColor, borderRadius, borderStyle on a wrapping <View>, NOT on <Pressable>');
1238
+ console.log(chalk.dim(' Pressable renders these styles on web but FAILS SILENTLY on native devices.'));
1239
+ console.log(chalk.dim(' Use a <View> with overflow:"hidden" for the visual shell, <Pressable> inside for touch only.'));
1240
+ console.log(chalk.dim(' For press feedback: use onPressIn/onPressOut + useState to toggle opacity, not function-style style.'));
1241
+ }
1242
+ else {
1243
+ checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
1244
+ console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
1245
+ console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
1246
+ console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
1247
+ console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
1248
+ console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
1249
+ checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
1250
+ console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
1251
+ console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
1252
+ console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
1253
+ }
993
1254
  }
994
1255
  console.log();
995
1256
  console.log(chalk.bold.cyan('Keep the preview moving:'));
@@ -1000,16 +1261,41 @@ function printStep2(root, feature) {
1000
1261
  console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
1001
1262
  console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
1002
1263
  printDimensionGuidance(dim, dimNames);
1264
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
1265
+ console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
1266
+ console.log();
1267
+ stopGate(3);
1268
+ }
1269
+ // ─── Step 4: Verify Prototype ─────────────────────────────────────────
1270
+ function printStep4(root, feature) {
1271
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1272
+ const prevState = readState(root);
1273
+ const isResuming = prevState?.step === 4;
1274
+ const now = new Date().toISOString();
1275
+ writeState(root, {
1276
+ feature,
1277
+ step: 4,
1278
+ label: STEP_LABELS[4],
1279
+ startedAt: isResuming ? prevState.startedAt : now,
1280
+ featureStartedAt: prevState?.featureStartedAt || now,
1281
+ appFormats: prevState?.appFormats,
1282
+ techStackId: prevState?.techStackId,
1283
+ });
1284
+ logEvent(root, 'step', { step: 4, label: 'Verify Prototype', feature });
1285
+ stepHeader(4, 'Verify Prototype', feature);
1286
+ if (isResuming) {
1287
+ printResumptionHeader(4);
1288
+ }
1289
+ console.log('Verify everything works before presenting the prototype.');
1003
1290
  console.log();
1004
1291
  console.log(chalk.bold('Verify the dev server:'));
1005
- console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
1006
- console.log(chalk.dim(' # Check page loads: curl -s -o /dev/null -w "%{http_code}" http://localhost:<dev-port>'));
1007
- console.log(chalk.dim(' # Check API routes: curl -s http://localhost:<dev-port>/api/your-route'));
1292
+ console.log(chalk.dim(' # Verify pages and API routes load:'));
1293
+ console.log(chalk.dim(` codeyam editor verify-routes '{"paths":["/your-page"],"apiRoutes":["/api/your-route"]}'`));
1008
1294
  console.log();
1009
1295
  console.log(chalk.bold('Verify before proceeding:'));
1010
1296
  console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
1011
- checkbox('Verify the page loads: curl the dev server URL and confirm HTTP 200 (not an error page)');
1012
- checkbox('Verify API routes return valid JSON: curl each route and confirm no error responses');
1297
+ checkbox('Verify page and API routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
1298
+ console.log(chalk.dim(' Include ALL page paths you built and ALL API routes they depend on.'));
1013
1299
  checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
1014
1300
  console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
1015
1301
  console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
@@ -1020,6 +1306,14 @@ function printStep2(root, feature) {
1020
1306
  console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
1021
1307
  console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
1022
1308
  console.log();
1309
+ const ctx4 = getTechStackContext(root);
1310
+ if (ctx4.isExpo) {
1311
+ console.log(chalk.magenta.bold(' EXPO WEB PREVIEW NOTE:'));
1312
+ console.log(chalk.magenta(' The preview renders via react-native-web in a browser. Some differences'));
1313
+ console.log(chalk.magenta(' from native devices are expected (fonts, SafeAreaView, shadows, Platform.OS).'));
1314
+ console.log(chalk.magenta(' The preview is for layout and data verification. Test final polish on device.'));
1315
+ console.log();
1316
+ }
1023
1317
  console.log(chalk.bold('Update README and setup script:'));
1024
1318
  checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
1025
1319
  checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
@@ -1027,32 +1321,34 @@ function printStep2(root, feature) {
1027
1321
  console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
1028
1322
  console.log();
1029
1323
  console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
1030
- stopGate(2);
1324
+ stopGate(4);
1031
1325
  }
1032
- // ─── Step 3: Confirm ──────────────────────────────────────────────────
1033
- function printStep3(root, feature) {
1326
+ // ─── Step 5: Confirm ──────────────────────────────────────────────────
1327
+ function printStep5(root, feature) {
1034
1328
  const port = getServerPort();
1035
1329
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1036
1330
  const prevState = readState(root);
1037
- const isResuming = prevState?.step === 3;
1331
+ const isResuming = prevState?.step === 5;
1038
1332
  const now = new Date().toISOString();
1039
1333
  writeState(root, {
1040
1334
  feature,
1041
- step: 3,
1042
- label: STEP_LABELS[3],
1335
+ step: 5,
1336
+ label: STEP_LABELS[5],
1043
1337
  startedAt: isResuming ? prevState.startedAt : now,
1044
1338
  featureStartedAt: prevState?.featureStartedAt || now,
1339
+ appFormats: prevState?.appFormats,
1340
+ techStackId: prevState?.techStackId,
1045
1341
  });
1046
- logEvent(root, 'step', { step: 3, label: 'Confirm', feature });
1047
- stepHeader(3, 'Confirm', feature);
1342
+ logEvent(root, 'step', { step: 5, label: 'Confirm', feature });
1343
+ stepHeader(5, 'Confirm', feature);
1048
1344
  if (isResuming) {
1049
- printResumptionHeader(3);
1345
+ printResumptionHeader(5);
1050
1346
  }
1051
1347
  console.log('Summarize what was built and get user confirmation.');
1052
1348
  console.log();
1053
1349
  console.log(chalk.bold('Before presenting — verify everything works:'));
1054
1350
  checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
1055
- checkbox('Verify API routes return valid data (curl each route)');
1351
+ checkbox('Verify routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
1056
1352
  console.log();
1057
1353
  console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
1058
1354
  checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
@@ -1063,12 +1359,19 @@ function printStep3(root, feature) {
1063
1359
  console.log(chalk.dim(' If there are errors, fix the underlying issue before presenting.'));
1064
1360
  checkbox('Verify `hasContent=true` and `liveErrors=0` — do NOT ask the user to confirm if the preview is broken');
1065
1361
  console.log();
1362
+ console.log(chalk.bold('Verify the captured user prompt:'));
1363
+ checkbox("Read `.codeyam/editor-user-prompt.txt` — this is the user's original feature request");
1364
+ checkbox('If the file is missing or does not match what the user actually asked for, write the correct prompt text to `.codeyam/editor-user-prompt.txt`');
1365
+ console.log(chalk.dim(" This must be the user's exact words, not a summary. It gets recorded in the journal."));
1366
+ console.log();
1066
1367
  console.log(chalk.bold('Then present to the user:'));
1067
1368
  checkbox('Summarize what was built (routes, components, data)');
1068
1369
  checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
1069
1370
  printDimensionGuidance(dim, dimNames);
1070
1371
  console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
1071
1372
  console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
1373
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
1374
+ console.log(chalk.red(' The preview only updates when you explicitly call `codeyam editor preview`. Verify, then describe.'));
1072
1375
  console.log();
1073
1376
  console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
1074
1377
  checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
@@ -1076,80 +1379,91 @@ function printStep3(root, feature) {
1076
1379
  checkbox('Ask the user: "Does everything work as expected?"');
1077
1380
  console.log();
1078
1381
  console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
1079
- console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 4'));
1382
+ console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 6'));
1080
1383
  console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
1081
- chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 3`'));
1384
+ chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 5`'));
1082
1385
  console.log();
1083
1386
  console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
1084
- stopGate(3, { confirm: true });
1387
+ stopGate(5, { confirm: true });
1085
1388
  }
1086
- // ─── Step 4: Deconstruct ──────────────────────────────────────────────
1087
- function printStep4(root, feature) {
1389
+ // ─── Step 6: Deconstruct ──────────────────────────────────────────────
1390
+ function printStep6(root, feature) {
1088
1391
  const prevState = readState(root);
1089
- const isResuming = prevState?.step === 4;
1392
+ const isResuming = prevState?.step === 6;
1090
1393
  const now = new Date().toISOString();
1091
1394
  writeState(root, {
1092
1395
  feature,
1093
- step: 4,
1094
- label: STEP_LABELS[4],
1396
+ step: 6,
1397
+ label: STEP_LABELS[6],
1095
1398
  startedAt: isResuming ? prevState.startedAt : now,
1096
1399
  featureStartedAt: prevState?.featureStartedAt || now,
1400
+ appFormats: prevState?.appFormats,
1401
+ techStackId: prevState?.techStackId,
1097
1402
  });
1098
- logEvent(root, 'step', { step: 4, label: 'Deconstruct', feature });
1099
- stepHeader(4, 'Deconstruct', feature);
1403
+ logEvent(root, 'step', { step: 6, label: 'Deconstruct', feature });
1404
+ stepHeader(6, 'Deconstruct', feature);
1100
1405
  if (isResuming) {
1101
- printResumptionHeader(4);
1406
+ printResumptionHeader(6);
1102
1407
  }
1103
1408
  console.log(chalk.bold('Goal: pages contain ONLY components. Components contain ONLY sub-components.'));
1104
- console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 5.'));
1409
+ console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
1105
1410
  console.log();
1106
1411
  printExtractionPlanInstructions();
1107
- stopGate(4);
1412
+ stopGate(6);
1108
1413
  }
1109
- // ─── Step 5: Extract ──────────────────────────────────────────────────
1110
- function printStep5(root, feature) {
1414
+ // ─── Step 7: Extract ──────────────────────────────────────────────────
1415
+ function printStep7(root, feature) {
1111
1416
  const port = getServerPort();
1112
1417
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1113
1418
  const prevState = readState(root);
1114
- const isResuming = prevState?.step === 5;
1419
+ const isResuming = prevState?.step === 7;
1115
1420
  const now = new Date().toISOString();
1116
1421
  writeState(root, {
1117
1422
  feature,
1118
- step: 5,
1119
- label: STEP_LABELS[5],
1423
+ step: 7,
1424
+ label: STEP_LABELS[7],
1120
1425
  startedAt: isResuming ? prevState.startedAt : now,
1121
1426
  featureStartedAt: prevState?.featureStartedAt || now,
1427
+ appFormats: prevState?.appFormats,
1428
+ techStackId: prevState?.techStackId,
1122
1429
  });
1123
- logEvent(root, 'step', { step: 5, label: 'Extract', feature });
1124
- stepHeader(5, 'Extract', feature);
1430
+ logEvent(root, 'step', { step: 7, label: 'Extract', feature });
1431
+ stepHeader(7, 'Extract', feature);
1125
1432
  if (isResuming) {
1126
- printResumptionHeader(5);
1433
+ printResumptionHeader(7);
1127
1434
  }
1128
- console.log('Execute your extraction plan from step 4.');
1435
+ const ctx = getTechStackContext(root);
1436
+ console.log('Execute your extraction plan from step 6.');
1129
1437
  console.log();
1130
1438
  console.log(chalk.bold('Components:'));
1131
1439
  checkbox('Extract each component from your plan into its own file');
1132
1440
  checkbox('Page/route files must contain ZERO direct JSX — only imported components');
1133
1441
  checkbox('Every component that renders multiple sections must be split into sub-components');
1134
- console.log(chalk.dim(' No tests needed — visual verification happens in step 7'));
1442
+ console.log(chalk.dim(' No tests needed — visual verification happens in step 9'));
1135
1443
  console.log();
1136
1444
  console.log(chalk.bold('Library functions AND hooks (TDD):'));
1137
1445
  checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
1138
1446
  console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
1139
1447
  console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
1140
1448
  console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
1141
- checkbox('Place test files next to source: `app/lib/drinks.ts` `app/lib/drinks.test.ts`');
1142
- console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 7 only captures component screenshots.'));
1449
+ console.log(chalk.dim(' Wrap all tests in a describe("FunctionName", ...) block — the audit matches on this name'));
1450
+ if (ctx.isExpo) {
1451
+ checkbox('Place test files next to source but OUTSIDE `app/` (Expo Router treats all files in app/ as routes): `lib/storage.ts` → `lib/storage.test.ts`, `app/hooks/useCounter.ts` → `__tests__/hooks/useCounter.test.ts`');
1452
+ }
1453
+ else {
1454
+ checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
1455
+ }
1456
+ console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
1143
1457
  console.log();
1144
1458
  console.log(chalk.bold('Recursive pass:'));
1145
1459
  checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
1146
1460
  checkbox('Keep going until every file is a thin shell: just imports and composition');
1147
- console.log(chalk.yellow(' Check: does any file contain raw <div>, <span>, <h1>, <p>, <img>, or <ul>?'));
1461
+ console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
1148
1462
  console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
1149
1463
  console.log();
1150
1464
  console.log(chalk.bold('Verify before proceeding:'));
1151
1465
  checkbox('Run all tests and verify they pass');
1152
- checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
1466
+ checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
1153
1467
  checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
1154
1468
  checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1155
1469
  printDimensionGuidance(dim, dimNames);
@@ -1157,93 +1471,106 @@ function printStep5(root, feature) {
1157
1471
  console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
1158
1472
  console.log();
1159
1473
  console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
1160
- stopGate(5);
1474
+ stopGate(7);
1161
1475
  }
1162
- // ─── Step 6: Glossary ─────────────────────────────────────────────────
1163
- function printStep6(root, feature) {
1476
+ // ─── Step 8: Glossary ─────────────────────────────────────────────────
1477
+ function printStep8(root, feature) {
1164
1478
  const prevState = readState(root);
1165
- const isResuming = prevState?.step === 6;
1479
+ const isResuming = prevState?.step === 8;
1166
1480
  const now = new Date().toISOString();
1167
1481
  writeState(root, {
1168
1482
  feature,
1169
- step: 6,
1170
- label: STEP_LABELS[6],
1483
+ step: 8,
1484
+ label: STEP_LABELS[8],
1171
1485
  startedAt: isResuming ? prevState.startedAt : now,
1172
1486
  featureStartedAt: prevState?.featureStartedAt || now,
1487
+ appFormats: prevState?.appFormats,
1488
+ techStackId: prevState?.techStackId,
1173
1489
  });
1174
- logEvent(root, 'step', { step: 6, label: 'Glossary', feature });
1175
- stepHeader(6, 'Glossary', feature);
1490
+ logEvent(root, 'step', { step: 8, label: 'Glossary', feature });
1491
+ stepHeader(8, 'Glossary', feature);
1176
1492
  if (isResuming) {
1177
- printResumptionHeader(6);
1493
+ printResumptionHeader(8);
1178
1494
  }
1179
1495
  console.log('Record all new functions/components in `.codeyam/glossary.json`.');
1180
1496
  console.log();
1181
1497
  printGlossaryInstructions(feature);
1182
1498
  console.log();
1183
1499
  console.log(chalk.dim('Focus on updating the glossary. Application code and scenarios come in later steps.'));
1184
- stopGate(6);
1500
+ stopGate(8);
1185
1501
  }
1186
- // ─── Step 7: Analyze ──────────────────────────────────────────────────
1187
- function printStep7(root, feature) {
1502
+ // ─── Step 9: Analyze ──────────────────────────────────────────────────
1503
+ function printStep9(root, feature) {
1188
1504
  const port = getServerPort();
1189
1505
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1190
1506
  const prevState = readState(root);
1191
- const isResuming = prevState?.step === 7;
1507
+ const isResuming = prevState?.step === 9;
1192
1508
  const now = new Date().toISOString();
1193
1509
  writeState(root, {
1194
1510
  feature,
1195
- step: 7,
1196
- label: STEP_LABELS[7],
1511
+ step: 9,
1512
+ label: STEP_LABELS[9],
1197
1513
  startedAt: isResuming ? prevState.startedAt : now,
1198
1514
  featureStartedAt: prevState?.featureStartedAt || now,
1515
+ appFormats: prevState?.appFormats,
1516
+ techStackId: prevState?.techStackId,
1199
1517
  });
1200
- logEvent(root, 'step', { step: 7, label: 'Analyze', feature });
1201
- stepHeader(7, 'Analyze and Verify', feature);
1518
+ logEvent(root, 'step', { step: 9, label: 'Analyze', feature });
1519
+ stepHeader(9, 'Analyze and Verify', feature);
1202
1520
  if (isResuming) {
1203
- printResumptionHeader(7);
1521
+ printResumptionHeader(9);
1204
1522
  }
1205
1523
  console.log('Verify visual components (via isolation routes) and library functions (via tests).');
1206
1524
  console.log();
1207
1525
  console.log(chalk.bold('Visual Components — Component Isolation:'));
1208
- checkbox('List all files with new/modified visual components from step 5');
1526
+ checkbox('List all files with new/modified visual components from step 7');
1209
1527
  checkbox('Check existing scenarios: `codeyam editor scenarios`');
1210
1528
  console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
1211
1529
  console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
1212
1530
  console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
1213
- printComponentCaptureInstructions();
1531
+ const ctx9 = getTechStackContext(root);
1532
+ printComponentCaptureInstructions(root);
1214
1533
  console.log();
1215
1534
  console.log(chalk.bold('Library Functions — run tests:'));
1216
- checkbox('Run ALL test files created in step 5');
1217
- console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
1535
+ checkbox('Run ALL test files created in step 7');
1536
+ console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
1218
1537
  checkbox('Verify every test passes');
1219
1538
  checkbox('If any test fails, fix the source code and re-run');
1220
1539
  console.log();
1221
1540
  console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
1222
1541
  console.log();
1223
1542
  checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
1224
- console.log(chalk.red.bold(' The audit is a HARD GATE — step 8 will refuse to run until the audit passes.'));
1225
- console.log(chalk.dim(' When audit passes, the import graph is built automatically for change tracking.'));
1543
+ console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
1544
+ console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
1545
+ console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
1546
+ console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
1547
+ console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
1548
+ console.log(chalk.dim(' If the audit reports "MANUAL ANALYSIS REQUIRED" for any entities,'));
1549
+ console.log(chalk.dim(' follow the manual analysis steps in the audit output.'));
1550
+ console.log(chalk.dim(' Do NOT re-run analyze-imports for those entities — it will fail again.'));
1226
1551
  console.log();
1227
- stopGate(7);
1552
+ stopGate(9);
1228
1553
  }
1229
- // ─── Step 8: App Scenarios ────────────────────────────────────────────
1230
- function printStep8(root, feature) {
1554
+ // ─── Step 10: App Scenarios ────────────────────────────────────────────
1555
+ function printStep10(root, feature) {
1231
1556
  const port = getServerPort();
1232
1557
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1233
1558
  const prevState = readState(root);
1234
- const isResuming = prevState?.step === 8;
1559
+ const isResuming = prevState?.step === 10;
1235
1560
  const now = new Date().toISOString();
1236
1561
  writeState(root, {
1237
1562
  feature,
1238
- step: 8,
1239
- label: STEP_LABELS[8],
1563
+ step: 10,
1564
+ label: STEP_LABELS[10],
1240
1565
  startedAt: isResuming ? prevState.startedAt : now,
1241
1566
  featureStartedAt: prevState?.featureStartedAt || now,
1567
+ appFormats: prevState?.appFormats,
1568
+ techStackId: prevState?.techStackId,
1242
1569
  });
1243
- logEvent(root, 'step', { step: 8, label: 'App Scenarios', feature });
1244
- stepHeader(8, 'App Scenarios', feature);
1570
+ logEvent(root, 'step', { step: 10, label: 'App Scenarios', feature });
1571
+ stepHeader(10, 'App Scenarios', feature);
1245
1572
  if (isResuming) {
1246
- printResumptionHeader(8);
1573
+ printResumptionHeader(10);
1247
1574
  }
1248
1575
  console.log('Create app-level scenarios with rich data that robustly demonstrates this feature.');
1249
1576
  console.log();
@@ -1257,6 +1584,7 @@ function printStep8(root, feature) {
1257
1584
  console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
1258
1585
  console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
1259
1586
  console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
1587
+ console.log(chalk.cyan(' • Use real image URLs (Unsplash, Pravatar) — not broken placeholders or empty strings'));
1260
1588
  console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
1261
1589
  console.log();
1262
1590
  console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
@@ -1265,13 +1593,19 @@ function printStep8(root, feature) {
1265
1593
  console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
1266
1594
  console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
1267
1595
  console.log();
1596
+ console.log(chalk.bold.cyan('Scenario naming — describe data states, not features:'));
1597
+ console.log(chalk.cyan(' • Name scenarios by what they represent: "Rich Data", "Empty", "Mobile", "Minimal"'));
1598
+ console.log(chalk.cyan(' • When enhancing a scenario with new feature data, update the name if it has grown beyond its original scope'));
1599
+ console.log(chalk.cyan(' • A single rich scenario exercising multiple features is more valuable than separate thin scenarios per feature'));
1600
+ console.log(chalk.cyan(' • Re-register with the same name to update; to rename, register with the new name'));
1601
+ console.log();
1268
1602
  checkbox('Ensure scenarios clearly demonstrate what changed in this session');
1269
1603
  console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
1270
1604
  console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
1271
1605
  console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
1272
1606
  printAppScenarioInstructions();
1273
1607
  console.log();
1274
- console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 10 if needed.'));
1608
+ console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 12 if needed.'));
1275
1609
  console.log();
1276
1610
  console.log(chalk.bold.cyan("Verify your work — screenshots don't lie:"));
1277
1611
  console.log(chalk.cyan(' • View every captured screenshot. Does it show what the scenario name promises?'));
@@ -1282,43 +1616,45 @@ function printStep8(root, feature) {
1282
1616
  console.log(chalk.bold.yellow('GATE: Before proceeding, run `codeyam editor scenario-coverage`'));
1283
1617
  console.log(chalk.yellow(' This checks which existing scenarios have stale screenshots.'));
1284
1618
  console.log(chalk.yellow(' Re-register every stale scenario listed until the check passes.'));
1285
- stopGate(8);
1619
+ stopGate(10);
1286
1620
  }
1287
- // ─── Step 9: User Scenarios ───────────────────────────────────────────
1288
- function printStep9(root, feature) {
1621
+ // ─── Step 11: User Scenarios ───────────────────────────────────────────
1622
+ function printStep11(root, feature) {
1289
1623
  const port = getServerPort();
1290
1624
  const prevState = readState(root);
1291
- const isResuming = prevState?.step === 9;
1625
+ const isResuming = prevState?.step === 11;
1292
1626
  const now = new Date().toISOString();
1293
1627
  writeState(root, {
1294
1628
  feature,
1295
- step: 9,
1296
- label: STEP_LABELS[9],
1629
+ step: 11,
1630
+ label: STEP_LABELS[11],
1297
1631
  startedAt: isResuming ? prevState.startedAt : now,
1298
1632
  featureStartedAt: prevState?.featureStartedAt || now,
1633
+ appFormats: prevState?.appFormats,
1634
+ techStackId: prevState?.techStackId,
1299
1635
  });
1300
- logEvent(root, 'step', { step: 9, label: 'User Scenarios', feature });
1301
- stepHeader(9, 'User Scenarios', feature);
1636
+ logEvent(root, 'step', { step: 11, label: 'User Scenarios', feature });
1637
+ stepHeader(11, 'User Scenarios', feature);
1302
1638
  if (isResuming) {
1303
- printResumptionHeader(9);
1639
+ printResumptionHeader(11);
1304
1640
  }
1305
- console.log('Create per-persona variations of existing scenarios. Skip to step 10 if no users.');
1641
+ console.log('Create per-persona variations of existing scenarios. Skip to step 12 if no users.');
1306
1642
  console.log();
1307
1643
  console.log(chalk.bold('If the app has NO users/auth:'));
1308
- console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
1644
+ console.log(chalk.dim(' Skip this step and proceed to step 12 (Verify).'));
1309
1645
  console.log();
1310
1646
  console.log(chalk.bold('If the app has users/auth:'));
1311
1647
  console.log();
1312
1648
  console.log(chalk.bold('Goal: Create persona variations of EXISTING app scenarios.'));
1313
1649
  console.log(chalk.dim(' Do NOT create standalone persona scenarios. Each user-persona scenario should be'));
1314
- console.log(chalk.dim(' a variation of an existing app scenario from step 8, with user-specific state layered on.'));
1650
+ console.log(chalk.dim(' a variation of an existing app scenario from step 10, with user-specific state layered on.'));
1315
1651
  console.log();
1316
1652
  console.log(chalk.bold('Checklist:'));
1317
1653
  checkbox('Run `codeyam editor scenarios` — list all existing app scenarios');
1318
1654
  checkbox('For EACH existing app scenario, create a logged-in variation:');
1319
1655
  console.log(chalk.dim(' Copy the scenario seed data and add "session":{"cookieValue":"sess_alice"} + user seed'));
1320
1656
  console.log(chalk.dim(' Name: "<Original Name> - Logged In" (e.g. "Full Catalog - Logged In")'));
1321
- console.log(chalk.dim(' Step 8 scenarios already serve as logged-out versions (no session cookie)'));
1657
+ console.log(chalk.dim(' Step 10 scenarios already serve as logged-out versions (no session cookie)'));
1322
1658
  checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
1323
1659
  console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
1324
1660
  checkbox('Include "dimensions" — inherit from the base app scenario, or override if the persona implies a different device');
@@ -1327,26 +1663,28 @@ function printStep9(root, feature) {
1327
1663
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
1328
1664
  console.log();
1329
1665
  console.log(chalk.dim('See FEATURE_PATTERNS.md and AUTH_PATTERNS.md for auth scenario guidance.'));
1330
- stopGate(9);
1666
+ stopGate(11);
1331
1667
  }
1332
- // ─── Step 10: Verify ──────────────────────────────────────────────────
1333
- function printStep10(root, feature) {
1668
+ // ─── Step 12: Verify ──────────────────────────────────────────────────
1669
+ function printStep12(root, feature) {
1334
1670
  const port = getServerPort();
1335
1671
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1336
1672
  const prevState = readState(root);
1337
- const isResuming = prevState?.step === 10;
1673
+ const isResuming = prevState?.step === 12;
1338
1674
  const now = new Date().toISOString();
1339
1675
  writeState(root, {
1340
1676
  feature,
1341
- step: 10,
1342
- label: STEP_LABELS[10],
1677
+ step: 12,
1678
+ label: STEP_LABELS[12],
1343
1679
  startedAt: isResuming ? prevState.startedAt : now,
1344
1680
  featureStartedAt: prevState?.featureStartedAt || now,
1681
+ appFormats: prevState?.appFormats,
1682
+ techStackId: prevState?.techStackId,
1345
1683
  });
1346
- logEvent(root, 'step', { step: 10, label: 'Verify', feature });
1347
- stepHeader(10, 'Verify', feature);
1684
+ logEvent(root, 'step', { step: 12, label: 'Verify', feature });
1685
+ stepHeader(12, 'Verify', feature);
1348
1686
  if (isResuming) {
1349
- printResumptionHeader(10);
1687
+ printResumptionHeader(12);
1350
1688
  }
1351
1689
  console.log('Verify component isolation screenshots, editor scenarios, and library tests.');
1352
1690
  console.log();
@@ -1369,63 +1707,67 @@ function printStep10(root, feature) {
1369
1707
  checkbox('Verify no broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
1370
1708
  console.log();
1371
1709
  console.log(chalk.bold('Library functions — test check:'));
1372
- checkbox('Re-run all test files from step 5 to confirm they still pass');
1710
+ checkbox('Re-run all test files from step 7 to confirm they still pass');
1373
1711
  console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
1374
1712
  checkbox('If any test fails, fix the source code and re-run');
1375
1713
  console.log();
1376
1714
  console.log(chalk.dim('Focus on fixing issues. All component screenshots, scenarios, and tests must be clean before proceeding.'));
1377
- stopGate(10);
1715
+ stopGate(12);
1378
1716
  }
1379
- // ─── Step 11: Journal ─────────────────────────────────────────────────
1380
- function printStep11(root, feature) {
1717
+ // ─── Step 13: Journal ─────────────────────────────────────────────────
1718
+ function printStep13(root, feature) {
1381
1719
  const port = getServerPort();
1382
1720
  const prevState = readState(root);
1383
- const isResuming = prevState?.step === 11;
1721
+ const isResuming = prevState?.step === 13;
1384
1722
  const now = new Date().toISOString();
1385
1723
  writeState(root, {
1386
1724
  feature,
1387
- step: 11,
1388
- label: STEP_LABELS[11],
1725
+ step: 13,
1726
+ label: STEP_LABELS[13],
1389
1727
  startedAt: isResuming ? prevState.startedAt : now,
1390
1728
  featureStartedAt: prevState?.featureStartedAt || now,
1729
+ appFormats: prevState?.appFormats,
1730
+ techStackId: prevState?.techStackId,
1391
1731
  });
1392
- logEvent(root, 'step', { step: 11, label: 'Journal', feature });
1393
- stepHeader(11, 'Journal', feature);
1732
+ logEvent(root, 'step', { step: 13, label: 'Journal', feature });
1733
+ stepHeader(13, 'Journal', feature);
1394
1734
  if (isResuming) {
1395
- printResumptionHeader(11);
1735
+ printResumptionHeader(13);
1396
1736
  }
1397
1737
  console.log('Create or update the journal entry for this feature.');
1398
1738
  console.log();
1399
1739
  console.log(chalk.bold('Checklist:'));
1400
1740
  checkbox('Write a concise description of what was built (2-3 sentences)');
1401
- checkbox(`First time at step 11 — create journal entry with ALL session screenshots:`);
1741
+ checkbox(`First time at step 13 — create journal entry with ALL session screenshots:`);
1402
1742
  console.log(chalk.dim(` codeyam editor journal '{"title":"...","type":"feature","description":"...","includeSessionScenarios":true}'`));
1403
1743
  console.log(chalk.dim(' includeSessionScenarios auto-discovers component + app + user persona screenshots'));
1404
- checkbox(`Returning to step 11 (before commit) — update the existing journal entry:`);
1744
+ checkbox(`Returning to step 13 (before commit) — update the existing journal entry:`);
1405
1745
  console.log(chalk.dim(` codeyam editor journal-update '{"time":"<journal entry time>","description":"<updated>","includeSessionScenarios":true}'`));
1406
1746
  console.log(chalk.dim(' Note: PATCH only works before the entry is committed. After commit, use POST to create a new entry.'));
1407
1747
  console.log();
1408
- console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step 13.'));
1409
- stopGate(11);
1748
+ console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step 15.'));
1749
+ stopGate(13);
1410
1750
  }
1411
- // ─── Step 12: Review ──────────────────────────────────────────────────
1412
- function printStep12(root, feature) {
1751
+ // ─── Step 14: Review ──────────────────────────────────────────────────
1752
+ function printStep14(root, feature) {
1413
1753
  const port = getServerPort();
1414
1754
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1415
1755
  const prevState = readState(root);
1416
- const isResuming = prevState?.step === 12;
1756
+ const isResuming = prevState?.step === 14;
1417
1757
  const now = new Date().toISOString();
1418
1758
  writeState(root, {
1419
1759
  feature,
1420
- step: 12,
1421
- label: STEP_LABELS[12],
1760
+ step: 14,
1761
+ label: STEP_LABELS[14],
1422
1762
  startedAt: isResuming ? prevState.startedAt : now,
1423
1763
  featureStartedAt: prevState?.featureStartedAt || now,
1764
+ appFormats: prevState?.appFormats,
1765
+ techStackId: prevState?.techStackId,
1424
1766
  });
1425
- logEvent(root, 'step', { step: 12, label: 'Review', feature });
1426
- stepHeader(12, 'Review', feature);
1767
+ logEvent(root, 'step', { step: 14, label: 'Review', feature });
1768
+ stepHeader(14, 'Review', feature);
1427
1769
  if (isResuming) {
1428
- printResumptionHeader(12);
1770
+ printResumptionHeader(14);
1429
1771
  }
1430
1772
  console.log('Verify all screenshots and checks pass before presenting to the user.');
1431
1773
  console.log();
@@ -1438,33 +1780,36 @@ function printStep12(root, feature) {
1438
1780
  checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
1439
1781
  checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
1440
1782
  checkbox('Fix or remove any broken images before continuing');
1783
+ checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
1441
1784
  checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
1442
1785
  checkbox('Do not proceed until all checks pass');
1443
- stopGate(12);
1786
+ stopGate(14);
1444
1787
  }
1445
- // ─── Step 13: Present ─────────────────────────────────────────────────
1446
- function printStep13(root, feature) {
1788
+ // ─── Step 15: Present ─────────────────────────────────────────────────
1789
+ function printStep15(root, feature) {
1447
1790
  const prevState = readState(root);
1448
- const isResuming = prevState?.step === 13;
1791
+ const isResuming = prevState?.step === 15;
1449
1792
  const now = new Date().toISOString();
1450
1793
  writeState(root, {
1451
1794
  feature,
1452
- step: 13,
1453
- label: STEP_LABELS[13],
1795
+ step: 15,
1796
+ label: STEP_LABELS[15],
1454
1797
  startedAt: isResuming ? prevState.startedAt : now,
1455
1798
  featureStartedAt: prevState?.featureStartedAt || now,
1799
+ appFormats: prevState?.appFormats,
1800
+ techStackId: prevState?.techStackId,
1456
1801
  });
1457
- logEvent(root, 'step', { step: 13, label: 'Present', feature });
1458
- stepHeader(13, 'Present', feature);
1802
+ logEvent(root, 'step', { step: 15, label: 'Present', feature });
1803
+ stepHeader(15, 'Present', feature);
1459
1804
  if (isResuming) {
1460
- printResumptionHeader(13);
1805
+ printResumptionHeader(15);
1461
1806
  }
1462
1807
  console.log('Present the results to the user and get their approval.');
1463
1808
  console.log();
1464
1809
  console.log(chalk.bold('Checklist:'));
1465
1810
  checkbox(`Show the results panel: \`codeyam editor show-results\``);
1466
1811
  console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
1467
- console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
1812
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1468
1813
  console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
1469
1814
  checkbox('Write a 1-2 sentence summary of what was built');
1470
1815
  checkbox('Report test count and audit status (one line)');
@@ -1476,7 +1821,7 @@ function printStep13(root, feature) {
1476
1821
  chalk.dim(' — describe changes, then re-verify'));
1477
1822
  console.log();
1478
1823
  console.log(chalk.bold('If the user chooses "Save & commit":'));
1479
- checkbox('Advance to the commit step: `codeyam editor 14`');
1824
+ checkbox('Advance to the commit step: `codeyam editor 16`');
1480
1825
  console.log();
1481
1826
  console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
1482
1827
  checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
@@ -1484,7 +1829,7 @@ function printStep13(root, feature) {
1484
1829
  checkbox(`Run: \`codeyam editor change "${feature}"\` — this gives you the change checklist`);
1485
1830
  checkbox('THEN make the requested changes and follow the checklist');
1486
1831
  console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
1487
- stopGate(13, { confirm: true });
1832
+ stopGate(15, { confirm: true });
1488
1833
  }
1489
1834
  // ─── Migration Mode ──────────────────────────────────────────────────
1490
1835
  /**
@@ -1681,7 +2026,7 @@ function printMigrateStep3(root) {
1681
2026
  console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
1682
2027
  console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
1683
2028
  console.log();
1684
- printComponentCaptureInstructions();
2029
+ printComponentCaptureInstructions(root);
1685
2030
  console.log();
1686
2031
  migrationStopGate(3, pageName, pageIndex, totalPages);
1687
2032
  }
@@ -1704,7 +2049,7 @@ function printMigrateStep4(root) {
1704
2049
  checkbox('Check for client errors: `codeyam editor client-errors`');
1705
2050
  checkbox('If any issues: fix the scenario data, re-register affected scenarios');
1706
2051
  checkbox('Show results: `codeyam editor show-results`');
1707
- console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
2052
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1708
2053
  console.log();
1709
2054
  console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
1710
2055
  migrationStopGate(4, pageName, pageIndex, totalPages);
@@ -1816,7 +2161,7 @@ function printMigrateStep8(root) {
1816
2161
  console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
1817
2162
  console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
1818
2163
  checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
1819
- checkbox('Find all scenarios that use the real page route (not /codeyam-isolate/ paths)');
2164
+ checkbox('Find all scenarios that use the real page route (not /isolated-components/ paths)');
1820
2165
  checkbox('Re-register each one with the SAME name to update its screenshot');
1821
2166
  console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
1822
2167
  checkbox('After each registration, check the response for `clientErrors`');
@@ -1824,10 +2169,10 @@ function printMigrateStep8(root) {
1824
2169
  console.log();
1825
2170
  console.log(chalk.bold('Component Scenarios:'));
1826
2171
  console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
1827
- printComponentCaptureInstructions();
2172
+ printComponentCaptureInstructions(root);
1828
2173
  console.log();
1829
2174
  console.log(chalk.bold('Verify:'));
1830
- checkbox('Run `codeyam editor analyze-imports` to populate import graph');
2175
+ checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
1831
2176
  checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
1832
2177
  checkbox('Run `codeyam editor audit` to check completeness');
1833
2178
  checkbox('Check for client errors: `codeyam editor client-errors`');
@@ -1871,7 +2216,7 @@ function printMigrateStep10(root) {
1871
2216
  console.log();
1872
2217
  console.log(chalk.bold('Checklist:'));
1873
2218
  checkbox('Show results: `codeyam editor show-results`');
1874
- console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
2219
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1875
2220
  checkbox('Write a 1-2 sentence summary of what was migrated');
1876
2221
  console.log();
1877
2222
  console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
@@ -2091,47 +2436,52 @@ function handleMigrateCommand(root, subArg) {
2091
2436
  };
2092
2437
  stepFns[step](root);
2093
2438
  }
2094
- // ─── Step 14: Commit ─────────────────────────────────────────────────
2095
- function printStep14(root, feature) {
2439
+ // ─── Step 16: Commit ─────────────────────────────────────────────────
2440
+ function printStep16(root, feature) {
2096
2441
  const prevState = readState(root);
2097
- const isResuming = prevState?.step === 14;
2442
+ const isResuming = prevState?.step === 16;
2098
2443
  const now = new Date().toISOString();
2099
2444
  writeState(root, {
2100
2445
  feature,
2101
- step: 14,
2102
- label: STEP_LABELS[14],
2446
+ step: 16,
2447
+ label: STEP_LABELS[16],
2103
2448
  startedAt: isResuming ? prevState.startedAt : now,
2104
2449
  featureStartedAt: prevState?.featureStartedAt || now,
2450
+ appFormats: prevState?.appFormats,
2451
+ techStackId: prevState?.techStackId,
2105
2452
  });
2106
- logEvent(root, 'step', { step: 14, label: 'Commit', feature });
2107
- stepHeader(14, 'Commit', feature);
2453
+ logEvent(root, 'step', { step: 16, label: 'Commit', feature });
2454
+ stepHeader(16, 'Commit', feature);
2108
2455
  if (isResuming) {
2109
- printResumptionHeader(14);
2456
+ printResumptionHeader(16);
2110
2457
  }
2111
2458
  console.log('Commit all changes for this feature.');
2112
2459
  console.log();
2113
2460
  console.log(chalk.bold('Checklist:'));
2461
+ checkbox('Ensure all screenshots are fresh: `codeyam editor recapture-stale`');
2114
2462
  checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
2115
2463
  checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
2116
2464
  console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
2117
- stopGate(14);
2465
+ stopGate(16);
2118
2466
  }
2119
- // ─── Step 15: Finalize ───────────────────────────────────────────────
2120
- function printStep15(root, feature) {
2467
+ // ─── Step 17: Finalize ───────────────────────────────────────────────
2468
+ function printStep17(root, feature) {
2121
2469
  const prevState = readState(root);
2122
- const isResuming = prevState?.step === 15;
2470
+ const isResuming = prevState?.step === 17;
2123
2471
  const now = new Date().toISOString();
2124
2472
  writeState(root, {
2125
2473
  feature,
2126
- step: 15,
2127
- label: STEP_LABELS[15],
2474
+ step: 17,
2475
+ label: STEP_LABELS[17],
2128
2476
  startedAt: isResuming ? prevState.startedAt : now,
2129
2477
  featureStartedAt: prevState?.featureStartedAt || now,
2478
+ appFormats: prevState?.appFormats,
2479
+ techStackId: prevState?.techStackId,
2130
2480
  });
2131
- logEvent(root, 'step', { step: 15, label: 'Finalize', feature });
2132
- stepHeader(15, 'Finalize', feature);
2481
+ logEvent(root, 'step', { step: 17, label: 'Finalize', feature });
2482
+ stepHeader(17, 'Finalize', feature);
2133
2483
  if (isResuming) {
2134
- printResumptionHeader(15);
2484
+ printResumptionHeader(17);
2135
2485
  }
2136
2486
  console.log('Update the journal with the commit SHA and amend the commit.');
2137
2487
  console.log();
@@ -2139,24 +2489,26 @@ function printStep15(root, feature) {
2139
2489
  checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
2140
2490
  checkbox('Amend the commit to include the journal update: `git add .codeyam/journal/ && git commit --amend --no-edit`');
2141
2491
  console.log(chalk.dim(' The journal-update modifies journal files after the commit — amend to keep the tree clean.'));
2142
- stopGate(15);
2492
+ stopGate(17);
2143
2493
  }
2144
- // ─── Step 16: Push ───────────────────────────────────────────────────
2145
- function printStep16(root, feature) {
2494
+ // ─── Step 18: Push ───────────────────────────────────────────────────
2495
+ function printStep18(root, feature) {
2146
2496
  const prevState = readState(root);
2147
- const isResuming = prevState?.step === 16;
2497
+ const isResuming = prevState?.step === 18;
2148
2498
  const now = new Date().toISOString();
2149
2499
  writeState(root, {
2150
2500
  feature,
2151
- step: 16,
2152
- label: STEP_LABELS[16],
2501
+ step: 18,
2502
+ label: STEP_LABELS[18],
2153
2503
  startedAt: isResuming ? prevState.startedAt : now,
2154
2504
  featureStartedAt: prevState?.featureStartedAt || now,
2505
+ appFormats: prevState?.appFormats,
2506
+ techStackId: prevState?.techStackId,
2155
2507
  });
2156
- logEvent(root, 'step', { step: 16, label: 'Push', feature });
2157
- stepHeader(16, 'Push', feature);
2508
+ logEvent(root, 'step', { step: 18, label: 'Push', feature });
2509
+ stepHeader(18, 'Push', feature);
2158
2510
  if (isResuming) {
2159
- printResumptionHeader(16);
2511
+ printResumptionHeader(18);
2160
2512
  }
2161
2513
  console.log('Push the commit to the remote repository.');
2162
2514
  console.log();
@@ -2167,12 +2519,15 @@ function printStep16(root, feature) {
2167
2519
  console.log(chalk.dim(' If the user says yes, run: `git push`'));
2168
2520
  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"'));
2169
2521
  console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
2170
- checkbox('After the user responds, run: `codeyam editor steps` to start the next feature');
2171
- console.log();
2172
- console.log(chalk.red.bold(' If the user reports a bug or requests a fix after committing:'));
2173
- console.log(chalk.red.bold(' You MUST still run `codeyam editor change` before making any changes.'));
2174
- console.log(chalk.red.bold(' The change workflow applies to ALL changes post-commit fixes are not an exception.'));
2175
- stopGate(16, { confirm: true });
2522
+ console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
2523
+ console.log();
2524
+ console.log(chalk.bold.yellow('IMPORTANT: After this step, the current feature is DONE.'));
2525
+ console.log(chalk.yellow(' Any new user request even if it sounds related — is a NEW feature.'));
2526
+ console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
2527
+ console.log(chalk.yellow(' Ask the user what they want to build. Once they tell you, run `codeyam editor 1` yourself.'));
2528
+ console.log(chalk.yellow(' NEVER tell the user to run `codeyam editor` commands — these are internal. Just ask what to build.'));
2529
+ console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
2530
+ stopGate(18, { confirm: true });
2176
2531
  }
2177
2532
  // ─── Command definition ───────────────────────────────────────────────
2178
2533
  // ─── Analyze-imports subcommand ────────────────────────────────────────
@@ -2192,7 +2547,7 @@ async function handleAnalyzeImports(options = {}) {
2192
2547
  return;
2193
2548
  }
2194
2549
  console.error(chalk.red('Error: .codeyam/glossary.json not found.'));
2195
- console.error(chalk.dim(' Run codeyam editor 6 to create the glossary first.'));
2550
+ console.error(chalk.dim(' Run codeyam editor 8 to create the glossary first.'));
2196
2551
  process.exit(1);
2197
2552
  }
2198
2553
  let glossaryEntries;
@@ -2201,10 +2556,14 @@ async function handleAnalyzeImports(options = {}) {
2201
2556
  glossaryEntries = sanitizeGlossaryEntries(parsed);
2202
2557
  }
2203
2558
  catch {
2559
+ if (options.silent)
2560
+ return;
2204
2561
  console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
2205
2562
  process.exit(1);
2206
2563
  }
2207
2564
  if (glossaryEntries.length === 0) {
2565
+ if (options.silent)
2566
+ return;
2208
2567
  console.error(chalk.red('Error: glossary.json is empty.'));
2209
2568
  process.exit(1);
2210
2569
  }
@@ -2246,25 +2605,137 @@ async function handleAnalyzeImports(options = {}) {
2246
2605
  // Non-fatal — scenario file paths just won't be included
2247
2606
  }
2248
2607
  const progress = new ProgressReporter();
2249
- // Run data-structure-only analysis for all entities (glossary + pages)
2608
+ // Filter to only files that actually need analysis avoids re-analyzing
2609
+ // ~120 files when only 2 are incomplete.
2610
+ let targetFilePaths = filePaths;
2611
+ try {
2612
+ const { getDatabase } = await import('../../../packages/database/index.js');
2613
+ const db = getDatabase();
2614
+ const { requireBranchAndProject: rbp } = await import('../utils/database.js');
2615
+ const { project } = await rbp(JSON.parse(fs.readFileSync(path.join(root, '.codeyam', 'config.json'), 'utf8')).projectSlug);
2616
+ const { filterToIncompleteFilePaths } = await import('../utils/editorAudit.js');
2617
+ const incomplete = await filterToIncompleteFilePaths(db, project.id, filePaths);
2618
+ if (incomplete.length < filePaths.length && incomplete.length > 0) {
2619
+ if (!options.silent) {
2620
+ console.log(chalk.dim(`Skipping ${filePaths.length - incomplete.length} already-analyzed files, analyzing ${incomplete.length} remaining...`));
2621
+ }
2622
+ targetFilePaths = incomplete;
2623
+ }
2624
+ else if (incomplete.length === 0) {
2625
+ if (!options.silent) {
2626
+ console.log(chalk.green('All entities already have analyses — nothing to do.'));
2627
+ }
2628
+ // Still need to run the import graph + backfill below
2629
+ targetFilePaths = [];
2630
+ }
2631
+ }
2632
+ catch {
2633
+ // Non-fatal — fall back to analyzing all files
2634
+ }
2635
+ // If specific file paths were requested, scope analysis to only those files.
2636
+ // The full filePaths set is still used for the import graph below.
2637
+ if (options.filePaths && options.filePaths.length > 0) {
2638
+ const requested = new Set(options.filePaths);
2639
+ targetFilePaths = targetFilePaths.filter((fp) => requested.has(fp));
2640
+ if (targetFilePaths.length === 0 && !options.silent) {
2641
+ console.log(chalk.dim('Requested file(s) already analyzed or not in glossary — skipping analysis.'));
2642
+ }
2643
+ }
2644
+ // Run data-structure-only analysis for entities that need it.
2250
2645
  // Don't pass entityNames — entities may not exist yet (fresh clone).
2251
2646
  // The analyzer will discover and create them from file paths.
2252
- progress.start('Running import analysis for all entities...');
2253
- try {
2254
- await runAnalysisForEntities({
2255
- projectRoot: root,
2256
- filePaths,
2257
- progress,
2258
- onlyDataStructure: true,
2259
- });
2647
+ if (targetFilePaths.length > 0) {
2648
+ progress.start(`Running import analysis for ${targetFilePaths.length} entit${targetFilePaths.length !== 1 ? 'ies' : 'y'}...`);
2649
+ try {
2650
+ await runAnalysisForEntities({
2651
+ projectRoot: root,
2652
+ filePaths: targetFilePaths,
2653
+ progress,
2654
+ onlyDataStructure: true,
2655
+ });
2656
+ }
2657
+ catch (err) {
2658
+ progress.fail('Analysis failed');
2659
+ const msg = err instanceof Error ? err.message : String(err);
2660
+ if (options.silent) {
2661
+ // Internal caller — don't kill the process, let the caller handle it
2662
+ return;
2663
+ }
2664
+ console.error(chalk.red(`Error: ${msg}`));
2665
+ process.exit(1);
2666
+ }
2667
+ progress.succeed('Analysis complete');
2260
2668
  }
2261
- catch (err) {
2262
- progress.fail('Analysis failed');
2263
- const msg = err instanceof Error ? err.message : String(err);
2264
- console.error(chalk.red(`Error: ${msg}`));
2265
- process.exit(1);
2669
+ // Surface analysis errors if any occurred
2670
+ const errorReportPath = path.join(root, '.codeyam', 'analysis-errors.txt');
2671
+ if (fs.existsSync(errorReportPath)) {
2672
+ if (!options.silent) {
2673
+ console.log(chalk.yellow('\nSome entity analyses failed. Full details in .codeyam/analysis-errors.txt'));
2674
+ console.log(chalk.yellow('Send this file to the CodeYam team for diagnosis.'));
2675
+ console.log(chalk.dim('Affected entities will be missing from the import graph.'));
2676
+ }
2677
+ // Write structured analysis-failures.json for the audit to detect.
2678
+ // Parse the error report to find which entities failed, and cross-reference
2679
+ // with the files we tried to analyze.
2680
+ try {
2681
+ const { readAnalysisFailures, writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
2682
+ const errorContent = fs.readFileSync(errorReportPath, 'utf8');
2683
+ const existingFailures = readAnalysisFailures(root);
2684
+ // Parse error messages to identify failed file paths.
2685
+ // Error format: "Error on step <StepName> for entity <Name> (<filePath>)"
2686
+ // or more generic "CodeYam Error: <message>"
2687
+ const entityErrorRegex = /(?:Error on step \w+ for entity (\w+))|(?:entity[: ]+["']?(\w+)["']?)/gi;
2688
+ const failedEntityNames = new Set();
2689
+ let match;
2690
+ while ((match = entityErrorRegex.exec(errorContent)) !== null) {
2691
+ const name = match[1] || match[2];
2692
+ if (name)
2693
+ failedEntityNames.add(name);
2694
+ }
2695
+ // Map failed entity names back to file paths from targetFilePaths
2696
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
2697
+ let glossary = [];
2698
+ try {
2699
+ glossary = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
2700
+ }
2701
+ catch {
2702
+ // Non-fatal
2703
+ }
2704
+ // Also: any targetFilePaths that didn't produce entities are failures
2705
+ const now = new Date().toISOString();
2706
+ const updatedFailures = { ...existingFailures };
2707
+ if (failedEntityNames.size > 0) {
2708
+ for (const name of failedEntityNames) {
2709
+ const entry = glossary.find((e) => e.name === name);
2710
+ if (entry) {
2711
+ updatedFailures[entry.filePath] = {
2712
+ entityName: name,
2713
+ error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
2714
+ failedAt: now,
2715
+ };
2716
+ }
2717
+ }
2718
+ }
2719
+ // When we can't parse specific entity names from the error, DON'T mark
2720
+ // all target files as failed. The error may be non-fatal (e.g., a cache
2721
+ // miss logged as "CodeYam Error") and blanket-marking every file as
2722
+ // permanently failed blocks future audits from resolving them.
2723
+ writeAnalysisFailures(root, updatedFailures);
2724
+ }
2725
+ catch {
2726
+ // Non-fatal — failure tracking is best-effort
2727
+ }
2728
+ }
2729
+ else {
2730
+ // No errors — clear any stale failure tracking
2731
+ try {
2732
+ const { writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
2733
+ writeAnalysisFailures(root, {});
2734
+ }
2735
+ catch {
2736
+ // Non-fatal
2737
+ }
2266
2738
  }
2267
- progress.succeed('Analysis complete');
2268
2739
  // Load entities WITH metadata from database
2269
2740
  progress.start('Loading entity metadata...');
2270
2741
  await initializeEnvironment();
@@ -2447,43 +2918,98 @@ async function handleDelete(scenarioId) {
2447
2918
  * Creates isolation route directories and the layout guard file.
2448
2919
  * This avoids brace-expansion permission prompts in the embedded terminal.
2449
2920
  */
2921
+ function handleDesignSystem(designSystemId) {
2922
+ const root = process.cwd();
2923
+ const ds = DESIGN_SYSTEMS.find((d) => d.id === designSystemId);
2924
+ if (!ds) {
2925
+ console.error(chalk.red(`Error: Unknown design system "${designSystemId}". Valid options: ${DESIGN_SYSTEMS.map((d) => d.id).join(', ')}`));
2926
+ process.exit(1);
2927
+ }
2928
+ const srcPath = path.join(__dirname, '..', '..', 'templates', 'design-systems', ds.fileName);
2929
+ if (!fs.existsSync(srcPath)) {
2930
+ console.error(chalk.red(`Error: Design system file not found: ${srcPath}`));
2931
+ process.exit(1);
2932
+ }
2933
+ const destDir = path.join(root, '.codeyam');
2934
+ fs.mkdirSync(destDir, { recursive: true });
2935
+ const destPath = path.join(destDir, 'design-system.md');
2936
+ fs.copyFileSync(srcPath, destPath);
2937
+ console.log(chalk.green(`Installed "${ds.name}" design system → .codeyam/design-system.md`));
2938
+ }
2450
2939
  function handleIsolate(componentNames) {
2451
2940
  const root = process.cwd();
2941
+ const ctx = getTechStackContext(root);
2452
2942
  if (componentNames.length === 0) {
2453
2943
  console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
2454
2944
  process.exit(1);
2455
2945
  }
2456
2946
  const isolateDir = path.join(root, 'app', 'isolated-components');
2457
- // Create layout.tsx with production guard if missing
2458
- const layoutPath = path.join(isolateDir, 'layout.tsx');
2947
+ // Create the framework-appropriate layout guard if missing.
2948
+ // Clean up wrong-framework layout from a previous CLI version.
2949
+ const layoutPath = path.join(isolateDir, ctx.isExpo ? '_layout.tsx' : 'layout.tsx');
2950
+ const wrongLayoutPath = path.join(isolateDir, ctx.isExpo ? 'layout.tsx' : '_layout.tsx');
2951
+ if (fs.existsSync(wrongLayoutPath)) {
2952
+ fs.unlinkSync(wrongLayoutPath);
2953
+ }
2459
2954
  if (!fs.existsSync(layoutPath)) {
2460
2955
  fs.mkdirSync(isolateDir, { recursive: true });
2461
- fs.writeFileSync(layoutPath, [
2462
- 'import { notFound } from "next/navigation";',
2463
- '',
2464
- 'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
2465
- ' if (process.env.NODE_ENV === "production") notFound();',
2466
- ' return <>{children}</>;',
2467
- '}',
2468
- '',
2469
- ].join('\n'), 'utf8');
2470
- console.log(chalk.green(`Created layout guard: app/isolated-components/layout.tsx`));
2471
- }
2472
- // Create a directory for each component
2956
+ if (ctx.isExpo) {
2957
+ fs.writeFileSync(layoutPath, [
2958
+ 'import { Redirect, Slot } from "expo-router";',
2959
+ '',
2960
+ 'export default function CaptureLayout() {',
2961
+ ' if (!__DEV__) return <Redirect href="/" />;',
2962
+ ' return <Slot />;',
2963
+ '}',
2964
+ '',
2965
+ ].join('\n'), 'utf8');
2966
+ }
2967
+ else {
2968
+ fs.writeFileSync(layoutPath, [
2969
+ 'import { notFound } from "next/navigation";',
2970
+ '',
2971
+ 'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
2972
+ ' if (process.env.NODE_ENV === "production") notFound();',
2973
+ ' return <>{children}</>;',
2974
+ '}',
2975
+ '',
2976
+ ].join('\n'), 'utf8');
2977
+ }
2978
+ console.log(chalk.green(`Created layout guard: app/isolated-components/${ctx.isExpo ? '_layout.tsx' : 'layout.tsx'}`));
2979
+ }
2980
+ // Create isolation route for each component.
2981
+ // Expo Router uses flat files (ComponentName.tsx), Next.js uses subdirectories (ComponentName/page.tsx).
2473
2982
  const created = [];
2474
2983
  const existed = [];
2475
2984
  for (const name of componentNames) {
2476
- const dir = path.join(isolateDir, name);
2477
- if (fs.existsSync(dir)) {
2478
- existed.push(name);
2985
+ if (ctx.isExpo) {
2986
+ const filePath = path.join(isolateDir, `${name}.tsx`);
2987
+ if (fs.existsSync(filePath)) {
2988
+ existed.push(name);
2989
+ }
2990
+ else {
2991
+ created.push(name);
2992
+ }
2479
2993
  }
2480
2994
  else {
2481
- fs.mkdirSync(dir, { recursive: true });
2482
- created.push(name);
2995
+ const dir = path.join(isolateDir, name);
2996
+ if (fs.existsSync(dir)) {
2997
+ existed.push(name);
2998
+ }
2999
+ else {
3000
+ fs.mkdirSync(dir, { recursive: true });
3001
+ created.push(name);
3002
+ }
2483
3003
  }
2484
3004
  }
2485
3005
  if (created.length > 0) {
2486
- console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
3006
+ if (ctx.isExpo) {
3007
+ console.log(chalk.green(`Ready for ${created.length} isolation route(s): ${created.map((n) => `app/isolated-components/${n}.tsx`).join(', ')}`));
3008
+ console.log(chalk.dim(' Create each file with useLocalSearchParams, a scenarios map, and nativeID="codeyam-capture"'));
3009
+ }
3010
+ else {
3011
+ console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
3012
+ }
2487
3013
  }
2488
3014
  if (existed.length > 0) {
2489
3015
  console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
@@ -2537,6 +3063,12 @@ function formatApiSubcommandResult(subcommand, data) {
2537
3063
  parts.push(`scenarioId="${data.scenarioId}"`);
2538
3064
  if (data.sessionsNotified != null)
2539
3065
  parts.push(`notified=${data.sessionsNotified}`);
3066
+ // Surface the HTTP health check from the dev server
3067
+ if (data.preview) {
3068
+ parts.push(`healthy=${data.preview.healthy}`);
3069
+ if (data.preview.error)
3070
+ parts.push(`error="${data.preview.error}"`);
3071
+ }
2540
3072
  return parts.join(' ');
2541
3073
  }
2542
3074
  case 'dev-server': {
@@ -2586,6 +3118,34 @@ function formatApiSubcommandResult(subcommand, data) {
2586
3118
  }
2587
3119
  return parts.join(' ');
2588
3120
  }
3121
+ case 'verify-routes': {
3122
+ const lines = [];
3123
+ lines.push(chalk.bold.yellow('━━━ Route Verification ━━━'));
3124
+ for (const [route, info] of Object.entries(data.routes || {})) {
3125
+ const r = info;
3126
+ if (r.ok) {
3127
+ lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}`));
3128
+ }
3129
+ else {
3130
+ lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
3131
+ }
3132
+ }
3133
+ for (const [route, info] of Object.entries(data.apiRoutes || {})) {
3134
+ const r = info;
3135
+ if (r.ok && r.isJSON) {
3136
+ lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}, valid JSON`));
3137
+ }
3138
+ else if (r.ok) {
3139
+ lines.push(chalk.yellow(` ⚠ ${route} — HTTP ${r.status}, not valid JSON`));
3140
+ }
3141
+ else {
3142
+ lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
3143
+ }
3144
+ }
3145
+ lines.push('');
3146
+ lines.push(data.ok ? chalk.green(data.summary) : chalk.red(data.summary));
3147
+ return lines.join('\n');
3148
+ }
2589
3149
  default:
2590
3150
  return null; // journal-list, show/hide-results: keep full JSON
2591
3151
  }
@@ -2614,11 +3174,15 @@ async function handleRegister(jsonArg) {
2614
3174
  }
2615
3175
  // Normalize to array for uniform handling
2616
3176
  const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
3177
+ const root = getProjectRoot();
2617
3178
  const port = getServerPort();
2618
3179
  const url = `http://localhost:${port}/api/editor-register-scenario`;
2619
3180
  const isBatch = items.length > 1;
2620
3181
  let succeeded = 0;
2621
3182
  let failed = 0;
3183
+ let warnings = 0;
3184
+ const issues = [];
3185
+ const screenshotEntries = [];
2622
3186
  for (let i = 0; i < items.length; i++) {
2623
3187
  const body = items[i];
2624
3188
  const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
@@ -2646,11 +3210,30 @@ async function handleRegister(jsonArg) {
2646
3210
  else {
2647
3211
  parts.push(`errors=0`);
2648
3212
  }
2649
- if (data.seedResult)
2650
- parts.push(`seed=${data.seedResult.success}`);
3213
+ if (data.visibleText) {
3214
+ const truncated = data.visibleText.length > 100
3215
+ ? data.visibleText.substring(0, 97) + '...'
3216
+ : data.visibleText;
3217
+ parts.push(`visibleText="${truncated}"`);
3218
+ }
3219
+ if (data.seedResult) {
3220
+ if (data.seedResult.success) {
3221
+ parts.push(`seed=ok`);
3222
+ }
3223
+ else {
3224
+ parts.push(chalk.red(`seed=FAILED${data.seedResult.error ? ` (${data.seedResult.error})` : ''}`));
3225
+ }
3226
+ }
2651
3227
  if (data.captureError)
2652
3228
  parts.push(chalk.yellow(`captureError="${data.captureError}"`));
2653
3229
  console.log(prefix + parts.join(' '));
3230
+ // Warn when application scenario is missing seed data
3231
+ if (data.missingSeedWarning) {
3232
+ console.log(chalk.red.bold('WARNING: No seed data in this application scenario!'));
3233
+ console.log(chalk.yellow(' This scenario has type "application" but no "seed" data was provided.'));
3234
+ console.log(chalk.yellow(' The page will render with an empty database — nothing will be visible.'));
3235
+ console.log(chalk.yellow(' Re-register with "seed":{...} containing the database rows this page needs.'));
3236
+ }
2654
3237
  // Surface client errors prominently so they can't be missed
2655
3238
  if (data.clientErrors && data.clientErrors.length > 0) {
2656
3239
  console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
@@ -2658,14 +3241,39 @@ async function handleRegister(jsonArg) {
2658
3241
  console.log(chalk.red(` → ${err}`));
2659
3242
  }
2660
3243
  console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
3244
+ // Provide actionable debugging hints for HTTP/API errors
3245
+ const hasApiResponseError = data.clientErrors.some((e) => e.includes('API response error:'));
3246
+ const hasHttpError = data.clientErrors.some((e) => e.includes('HTTP error:'));
3247
+ if (hasHttpError || hasApiResponseError) {
3248
+ console.log(chalk.yellow(' To debug: look at the "API response error" lines above — they show the exact API endpoint'));
3249
+ console.log(chalk.yellow(' that failed and what the server returned. Then check the scenario seed data to ensure'));
3250
+ console.log(chalk.yellow(' all IDs referenced in the URL exist in the seed, and that the route is correct.'));
3251
+ }
2661
3252
  console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
2662
3253
  }
3254
+ const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
2663
3255
  if (!res.ok) {
2664
3256
  console.error(chalk.dim(JSON.stringify(data, null, 2)));
2665
3257
  failed++;
2666
3258
  }
2667
3259
  else {
3260
+ if (resultIssues.length > 0)
3261
+ warnings++;
2668
3262
  succeeded++;
3263
+ // Collect screenshot paths for duplicate detection
3264
+ if (data.screenshotCaptured &&
3265
+ data.scenario?.screenshotPath &&
3266
+ data.scenario?.name) {
3267
+ const absPath = path.join(root, '.codeyam', 'editor-scenarios', data.scenario.screenshotPath);
3268
+ screenshotEntries.push({
3269
+ scenarioName: data.scenario.name,
3270
+ screenshotAbsPath: absPath,
3271
+ });
3272
+ }
3273
+ }
3274
+ // Collect issues from this registration
3275
+ for (const issue of resultIssues) {
3276
+ issues.push(`"${issue.scenarioName}": ${issue.message}`);
2669
3277
  }
2670
3278
  }
2671
3279
  catch (error) {
@@ -2676,12 +3284,99 @@ async function handleRegister(jsonArg) {
2676
3284
  failed++;
2677
3285
  }
2678
3286
  }
2679
- if (isBatch) {
2680
- console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
3287
+ // Detect duplicate screenshots in the batch
3288
+ if (isBatch && screenshotEntries.length > 1) {
3289
+ const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
3290
+ const hashEntries = screenshotEntries
3291
+ .map((e) => {
3292
+ const hash = computeScreenshotHash(e.screenshotAbsPath);
3293
+ return hash
3294
+ ? {
3295
+ scenarioName: e.scenarioName,
3296
+ screenshotPath: e.screenshotAbsPath,
3297
+ hash,
3298
+ }
3299
+ : null;
3300
+ })
3301
+ .filter((e) => e !== null);
3302
+ const duplicates = findDuplicateScreenshots(hashEntries);
3303
+ if (duplicates.size > 0) {
3304
+ console.log('');
3305
+ console.log(chalk.yellow.bold('WARNING: Identical screenshots detected:'));
3306
+ for (const [, names] of duplicates) {
3307
+ const quoted = names.map((n) => `"${n}"`);
3308
+ console.log(chalk.yellow(` ${quoted.join(' and ')} produced the same screenshot`));
3309
+ }
3310
+ console.log(chalk.yellow(" This usually means the app's view state depends on something scenarios can't control"));
3311
+ console.log(chalk.yellow(' (e.g., a hardcoded function return value, or identical localStorage not differentiating views).'));
3312
+ }
3313
+ }
3314
+ if (isBatch) {
3315
+ console.log('');
3316
+ if (failed > 0 || warnings > 0) {
3317
+ console.log(chalk.red.bold(`ERROR: ${failed} failed, ${warnings} with warnings out of ${items.length} scenarios`));
3318
+ console.log('');
3319
+ console.log(chalk.red.bold('Issues that MUST be fixed:'));
3320
+ for (const issue of issues) {
3321
+ console.log(chalk.red(` ✗ ${issue}`));
3322
+ }
3323
+ console.log('');
3324
+ console.log(chalk.red('Do NOT skip these errors. Fix each issue and re-register the affected scenarios.'));
3325
+ }
3326
+ else {
3327
+ console.log(chalk.green(`✓ Batch complete: ${succeeded}/${items.length} succeeded with no issues`));
3328
+ }
3329
+ }
3330
+ if (failed > 0 || warnings > 0) {
3331
+ process.exit(1);
3332
+ }
3333
+ }
3334
+ // ─── Glossary-add subcommand ──────────────────────────────────────────
3335
+ /**
3336
+ * `codeyam editor glossary-add '{"name":"...", "filePath":"...", "description":"..."}'`
3337
+ *
3338
+ * Safely adds/updates entries in .codeyam/glossary.json via the CLI,
3339
+ * avoiding hand-editing that breaks on unicode characters.
3340
+ */
3341
+ async function handleGlossaryAdd(jsonArg) {
3342
+ if (!jsonArg) {
3343
+ console.error(chalk.red('Error: JSON argument required.'));
3344
+ console.error(chalk.dim(' Usage: codeyam editor glossary-add \'{"name":"DrinkCard","filePath":"app/components/DrinkCard.tsx","description":"Displays a drink item"}\''));
3345
+ console.error(chalk.dim(' For large payloads: codeyam editor glossary-add @.codeyam/tmp/entry.json'));
3346
+ process.exit(1);
3347
+ }
3348
+ const parsed = parseRegisterArg(jsonArg);
3349
+ if (parsed.error) {
3350
+ console.error(chalk.red(`Error: ${parsed.error}`));
3351
+ process.exit(1);
3352
+ }
3353
+ // Normalize to array
3354
+ const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
3355
+ // Read existing glossary
3356
+ const root = getProjectRoot();
3357
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
3358
+ let existing = [];
3359
+ try {
3360
+ const raw = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
3361
+ existing = sanitizeGlossaryEntries(raw);
3362
+ }
3363
+ catch {
3364
+ // Glossary doesn't exist yet or can't be parsed — start fresh
3365
+ }
3366
+ // Merge
3367
+ const { validateGlossaryEntry, mergeGlossaryEntries } = await import('../utils/glossaryAdd.js');
3368
+ const result = mergeGlossaryEntries(existing, items);
3369
+ // Report validation errors
3370
+ for (const err of result.errors) {
3371
+ console.error(chalk.red(`Error at index ${err.index}: ${err.message}`));
2681
3372
  }
2682
- if (failed > 0) {
3373
+ if (result.added === 0 && result.updated === 0 && result.errors.length > 0) {
2683
3374
  process.exit(1);
2684
3375
  }
3376
+ // Write back with utf8 encoding to safely handle unicode
3377
+ fs.mkdirSync(path.dirname(glossaryPath), { recursive: true });
3378
+ fs.writeFileSync(glossaryPath, JSON.stringify(result.entries, null, 2), 'utf8');
3379
+ console.log(`added=${result.added} updated=${result.updated} total=${result.entries.length}`);
2685
3380
  }
2686
3381
  // ─── Dependents subcommand ────────────────────────────────────────────
2687
3382
  /**
@@ -2804,7 +3499,7 @@ async function handleDependents(entityName) {
2804
3499
  *
2805
3500
  * Prints a condensed post-change checklist that guides Claude through
2806
3501
  * re-verifying after user-requested modifications. When called from
2807
- * step 13+, this loops back to step 13 (present). When called from an
3502
+ * step 15+, this loops back to step 15 (present). When called from an
2808
3503
  * earlier step, it returns to that step so the normal flow continues.
2809
3504
  */
2810
3505
  function handleChange(feature) {
@@ -2822,7 +3517,7 @@ function handleChange(feature) {
2822
3517
  process.exit(1);
2823
3518
  }
2824
3519
  }
2825
- const currentStep = state?.step ?? 13;
3520
+ const currentStep = state?.step ?? 15;
2826
3521
  const port = getServerPort();
2827
3522
  console.log();
2828
3523
  console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
@@ -2844,6 +3539,8 @@ function handleChange(feature) {
2844
3539
  console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
2845
3540
  printDimensionGuidance(dim, dimNames);
2846
3541
  console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
3542
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
3543
+ console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
2847
3544
  console.log();
2848
3545
  console.log(chalk.bold('0. Close the results panel:'));
2849
3546
  checkbox(`Hide results: \`codeyam editor hide-results\``);
@@ -2870,6 +3567,7 @@ function handleChange(feature) {
2870
3567
  console.log(chalk.dim(' re-register "Home - Default", "Catalog - Full", "Detail - WithReviews", etc.'));
2871
3568
  checkbox("Enrich existing scenario data to exercise the change — don't just re-register unchanged data");
2872
3569
  console.log(chalk.dim(' Add data that demonstrates what changed: new fields, relationships, states, content variety.'));
3570
+ console.log(chalk.dim(' If the enriched data makes the scenario name too narrow, rename it to reflect its broader coverage.'));
2873
3571
  checkbox('After each re-registration, view the screenshot to verify data is visible');
2874
3572
  console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
2875
3573
  console.log();
@@ -2878,6 +3576,7 @@ function handleChange(feature) {
2878
3576
  checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
2879
3577
  printDimensionGuidance(dim, dimNames);
2880
3578
  checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
3579
+ checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
2881
3580
  checkbox('Run `codeyam editor audit` — all checks must pass');
2882
3581
  checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
2883
3582
  console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
@@ -2892,10 +3591,10 @@ function handleChange(feature) {
2892
3591
  console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
2893
3592
  console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
2894
3593
  console.log();
2895
- // If the change was initiated from a step before 13, return to that step
2896
- // instead of jumping to step 13. The change workflow should only loop to
2897
- // step 13 when changes are requested FROM step 13.
2898
- if (currentStep < 13) {
3594
+ // If the change was initiated from a step before 15, return to that step
3595
+ // instead of jumping to step 15. The change workflow should only loop to
3596
+ // step 15 when changes are requested FROM step 15.
3597
+ if (currentStep < 15) {
2899
3598
  console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2900
3599
  console.log(chalk.red.bold(' REQUIRED: Return to current step'));
2901
3600
  console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
@@ -2906,7 +3605,7 @@ function handleChange(feature) {
2906
3605
  console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2907
3606
  console.log(chalk.red.bold(' REQUIRED: Show Results'));
2908
3607
  console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
2909
- chalk.bold(`codeyam editor 13`));
3608
+ chalk.bold(`codeyam editor 15`));
2910
3609
  console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
2911
3610
  console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2912
3611
  }
@@ -2917,10 +3616,11 @@ function handleChange(feature) {
2917
3616
  * Fetch the audit result from the server.
2918
3617
  * Returns null if the server is unreachable.
2919
3618
  */
2920
- async function fetchAuditResult() {
3619
+ async function fetchAuditResult(options) {
2921
3620
  const port = getServerPort();
3621
+ const params = options?.skipTests ? '?skipTests=true' : '';
2922
3622
  try {
2923
- const res = await fetch(`http://localhost:${port}/api/editor-audit`);
3623
+ const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
2924
3624
  if (!res.ok)
2925
3625
  return null;
2926
3626
  return await res.json();
@@ -2943,17 +3643,34 @@ async function checkAuditGate() {
2943
3643
  return true; // Server unreachable — don't block
2944
3644
  if (data.summary?.allPassing === true)
2945
3645
  return true;
2946
- // If incomplete entities are the only issue, auto-fix with analyze-imports (once)
2947
- const { isAutoRemediable } = await import('../utils/editorAudit.js');
2948
- if (isAutoRemediable(data.summary, false)) {
3646
+ // Lightweight auto-fix: backfill entity_sha (DB-only, fast).
3647
+ // Never run handleAnalyzeImports here it takes minutes on large projects.
3648
+ const { isOnlyIncompleteEntities, isOnlyPreExistingIncomplete } = await import('../utils/editorAudit.js');
3649
+ // If the only failures are pre-existing incomplete entities (not caused by
3650
+ // this session), don't block — the audit text already calls these "non-blocking".
3651
+ if (isOnlyPreExistingIncomplete(data.summary, data.incompleteEntities)) {
3652
+ return true;
3653
+ }
3654
+ if (isOnlyIncompleteEntities(data.summary)) {
2949
3655
  try {
2950
- await handleAnalyzeImports({ silent: true });
3656
+ const entities = await loadEntities({});
3657
+ if (entities && entities.length > 0) {
3658
+ const { getDatabase } = await import('../../../packages/database/index.js');
3659
+ const db = getDatabase();
3660
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3661
+ sha: e.sha,
3662
+ name: e.name,
3663
+ filePath: e.filePath || '',
3664
+ isDefaultExport: e.metadata?.notExported === false &&
3665
+ e.metadata?.namedExport === false,
3666
+ })));
3667
+ }
2951
3668
  }
2952
3669
  catch {
2953
- return false;
3670
+ // Fall through
2954
3671
  }
2955
- // Re-check after fix
2956
- const retry = await fetchAuditResult();
3672
+ // Re-check after backfill — skip re-running tests (backfill is DB-only)
3673
+ const retry = await fetchAuditResult({ skipTests: true });
2957
3674
  if (retry?.summary?.allPassing === true)
2958
3675
  return true;
2959
3676
  }
@@ -2971,24 +3688,152 @@ function printAuditGateFailures(data) {
2971
3688
  if (!s)
2972
3689
  return;
2973
3690
  const issues = [];
2974
- if (s.componentsMissing > 0)
3691
+ if (s.componentsMissing > 0) {
2975
3692
  issues.push(`${s.componentsMissing} component(s) missing scenarios`);
2976
- if (s.componentsWithErrors > 0)
3693
+ const missing = (data.components || []).filter((c) => c.status === 'missing');
3694
+ for (const c of missing) {
3695
+ issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
3696
+ if (c.hint)
3697
+ issues.push(` Fix: ${c.hint}`);
3698
+ }
3699
+ }
3700
+ if (s.componentsWithErrors > 0) {
2977
3701
  issues.push(`${s.componentsWithErrors} component(s) with client errors (browser API or runtime errors in captured scenarios)`);
2978
- if (s.functionsMissing > 0)
3702
+ const withErrors = (data.components || []).filter((c) => c.status === 'has_errors');
3703
+ for (const c of withErrors) {
3704
+ issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
3705
+ }
3706
+ }
3707
+ if (s.functionsMissing > 0) {
2979
3708
  issues.push(`${s.functionsMissing} function(s) missing test files`);
2980
- if (s.functionsFailing > 0)
3709
+ const missing = (data.functions || []).filter((f) => f.status === 'missing');
3710
+ for (const f of missing) {
3711
+ if (f.testFile) {
3712
+ issues.push(` → ${f.name} — test file missing: ${f.testFile}`);
3713
+ }
3714
+ else {
3715
+ issues.push(` → ${f.name} (${f.filePath}) — no testFile in glossary`);
3716
+ if (f.suggestedTestFile) {
3717
+ issues.push(` Fix: Create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to .codeyam/glossary.json`);
3718
+ }
3719
+ }
3720
+ }
3721
+ }
3722
+ if (s.functionsFailing > 0) {
2981
3723
  issues.push(`${s.functionsFailing} function(s) with failing tests`);
2982
- if (s.functionsRunnerError > 0)
3724
+ const failing = (data.functions || []).filter((f) => f.status === 'failing');
3725
+ for (const f of failing) {
3726
+ issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3727
+ }
3728
+ }
3729
+ if (s.functionsRunnerError > 0) {
2983
3730
  issues.push(`${s.functionsRunnerError} function(s) with test runner errors (the runner crashed — not a test failure)`);
2984
- if (s.functionsNameMismatch > 0)
3731
+ const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
3732
+ for (const f of runnerErrors) {
3733
+ issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3734
+ if (f.hint)
3735
+ issues.push(` ${f.hint}`);
3736
+ else if (f.errorMessage)
3737
+ issues.push(` Error: ${f.errorMessage}`);
3738
+ }
3739
+ }
3740
+ if (s.functionsNameMismatch > 0) {
2985
3741
  issues.push(`${s.functionsNameMismatch} function(s) with test name mismatch`);
2986
- if (s.missingFromGlossary > 0)
3742
+ const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
3743
+ for (const f of mismatch) {
3744
+ issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3745
+ if (f.hint)
3746
+ issues.push(` Fix: ${f.hint}`);
3747
+ }
3748
+ }
3749
+ if (s.missingFromGlossary > 0) {
2987
3750
  issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
2988
- if (s.incompleteEntities > 0)
2989
- issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
2990
- if (s.miscategorizedScenarios > 0)
3751
+ const missingGlossary = data.missingFromGlossary || [];
3752
+ for (const m of missingGlossary) {
3753
+ issues.push(` ${m.name} (${m.filePath})`);
3754
+ }
3755
+ const missingPaths = missingGlossary
3756
+ .map((m) => m.filePath)
3757
+ .filter(Boolean);
3758
+ if (missingPaths.length > 0) {
3759
+ issues.push(` Fix: Run \`codeyam editor analyze-imports ${missingPaths.join(' ')}\``);
3760
+ }
3761
+ else {
3762
+ issues.push(` Fix: Run \`codeyam editor analyze-imports\` to add these files to the glossary`);
3763
+ }
3764
+ }
3765
+ if (s.incompleteEntities > 0) {
3766
+ // Check for persistent analysis failures
3767
+ let analysisFailures = {};
3768
+ try {
3769
+ const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
3770
+ if (fs.existsSync(failuresPath)) {
3771
+ analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
3772
+ }
3773
+ }
3774
+ catch {
3775
+ // Non-fatal
3776
+ }
3777
+ const preCount = s.preExistingIncompleteEntities || 0;
3778
+ const hasFailures = Object.keys(analysisFailures).length > 0;
3779
+ if (hasFailures) {
3780
+ issues.push(`${s.incompleteEntities} entity/entities — automated analysis failed, manual analysis required`);
3781
+ }
3782
+ else if (preCount > 0 && preCount === s.incompleteEntities) {
3783
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
3784
+ }
3785
+ else if (preCount > 0) {
3786
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis (${preCount} pre-existing — not from your changes)`);
3787
+ }
3788
+ else {
3789
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
3790
+ }
3791
+ const incomplete = data.incompleteEntities || [];
3792
+ for (const e of incomplete) {
3793
+ const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
3794
+ if (failureEntry) {
3795
+ issues.push(` → ${e.name} — MANUAL ANALYSIS REQUIRED (automated analysis failed)`);
3796
+ issues.push(` Run \`codeyam editor audit\` for step-by-step manual analysis instructions`);
3797
+ }
3798
+ else {
3799
+ issues.push(` → ${e.name} (${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''})${e.preExisting ? ' [pre-existing]' : ''}`);
3800
+ }
3801
+ }
3802
+ if (!hasFailures) {
3803
+ issues.push(` Fix: Run \`codeyam editor analyze-imports\` to build import graphs`);
3804
+ }
3805
+ }
3806
+ if (s.unassociatedScenarios > 0) {
3807
+ const unassociated = data.unassociatedScenarios || [];
3808
+ const unassocPaths = unassociated
3809
+ .map((u) => u.filePath)
3810
+ .filter(Boolean);
3811
+ if (unassocPaths.length > 0) {
3812
+ issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports ${unassocPaths.join(' ')}\` then re-run audit`);
3813
+ }
3814
+ else {
3815
+ issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
3816
+ }
3817
+ for (const u of unassociated) {
3818
+ issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
3819
+ }
3820
+ }
3821
+ if (s.miscategorizedScenarios > 0) {
2991
3822
  issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
3823
+ const miscategorized = data.miscategorizedScenarios || [];
3824
+ for (const m of miscategorized) {
3825
+ issues.push(` → ${m.componentName} at ${m.url} (${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''})`);
3826
+ }
3827
+ issues.push(` Fix: Re-register these as app-level scenarios (with pageFilePath, no componentName) or use isolation routes`);
3828
+ }
3829
+ if (s.scenariosNeedingRecapture > 0) {
3830
+ issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
3831
+ const recapture = data.scenariosNeedingRecapture || [];
3832
+ for (const r of recapture) {
3833
+ issues.push(` → "${r.scenarioName}" (entity: ${r.entityName}, ${r.status?.status || 'changed'})`);
3834
+ }
3835
+ issues.push(` Fix: Re-capture affected scenarios to update screenshots after code changes`);
3836
+ }
2992
3837
  if (issues.length > 0) {
2993
3838
  console.error(chalk.yellow('\nAudit failures:'));
2994
3839
  for (const issue of issues) {
@@ -3011,8 +3856,6 @@ function printAuditGateFailures(data) {
3011
3856
  }
3012
3857
  }
3013
3858
  console.error(chalk.yellow('\nFix: Fix the code errors above, then re-capture the affected scenarios.'));
3014
- console.error(chalk.yellow('If errors reference browser APIs (localStorage, sessionStorage, window, document),'));
3015
- console.error(chalk.yellow('create a universal mock: codeyam detect-universal-mocks'));
3016
3859
  }
3017
3860
  }
3018
3861
  console.error(chalk.dim('\nRun `codeyam editor audit` for full details.\n'));
@@ -3025,59 +3868,137 @@ function printAuditGateFailures(data) {
3025
3868
  * which glossary components have registered scenarios and which functions
3026
3869
  * have test files. Exits with code 1 if anything is missing.
3027
3870
  */
3028
- async function handleAudit() {
3871
+ async function handleAudit(options) {
3029
3872
  let data = await fetchAuditResult();
3030
3873
  if (!data) {
3031
3874
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3032
3875
  process.exit(1);
3033
3876
  }
3034
- // Auto-fix incomplete entities — but only once.
3035
- // If analyze-imports runs and entities are STILL incomplete, report clearly
3036
- // instead of retrying. The analysis may have errors (e.g., stale entity
3037
- // references from refactoring) that can't be fixed by re-running.
3877
+ // Two-phase auto-fix for entity associations:
3878
+ // Phase 1: DB-only backfill fast, matches existing entities to scenarios.
3879
+ // Phase 2: If backfill fails, targeted analyze-imports for only the
3880
+ // specific unresolved files (1-3 files, not the full glossary scan).
3038
3881
  const incompleteBeforeFix = data.incompleteEntities || [];
3882
+ const unassociatedBeforeFix = data.unassociatedScenarios || [];
3039
3883
  let autoRemediationFailed = false;
3040
- if (incompleteBeforeFix.length > 0) {
3041
- console.log(chalk.dim(`Running import analysis for ${incompleteBeforeFix.length} incomplete entit${incompleteBeforeFix.length !== 1 ? 'ies' : 'y'}...`));
3884
+ if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
3885
+ const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
3886
+ console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
3887
+ // Backfill entity_sha on scenarios that were registered before entities existed
3042
3888
  try {
3043
- await handleAnalyzeImports({ silent: true });
3889
+ const entities = await loadEntities({});
3890
+ if (entities && entities.length > 0) {
3891
+ const { getDatabase } = await import('../../../packages/database/index.js');
3892
+ const db = getDatabase();
3893
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3894
+ sha: e.sha,
3895
+ name: e.name,
3896
+ filePath: e.filePath || '',
3897
+ isDefaultExport: e.metadata?.notExported === false &&
3898
+ e.metadata?.namedExport === false,
3899
+ })));
3900
+ }
3044
3901
  }
3045
3902
  catch {
3046
- // Fall through — the audit will still report them as incomplete
3903
+ // Fall through — re-fetch will show remaining issues
3047
3904
  }
3048
- // Re-fetch audit results after the fix
3049
- data = await fetchAuditResult();
3905
+ // Re-fetch audit results after the backfill — skip re-running tests
3906
+ // since they haven't changed (backfill is DB-only).
3907
+ data = await fetchAuditResult({ skipTests: true });
3050
3908
  if (!data) {
3051
- console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
3909
+ console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
3052
3910
  process.exit(1);
3053
3911
  }
3054
- // If entities are still incomplete after running analyze-imports,
3055
- // flag it so we can show a clear message instead of looping
3912
+ // If issues persist after DB-only backfill, run targeted analyze-imports
3913
+ // for ONLY the specific files that need entities. This is fast (1-3 files)
3914
+ // unlike full analyze-imports which scans all glossary entries.
3915
+ const incompleteAfterBackfill = data.incompleteEntities || [];
3916
+ const unassociatedAfterBackfill = data.unassociatedScenarios || [];
3917
+ if (incompleteAfterBackfill.length > 0 ||
3918
+ unassociatedAfterBackfill.length > 0) {
3919
+ const { determineTargetedAnalysisPaths } = await import('../utils/editorAudit.js');
3920
+ const targetPaths = determineTargetedAnalysisPaths({
3921
+ unassociatedScenarios: unassociatedAfterBackfill,
3922
+ incompleteEntities: incompleteAfterBackfill,
3923
+ });
3924
+ if (targetPaths.length > 0) {
3925
+ console.log(chalk.dim(`Running targeted analysis for ${targetPaths.length} file${targetPaths.length !== 1 ? 's' : ''}...`));
3926
+ try {
3927
+ await handleAnalyzeImports({
3928
+ silent: true,
3929
+ filePaths: targetPaths,
3930
+ });
3931
+ // Retry backfill after analysis created entities
3932
+ const entities = await loadEntities({});
3933
+ if (entities && entities.length > 0) {
3934
+ const { getDatabase: getDb } = await import('../../../packages/database/index.js');
3935
+ const freshDb = getDb();
3936
+ await backfillEntityShaOnScenarios(freshDb, entities.map((e) => ({
3937
+ sha: e.sha,
3938
+ name: e.name,
3939
+ filePath: e.filePath || '',
3940
+ isDefaultExport: e.metadata?.notExported === false &&
3941
+ e.metadata?.namedExport === false,
3942
+ })));
3943
+ }
3944
+ // Re-fetch audit results after targeted analysis
3945
+ data = await fetchAuditResult({ skipTests: true });
3946
+ if (!data) {
3947
+ console.error(chalk.red('Error: Could not reach the CodeYam server after analysis.'));
3948
+ process.exit(1);
3949
+ }
3950
+ }
3951
+ catch {
3952
+ // Targeted analysis failed — fall through to show remaining issues
3953
+ }
3954
+ }
3955
+ }
3956
+ // Check if issues persist after all remediation attempts
3056
3957
  const incompleteAfterFix = data.incompleteEntities || [];
3057
- if (incompleteAfterFix.length > 0) {
3958
+ const unassociatedAfterFix = data.unassociatedScenarios || [];
3959
+ if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
3058
3960
  autoRemediationFailed = true;
3059
3961
  }
3060
3962
  }
3061
- const { components, functions, summary } = data;
3963
+ let { components, functions, summary } = data;
3062
3964
  console.log();
3063
3965
  console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
3064
3966
  console.log();
3065
3967
  // Components
3066
3968
  if (components.length > 0) {
3969
+ // Build name frequency map for disambiguation
3970
+ const componentNameCounts = new Map();
3971
+ for (const c of components) {
3972
+ componentNameCounts.set(c.name, (componentNameCounts.get(c.name) || 0) + 1);
3973
+ }
3067
3974
  console.log(chalk.bold('Components (scenarios):'));
3068
3975
  for (const c of components) {
3069
- const icon = c.status === 'ok' ? chalk.green('✓') : chalk.red('✗');
3976
+ const icon = c.status === 'ok'
3977
+ ? chalk.green('✓')
3978
+ : c.status === 'needs_recapture'
3979
+ ? chalk.yellow('↻')
3980
+ : chalk.red('✗');
3070
3981
  let detail;
3071
3982
  if (c.status === 'has_errors') {
3072
3983
  detail = chalk.red(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
3073
3984
  }
3985
+ else if (c.status === 'needs_recapture') {
3986
+ detail = chalk.yellow(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''}, needs recapture`);
3987
+ }
3074
3988
  else if (c.status === 'ok') {
3075
3989
  detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
3076
3990
  }
3077
3991
  else {
3078
3992
  detail = chalk.red(' — no scenarios registered');
3993
+ if (c.hint) {
3994
+ detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
3995
+ }
3079
3996
  }
3080
- console.log(` ${icon} ${c.name}${detail}`);
3997
+ // Show file path for failing components always, for OK only when name is ambiguous
3998
+ const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
3999
+ const showPath = (c.status !== 'ok' && c.status !== 'needs_recapture') || isDuplicate;
4000
+ const pathSuffix = showPath && c.filePath ? chalk.dim(` (${c.filePath})`) : '';
4001
+ console.log(` ${icon} ${c.name}${pathSuffix}${detail}`);
3081
4002
  if (c.clientErrors && c.clientErrors.length > 0) {
3082
4003
  for (const err of c.clientErrors.slice(0, 3)) {
3083
4004
  console.log(chalk.red(` → ${err}`));
@@ -3085,14 +4006,6 @@ async function handleAudit() {
3085
4006
  if (c.clientErrors.length > 3) {
3086
4007
  console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
3087
4008
  }
3088
- // Detect browser API errors and provide actionable guidance
3089
- const browserApiPattern = /\b(localStorage|sessionStorage|window\.|document\.|navigator\.|indexedDB|matchMedia|ResizeObserver|IntersectionObserver|MutationObserver)\b/;
3090
- const hasBrowserApiErrors = c.clientErrors.some((err) => browserApiPattern.test(err));
3091
- if (hasBrowserApiErrors) {
3092
- console.log(chalk.yellow(` ⚠ These errors are caused by browser APIs that don't exist during server-side analysis.`));
3093
- console.log(chalk.yellow(` Fix: Create a universal mock to stub the missing API. Run: codeyam detect-universal-mocks`));
3094
- console.log(chalk.yellow(` DO NOT re-run the audit or analyze-imports — the error will persist until a mock is created.`));
3095
- }
3096
4009
  }
3097
4010
  }
3098
4011
  console.log();
@@ -3112,7 +4025,8 @@ async function handleAudit() {
3112
4025
  if (f.errorMessage) {
3113
4026
  detail += `\n ${chalk.red(f.errorMessage)}`;
3114
4027
  detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
3115
- detail += `\n ${chalk.yellow('Try running the test manually: npx vitest run ' + f.testFile)}`;
4028
+ const ctx = getTechStackContext(process.cwd());
4029
+ detail += `\n ${chalk.yellow('Try running the test manually: ' + ctx.testRunCommand + ' ' + f.testFile)}`;
3116
4030
  }
3117
4031
  break;
3118
4032
  case 'failing':
@@ -3123,9 +4037,16 @@ async function handleAudit() {
3123
4037
  break;
3124
4038
  case 'missing':
3125
4039
  default:
3126
- detail = f.testFile
3127
- ? chalk.red(` — test file missing: ${f.testFile}`)
3128
- : chalk.red(' — no test file specified');
4040
+ if (f.testFile) {
4041
+ detail = chalk.red(` — test file missing: ${f.testFile}`);
4042
+ }
4043
+ else {
4044
+ detail = chalk.red(` — no test file specified in glossary`);
4045
+ detail += chalk.dim(` (source: ${f.filePath})`);
4046
+ if (f.suggestedTestFile) {
4047
+ detail += `\n ${chalk.yellow(`Fix: Either create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to this entry in .codeyam/glossary.json`)}`;
4048
+ }
4049
+ }
3129
4050
  break;
3130
4051
  }
3131
4052
  console.log(` ${icon} ${f.name}${detail}`);
@@ -3139,28 +4060,85 @@ async function handleAudit() {
3139
4060
  for (const m of missingFromGlossary) {
3140
4061
  console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
3141
4062
  }
3142
- console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
4063
+ const mgPaths = missingFromGlossary
4064
+ .map((m) => m.filePath)
4065
+ .filter(Boolean);
4066
+ if (mgPaths.length > 0) {
4067
+ console.log(chalk.yellow(` Add these to .codeyam/glossary.json and run \`codeyam editor analyze-imports ${mgPaths.join(' ')}\``));
4068
+ }
4069
+ else {
4070
+ console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
4071
+ }
3143
4072
  console.log();
3144
4073
  }
3145
- // Incomplete entities (scenarios without analyses)
4074
+ // Incomplete entities (scenarios without analyses) — report with guidance.
4075
+ // We intentionally do NOT run analysis here: it starts the analyzer
4076
+ // template which is slow even for a few files. Users should run
4077
+ // `codeyam editor analyze-imports` separately if needed.
3146
4078
  const incompleteEntities = data.incompleteEntities || [];
3147
4079
  if (incompleteEntities.length > 0) {
4080
+ const { formatIncompleteEntityGuidance, formatManualAnalysisGuidance } = await import('../utils/editorAudit.js');
4081
+ const { readAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
4082
+ // Check for persistent analysis failures
4083
+ const analysisFailures = readAnalysisFailures(getProjectRoot());
3148
4084
  console.log(chalk.bold('Incomplete entities (need import analysis):'));
3149
4085
  for (const e of incompleteEntities) {
3150
- console.log(` ${chalk.red('✗')} ${e.name} ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
4086
+ // Check if this entity has a persistent failure
4087
+ const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
4088
+ if (failureEntry) {
4089
+ // Show manual analysis instructions instead of "run analyze-imports"
4090
+ const filePath = Object.entries(analysisFailures).find(([, f]) => f.entityName === e.name)?.[0];
4091
+ const guidance = formatManualAnalysisGuidance({
4092
+ name: e.name,
4093
+ filePath: filePath || '',
4094
+ scenarioCount: e.scenarioCount,
4095
+ error: failureEntry.error,
4096
+ });
4097
+ // Print each line with proper indentation
4098
+ for (const line of guidance.split('\n')) {
4099
+ console.log(` ${chalk.red('✗')} ${line}`);
4100
+ }
4101
+ }
4102
+ else {
4103
+ const guidance = formatIncompleteEntityGuidance(e);
4104
+ console.log(` ${chalk.red('✗')} ${guidance}`);
4105
+ }
4106
+ }
4107
+ const incompleteErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
4108
+ if (fs.existsSync(incompleteErrorReportPath)) {
4109
+ console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
4110
+ }
4111
+ console.log();
4112
+ }
4113
+ // Unassociated scenarios (NULL entity_sha with file paths)
4114
+ const unassociatedScenarios = data.unassociatedScenarios || [];
4115
+ if (unassociatedScenarios.length > 0) {
4116
+ console.log(chalk.bold('Unassociated scenarios (missing entity link):'));
4117
+ for (const u of unassociatedScenarios) {
4118
+ console.log(` ${chalk.red('✗')} ${u.name} ${chalk.dim(`(${u.filePath})`)} — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''} with no entity_sha`);
4119
+ for (const sn of u.scenarioNames.slice(0, 3)) {
4120
+ console.log(chalk.dim(` "${sn}"`));
4121
+ }
4122
+ if (u.scenarioNames.length > 3) {
4123
+ console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
4124
+ }
3151
4125
  }
4126
+ const uaPaths = unassociatedScenarios
4127
+ .map((u) => u.filePath)
4128
+ .filter(Boolean);
4129
+ const uaCmd = uaPaths.length > 0
4130
+ ? `codeyam editor analyze-imports ${uaPaths.join(' ')}`
4131
+ : 'codeyam editor analyze-imports';
3152
4132
  if (autoRemediationFailed) {
3153
- console.log(chalk.red(' analyze-imports was run automatically but these entities are STILL incomplete.'));
3154
- console.log(chalk.red(' DO NOT re-run analyze-imports or the audit in a loop — the result will be the same.'));
3155
- console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
3156
- console.log(chalk.yellow(' • Entity code uses browser APIs (localStorage, window, document) — create a universal mock'));
3157
- console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
3158
- console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
3159
- console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
3160
- console.log(chalk.yellow(' To fix browser API issues: run `codeyam detect-universal-mocks`'));
4133
+ console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
4134
+ console.log(chalk.yellow(` Run \`${uaCmd}\` to see the full error output.`));
3161
4135
  }
3162
4136
  else {
3163
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
4137
+ console.log(chalk.yellow(` Run \`${uaCmd}\` to create entity records, then re-run audit to backfill.`));
4138
+ }
4139
+ const unassocErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
4140
+ if (fs.existsSync(unassocErrorReportPath)) {
4141
+ console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
3164
4142
  }
3165
4143
  console.log();
3166
4144
  }
@@ -3179,6 +4157,51 @@ async function handleAudit() {
3179
4157
  console.log(chalk.yellow(' or re-register with an isolation URL.'));
3180
4158
  console.log();
3181
4159
  }
4160
+ // Scenarios needing recapture (entity or dependency tree changed)
4161
+ const scenariosNeedingRecapture = data.scenariosNeedingRecapture || [];
4162
+ if (scenariosNeedingRecapture.length > 0) {
4163
+ console.log(chalk.bold('Scenarios needing recapture (dependency tree changed):'));
4164
+ for (const s of scenariosNeedingRecapture) {
4165
+ const reason = s.status.status === 'impacted' && s.status.impactedBy?.length
4166
+ ? `impacted by: ${s.status.impactedBy.map((d) => `${d.name} [${d.changeType}]`).join(', ')}`
4167
+ : `${s.status.status}`;
4168
+ console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
4169
+ }
4170
+ if (options?.fix) {
4171
+ // --fix: auto-recapture stale scenarios instead of just reporting them
4172
+ const { shouldAutoRecapture } = await import('../utils/editorAudit.js');
4173
+ if (shouldAutoRecapture({ fix: true, scenariosNeedingRecapture })) {
4174
+ console.log(chalk.cyan(' --fix: auto-recapturing stale scenarios...'));
4175
+ console.log();
4176
+ await handleRecaptureStale();
4177
+ // Re-fetch audit results so the summary and exit code reflect
4178
+ // the post-fix state, not the pre-fix state.
4179
+ const refreshed = await fetchAuditResult({ skipTests: true });
4180
+ if (refreshed) {
4181
+ data = refreshed;
4182
+ ({ components, functions, summary } = data);
4183
+ }
4184
+ }
4185
+ }
4186
+ else {
4187
+ console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
4188
+ console.log(chalk.dim(' Or: codeyam editor audit --fix (to auto-recapture)'));
4189
+ }
4190
+ console.log();
4191
+ }
4192
+ // Duplicate glossary names (warning, not a failure)
4193
+ const duplicateNames = data.duplicateNames || [];
4194
+ if (duplicateNames.length > 0) {
4195
+ console.log(chalk.bold('Duplicate names in glossary (confusing for audit):'));
4196
+ for (const dn of duplicateNames) {
4197
+ console.log(` ${chalk.yellow('⚠')} "${dn.name}" appears ${dn.filePaths.length} times:`);
4198
+ for (const fp of dn.filePaths) {
4199
+ console.log(` ${fp}`);
4200
+ }
4201
+ }
4202
+ console.log(chalk.yellow(' Fix: remove duplicate entries or rename them to be unique in .codeyam/glossary.json'));
4203
+ console.log();
4204
+ }
3182
4205
  // Summary
3183
4206
  const allOk = summary.allPassing;
3184
4207
  if (allOk) {
@@ -3211,25 +4234,123 @@ async function handleAudit() {
3211
4234
  if (summary.incompleteEntities > 0) {
3212
4235
  parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
3213
4236
  }
4237
+ if (summary.unassociatedScenarios > 0) {
4238
+ parts.push(`${summary.unassociatedScenarios} component${summary.unassociatedScenarios !== 1 ? 's' : ''} with scenarios missing entity link`);
4239
+ }
3214
4240
  if (summary.miscategorizedScenarios > 0) {
3215
4241
  parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
3216
4242
  }
4243
+ if (summary.scenariosNeedingRecapture > 0) {
4244
+ parts.push(`${summary.scenariosNeedingRecapture} scenario${summary.scenariosNeedingRecapture !== 1 ? 's' : ''} need recapture`);
4245
+ }
3217
4246
  console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
3218
4247
  }
3219
4248
  console.log();
3220
4249
  if (!allOk) {
3221
4250
  process.exit(1);
3222
4251
  }
3223
- // Auto-run analyze-imports when audit passes — this builds the import graph
3224
- // so the App tab can show component/function dependencies for each page.
3225
- // Previously this was a manual step that Claude had to remember to run.
3226
- console.log(chalk.dim('Building import graph...'));
4252
+ }
4253
+ // ─── Recapture-stale subcommand ────────────────────────────────────────
4254
+ /**
4255
+ * `codeyam editor recapture-stale`
4256
+ *
4257
+ * Identifies all scenarios whose entity (or dependency) has changed but
4258
+ * whose screenshot hasn't been recaptured this session, then recaptures
4259
+ * them in batch.
4260
+ */
4261
+ async function handleRecaptureStale() {
4262
+ const port = getServerPort();
4263
+ const url = `http://localhost:${port}/api/editor-recapture-stale`;
4264
+ console.log();
4265
+ console.log(chalk.bold.cyan('━━━ Recapture Stale Scenarios ━━━'));
4266
+ console.log();
4267
+ let res;
3227
4268
  try {
3228
- await handleAnalyzeImports({ silent: true });
4269
+ res = await fetch(url, { method: 'POST' });
3229
4270
  }
3230
- catch {
3231
- // Non-fatal audit passed, import graph is a bonus
3232
- console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
4271
+ catch (err) {
4272
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
4273
+ console.error(chalk.dim(` ${err.message}`));
4274
+ process.exit(1);
4275
+ }
4276
+ if (!res.ok) {
4277
+ const body = await res.json().catch(() => null);
4278
+ console.error(chalk.red(`Error: Recapture endpoint returned ${res.status}`));
4279
+ if (body?.error)
4280
+ console.error(chalk.red(` ${body.error}`));
4281
+ process.exit(1);
4282
+ }
4283
+ // Handle JSON response (early returns like "no changes" / "all up to date")
4284
+ const contentType = res.headers.get('content-type') || '';
4285
+ if (contentType.includes('application/json')) {
4286
+ const data = await res.json();
4287
+ if (data.note) {
4288
+ console.log(chalk.dim(data.note));
4289
+ }
4290
+ else if (data.total === 0) {
4291
+ console.log(chalk.green('All scenarios are up to date.'));
4292
+ }
4293
+ console.log();
4294
+ return;
4295
+ }
4296
+ // Stream NDJSON progress events
4297
+ let total = 0;
4298
+ let recapturedCount = 0;
4299
+ let failedCount = 0;
4300
+ const reader = res.body?.getReader();
4301
+ if (!reader) {
4302
+ console.error(chalk.red('Error: No response body'));
4303
+ process.exit(1);
4304
+ }
4305
+ const decoder = new TextDecoder();
4306
+ let buffer = '';
4307
+ while (true) {
4308
+ const { done, value } = await reader.read();
4309
+ if (done)
4310
+ break;
4311
+ buffer += decoder.decode(value, { stream: true });
4312
+ const lines = buffer.split('\n');
4313
+ buffer = lines.pop() || ''; // Keep incomplete last line in buffer
4314
+ for (const line of lines) {
4315
+ if (!line.trim())
4316
+ continue;
4317
+ try {
4318
+ const event = JSON.parse(line);
4319
+ switch (event.type) {
4320
+ case 'start':
4321
+ total = event.total;
4322
+ console.log(`Found ${total} stale scenario(s). Recapturing...\n`);
4323
+ break;
4324
+ case 'capturing':
4325
+ process.stdout.write(chalk.dim(` … ${event.name}`));
4326
+ break;
4327
+ case 'success':
4328
+ // Clear the "capturing" line and print success
4329
+ process.stdout.write('\r\x1b[K');
4330
+ console.log(` ${chalk.green('✓')} ${event.name}`);
4331
+ recapturedCount++;
4332
+ break;
4333
+ case 'failure':
4334
+ process.stdout.write('\r\x1b[K');
4335
+ console.log(` ${chalk.red('✗')} ${event.name} — ${chalk.dim(event.error)}`);
4336
+ failedCount++;
4337
+ break;
4338
+ case 'done':
4339
+ // Final summary
4340
+ console.log();
4341
+ const color = failedCount > 0 ? chalk.yellow : chalk.green;
4342
+ console.log(color(`Recaptured ${recapturedCount}/${total} scenario(s).`));
4343
+ console.log();
4344
+ break;
4345
+ }
4346
+ }
4347
+ catch {
4348
+ // Skip unparseable lines
4349
+ }
4350
+ }
4351
+ }
4352
+ if (failedCount > 0) {
4353
+ process.exit(1);
3233
4354
  }
3234
4355
  }
3235
4356
  // ─── Scenarios subcommand ─────────────────────────────────────────────
@@ -3334,14 +4455,14 @@ async function handleScenarioCoverage() {
3334
4455
  // Safety net: heal any scenarios with null entity_sha before checking coverage
3335
4456
  try {
3336
4457
  const { getDatabase } = await import('../../../packages/database/index.js');
3337
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
4458
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
3338
4459
  const db = getDatabase();
3339
4460
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
3340
4461
  if (backfillCount > 0) {
3341
4462
  console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
3342
4463
  await handleAnalyzeImports({ silent: true });
3343
4464
  // Run backfill after analysis
3344
- const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
4465
+ const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
3345
4466
  const entities = await loadEntities({});
3346
4467
  if (entities && entities.length > 0) {
3347
4468
  await backfillEntityShaOnScenarios(db, entities.map((e) => ({
@@ -3479,7 +4600,7 @@ async function handleTemplate() {
3479
4600
  console.log(chalk.green(' Git initialized.'));
3480
4601
  }
3481
4602
  // 4. Run codeyam init
3482
- console.log(chalk.bold('Running codeyam init...'));
4603
+ console.log(chalk.bold('Initializing project...'));
3483
4604
  await initCommand.handler({
3484
4605
  force: true,
3485
4606
  'keep-server': true,
@@ -3488,7 +4609,7 @@ async function handleTemplate() {
3488
4609
  _: [],
3489
4610
  });
3490
4611
  console.log(chalk.green(' CodeYam initialized.'));
3491
- // 5. Verify config has startCommand
4612
+ // 5. Verify config has startCommand and set format-specific defaults
3492
4613
  const configPath = path.join(root, '.codeyam', 'config.json');
3493
4614
  if (fs.existsSync(configPath)) {
3494
4615
  try {
@@ -3499,6 +4620,26 @@ async function handleTemplate() {
3499
4620
  console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
3500
4621
  console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
3501
4622
  }
4623
+ // Store appFormats from the tech stack so the editor UI can adapt
4624
+ if (stack?.supportedFormats) {
4625
+ config.appFormats = stack.supportedFormats;
4626
+ }
4627
+ // Set mobile-first defaults for mobile-app projects
4628
+ if (stack?.supportedFormats?.includes('mobile-app')) {
4629
+ config.defaultScreenSize = {
4630
+ name: 'iPhone 16',
4631
+ width: 393,
4632
+ height: 852,
4633
+ };
4634
+ config.screenSizes = {
4635
+ 'iPhone 16': { width: 393, height: 852 },
4636
+ 'iPhone 16 Pro Max': { width: 430, height: 932 },
4637
+ 'iPhone SE': { width: 375, height: 667 },
4638
+ 'Pixel 8': { width: 412, height: 915 },
4639
+ 'iPad mini': { width: 744, height: 1133 },
4640
+ };
4641
+ }
4642
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
3502
4643
  }
3503
4644
  catch {
3504
4645
  // Config parse error is non-fatal
@@ -3533,7 +4674,15 @@ async function handleTemplate() {
3533
4674
  }
3534
4675
  console.log();
3535
4676
  console.log(chalk.green.bold('Project scaffolded and ready!'));
3536
- console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
4677
+ if (stack?.id === 'expo-react-native') {
4678
+ console.log(chalk.dim('Next: Set up your data types, configure the theme in lib/theme.ts, and build your feature.'));
4679
+ }
4680
+ else if (stack?.id === 'chrome-extension-react') {
4681
+ console.log(chalk.dim('Next: Configure your extension manifest and build your feature.'));
4682
+ }
4683
+ else {
4684
+ console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
4685
+ }
3537
4686
  }
3538
4687
  // ─── Sync subcommand ─────────────────────────────────────────────────
3539
4688
  /**
@@ -3558,7 +4707,7 @@ async function handleSync() {
3558
4707
  // fall through
3559
4708
  }
3560
4709
  if (!projectSlug) {
3561
- console.error(chalk.red('Error: No project slug found. Run codeyam init first.'));
4710
+ console.error(chalk.red('Error: No project slug found. Run `codeyam editor template` to initialize the project.'));
3562
4711
  process.exit(1);
3563
4712
  }
3564
4713
  const connectionOk = await withoutSpinner(() => testEnvironment());
@@ -3742,7 +4891,7 @@ function handleEditorDebug(args) {
3742
4891
  scenarios.push({
3743
4892
  id: 'overview-with-state',
3744
4893
  title: 'Cycle overview (project, with state)',
3745
- render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(4, feature)))),
4894
+ render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(6, feature)))),
3746
4895
  });
3747
4896
  }
3748
4897
  const stepFns = {
@@ -3762,8 +4911,10 @@ function handleEditorDebug(args) {
3762
4911
  14: printStep14,
3763
4912
  15: printStep15,
3764
4913
  16: printStep16,
4914
+ 17: printStep17,
4915
+ 18: printStep18,
3765
4916
  };
3766
- for (let step = 1; step <= 16; step++) {
4917
+ for (let step = 1; step <= 18; step++) {
3767
4918
  const stepId = `step-${step}`;
3768
4919
  if (!wants(stepId))
3769
4920
  continue;
@@ -3783,7 +4934,7 @@ function handleEditorDebug(args) {
3783
4934
  if (step === 2) {
3784
4935
  scenarios.push({
3785
4936
  id: 'step-2-scaffold',
3786
- title: 'Step 2 (Prototype) — scaffold flow (no project)',
4937
+ title: 'Step 2 (Prepare) — scaffold flow (no project)',
3787
4938
  render: () => withTempRoot(false, (tempRoot) => captureOutput(() => printStep2(tempRoot, feature))),
3788
4939
  });
3789
4940
  }
@@ -3836,14 +4987,205 @@ function handleEditorDebug(args) {
3836
4987
  console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
3837
4988
  console.log();
3838
4989
  }
4990
+ // ─── Manual entity analysis ───────────────────────────────────────────
4991
+ /**
4992
+ * `codeyam editor manual-entity <JSON|@file>`
4993
+ *
4994
+ * Creates entity and analysis records from Claude-provided metadata when
4995
+ * automated analyze-imports fails. This unblocks the audit gate without
4996
+ * needing the analyzer template to parse the source file.
4997
+ */
4998
+ async function handleManualEntity(jsonArg) {
4999
+ const root = getProjectRoot();
5000
+ // Parse JSON input (supports @file.json convention)
5001
+ const parsed = parseRegisterArg(jsonArg);
5002
+ if (parsed.error || !parsed.body) {
5003
+ console.error(chalk.red(`Error: ${parsed.error || 'Invalid input'}`));
5004
+ console.error(chalk.dim(' Usage: codeyam editor manual-entity \'{"name":"Header","filePath":"src/components/Header.tsx","entityType":"visual",...}\''));
5005
+ console.error(chalk.dim(' Or: codeyam editor manual-entity @.codeyam/tmp/manual-entity.json'));
5006
+ process.exit(1);
5007
+ }
5008
+ const input = parsed.body;
5009
+ // Validate input
5010
+ const { validateManualEntityInput, convertTypeInfoToDataForMocks, readAnalysisFailures, clearFailureForPath, writeAnalysisFailures, } = await import('../utils/manualEntityAnalysis.js');
5011
+ // Read glossary for validation
5012
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
5013
+ let glossaryEntries = [];
5014
+ try {
5015
+ glossaryEntries = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
5016
+ }
5017
+ catch {
5018
+ // Empty glossary — validation will still check file existence
5019
+ }
5020
+ const errors = validateManualEntityInput(input, glossaryEntries, {
5021
+ fileExists: (p) => fs.existsSync(path.join(root, p)),
5022
+ });
5023
+ if (errors.length > 0) {
5024
+ console.error(chalk.red('Validation errors:'));
5025
+ for (const err of errors) {
5026
+ console.error(chalk.red(` • ${err}`));
5027
+ }
5028
+ process.exit(1);
5029
+ }
5030
+ // Read source file and compute SHA
5031
+ const sourceContent = fs.readFileSync(path.join(root, input.filePath), 'utf8');
5032
+ const { generateSha } = await import('../../../packages/database/index.js');
5033
+ const entitySha = generateSha(input.filePath, input.name, sourceContent);
5034
+ // Get project and branch
5035
+ await initializeEnvironment();
5036
+ const configPath = path.join(root, '.codeyam', 'config.json');
5037
+ let projectSlug;
5038
+ try {
5039
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
5040
+ projectSlug = config.projectSlug;
5041
+ }
5042
+ catch {
5043
+ console.error(chalk.red('Error: .codeyam/config.json not found or invalid.'));
5044
+ process.exit(1);
5045
+ }
5046
+ const { project, branch } = await requireBranchAndProject(projectSlug);
5047
+ const { getDatabase } = await import('../../../packages/database/index.js');
5048
+ const db = getDatabase();
5049
+ // Convert type info to dataForMocks format
5050
+ const dataForMocks = convertTypeInfoToDataForMocks(input.typeInfo);
5051
+ // Create entity record
5052
+ const entityMetadata = {
5053
+ importedExports: (input.importedExports || []).map((ie) => ({
5054
+ name: ie.name,
5055
+ filePath: ie.filePath,
5056
+ })),
5057
+ manuallyAnalyzed: true,
5058
+ };
5059
+ // Check if entity already exists
5060
+ const existingEntity = await db
5061
+ .selectFrom('entities')
5062
+ .select('sha')
5063
+ .where('sha', '=', entitySha)
5064
+ .executeTakeFirst();
5065
+ if (!existingEntity) {
5066
+ await db
5067
+ .insertInto('entities')
5068
+ .values({
5069
+ sha: entitySha,
5070
+ project_id: project.id,
5071
+ name: input.name,
5072
+ entity_type: input.entityType,
5073
+ file_path: input.filePath,
5074
+ metadata: JSON.stringify(entityMetadata),
5075
+ })
5076
+ .onConflict((oc) => oc.column('sha').doNothing())
5077
+ .execute();
5078
+ console.log(chalk.dim(`Created entity record: ${input.name} (${entitySha.slice(0, 8)}...)`));
5079
+ }
5080
+ else {
5081
+ // Update metadata on existing entity
5082
+ await db
5083
+ .updateTable('entities')
5084
+ .set({
5085
+ metadata: JSON.stringify(entityMetadata),
5086
+ })
5087
+ .where('sha', '=', entitySha)
5088
+ .execute();
5089
+ console.log(chalk.dim(`Updated existing entity: ${input.name} (${entitySha.slice(0, 8)}...)`));
5090
+ }
5091
+ // Create entity_branch record
5092
+ await db
5093
+ .insertInto('entity_branches')
5094
+ .values({
5095
+ entity_sha: entitySha,
5096
+ branch_id: branch.id,
5097
+ active: true,
5098
+ })
5099
+ .onConflict((oc) => oc.columns(['entity_sha', 'branch_id']).doUpdateSet({ active: true }))
5100
+ .execute();
5101
+ // Create analysis record
5102
+ const { randomUUID } = await import('crypto');
5103
+ const analysisId = randomUUID();
5104
+ const now = new Date().toISOString();
5105
+ const analysisMetadata = {
5106
+ scenariosDataStructure: {
5107
+ dataForMocks: Object.keys(dataForMocks).length > 0 ? dataForMocks : null,
5108
+ },
5109
+ mergedDataStructure: {
5110
+ dependencySchemas: null,
5111
+ },
5112
+ manuallyAnalyzed: true,
5113
+ };
5114
+ const analysisStatus = {
5115
+ startedAt: now,
5116
+ finishedAt: now,
5117
+ steps: [],
5118
+ errors: [],
5119
+ };
5120
+ // Check if analysis already exists for this entity
5121
+ const existingAnalysis = await db
5122
+ .selectFrom('analyses')
5123
+ .select('id')
5124
+ .where('entity_sha', '=', entitySha)
5125
+ .executeTakeFirst();
5126
+ if (!existingAnalysis) {
5127
+ await db
5128
+ .insertInto('analyses')
5129
+ .values({
5130
+ id: analysisId,
5131
+ project_id: project.id,
5132
+ entity_sha: entitySha,
5133
+ entity_name: input.name,
5134
+ entity_type: input.entityType,
5135
+ file_path: input.filePath,
5136
+ status: JSON.stringify(analysisStatus),
5137
+ metadata: JSON.stringify(analysisMetadata),
5138
+ })
5139
+ .execute();
5140
+ console.log(chalk.dim(`Created analysis record: ${analysisId.slice(0, 8)}...`));
5141
+ }
5142
+ else {
5143
+ // Update existing analysis with manual metadata
5144
+ await db
5145
+ .updateTable('analyses')
5146
+ .set({
5147
+ metadata: JSON.stringify(analysisMetadata),
5148
+ status: JSON.stringify(analysisStatus),
5149
+ })
5150
+ .where('entity_sha', '=', entitySha)
5151
+ .execute();
5152
+ console.log(chalk.dim(`Updated existing analysis for ${input.name}`));
5153
+ }
5154
+ // Backfill entity_sha on scenarios
5155
+ const backfillResult = await backfillEntityShaOnScenarios(db, [
5156
+ {
5157
+ sha: entitySha,
5158
+ name: input.name,
5159
+ filePath: input.filePath,
5160
+ isDefaultExport: false,
5161
+ },
5162
+ ]);
5163
+ if (backfillResult.updated > 0) {
5164
+ console.log(chalk.dim(`Linked ${backfillResult.updated} scenario(s) to entity`));
5165
+ }
5166
+ // Clear from analysis failures tracker
5167
+ const failures = readAnalysisFailures(root);
5168
+ if (failures[input.filePath]) {
5169
+ const updated = clearFailureForPath(failures, input.filePath);
5170
+ writeAnalysisFailures(root, updated);
5171
+ console.log(chalk.dim(`Cleared analysis failure for ${input.filePath}`));
5172
+ }
5173
+ console.log();
5174
+ console.log(chalk.green.bold(`✓ Manual entity analysis complete: ${input.name}`));
5175
+ console.log(chalk.dim(` Entity SHA: ${entitySha.slice(0, 12)}...`));
5176
+ console.log(chalk.dim(` Imports: ${(input.importedExports || []).length} glossary entities`));
5177
+ console.log(chalk.dim(` Type info: ${Object.keys(dataForMocks).length} fields`));
5178
+ console.log();
5179
+ console.log(chalk.dim('Re-run `codeyam editor audit` to verify.'));
5180
+ }
3839
5181
  // ─── Command definition ───────────────────────────────────────────────
3840
5182
  const editorCommand = {
3841
5183
  command: 'editor [step] [json]',
3842
5184
  describe: 'Editor mode guided workflow',
3843
5185
  builder: (yargs) => {
3844
5186
  const stepDescription = IS_INTERNAL_BUILD
3845
- ? '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)'
3846
- : '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)';
5187
+ ? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, design-system, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)'
5188
+ : 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, design-system, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)';
3847
5189
  let builder = yargs
3848
5190
  .positional('step', {
3849
5191
  type: 'string',
@@ -3874,12 +5216,17 @@ const editorCommand = {
3874
5216
  alias: 'p',
3875
5217
  describe: 'Port to run the web server on',
3876
5218
  default: 3111,
5219
+ })
5220
+ .option('fix', {
5221
+ type: 'boolean',
5222
+ describe: 'For audit: also recapture stale scenarios after displaying results',
5223
+ default: false,
3877
5224
  });
3878
5225
  if (IS_INTERNAL_BUILD) {
3879
5226
  builder = builder
3880
5227
  .option('target', {
3881
5228
  type: 'string',
3882
- describe: 'Debug target (setup, overview, overview-with-state, step-1..step-16, or comma-separated list)',
5229
+ describe: 'Debug target (setup, overview, overview-with-state, step-1..step-18, or comma-separated list)',
3883
5230
  })
3884
5231
  .option('resume', {
3885
5232
  type: 'boolean',
@@ -3904,6 +5251,18 @@ const editorCommand = {
3904
5251
  // API subcommands: preview, show-results, hide-results, commit,
3905
5252
  // journal, journal-update, dev-server, client-errors
3906
5253
  if (argv.step && EDITOR_API_SUBCOMMANDS.includes(argv.step)) {
5254
+ // Guard: commit requires step 16 to have been run first.
5255
+ // Without this, Claude shortcuts the process by running
5256
+ // `codeyam editor commit` directly from step 15, skipping
5257
+ // steps 16-18 entirely.
5258
+ if (argv.step === 'commit') {
5259
+ const state = readState(root);
5260
+ if (state && state.step !== 16) {
5261
+ console.error(chalk.red('Error: Run `codeyam editor 16` before committing.'));
5262
+ console.error(chalk.dim(` Current step: ${state.step} (${state.label || 'unknown'}). Step 16 (Commit) must be run first.`));
5263
+ process.exit(1);
5264
+ }
5265
+ }
3907
5266
  const port = getServerPort();
3908
5267
  try {
3909
5268
  const request = buildEditorApiRequest(argv.step, argv.json || undefined);
@@ -3923,6 +5282,12 @@ const editorCommand = {
3923
5282
  console.log(JSON.stringify(result.data, null, 2));
3924
5283
  }
3925
5284
  }
5285
+ // After a successful commit, remind Claude to continue to step 17
5286
+ if (argv.step === 'commit' && result.ok) {
5287
+ console.log();
5288
+ console.log(chalk.green('Commit done. Now run: ') +
5289
+ chalk.bold('codeyam editor 17'));
5290
+ }
3926
5291
  }
3927
5292
  catch (err) {
3928
5293
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
@@ -3936,9 +5301,63 @@ const editorCommand = {
3936
5301
  await handleRegister(argv.json || '');
3937
5302
  return;
3938
5303
  }
3939
- // Subcommand: codeyam editor analyze-imports
5304
+ // Subcommand: codeyam editor glossary-add '{"name":"...", ...}'
5305
+ if (argv.step === 'glossary-add') {
5306
+ await handleGlossaryAdd(argv.json || '');
5307
+ return;
5308
+ }
5309
+ // Subcommand: codeyam editor task-ontrack
5310
+ // Corrective command when Claude advanced without creating a task.
5311
+ if (argv.step === 'task-ontrack') {
5312
+ const state = readState(root);
5313
+ if (!state?.step) {
5314
+ console.error(chalk.red('No editor state found. Run `codeyam editor 1` to start.'));
5315
+ process.exit(1);
5316
+ }
5317
+ const currentLabel = STEP_LABELS[state.step] || `Step ${state.step}`;
5318
+ const totalSteps = Object.keys(STEP_LABELS).length;
5319
+ const nextLabel = state.step < totalSteps ? STEP_LABELS[state.step + 1] : undefined;
5320
+ console.log();
5321
+ console.log(chalk.bold.yellow('━━━ GETTING BACK ON TRACK ━━━'));
5322
+ console.log();
5323
+ console.log(chalk.yellow('You went off-track by not creating a task. Create the task below to continue.'));
5324
+ console.log();
5325
+ // Print the TASK directive for the current step
5326
+ let taskTitle;
5327
+ if (state.step < totalSteps) {
5328
+ taskTitle = `Complete codeyam editor step ${state.step}: '${currentLabel}' and move on to step ${state.step + 1}: '${nextLabel}'`;
5329
+ }
5330
+ else {
5331
+ taskTitle =
5332
+ 'Ask user what to build next and restart codeyam editor workflow';
5333
+ }
5334
+ console.log(chalk.bold.cyan('━━━ TASK ━━━'));
5335
+ console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
5336
+ console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
5337
+ console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
5338
+ console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
5339
+ console.log();
5340
+ console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
5341
+ console.log();
5342
+ console.log(chalk.green(`After creating the task, re-run: codeyam editor ${state.step + 1}`));
5343
+ console.log();
5344
+ // Mark task as expected-created so the next step can proceed.
5345
+ // The hook will set taskCreated=true when it sees the actual TaskCreate call.
5346
+ // But we also allow advancement now since task-ontrack itself is the corrective action.
5347
+ const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
5348
+ fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
5349
+ return;
5350
+ }
5351
+ // Subcommand: codeyam editor analyze-imports [file1.tsx file2.tsx ...]
3940
5352
  if (argv.step === 'analyze-imports') {
3941
- await handleAnalyzeImports();
5353
+ const { parseAnalyzeImportsArgs } = await import('./editorAnalyzeImportsArgs.js');
5354
+ const requestedPaths = parseAnalyzeImportsArgs(argv.json, argv._);
5355
+ await handleAnalyzeImports(requestedPaths.length > 0 ? { filePaths: requestedPaths } : {});
5356
+ return;
5357
+ }
5358
+ // Subcommand: codeyam editor manual-entity <JSON|@file>
5359
+ if (argv.step === 'manual-entity') {
5360
+ await handleManualEntity(argv.json || '');
3942
5361
  return;
3943
5362
  }
3944
5363
  // Subcommand: codeyam editor dependents <EntityName>
@@ -3946,9 +5365,9 @@ const editorCommand = {
3946
5365
  await handleDependents(argv.json || '');
3947
5366
  return;
3948
5367
  }
3949
- // Subcommand: codeyam editor audit
5368
+ // Subcommand: codeyam editor audit [--fix]
3950
5369
  if (argv.step === 'audit') {
3951
- await handleAudit();
5370
+ await handleAudit({ fix: argv.fix || false });
3952
5371
  return;
3953
5372
  }
3954
5373
  // Subcommand: codeyam editor scenarios
@@ -3961,6 +5380,11 @@ const editorCommand = {
3961
5380
  await handleScenarioCoverage();
3962
5381
  return;
3963
5382
  }
5383
+ // Subcommand: codeyam editor recapture-stale
5384
+ if (argv.step === 'recapture-stale') {
5385
+ await handleRecaptureStale();
5386
+ return;
5387
+ }
3964
5388
  // Subcommand: codeyam editor change <feature>
3965
5389
  if (argv.step === 'change') {
3966
5390
  handleChange(argv.json || '');
@@ -3976,6 +5400,11 @@ const editorCommand = {
3976
5400
  await handleValidateSeed(argv.json || '');
3977
5401
  return;
3978
5402
  }
5403
+ // Subcommand: codeyam editor design-system <id>
5404
+ if (argv.step === 'design-system') {
5405
+ handleDesignSystem(argv.json || '');
5406
+ return;
5407
+ }
3979
5408
  // Subcommand: codeyam editor delete <scenarioId>
3980
5409
  if (argv.step === 'delete') {
3981
5410
  await handleDelete(argv.json || '');
@@ -4020,18 +5449,13 @@ const editorCommand = {
4020
5449
  }
4021
5450
  else {
4022
5451
  const state = readState(root);
4023
- // Clear prompt file when feature is done (step 16) so the hook
4024
- // can capture the next feature request from the user.
4025
- if (state?.step === 16) {
4026
- clearEditorUserPrompt(root);
4027
- }
4028
5452
  printCycleOverview(root, state);
4029
5453
  }
4030
5454
  return;
4031
5455
  }
4032
5456
  const step = argv.step ? parseInt(argv.step, 10) : undefined;
4033
- if (step != null && (isNaN(step) || step < 1 || step > 16)) {
4034
- console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-16.`));
5457
+ if (step != null && (isNaN(step) || step < 1 || step > 18)) {
5458
+ console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-18.`));
4035
5459
  process.exit(1);
4036
5460
  }
4037
5461
  if (step == null) {
@@ -4055,7 +5479,7 @@ const editorCommand = {
4055
5479
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
4056
5480
  const { projectSlug } = config;
4057
5481
  if (!projectSlug) {
4058
- errorLog('Missing project slug. Try reinitializing with: codeyam init --force');
5482
+ errorLog('Missing project slug. Try reinitializing with: `codeyam editor template`');
4059
5483
  return;
4060
5484
  }
4061
5485
  const connectionOk = await withoutSpinner(() => testEnvironment());
@@ -4181,13 +5605,24 @@ const editorCommand = {
4181
5605
  try {
4182
5606
  const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4183
5607
  const seedDb = getDb();
4184
- const appScenario = await seedDb
5608
+ // Prefer the home page scenario (url "/") since the App tab
5609
+ // sorts "Home" first — fall back to any application scenario.
5610
+ const homeScenario = await seedDb
4185
5611
  .selectFrom('editor_scenarios')
4186
5612
  .select(['id', 'name', 'type'])
4187
5613
  .where('project_id', '=', project.id)
4188
- .where('type', '=', 'application')
5614
+ .where('url', '=', '/')
5615
+ .where('component_name', 'is', null)
4189
5616
  .orderBy('created_at', 'asc')
4190
5617
  .executeTakeFirst();
5618
+ const appScenario = homeScenario ||
5619
+ (await seedDb
5620
+ .selectFrom('editor_scenarios')
5621
+ .select(['id', 'name', 'type'])
5622
+ .where('project_id', '=', project.id)
5623
+ .where('type', '=', 'application')
5624
+ .orderBy('created_at', 'asc')
5625
+ .executeTakeFirst());
4191
5626
  if (appScenario) {
4192
5627
  const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
4193
5628
  if (fs.existsSync(seedFile)) {
@@ -4330,7 +5765,7 @@ const editorCommand = {
4330
5765
  if (!needsAnalysis) {
4331
5766
  try {
4332
5767
  const { getDatabase } = await import('../../../packages/database/index.js');
4333
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
5768
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
4334
5769
  const db = getDatabase();
4335
5770
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
4336
5771
  if (backfillCount > 0) {
@@ -4369,7 +5804,7 @@ const editorCommand = {
4369
5804
  .execute();
4370
5805
  if (unresolved.length > 0) {
4371
5806
  const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
4372
- const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
5807
+ const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
4373
5808
  const { allFiles: pfpFiles } = scanPfp(projectRoot);
4374
5809
  let pfpResolved = 0;
4375
5810
  for (const row of unresolved) {
@@ -4452,7 +5887,7 @@ const editorCommand = {
4452
5887
  // Step 1 is planning-only and may not persist state (no --feature flag).
4453
5888
  const skipValidation = step === 2 && argv.feature;
4454
5889
  if (!skipValidation) {
4455
- const stepError = validateStepTransition(step, state?.step ?? null);
5890
+ const stepError = validateStepTransition(step, state?.step ?? null, state?.startedAt, root);
4456
5891
  if (stepError) {
4457
5892
  console.error(chalk.red(`Error: ${stepError}`));
4458
5893
  process.exit(1);
@@ -4492,19 +5927,20 @@ const editorCommand = {
4492
5927
  case 13:
4493
5928
  case 14:
4494
5929
  case 15:
4495
- case 16: {
5930
+ case 16:
5931
+ case 17:
5932
+ case 18: {
4496
5933
  const feature = argv.feature || state?.feature;
4497
5934
  if (!feature) {
4498
5935
  console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
4499
5936
  process.exit(1);
4500
5937
  }
4501
- // Hard gate: steps 8+ require audit to have passed
4502
- if (step >= 8) {
5938
+ // Hard gate: steps 10+ require audit to have passed
5939
+ if (step >= 10) {
4503
5940
  const auditOk = await checkAuditGate();
4504
5941
  if (!auditOk) {
4505
5942
  // checkAuditGate() already printed specific failure details above
4506
5943
  console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
4507
- console.error(chalk.yellow('DO NOT re-run the audit in a loop — fix the underlying issues first.'));
4508
5944
  process.exit(1);
4509
5945
  }
4510
5946
  }
@@ -4523,6 +5959,8 @@ const editorCommand = {
4523
5959
  14: printStep14,
4524
5960
  15: printStep15,
4525
5961
  16: printStep16,
5962
+ 17: printStep17,
5963
+ 18: printStep18,
4526
5964
  };
4527
5965
  stepFns[step](root, feature);
4528
5966
  break;