@codeyam/codeyam-cli 0.1.0-staging.103bd05 → 0.1.0-staging.121e983

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 (390) hide show
  1. package/analyzer-template/.build-info.json +8 -8
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +4 -4
  4. package/analyzer-template/packages/ai/src/lib/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 +6 -6
  23. package/analyzer-template/packages/database/package.json +2 -2
  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 +24 -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.isolateArgs.test.js +51 -0
  53. package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js.map +1 -0
  54. package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js +55 -0
  55. package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js.map +1 -0
  56. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +9 -9
  57. package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +39 -3
  58. package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
  59. package/codeyam-cli/src/commands/editor.js +2407 -466
  60. package/codeyam-cli/src/commands/editor.js.map +1 -1
  61. package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js +23 -0
  62. package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js.map +1 -0
  63. package/codeyam-cli/src/commands/editorIsolateArgs.js +25 -0
  64. package/codeyam-cli/src/commands/editorIsolateArgs.js.map +1 -0
  65. package/codeyam-cli/src/commands/init.js +21 -0
  66. package/codeyam-cli/src/commands/init.js.map +1 -1
  67. package/codeyam-cli/src/commands/telemetry.js +37 -0
  68. package/codeyam-cli/src/commands/telemetry.js.map +1 -0
  69. package/codeyam-cli/src/data/designSystems.js +27 -0
  70. package/codeyam-cli/src/data/designSystems.js.map +1 -0
  71. package/codeyam-cli/src/data/techStacks.js +1 -1
  72. package/codeyam-cli/src/utils/__tests__/devServerState.test.js +93 -1
  73. package/codeyam-cli/src/utils/__tests__/devServerState.test.js.map +1 -1
  74. package/codeyam-cli/src/utils/__tests__/editorApi.test.js +44 -0
  75. package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -1
  76. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +2328 -47
  77. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  78. package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js +137 -0
  79. package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js.map +1 -0
  80. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +70 -0
  81. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -1
  82. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +124 -4
  83. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -1
  84. package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js +67 -0
  85. package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js.map +1 -0
  86. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
  87. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
  88. package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +98 -1
  89. package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -1
  90. package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js +1108 -0
  91. package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js.map +1 -0
  92. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +190 -0
  93. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -1
  94. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +286 -1
  95. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  96. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +134 -1
  97. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
  98. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +266 -2
  99. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  100. package/codeyam-cli/src/utils/__tests__/envFile.test.js +125 -0
  101. package/codeyam-cli/src/utils/__tests__/envFile.test.js.map +1 -0
  102. package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js +177 -0
  103. package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js.map +1 -0
  104. package/codeyam-cli/src/utils/__tests__/handoffContext.test.js +500 -0
  105. package/codeyam-cli/src/utils/__tests__/handoffContext.test.js.map +1 -0
  106. package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js +16 -1
  107. package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js.map +1 -1
  108. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
  109. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
  110. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js +127 -0
  111. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js.map +1 -0
  112. package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +57 -0
  113. package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -1
  114. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +180 -1
  115. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
  116. package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js +84 -0
  117. package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js.map +1 -0
  118. package/codeyam-cli/src/utils/__tests__/telemetry.test.js +159 -0
  119. package/codeyam-cli/src/utils/__tests__/telemetry.test.js.map +1 -0
  120. package/codeyam-cli/src/utils/__tests__/testRunner.test.js +216 -0
  121. package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
  122. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +6 -0
  123. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -1
  124. package/codeyam-cli/src/utils/analysisRunner.js +36 -7
  125. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  126. package/codeyam-cli/src/utils/analyzer.js +11 -1
  127. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  128. package/codeyam-cli/src/utils/backgroundServer.js +1 -1
  129. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  130. package/codeyam-cli/src/utils/designSystemShowcase.js +810 -0
  131. package/codeyam-cli/src/utils/designSystemShowcase.js.map +1 -0
  132. package/codeyam-cli/src/utils/devServerState.js +32 -0
  133. package/codeyam-cli/src/utils/devServerState.js.map +1 -1
  134. package/codeyam-cli/src/utils/editorApi.js +16 -0
  135. package/codeyam-cli/src/utils/editorApi.js.map +1 -1
  136. package/codeyam-cli/src/utils/editorAudit.js +532 -38
  137. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  138. package/codeyam-cli/src/utils/editorEntityChangeStatus.js +13 -7
  139. package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -1
  140. package/codeyam-cli/src/utils/editorEntityHelpers.js +18 -3
  141. package/codeyam-cli/src/utils/editorEntityHelpers.js.map +1 -1
  142. package/codeyam-cli/src/utils/editorGuard.js +36 -0
  143. package/codeyam-cli/src/utils/editorGuard.js.map +1 -0
  144. package/codeyam-cli/src/utils/editorPreview.js +5 -3
  145. package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
  146. package/codeyam-cli/src/utils/editorRecapture.js +109 -0
  147. package/codeyam-cli/src/utils/editorRecapture.js.map +1 -0
  148. package/codeyam-cli/src/utils/editorRoadmap.js +574 -0
  149. package/codeyam-cli/src/utils/editorRoadmap.js.map +1 -0
  150. package/codeyam-cli/src/utils/editorScenarioSwitch.js +39 -2
  151. package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -1
  152. package/codeyam-cli/src/utils/editorScenarios.js +146 -7
  153. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  154. package/codeyam-cli/src/utils/editorSeedAdapter.js +69 -16
  155. package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
  156. package/codeyam-cli/src/utils/entityChangeStatus.js +31 -3
  157. package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
  158. package/codeyam-cli/src/utils/entityChangeStatus.server.js +31 -0
  159. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  160. package/codeyam-cli/src/utils/envFile.js +90 -0
  161. package/codeyam-cli/src/utils/envFile.js.map +1 -0
  162. package/codeyam-cli/src/utils/glossaryAdd.js +74 -0
  163. package/codeyam-cli/src/utils/glossaryAdd.js.map +1 -0
  164. package/codeyam-cli/src/utils/handoffContext.js +257 -0
  165. package/codeyam-cli/src/utils/handoffContext.js.map +1 -0
  166. package/codeyam-cli/src/utils/install-skills.js +41 -6
  167. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  168. package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
  169. package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
  170. package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js +159 -0
  171. package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js.map +1 -0
  172. package/codeyam-cli/src/utils/queue/job.js +35 -6
  173. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  174. package/codeyam-cli/src/utils/registerScenarioResult.js +52 -0
  175. package/codeyam-cli/src/utils/registerScenarioResult.js.map +1 -0
  176. package/codeyam-cli/src/utils/scenarioCoverage.js +4 -1
  177. package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -1
  178. package/codeyam-cli/src/utils/scenariosManifest.js +66 -2
  179. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  180. package/codeyam-cli/src/utils/screenshotHash.js +26 -0
  181. package/codeyam-cli/src/utils/screenshotHash.js.map +1 -0
  182. package/codeyam-cli/src/utils/simulationGateMiddleware.js +9 -0
  183. package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -1
  184. package/codeyam-cli/src/utils/techStackConfig.js +38 -0
  185. package/codeyam-cli/src/utils/techStackConfig.js.map +1 -0
  186. package/codeyam-cli/src/utils/techStackConfig.test.js +85 -0
  187. package/codeyam-cli/src/utils/techStackConfig.test.js.map +1 -0
  188. package/codeyam-cli/src/utils/telemetry.js +106 -0
  189. package/codeyam-cli/src/utils/telemetry.js.map +1 -0
  190. package/codeyam-cli/src/utils/telemetryMiddleware.js +22 -0
  191. package/codeyam-cli/src/utils/telemetryMiddleware.js.map +1 -0
  192. package/codeyam-cli/src/utils/testResultCache.js +53 -0
  193. package/codeyam-cli/src/utils/testResultCache.js.map +1 -0
  194. package/codeyam-cli/src/utils/testResultCache.server.js +81 -0
  195. package/codeyam-cli/src/utils/testResultCache.server.js.map +1 -0
  196. package/codeyam-cli/src/utils/testResultCache.server.test.js +187 -0
  197. package/codeyam-cli/src/utils/testResultCache.server.test.js.map +1 -0
  198. package/codeyam-cli/src/utils/testResultCache.test.js +230 -0
  199. package/codeyam-cli/src/utils/testResultCache.test.js.map +1 -0
  200. package/codeyam-cli/src/utils/testRunner.js +193 -1
  201. package/codeyam-cli/src/utils/testRunner.js.map +1 -1
  202. package/codeyam-cli/src/utils/webappDetection.js +4 -2
  203. package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
  204. package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js +99 -0
  205. package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js.map +1 -0
  206. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +119 -1
  207. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -1
  208. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +68 -1
  209. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -1
  210. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +412 -12
  211. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
  212. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +190 -21
  213. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
  214. package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js +135 -0
  215. package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js.map +1 -0
  216. package/codeyam-cli/src/webserver/app/lib/clientErrors.js +22 -1
  217. package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
  218. package/codeyam-cli/src/webserver/app/lib/database.js.map +1 -1
  219. package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js +34 -0
  220. package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js.map +1 -0
  221. package/codeyam-cli/src/webserver/backgroundServer.js +42 -57
  222. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  223. package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-CzTDWkF2.js → CopyButton-DTBZZfSk.js} +1 -1
  224. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-BFbq6iFk.js → EntityItem-BxclONWq.js} +1 -1
  225. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-B6OMi58N.js → EntityTypeIcon-BsnEOJZ_.js} +1 -1
  226. package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-DuYodzo1.js → InlineSpinner-ByaELMbv.js} +1 -1
  227. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-CXo9EeCl.js → InteractivePreview-6WjVfhxX.js} +2 -2
  228. package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-DYCNb2It.js → LibraryFunctionPreview-ChX-Hp7W.js} +1 -1
  229. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-CZgY3sxX.js → LogViewer-C-9zQdXg.js} +1 -1
  230. package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-Bs2_Oua4.js +36 -0
  231. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-CnYYwRDw.js → ReportIssueModal-DQsceHVv.js} +1 -1
  232. package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-CDoF7ZpU.js → SafeScreenshot-DThcm_9M.js} +1 -1
  233. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-DrnfvaLL.js → ScenarioViewer-Cl4oOA3A.js} +1 -1
  234. package/codeyam-cli/src/webserver/build/client/assets/Spinner-CIil5-gb.js +34 -0
  235. package/codeyam-cli/src/webserver/build/client/assets/{ViewportInspectBar-DRKR9T0U.js → ViewportInspectBar-BqkA9zyZ.js} +1 -1
  236. package/codeyam-cli/src/webserver/build/client/assets/{_index-ClR-g3tY.js → _index-DnOgyseQ.js} +1 -1
  237. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-DTH6ydEA.js → activity.(_tab)-DqM9hbNE.js} +1 -1
  238. package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-74hnHF59.js → addon-web-links-C58dYPwR.js} +1 -1
  239. package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-B8CYhCO9.js → agent-transcripts-B8NCeOrm.js} +1 -1
  240. package/codeyam-cli/src/webserver/build/client/assets/api.editor-database-verify-l0sNRNKZ.js +1 -0
  241. package/codeyam-cli/src/webserver/build/client/assets/api.editor-github-verify-l0sNRNKZ.js +1 -0
  242. package/codeyam-cli/src/webserver/build/client/assets/api.editor-handoff-l0sNRNKZ.js +1 -0
  243. package/codeyam-cli/src/webserver/build/client/assets/api.editor-hosting-verify-l0sNRNKZ.js +1 -0
  244. package/codeyam-cli/src/webserver/build/client/assets/api.editor-recapture-stale-l0sNRNKZ.js +1 -0
  245. package/codeyam-cli/src/webserver/build/client/assets/api.editor-roadmap-l0sNRNKZ.js +1 -0
  246. package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-scenario-data-l0sNRNKZ.js +1 -0
  247. package/codeyam-cli/src/webserver/build/client/assets/api.editor-schema-l0sNRNKZ.js +1 -0
  248. package/codeyam-cli/src/webserver/build/client/assets/api.editor-verify-routes-l0sNRNKZ.js +1 -0
  249. package/codeyam-cli/src/webserver/build/client/assets/api.interactive-switch-scenario-l0sNRNKZ.js +1 -0
  250. package/codeyam-cli/src/webserver/build/client/assets/{book-open-CLaoh4ac.js → book-open-BFSIqZgO.js} +1 -1
  251. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-BZ2DZxbW.js → chevron-down-B9fDzFVh.js} +1 -1
  252. package/codeyam-cli/src/webserver/build/client/assets/chunk-UVKPFVEO-Bmq2apuh.js +43 -0
  253. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-CT4unAk-.js → circle-check-DLPObLUx.js} +1 -1
  254. package/codeyam-cli/src/webserver/build/client/assets/{copy-zK0B6Nu-.js → copy-DXEmO0TD.js} +1 -1
  255. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-DJB0YQJL.js → createLucideIcon-BwyFiRot.js} +1 -1
  256. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-Coe5NhbS.js +1 -0
  257. package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CCKUIm0S.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
  258. package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-CkXFP_i-.js → dev.empty-iRhRIFlp.js} +1 -1
  259. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-BZPBzV73.js +1 -0
  260. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DhtVC4aI.js +161 -0
  261. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-C6fEYHrh.js +41 -0
  262. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BqAN7hyG.js → entity._sha._-pc-vc6wO.js} +13 -12
  263. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-BOi8kpwd.js → entity._sha.scenarios._scenarioId.dev-C8AyYgYT.js} +1 -1
  264. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-Dg1NhIms.js → entity._sha.scenarios._scenarioId.fullscreen-DziaVQX1.js} +1 -1
  265. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-CJX6kkkV.js → entity._sha_.create-scenario-BTcpgIpC.js} +1 -1
  266. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-BhVjZhKg.js → entity._sha_.edit._scenarioId-D_O_ajfZ.js} +1 -1
  267. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-_gzKltPN.js → entry.client-j1Vi0bco.js} +6 -6
  268. package/codeyam-cli/src/webserver/build/client/assets/{files-CV_17tZS.js → files-kuny2Q_s.js} +1 -1
  269. package/codeyam-cli/src/webserver/build/client/assets/{git-D-YXmMbR.js → git-DgCZPMie.js} +1 -1
  270. package/codeyam-cli/src/webserver/build/client/assets/globals-L-aUIeux.css +1 -0
  271. package/codeyam-cli/src/webserver/build/client/assets/{index-CCrgCshv.js → index-BliGSSpl.js} +1 -1
  272. package/codeyam-cli/src/webserver/build/client/assets/{index-Blo6EK8G.js → index-SqjQKTdH.js} +1 -1
  273. package/codeyam-cli/src/webserver/build/client/assets/{index-BsX0F-9C.js → index-vyrZD2g4.js} +1 -1
  274. package/codeyam-cli/src/webserver/build/client/assets/{labs-Byazq8Pv.js → labs-c3yLxSEp.js} +1 -1
  275. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-DVQ0oHR7.js → loader-circle-D-q28GLF.js} +1 -1
  276. package/codeyam-cli/src/webserver/build/client/assets/manifest-79d0d81a.js +1 -0
  277. package/codeyam-cli/src/webserver/build/client/assets/{memory-b-VmA2Vj.js → memory-CEWIUC4t.js} +1 -1
  278. package/codeyam-cli/src/webserver/build/client/assets/{pause-DGcndCAa.js → pause-BP6fitdh.js} +1 -1
  279. package/codeyam-cli/src/webserver/build/client/assets/root-L2V0jea7.js +80 -0
  280. package/codeyam-cli/src/webserver/build/client/assets/{search-C0Uw0bcK.js → search-BooqacKS.js} +1 -1
  281. package/codeyam-cli/src/webserver/build/client/assets/{settings-OoNgHIfW.js → settings-BM0nbryO.js} +1 -1
  282. package/codeyam-cli/src/webserver/build/client/assets/{simulations-Bcemfu8a.js → simulations-ovy6FjRY.js} +1 -1
  283. package/codeyam-cli/src/webserver/build/client/assets/{terminal-BgMmG7R9.js → terminal-DHemCJIs.js} +1 -1
  284. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-Cs87hJYK.js → triangle-alert-D87ekDl8.js} +1 -1
  285. package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-BR3Rs7JY.js → useCustomSizes-Dk0Tciqg.js} +1 -1
  286. package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-C8QvIe05.js +2 -0
  287. package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-BermyNU5.js → useReportContext-jkCytuYz.js} +1 -1
  288. package/codeyam-cli/src/webserver/build/client/assets/{useToast-a_QN_W9_.js → useToast-BgqkixU9.js} +1 -1
  289. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-QgInFGdU.js +16 -0
  290. package/codeyam-cli/src/webserver/build/server/assets/{index-Cr7d_IsG.js → index-zblh9auj.js} +1 -1
  291. package/codeyam-cli/src/webserver/build/server/assets/init-DaE0CBjk.js +14 -0
  292. package/codeyam-cli/src/webserver/build/server/assets/server-build-CNvgz1cC.js +853 -0
  293. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  294. package/codeyam-cli/src/webserver/build-info.json +5 -5
  295. package/codeyam-cli/src/webserver/editorProxy.js +329 -25
  296. package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
  297. package/codeyam-cli/src/webserver/idleDetector.js +65 -8
  298. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
  299. package/codeyam-cli/src/webserver/scripts/journalCapture.ts +53 -0
  300. package/codeyam-cli/src/webserver/server.js +151 -14
  301. package/codeyam-cli/src/webserver/server.js.map +1 -1
  302. package/codeyam-cli/src/webserver/terminalServer.js +253 -41
  303. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  304. package/codeyam-cli/templates/__tests__/editor-step-hook.prompt-capture.test.ts +118 -0
  305. package/codeyam-cli/templates/codeyam-editor-claude.md +2 -0
  306. package/codeyam-cli/templates/codeyam-editor-codex.md +61 -0
  307. package/codeyam-cli/templates/codeyam-editor-gemini.md +59 -0
  308. package/codeyam-cli/templates/codeyam-editor-reference.md +216 -0
  309. package/codeyam-cli/templates/design-systems/clean-dashboard-design-system.md +255 -0
  310. package/codeyam-cli/templates/design-systems/editorial-design-system.md +267 -0
  311. package/codeyam-cli/templates/design-systems/mono-brutalist-design-system.md +256 -0
  312. package/codeyam-cli/templates/design-systems/neo-brutalist-design-system.md +294 -0
  313. package/codeyam-cli/templates/editor-step-hook.py +93 -46
  314. package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +204 -5
  315. package/codeyam-cli/templates/expo-react-native/__tests__/.gitkeep +0 -0
  316. package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +6 -3
  317. package/codeyam-cli/templates/expo-react-native/app/index.tsx +36 -0
  318. package/codeyam-cli/templates/expo-react-native/app.json +11 -0
  319. package/codeyam-cli/templates/expo-react-native/babel.config.js +1 -0
  320. package/codeyam-cli/templates/expo-react-native/gitignore +2 -0
  321. package/codeyam-cli/templates/expo-react-native/global.css +7 -0
  322. package/codeyam-cli/templates/expo-react-native/lib/theme.ts +73 -0
  323. package/codeyam-cli/templates/expo-react-native/package.json +32 -16
  324. package/codeyam-cli/templates/expo-react-native/patches/expo-modules-autolinking+3.0.24.patch +29 -0
  325. package/codeyam-cli/templates/isolation-route/expo-router.tsx.template +54 -0
  326. package/codeyam-cli/templates/nextjs-prisma-sqlite/gitignore +1 -0
  327. package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +1 -1
  328. package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +47 -34
  329. package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +1 -1
  330. package/codeyam-cli/templates/seed-adapters/supabase.ts +271 -78
  331. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +34 -1
  332. package/package.json +2 -1
  333. package/packages/ai/src/lib/astScopes/methodSemantics.js +99 -0
  334. package/packages/ai/src/lib/astScopes/methodSemantics.js.map +1 -1
  335. package/packages/ai/src/lib/astScopes/nodeToSource.js +16 -0
  336. package/packages/ai/src/lib/astScopes/nodeToSource.js.map +1 -1
  337. package/packages/ai/src/lib/astScopes/paths.js +12 -3
  338. package/packages/ai/src/lib/astScopes/paths.js.map +1 -1
  339. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +27 -10
  340. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  341. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
  342. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
  343. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
  344. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  345. package/packages/analyze/index.js +1 -1
  346. package/packages/analyze/index.js.map +1 -1
  347. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
  348. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  349. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
  350. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  351. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +3 -2
  352. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
  353. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +9 -7
  354. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
  355. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
  356. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
  357. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
  358. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
  359. package/packages/analyze/src/lib/files/analyzeChange.js +1 -0
  360. package/packages/analyze/src/lib/files/analyzeChange.js.map +1 -1
  361. package/packages/analyze/src/lib/files/analyzeInitial.js +1 -0
  362. package/packages/analyze/src/lib/files/analyzeInitial.js.map +1 -1
  363. package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
  364. package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
  365. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +120 -28
  366. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  367. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +1368 -1193
  368. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  369. package/packages/database/src/lib/loadAnalysis.js +7 -1
  370. package/packages/database/src/lib/loadAnalysis.js.map +1 -1
  371. package/packages/database/src/lib/loadEntity.js +5 -5
  372. package/packages/database/src/lib/loadEntity.js.map +1 -1
  373. package/packages/utils/src/lib/fs/rsyncCopy.js +22 -1
  374. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  375. package/codeyam-cli/src/webserver/build/client/assets/Spinner-Df3UCi8k.js +0 -34
  376. package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-BBXArFPl.js +0 -43
  377. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DcX-ZS3p.js +0 -1
  378. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-DPw7NZHc.js +0 -1
  379. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DmBK1JBK.js +0 -58
  380. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DBa7T2FK.js +0 -41
  381. package/codeyam-cli/src/webserver/build/client/assets/globals-CGrDAxj0.css +0 -1
  382. package/codeyam-cli/src/webserver/build/client/assets/manifest-b3f77062.js +0 -1
  383. package/codeyam-cli/src/webserver/build/client/assets/root-D5Zi3U2Z.js +0 -67
  384. package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-BxxP_XF9.js +0 -2
  385. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-yTyb36j3.js +0 -13
  386. package/codeyam-cli/src/webserver/build/server/assets/init-M_wqNAfu.js +0 -10
  387. package/codeyam-cli/src/webserver/build/server/assets/server-build-_ybRgrlc.js +0 -551
  388. package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +0 -33
  389. package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +0 -12
  390. package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +0 -12
@@ -13,35 +13,41 @@ 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";
27
+ import { updateHandoffProgress } from "../utils/handoffContext.js";
25
28
  import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
29
+ import { getDisplayVersion } from "../utils/versionInfo.js";
26
30
  const __filename = fileURLToPath(import.meta.url);
27
31
  const __dirname = path.dirname(__filename);
28
32
  const STEP_LABELS = {
29
33
  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',
34
+ 2: 'Prepare',
35
+ 3: 'Prototype',
36
+ 4: 'Verify Prototype',
37
+ 5: 'Confirm',
38
+ 6: 'Deconstruct',
39
+ 7: 'Extract',
40
+ 8: 'Glossary',
41
+ 9: 'Analyze',
42
+ 10: 'App Scenarios',
43
+ 11: 'User Scenarios',
44
+ 12: 'Verify',
45
+ 13: 'Journal',
46
+ 14: 'Review',
47
+ 15: 'Present',
48
+ 16: 'Commit',
49
+ 17: 'Finalize',
50
+ 18: 'Push',
45
51
  };
46
52
  const MIGRATION_STEP_LABELS = {
47
53
  1: 'Survey',
@@ -119,11 +125,19 @@ function writeState(root, state) {
119
125
  const dir = path.dirname(statePath);
120
126
  fs.mkdirSync(dir, { recursive: true });
121
127
  fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
128
+ // Write task tracking file — marks that a task is expected for this step.
129
+ // The step hook sets taskCreated=true when it sees a TaskCreate tool call.
130
+ // Steps 1 and below don't require tasks (feature not named yet).
131
+ if (state.step && state.step >= 2) {
132
+ const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
133
+ fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: false }, null, 2), 'utf8');
134
+ }
122
135
  }
123
136
  /**
124
137
  * 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.
138
+ * Does NOT clear the user prompt file — the prompt is cleared
139
+ * by the journal API after recording, and the hook captures
140
+ * a fresh prompt for the next feature on UserPromptSubmit.
127
141
  */
128
142
  function clearState(root) {
129
143
  clearEditorState(root);
@@ -177,6 +191,181 @@ function getProjectDimensions(root) {
177
191
  return { defaultName: 'Desktop', names: [] };
178
192
  }
179
193
  }
194
+ function getTechStackContext(root) {
195
+ const state = readState(root);
196
+ const techStackId = state?.techStackId || '';
197
+ const stack = TECH_STACKS.find((s) => s.id === techStackId);
198
+ const isExpo = techStackId === 'expo-react-native';
199
+ const isChromeExt = techStackId === 'chrome-extension-react';
200
+ const isNextjs = techStackId.startsWith('nextjs-') || (!isExpo && !isChromeExt);
201
+ return {
202
+ id: techStackId || 'nextjs-prisma-sqlite',
203
+ isExpo,
204
+ isNextjs,
205
+ isChromeExt,
206
+ hasDatabase: isNextjs,
207
+ testRunner: isNextjs ? 'vitest' : 'jest',
208
+ testRunCommand: isNextjs ? 'npx vitest run' : 'npx jest',
209
+ storageType: isExpo
210
+ ? 'asyncStorage'
211
+ : isChromeExt
212
+ ? 'chromeStorage'
213
+ : 'prisma',
214
+ routerImport: isExpo
215
+ ? 'expo-router'
216
+ : isChromeExt
217
+ ? 'react-router-dom'
218
+ : 'next/navigation',
219
+ componentPrimitives: isExpo
220
+ ? '<View>, <Text>, <ScrollView>'
221
+ : '<div>, <span>, <h1>',
222
+ rawPrimitivesList: isExpo
223
+ ? '<View>, <Text>, <Image>, <ScrollView>, <FlatList>'
224
+ : '<div>, <span>, <h1>, <p>, <img>, <ul>',
225
+ patternsFile: isExpo
226
+ ? 'MOBILE_SETUP.md'
227
+ : isChromeExt
228
+ ? 'EXTENSION_SETUP.md'
229
+ : 'FEATURE_PATTERNS.md',
230
+ };
231
+ }
232
+ /**
233
+ * Returns a pre-populated tech stack for a given template ID.
234
+ * Written to .codeyam/config.json during `codeyam editor template`.
235
+ */
236
+ function getTechStackForTemplate(templateId) {
237
+ switch (templateId) {
238
+ case 'nextjs-prisma-sqlite':
239
+ return {
240
+ languages: [
241
+ {
242
+ name: 'TypeScript',
243
+ url: 'https://typescriptlang.org',
244
+ description: 'Statically typed JavaScript superset',
245
+ version: '5',
246
+ },
247
+ ],
248
+ frameworks: [
249
+ {
250
+ name: 'Next.js',
251
+ url: 'https://nextjs.org',
252
+ description: 'Full-stack React framework with App Router, SSR, and API routes',
253
+ version: '15',
254
+ },
255
+ {
256
+ name: 'React',
257
+ url: 'https://react.dev',
258
+ description: 'Component-based UI library',
259
+ version: '19',
260
+ },
261
+ ],
262
+ databases: [
263
+ {
264
+ name: 'SQLite',
265
+ url: 'https://sqlite.org',
266
+ description: 'Embedded relational database — zero config, upgradeable to hosted DB',
267
+ },
268
+ ],
269
+ libraries: [
270
+ {
271
+ name: 'Prisma',
272
+ url: 'https://prisma.io',
273
+ description: 'Type-safe database ORM and query builder',
274
+ version: '7',
275
+ },
276
+ {
277
+ name: 'Tailwind CSS',
278
+ url: 'https://tailwindcss.com',
279
+ description: 'Utility-first CSS framework',
280
+ version: '4',
281
+ },
282
+ ],
283
+ };
284
+ case 'chrome-extension-react':
285
+ return {
286
+ languages: [
287
+ {
288
+ name: 'TypeScript',
289
+ url: 'https://typescriptlang.org',
290
+ description: 'Statically typed JavaScript superset',
291
+ version: '5',
292
+ },
293
+ ],
294
+ frameworks: [
295
+ {
296
+ name: 'React',
297
+ url: 'https://react.dev',
298
+ description: 'Component-based UI library for the popup and options pages',
299
+ version: '19',
300
+ },
301
+ {
302
+ name: 'Vite',
303
+ url: 'https://vite.dev',
304
+ description: 'Fast build tool and dev server',
305
+ version: '6',
306
+ },
307
+ ],
308
+ libraries: [
309
+ {
310
+ name: 'Tailwind CSS',
311
+ url: 'https://tailwindcss.com',
312
+ description: 'Utility-first CSS framework',
313
+ version: '4',
314
+ },
315
+ ],
316
+ infrastructure: [
317
+ {
318
+ name: 'Chrome Manifest V3',
319
+ url: 'https://developer.chrome.com/docs/extensions/develop/migrate/what-is-mv3',
320
+ description: 'Extension platform with service workers, declarative APIs, and chrome.storage',
321
+ },
322
+ ],
323
+ };
324
+ case 'expo-react-native':
325
+ return {
326
+ languages: [
327
+ {
328
+ name: 'TypeScript',
329
+ url: 'https://typescriptlang.org',
330
+ description: 'Statically typed JavaScript superset',
331
+ version: '5',
332
+ },
333
+ ],
334
+ frameworks: [
335
+ {
336
+ name: 'Expo',
337
+ url: 'https://expo.dev',
338
+ description: 'React Native development platform with managed workflow and OTA updates',
339
+ version: 'SDK 54',
340
+ },
341
+ {
342
+ name: 'React Native',
343
+ url: 'https://reactnative.dev',
344
+ description: 'Cross-platform mobile UI framework',
345
+ },
346
+ {
347
+ name: 'Expo Router',
348
+ url: 'https://docs.expo.dev/router/introduction/',
349
+ description: 'File-based navigation for React Native apps',
350
+ },
351
+ ],
352
+ libraries: [
353
+ {
354
+ name: 'NativeWind',
355
+ url: 'https://www.nativewind.dev',
356
+ description: 'Tailwind CSS for React Native',
357
+ },
358
+ {
359
+ name: 'AsyncStorage',
360
+ url: 'https://react-native-async-storage.github.io/async-storage/',
361
+ description: 'Persistent key-value storage for React Native',
362
+ },
363
+ ],
364
+ };
365
+ default:
366
+ return {};
367
+ }
368
+ }
180
369
  /**
181
370
  * Print dimension guidance when the project has multiple screen sizes.
182
371
  * Tells Claude to pick the right dimension for the content being previewed
@@ -200,14 +389,97 @@ function checkbox(text) {
200
389
  const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
201
390
  console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
202
391
  }
392
+ /**
393
+ * Print a checklist item for the handoff summary.
394
+ */
395
+ function checkboxHandoff() {
396
+ checkbox('Update the handoff summary in `.codeyam/config.json` for the next session/AI: `codeyam editor handoff \'{"summary":"..."}\'`');
397
+ }
398
+ /**
399
+ * Print the handoff context from a previous session or AI provider.
400
+ * Prefers the auto-generated handoff-context.md over the raw config summary.
401
+ */
402
+ function printHandoffContext(root) {
403
+ try {
404
+ // Check for auto-generated handoff context (richer, includes progress timeline)
405
+ const handoffContextPath = path.join(root, '.codeyam', 'handoff-context.md');
406
+ if (fs.existsSync(handoffContextPath)) {
407
+ const content = fs.readFileSync(handoffContextPath, 'utf8');
408
+ console.log();
409
+ console.log(chalk.bold.magenta('━━━ PROVIDER HANDOFF CONTEXT ━━━'));
410
+ console.log(chalk.magenta(content));
411
+ console.log(chalk.bold.magenta('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
412
+ console.log();
413
+ return;
414
+ }
415
+ // Fall back to config.json handoff summary
416
+ const configPath = path.join(root, '.codeyam', 'config.json');
417
+ if (!fs.existsSync(configPath))
418
+ return;
419
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
420
+ const handoff = config.handoff;
421
+ if (!handoff || !handoff.summary)
422
+ return;
423
+ console.log();
424
+ console.log(chalk.bold.magenta(`━━━ HANDOFF FROM ${String(handoff.lastProvider || 'unknown').toUpperCase()} (Step ${handoff.lastStep || 'unknown'}) ━━━`));
425
+ console.log(chalk.magenta(handoff.summary));
426
+ if (handoff.lastUpdated) {
427
+ console.log(chalk.dim(` Updated: ${handoff.lastUpdated}`));
428
+ }
429
+ console.log(chalk.bold.magenta('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
430
+ console.log();
431
+ }
432
+ catch {
433
+ // Non-fatal
434
+ }
435
+ }
436
+ /**
437
+ * Instructions for creating/updating .codeyam/data-structure.json.
438
+ * Only prints creation instructions if the file doesn't exist yet.
439
+ * If it exists, reminds Claude to update it if data models changed.
440
+ */
441
+ function printServiceRecordingReminder(port) {
442
+ console.log(chalk.bold('Third-party services:'));
443
+ checkbox('When integrating any external service (auth, email, payments, storage, telemetry), update the tech stack');
444
+ console.log(chalk.dim(' Include the service name, URL, description, and envKeys (environment variable names it requires).'));
445
+ console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
446
+ console.log(chalk.dim(` -d '{"techStack": {"services": [{"name":"Stripe","url":"https://stripe.com","description":"Payments","envKeys":["STRIPE_SECRET_KEY","STRIPE_PUBLISHABLE_KEY"]}]}}'`));
447
+ console.log(chalk.dim(' This ensures the deploy roadmap tracks all services that need production credentials.'));
448
+ }
449
+ function printDataStructureInstructions() {
450
+ const root = getProjectRoot();
451
+ const dsPath = path.join(root, '.codeyam', 'data-structure.json');
452
+ const exists = fs.existsSync(dsPath);
453
+ if (!exists) {
454
+ console.log(chalk.bold('Create the data structure config:'));
455
+ checkbox('Create `.codeyam/data-structure.json` describing all data sources');
456
+ console.log(chalk.dim(" This file tells the editor what data the app uses and how it's stored."));
457
+ console.log(chalk.dim(' The file is a JSON array. Each entry describes one data source:'));
458
+ console.log(chalk.dim(' { "name": "Drink", "description": "...", "category": "datastore", "order": 1,'));
459
+ console.log(chalk.dim(' "fields": [{ "name": "id", "type": "number", "isId": true, "required": true }, ...] }'));
460
+ console.log();
461
+ console.log(chalk.dim(' category: "datastore" for persisted data (DB, localStorage)'));
462
+ console.log(chalk.dim(' "mock-api" for mocked external API responses'));
463
+ console.log(chalk.dim(' order: 1 = primary data store, 2 = secondary, etc.'));
464
+ console.log(chalk.dim(' Do NOT include types only used as component props.'));
465
+ }
466
+ else {
467
+ checkbox('If this feature changes data models, update `.codeyam/data-structure.json`');
468
+ console.log(chalk.dim(' Add new tables, update fields, or change descriptions to match.'));
469
+ }
470
+ console.log();
471
+ }
203
472
  /**
204
473
  * Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
205
474
  * Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
206
475
  */
207
476
  function printAppScenarioInstructions(pageName, route) {
477
+ const root = process.cwd();
478
+ const hasSeedAdapter = !!detectSeedAdapter(root);
208
479
  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.'));
480
+ console.log(chalk.dim(' Review existing scenarios — enhance their seed data to exercise new features.'));
481
+ console.log(chalk.dim(' A rich scenario that exercises 5 features is better than 5 thin scenarios with one feature each.'));
482
+ console.log(chalk.dim(' Rename scenarios if their data scope has grown beyond the original name.'));
211
483
  console.log();
212
484
  console.log(chalk.bold.yellow('App scenarios vs component scenarios — these are DIFFERENT:'));
213
485
  console.log(chalk.yellow(' App scenarios: show the FULL PAGE with seeded data at a real route (/, /library, etc.)'));
@@ -224,21 +496,42 @@ function printAppScenarioInstructions(pageName, route) {
224
496
  console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
225
497
  }
226
498
  console.log();
499
+ checkbox('Ensure every page file used in app scenarios has a glossary entry with its filePath:');
500
+ console.log(chalk.dim(' The register command validates pageFilePath against the glossary — add missing pages now.'));
501
+ console.log(chalk.dim(' Example: {"name":"CounterScreen","filePath":"app/(tabs)/index.tsx","type":"page","description":"Main counter page"}'));
502
+ console.log();
227
503
  checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
228
504
  console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
229
505
  console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
230
506
  console.log(chalk.yellow(' Required fields: "type":"application", "url" (real route), "pageFilePath" (source file)'));
231
507
  console.log(chalk.yellow(' Do NOT include "componentName" — that would make it a component scenario'));
232
508
  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'));
509
+ if (hasSeedAdapter) {
510
+ checkbox(chalk.bold.red('REQUIRED: Every app scenario MUST include "seed" data without it the page will be empty!'));
511
+ console.log(chalk.yellow(' A seed adapter exists — you MUST include "seed":{...} in every app scenario registration.'));
512
+ console.log(chalk.yellow(' An app scenario without seed data will render an empty page (no database rows = nothing to show).'));
513
+ console.log(chalk.dim(' "seed" maps table names to arrays of rows: "seed":{"user":[{"id":1,"name":"Alice"}],"article":[...]}'));
514
+ console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
515
+ }
516
+ else {
517
+ const appCtx = getTechStackContext(root);
518
+ checkbox('Include data in every app scenario — without it the page will be empty:');
519
+ if (appCtx.isExpo) {
520
+ console.log(chalk.dim(' Use "localStorage":{"items":"[...]"} to pre-populate AsyncStorage (values are JSON strings)'));
521
+ console.log(chalk.dim(" AsyncStorage uses localStorage on web — CodeYam's injection works automatically."));
522
+ }
523
+ console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
524
+ console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
525
+ }
526
+ console.log();
527
+ console.log(chalk.bold('Register ALL scenarios at once (bulk registration):'));
528
+ console.log(chalk.dim(' Write an array of scenario objects to a temp file:'));
529
+ console.log(chalk.dim(' [{"name":"...","type":"application","url":"/","seed":{...}}, {"name":"...","url":"/other",...}]'));
530
+ console.log(chalk.dim(' Then register all at once: codeyam editor register @.codeyam/tmp/scenarios.json'));
531
+ console.log(chalk.yellow(' Bulk registration is preferred — faster and avoids repeated capture overhead.'));
532
+ console.log();
240
533
  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`');
534
+ checkbox('After registration, check the response for `clientErrors`');
242
535
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
243
536
  console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
244
537
  }
@@ -274,6 +567,12 @@ function printExtractionPlanInstructions() {
274
567
  console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
275
568
  console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
276
569
  console.log();
570
+ console.log(chalk.bold.red('THE DECOMPOSITION RULE: All code should do one thing.'));
571
+ console.log(chalk.yellow(' Code either contains logic OR brings together smaller pieces to form a coordinated whole.'));
572
+ console.log(chalk.yellow(' Every opportunity to extract code into a sensible function, helper, or sub-component MUST be taken.'));
573
+ console.log(chalk.yellow(' Then extract AGAIN from the extracted code — keep going until each piece does one clearly defined thing.'));
574
+ console.log(chalk.yellow(' This applies to ALL code: backend routes, business logic, frontend components, utilities — everything.'));
575
+ console.log();
277
576
  console.log(chalk.bold('Checklist:'));
278
577
  checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
279
578
  checkbox('Read EVERY file used by this page/feature');
@@ -284,6 +583,12 @@ function printExtractionPlanInstructions() {
284
583
  console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
285
584
  console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
286
585
  console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
586
+ console.log(chalk.yellow(' Inline logic — backend AND frontend (MUST be extracted as named functions):'));
587
+ console.log(chalk.yellow(' conditionals/ternaries, computed/derived values, data transformations,'));
588
+ console.log(chalk.yellow(' string formatting/interpolation, array filtering/sorting/mapping callbacks,'));
589
+ console.log(chalk.yellow(' default value logic, null/undefined guards, date/number formatting,'));
590
+ console.log(chalk.yellow(' permission/role checks, status derivation (e.g. isOverdue(task)),'));
591
+ console.log(chalk.yellow(' request/response shaping, error message construction, config lookups'));
287
592
  console.log();
288
593
  checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
289
594
  console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
@@ -294,17 +599,34 @@ function printExtractionPlanInstructions() {
294
599
  console.log(chalk.yellow(' — What it is (component, function, hook)'));
295
600
  console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
296
601
  console.log(chalk.yellow(' — Where it will go (new file path)'));
602
+ console.log(chalk.yellow(' — How you will test it (key inputs/outputs and edge cases)'));
603
+ console.log(chalk.yellow(' — What logic inside it can be further extracted as a separate testable function'));
297
604
  console.log();
298
- console.log(chalk.dim('Present the numbered plan, then proceed to step 5 to execute it.'));
605
+ console.log(chalk.bold.red('BEFORE FINALIZING THE PLAN:'));
606
+ console.log(chalk.yellow(' Re-read every file ONE MORE TIME. For each piece of code, ask:'));
607
+ console.log(chalk.yellow(' 1. Does this do more than one thing? → Split it into pieces that each do one thing.'));
608
+ console.log(chalk.yellow(' 2. Is there logic here that could be its own function with clear inputs/outputs? → Extract it.'));
609
+ console.log(chalk.yellow(' 3. Can the extracted code itself be broken down further? → Keep extracting.'));
610
+ console.log(chalk.yellow(' If your plan has fewer functions than components, you probably missed extractable logic.'));
611
+ console.log();
612
+ console.log(chalk.dim('Present the numbered plan, then proceed to step 7 to execute it.'));
299
613
  }
300
614
  /**
301
615
  * Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
302
616
  * Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
303
617
  */
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.'));
618
+ function printComponentCaptureInstructions(root) {
619
+ const ctx = root ? getTechStackContext(root) : undefined;
620
+ const isExpo = ctx?.isExpo ?? false;
621
+ checkbox('Set up isolation routes: `codeyam editor isolate ComponentA ComponentB ...`');
622
+ if (isExpo) {
623
+ console.log(chalk.dim(' This creates app/isolated-components/_layout.tsx (with __DEV__ guard).'));
624
+ console.log(chalk.dim(' You then create a flat .tsx file per component (NOT subdirectories — Expo Router uses flat files).'));
625
+ }
626
+ else {
627
+ console.log(chalk.dim(` This creates app/isolated-components/layout.tsx (with notFound() guard) and`));
628
+ console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
629
+ }
308
630
  checkbox('For each visual component:');
309
631
  console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
310
632
  console.log(chalk.dim(' — Props/interface'));
@@ -315,23 +637,37 @@ function printComponentCaptureInstructions() {
315
637
  console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
316
638
  console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
317
639
  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'));
640
+ if (isExpo) {
641
+ console.log(chalk.dim(' Expo: app/isolated-components/ComponentName.tsx → /isolated-components/ComponentName'));
642
+ console.log(chalk.dim(' Use useLocalSearchParams() from expo-router to read ?s=ScenarioName.'));
643
+ }
644
+ else {
645
+ console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
646
+ console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
647
+ }
320
648
  console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
321
649
  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'));
650
+ if (isExpo) {
651
+ console.log(chalk.dim(' Wrap the component in a capture container with nativeID="codeyam-capture":'));
652
+ console.log(chalk.dim(' <View nativeID="codeyam-capture" style={{ display:"flex" }}>'));
653
+ }
654
+ else {
655
+ console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
656
+ console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
657
+ }
658
+ console.log(chalk.dim(isExpo
659
+ ? ' <View style={{ width:"100%", maxWidth:... }}> ← match the app\'s container width'
660
+ : ' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
661
+ console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth: 384, full-width component → omit maxWidth'));
326
662
  console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
327
663
  console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
328
664
  console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
329
665
  console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
330
666
  console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
331
667
  console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
332
- console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
668
+ console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
333
669
  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/...).'));
670
+ console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/isolated-components/...).'));
335
671
  console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
336
672
  console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
337
673
  console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
@@ -339,6 +675,10 @@ function printComponentCaptureInstructions() {
339
675
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
340
676
  console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
341
677
  console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
678
+ console.log();
679
+ console.log(chalk.bold(' Bulk registration: write all component scenarios to a temp file as an array:'));
680
+ console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
681
+ console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
342
682
  }
343
683
  /**
344
684
  * Print a section header.
@@ -352,13 +692,13 @@ function stepHeader(step, title, feature) {
352
692
  console.log();
353
693
  }
354
694
  /**
355
- * Print a colored progress tracker showing all 16 steps.
695
+ * Print a colored progress tracker showing all 18 steps.
356
696
  * Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
357
697
  */
358
698
  function printProgressTracker(current) {
359
699
  console.log();
360
700
  console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
361
- for (let i = 1; i <= 16; i++) {
701
+ for (let i = 1; i <= 18; i++) {
362
702
  const label = STEP_LABELS[i];
363
703
  const num = i < 10 ? ` ${i}` : `${i}`;
364
704
  const content = `${num}. ${label.padEnd(28)}`;
@@ -379,9 +719,33 @@ function printProgressTracker(current) {
379
719
  *
380
720
  * Options:
381
721
  * - confirm: true → step requires user confirmation before proceeding (steps 1, 3, 11)
722
+ * - feature: string → feature name for task directive
382
723
  */
383
724
  function stopGate(current, opts) {
384
- console.log();
725
+ const totalSteps = Object.keys(STEP_LABELS).length;
726
+ const currentLabel = STEP_LABELS[current] || `Step ${current}`;
727
+ const nextLabel = current < totalSteps ? STEP_LABELS[current + 1] : undefined;
728
+ console.log();
729
+ // ━━━ TASK ━━━ — deterministic task lifecycle directive
730
+ // Skip step 1 (no feature name yet — task starts at step 2)
731
+ if (current >= 2) {
732
+ console.log(chalk.bold.cyan('━━━ TASK ━━━'));
733
+ console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
734
+ console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
735
+ console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
736
+ console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
737
+ console.log();
738
+ let taskTitle;
739
+ if (current < totalSteps) {
740
+ taskTitle = `Complete codeyam editor step ${current}: '${currentLabel}' and move on to step ${current + 1}: '${nextLabel}'`;
741
+ }
742
+ else {
743
+ taskTitle =
744
+ 'Ask user what to build next and restart codeyam editor workflow';
745
+ }
746
+ console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
747
+ console.log();
748
+ }
385
749
  console.log(chalk.bold.red('━━━ STOP ━━━'));
386
750
  console.log();
387
751
  console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
@@ -390,20 +754,12 @@ function stopGate(current, opts) {
390
754
  console.log(chalk.yellow('Wait for user confirmation before moving on.'));
391
755
  }
392
756
  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) {
757
+ if (current < totalSteps) {
400
758
  console.log(chalk.green('When done, run: ') +
401
759
  chalk.bold(`codeyam editor ${current + 1}`));
402
760
  }
403
761
  else {
404
- console.log(chalk.green('Feature complete! Run: ') +
405
- chalk.bold('codeyam editor 1') +
406
- chalk.green(' to start the next feature'));
762
+ console.log(chalk.green('Feature complete! Ask the user what to build next, then run: ') + chalk.bold('codeyam editor 1'));
407
763
  }
408
764
  console.log();
409
765
  }
@@ -418,34 +774,36 @@ function printResumptionHeader(step) {
418
774
  'Check if plan was already written to context file:\n `cat .codeyam/editor-mode-context.md`',
419
775
  ],
420
776
  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: [
777
+ 3: ['Re-check is safe — just re-run the step'],
778
+ 4: ['Re-verify is safe to repeat — just re-run the checks'],
779
+ 5: ['This is a confirmation step — just re-present to user'],
780
+ 6: [
423
781
  'Check if extraction plan already exists in context file:\n `cat .codeyam/editor-mode-context.md`',
424
782
  ],
425
- 5: [
783
+ 7: [
426
784
  'Check if components/functions already extracted:\n `ls src/components/ src/lib/`',
427
785
  ],
428
- 6: [
786
+ 8: [
429
787
  'Check if glossary already populated:\n `cat .codeyam/glossary.json`',
430
788
  ],
431
- 7: ['Check if isolation routes and registered scenarios already exist'],
432
- 8: ['Check existing scenarios:\n `codeyam editor scenarios`'],
433
- 9: [
789
+ 9: ['Check if isolation routes and registered scenarios already exist'],
790
+ 10: ['Check existing scenarios:\n `codeyam editor scenarios`'],
791
+ 11: [
434
792
  'Check existing user-persona scenarios:\n `codeyam editor scenarios`',
435
793
  ],
436
- 10: ['Re-verify is safe to repeat — just re-run the checks'],
437
- 11: [
794
+ 12: ['Re-verify is safe to repeat — just re-run the checks'],
795
+ 13: [
438
796
  'Check if a journal entry already exists for this feature:\n `codeyam editor journal-list`',
439
797
  'If an entry exists, use PATCH to update it — do NOT create a new one',
440
798
  ],
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: [
799
+ 14: ['Re-verify is safe to repeat — just re-run the checks'],
800
+ 15: ['Check if commit already made:\n `git log --oneline -3`'],
801
+ 16: ['Check if commit already made:\n `git log --oneline -3`'],
802
+ 17: [
445
803
  'Check if journal was already updated with commit SHA:\n `codeyam editor journal-list`',
446
804
  'Check if commit was already amended:\n `git log --oneline -3`',
447
805
  ],
448
- 16: [
806
+ 18: [
449
807
  'Check if already pushed:\n `git remote -v && git log --oneline origin/HEAD..HEAD 2>/dev/null`',
450
808
  ],
451
809
  };
@@ -552,7 +910,7 @@ function parseDebugTargets(target) {
552
910
  }
553
911
  if (entry.startsWith('step-')) {
554
912
  const num = parseInt(entry.replace('step-', ''), 10);
555
- if (!isNaN(num) && num >= 1 && num <= 16) {
913
+ if (!isNaN(num) && num >= 1 && num <= 18) {
556
914
  valid.add(`step-${num}`);
557
915
  continue;
558
916
  }
@@ -638,11 +996,17 @@ function printSetup(root) {
638
996
  console.log();
639
997
  // ── Design System ────────────────────────────────────────────────
640
998
  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?"'));
999
+ console.log(chalk.dim(' Ask: "What visual style do you want? Pick a built-in design system or bring your own."'));
642
1000
  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"'));
1001
+ console.log();
1002
+ for (const ds of DESIGN_SYSTEMS) {
1003
+ console.log(chalk.yellow(` Option label: "${ds.name}"`) +
1004
+ chalk.dim(` — ${ds.description}`));
1005
+ console.log(chalk.dim(` → Run: codeyam editor design-system ${ds.id}`));
1006
+ }
1007
+ console.log(chalk.yellow(' Option label: "I\'ll paste my own design system"'));
644
1008
  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"'));
1009
+ console.log(chalk.yellow(' Option label: "Skip use sensible defaults"'));
646
1010
  console.log(chalk.dim(' → Skip'));
647
1011
  console.log();
648
1012
  console.log(chalk.bold('Checklist:'));
@@ -663,8 +1027,8 @@ function printSetup(root) {
663
1027
  console.log(chalk.bold('Tech Stack Selection:'));
664
1028
  console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
665
1029
  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.'));
1030
+ 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.").'));
1031
+ console.log(chalk.dim(' If MULTIPLE stacks match, use AskUserQuestion to let the user pick one.'));
668
1032
  console.log();
669
1033
  console.log(chalk.bold(' Available Tech Stacks:'));
670
1034
  for (const stack of TECH_STACKS) {
@@ -697,7 +1061,7 @@ function printSetup(root) {
697
1061
  console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
698
1062
  console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
699
1063
  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).'));
1064
+ 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
1065
  console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
702
1066
  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
1067
  console.log();
@@ -769,37 +1133,39 @@ function printCycleOverview(root, state) {
769
1133
  console.log();
770
1134
  console.log(chalk.green('Continue with: ') +
771
1135
  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'));
1136
+ 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
1137
  console.log();
776
1138
  console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
777
1139
  chalk.bold('codeyam editor change'));
778
1140
  console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
779
1141
  }
780
1142
  else {
781
- console.log('Each feature follows 16 steps. You MUST run each command in order:');
1143
+ console.log('Each feature follows 18 steps. You MUST run each command in order:');
782
1144
  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`);
1145
+ console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
1146
+ console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prepare')} Set up project and dependencies`);
1147
+ console.log(` ${chalk.bold.yellow(' 3')} ${chalk.bold('Prototype')} Build a working prototype fast`);
1148
+ console.log(` ${chalk.bold.yellow(' 4')} ${chalk.bold('Verify Prototype')} Verify prototype works correctly`);
1149
+ console.log(` ${chalk.bold.yellow(' 5')} ${chalk.bold('Confirm')} Confirm prototype with user`);
1150
+ console.log(` ${chalk.bold.yellow(' 6')} ${chalk.bold('Deconstruct')} — Read code, plan all extractions`);
1151
+ console.log(` ${chalk.bold.yellow(' 7')} ${chalk.bold('Extract')} TDD extraction of functions + components`);
1152
+ console.log(` ${chalk.bold.yellow(' 8')} ${chalk.bold('Glossary')} Record functions in glossary`);
1153
+ console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('Analyze')} Analyze and verify components`);
1154
+ console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('App Scenarios')} Create app-level scenarios`);
1155
+ console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('User Scenarios')} — Create user-persona scenarios`);
1156
+ console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Verify')} Review screenshots, check for errors`);
1157
+ console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Journal')} Create/update journal entry`);
1158
+ console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('Review')} Verify screenshots and audit`);
1159
+ console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('Present')} Present summary, get approval`);
1160
+ console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('Commit')} Commit all changes`);
1161
+ console.log(` ${chalk.bold.yellow('17')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
1162
+ console.log(` ${chalk.bold.yellow('18')} ${chalk.bold('Push')} — Push to remote`);
799
1163
  console.log();
800
- console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
1164
+ console.log(chalk.green('Start now: ') +
1165
+ 'Ask the user what they want to build, then run `codeyam editor 1` (never expose this command to the user)');
801
1166
  console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
802
1167
  }
1168
+ console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
803
1169
  console.log();
804
1170
  }
805
1171
  // ─── Step 1: Plan ─────────────────────────────────────────────────────
@@ -854,6 +1220,15 @@ function printStep1(root, feature, options, userPrompt) {
854
1220
  console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
855
1221
  console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
856
1222
  console.log();
1223
+ checkbox('Detect and set the project tech stack:');
1224
+ console.log(chalk.dim(' Scan package.json, framework configs (.env, .env.example, tsconfig.json, next.config.*, prisma/schema.prisma, etc.)'));
1225
+ console.log(chalk.dim(' to detect languages, frameworks, databases, services, libraries, and infrastructure.'));
1226
+ console.log(chalk.dim(' Each item MUST have name, url, and description. version and envKeys are optional.'));
1227
+ console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
1228
+ console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
1229
+ console.log(chalk.dim(' -d \'{"techStack":{"languages":[{"name":"TypeScript","url":"https://typescriptlang.org","description":"Statically typed JavaScript superset","version":"5"}],"frameworks":[{"name":"Next.js","url":"https://nextjs.org","description":"Full-stack React framework with SSR and API routes","version":"15"}],"databases":[{"name":"SQLite","url":"https://sqlite.org","description":"Embedded relational database"}],"services":[{"name":"Stripe","url":"https://stripe.com","description":"Payment processing and billing","envKeys":["STRIPE_SECRET_KEY"]}],"libraries":[{"name":"Prisma","url":"https://prisma.io","description":"Type-safe database ORM","version":"7"}],"infrastructure":[]}}\''));
1230
+ console.log(chalk.dim(' Replace the example values with what you detect in this project. Omit empty categories.'));
1231
+ console.log();
857
1232
  const designSystem = readDesignSystem(root);
858
1233
  if (designSystem) {
859
1234
  console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
@@ -867,6 +1242,8 @@ function printStep1(root, feature, options, userPrompt) {
867
1242
  console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
868
1243
  chalk.dim(' — user describes changes, you revise the plan, then re-present'));
869
1244
  console.log();
1245
+ checkboxHandoff();
1246
+ console.log();
870
1247
  console.log(chalk.dim('This step is for understanding user goals and getting buy-in. Code comes in Step 2.'));
871
1248
  console.log();
872
1249
  console.log(chalk.bold.red('━━━ STOP ━━━'));
@@ -875,18 +1252,12 @@ function printStep1(root, feature, options, userPrompt) {
875
1252
  console.log();
876
1253
  console.log(chalk.yellow('Wait for user confirmation before moving on.'));
877
1254
  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
1255
  console.log(chalk.green('When done, run: ') +
885
1256
  chalk.bold('codeyam editor 2 --feature "Feature Name"'));
886
1257
  console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
887
1258
  console.log();
888
1259
  }
889
- // ─── Step 2: Prototype ────────────────────────────────────────────────
1260
+ // ─── Step 2: Prepare ──────────────────────────────────────────────────
890
1261
  function printStep2(root, feature) {
891
1262
  const port = getServerPort();
892
1263
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
@@ -903,51 +1274,167 @@ function printStep2(root, feature) {
903
1274
  appFormats: prevState?.appFormats,
904
1275
  techStackId: prevState?.techStackId,
905
1276
  });
906
- logEvent(root, 'step', { step: 2, label: 'Prototype', feature });
907
- stepHeader(2, 'Prototype', feature);
1277
+ logEvent(root, 'step', { step: 2, label: 'Prepare', feature });
1278
+ stepHeader(2, 'Prepare', feature);
908
1279
  if (isResuming) {
909
1280
  printResumptionHeader(2);
910
1281
  }
911
- console.log('Build fast with real data. Prioritize speed over quality.');
1282
+ console.log('Get the project ready to build.');
912
1283
  console.log();
1284
+ const ctx = getTechStackContext(root);
913
1285
  // If no project exists yet, include scaffolding instructions first
914
1286
  if (!projectExists) {
915
1287
  console.log(chalk.bold('Scaffold the project:'));
916
1288
  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,'));
1289
+ if (ctx.isExpo) {
1290
+ console.log(chalk.dim(' This copies the Expo + React Native template, runs npm install,'));
1291
+ }
1292
+ else if (ctx.isChromeExt) {
1293
+ console.log(chalk.dim(' This copies the Chrome Extension + React template, runs npm install,'));
1294
+ }
1295
+ else {
1296
+ console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
1297
+ }
918
1298
  console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
919
1299
  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).'));
1300
+ if (ctx.isExpo) {
1301
+ // Expo: no database, use AsyncStorage + theme
1302
+ checkbox('Define your data types in `lib/types.ts`');
1303
+ console.log(chalk.dim(" Create TypeScript interfaces for your app's data models"));
1304
+ console.log();
1305
+ checkbox('Set up initial data using the storage helper in `lib/storage.ts`');
1306
+ console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
1307
+ console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "First item" }]);'));
1308
+ console.log();
1309
+ console.log(chalk.dim(' Read MOBILE_SETUP.md for data storage, navigation, and testing patterns.'));
1310
+ console.log();
1311
+ checkbox('Ask the user what to name the app (use AskUserQuestion), then update app.json');
1312
+ console.log(chalk.dim(' Update name, slug, scheme, ios.bundleIdentifier (com.codeyam.<slug>), and android.package'));
1313
+ console.log(chalk.dim(' Also set the projectTitle via: curl -s -X POST http://localhost:' +
1314
+ port +
1315
+ '/api/editor-project-info -H "Content-Type: application/json" -d \'{"projectTitle":"<name>"}\''));
1316
+ console.log();
1317
+ checkbox('Generate an app icon using `sharp`');
1318
+ console.log(chalk.dim(' Create `scripts/generate-icon.mjs` and `mkdir -p assets`'));
1319
+ console.log(chalk.yellow(' ICON DESIGN GOAL: The icon must look distinctive on a home screen full of other app icons.'));
1320
+ console.log(chalk.yellow(' Use multiple bold colors from the design system and complex, overlapping shapes.'));
1321
+ console.log(chalk.yellow(' NO simple/minimal icons — no single glyph on a flat background, no plain text, no basic circles.'));
1322
+ console.log(chalk.yellow(' Make it colorful, layered, and dense. Use gradients, multiple contrasting fills, and depth.'));
1323
+ console.log(chalk.dim(' Use sharp to render SVG to 1024x1024 PNG. iOS: flatten onto background (no alpha channel).'));
1324
+ console.log(chalk.dim(' Generate: icon.png, adaptive-icon.png, favicon.png (48x48). Run `node scripts/generate-icon.mjs`'));
1325
+ console.log();
1326
+ }
1327
+ else if (ctx.isChromeExt) {
1328
+ // Chrome Extension: use chrome.storage
1329
+ checkbox('Set up data storage using chrome.storage (with localStorage fallback for dev)');
1330
+ console.log();
1331
+ console.log(chalk.dim(' Read EXTENSION_SETUP.md for storage, messaging, and manifest patterns.'));
1332
+ console.log();
1333
+ }
1334
+ else {
1335
+ // Next.js: Prisma + database
1336
+ checkbox('Define your data models in `prisma/schema.prisma`');
1337
+ console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
1338
+ console.log();
1339
+ checkbox('Push schema and seed the database');
1340
+ console.log(chalk.dim(' npm run db:push'));
1341
+ console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
1342
+ console.log(chalk.dim(' npm run db:seed'));
1343
+ console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
1344
+ console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
1345
+ console.log();
1346
+ console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
1347
+ console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
1348
+ console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
1349
+ console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
1350
+ console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
1351
+ console.log();
1352
+ console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
1353
+ console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
1354
+ console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
1355
+ console.log();
1356
+ }
1357
+ printDataStructureInstructions();
1358
+ }
1359
+ else {
1360
+ console.log(chalk.bold('Prepare the database for this feature:'));
1361
+ checkbox('List existing scenarios: `codeyam editor scenarios`');
1362
+ console.log(chalk.dim(' Review existing scenarios to find seed data that best demonstrates the feature.'));
1363
+ console.log(chalk.dim(" Don't create throwaway seed data — use what's already been curated."));
1364
+ checkbox('Pick the scenario with seed data most relevant to this feature');
1365
+ console.log(chalk.dim(" Think: which existing data state best exercises the feature you're building?"));
1366
+ console.log(chalk.dim(' A scenario with diverse, realistic data is usually the best starting point.'));
1367
+ checkbox('Load that scenario to seed the database and preview it:');
1368
+ console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1369
+ console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
1370
+ console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
939
1371
  console.log();
940
- console.log(chalk.bold('Build the feature:'));
1372
+ printDataStructureInstructions();
941
1373
  }
1374
+ console.log();
1375
+ printServiceRecordingReminder(port);
1376
+ console.log();
1377
+ checkboxHandoff();
1378
+ stopGate(2);
1379
+ }
1380
+ // ─── Step 3: Prototype ────────────────────────────────────────────────
1381
+ function printStep3(root, feature) {
1382
+ const port = getServerPort();
1383
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1384
+ const projectExists = hasProject(root);
1385
+ const prevState = readState(root);
1386
+ const isResuming = prevState?.step === 3;
1387
+ const now = new Date().toISOString();
1388
+ writeState(root, {
1389
+ feature,
1390
+ step: 3,
1391
+ label: STEP_LABELS[3],
1392
+ startedAt: isResuming ? prevState.startedAt : now,
1393
+ featureStartedAt: prevState?.featureStartedAt || now,
1394
+ appFormats: prevState?.appFormats,
1395
+ techStackId: prevState?.techStackId,
1396
+ });
1397
+ logEvent(root, 'step', { step: 3, label: 'Prototype', feature });
1398
+ stepHeader(3, 'Prototype', feature);
1399
+ if (isResuming) {
1400
+ printResumptionHeader(3);
1401
+ }
1402
+ const ctx = getTechStackContext(root);
1403
+ console.log('Build fast with real data. Prioritize speed over quality.');
1404
+ console.log();
942
1405
  console.log(chalk.bold('Checklist:'));
943
- checkbox('Create API routes that read from the database via Prisma');
944
- checkbox('Seed the database with demo data');
945
- checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
946
- console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
947
- console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
948
- console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
1406
+ if (ctx.isExpo) {
1407
+ checkbox('Build screens that read from AsyncStorage via `lib/storage.ts` or fetch from APIs');
1408
+ if (!projectExists) {
1409
+ checkbox('Populate initial data in AsyncStorage for development');
1410
+ console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
1411
+ console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "Buy groceries" }]);'));
1412
+ console.log();
1413
+ console.log(chalk.bold.cyan('Make data visually rich:'));
1414
+ console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1415
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1416
+ console.log(chalk.cyan(' • Rich data makes the prototype look real and surfaces layout issues early'));
1417
+ }
1418
+ }
1419
+ else {
1420
+ checkbox('Create API routes that read from the database via Prisma');
1421
+ if (!projectExists) {
1422
+ checkbox('Seed the database with demo data');
1423
+ checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
1424
+ console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
1425
+ console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
1426
+ console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
1427
+ console.log();
1428
+ console.log(chalk.bold.cyan('Make seed data visually rich:'));
1429
+ console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
1430
+ console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
1431
+ console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1432
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1433
+ console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
1434
+ }
1435
+ }
949
1436
  checkbox('Verify the dev server shows the changes');
950
- checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
1437
+ checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
951
1438
  // Responsive design guidance when building a mobile-responsive web app
952
1439
  if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
953
1440
  console.log();
@@ -964,16 +1451,33 @@ function printStep2(root, feature) {
964
1451
  console.log();
965
1452
  console.log(designSystem);
966
1453
  console.log();
967
- checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
968
- console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
969
- console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
970
- console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
971
- console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
972
- console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
973
- checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
974
- console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
975
- console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
976
- console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
1454
+ if (ctx.isExpo) {
1455
+ checkbox('Define ALL design tokens in `lib/theme.ts` — this is the single source of truth');
1456
+ console.log(chalk.dim(' Colors: theme.colors.bgSurface, theme.colors.textPrimary, etc.'));
1457
+ console.log(chalk.dim(' Typography: theme.fontSize.sm, theme.fontSize.lg, theme.fontFamily.mono'));
1458
+ console.log(chalk.dim(' Spacing: theme.spacing.sm, theme.spacing.md, theme.spacing.lg, etc.'));
1459
+ console.log(chalk.dim(' Border radius: theme.borderRadius.sm, theme.borderRadius.lg, etc.'));
1460
+ checkbox('Import theme in every component — ZERO hardcoded color strings or pixel values');
1461
+ console.log(chalk.dim(' Bad: color: "#333", fontSize: 14, padding: 12'));
1462
+ console.log(chalk.dim(' Good: color: theme.colors.textPrimary, fontSize: theme.fontSize.sm, padding: theme.spacing.md'));
1463
+ console.log(chalk.dim(' Do NOT use CSS custom properties (var(--token)) they do not work in React Native.'));
1464
+ checkbox('Buttons: put backgroundColor, borderRadius, borderStyle on a wrapping <View>, NOT on <Pressable>');
1465
+ console.log(chalk.dim(' Pressable renders these styles on web but FAILS SILENTLY on native devices.'));
1466
+ console.log(chalk.dim(' Use a <View> with overflow:"hidden" for the visual shell, <Pressable> inside for touch only.'));
1467
+ console.log(chalk.dim(' For press feedback: use onPressIn/onPressOut + useState to toggle opacity, not function-style style.'));
1468
+ }
1469
+ else {
1470
+ checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
1471
+ console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
1472
+ console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
1473
+ console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
1474
+ console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
1475
+ console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
1476
+ checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
1477
+ console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
1478
+ console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
1479
+ console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
1480
+ }
977
1481
  }
978
1482
  console.log();
979
1483
  console.log(chalk.bold.cyan('Keep the preview moving:'));
@@ -984,16 +1488,44 @@ function printStep2(root, feature) {
984
1488
  console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
985
1489
  console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
986
1490
  printDimensionGuidance(dim, dimNames);
1491
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
1492
+ console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
1493
+ console.log();
1494
+ printServiceRecordingReminder(port);
1495
+ console.log();
1496
+ checkboxHandoff();
1497
+ stopGate(3);
1498
+ }
1499
+ // ─── Step 4: Verify Prototype ─────────────────────────────────────────
1500
+ function printStep4(root, feature) {
1501
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1502
+ const prevState = readState(root);
1503
+ const isResuming = prevState?.step === 4;
1504
+ const now = new Date().toISOString();
1505
+ writeState(root, {
1506
+ feature,
1507
+ step: 4,
1508
+ label: STEP_LABELS[4],
1509
+ startedAt: isResuming ? prevState.startedAt : now,
1510
+ featureStartedAt: prevState?.featureStartedAt || now,
1511
+ appFormats: prevState?.appFormats,
1512
+ techStackId: prevState?.techStackId,
1513
+ });
1514
+ logEvent(root, 'step', { step: 4, label: 'Verify Prototype', feature });
1515
+ stepHeader(4, 'Verify Prototype', feature);
1516
+ if (isResuming) {
1517
+ printResumptionHeader(4);
1518
+ }
1519
+ console.log('Verify everything works before presenting the prototype.');
987
1520
  console.log();
988
1521
  console.log(chalk.bold('Verify the dev server:'));
989
- console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
990
- console.log(chalk.dim(' # Check page loads: curl -s -o /dev/null -w "%{http_code}" http://localhost:<dev-port>'));
991
- console.log(chalk.dim(' # Check API routes: curl -s http://localhost:<dev-port>/api/your-route'));
1522
+ console.log(chalk.dim(' # Verify pages and API routes load:'));
1523
+ console.log(chalk.dim(` codeyam editor verify-routes '{"paths":["/your-page"],"apiRoutes":["/api/your-route"]}'`));
992
1524
  console.log();
993
1525
  console.log(chalk.bold('Verify before proceeding:'));
994
1526
  console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
995
- checkbox('Verify the page loads: curl the dev server URL and confirm HTTP 200 (not an error page)');
996
- checkbox('Verify API routes return valid JSON: curl each route and confirm no error responses');
1527
+ checkbox('Verify page and API routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
1528
+ console.log(chalk.dim(' Include ALL page paths you built and ALL API routes they depend on.'));
997
1529
  checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
998
1530
  console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
999
1531
  console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
@@ -1004,6 +1536,14 @@ function printStep2(root, feature) {
1004
1536
  console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
1005
1537
  console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
1006
1538
  console.log();
1539
+ const ctx4 = getTechStackContext(root);
1540
+ if (ctx4.isExpo) {
1541
+ console.log(chalk.magenta.bold(' EXPO WEB PREVIEW NOTE:'));
1542
+ console.log(chalk.magenta(' The preview renders via react-native-web in a browser. Some differences'));
1543
+ console.log(chalk.magenta(' from native devices are expected (fonts, SafeAreaView, shadows, Platform.OS).'));
1544
+ console.log(chalk.magenta(' The preview is for layout and data verification. Test final polish on device.'));
1545
+ console.log();
1546
+ }
1007
1547
  console.log(chalk.bold('Update README and setup script:'));
1008
1548
  checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
1009
1549
  checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
@@ -1011,32 +1551,36 @@ function printStep2(root, feature) {
1011
1551
  console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
1012
1552
  console.log();
1013
1553
  console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
1014
- stopGate(2);
1554
+ console.log();
1555
+ checkboxHandoff();
1556
+ stopGate(4);
1015
1557
  }
1016
- // ─── Step 3: Confirm ──────────────────────────────────────────────────
1017
- function printStep3(root, feature) {
1558
+ // ─── Step 5: Confirm ──────────────────────────────────────────────────
1559
+ function printStep5(root, feature) {
1018
1560
  const port = getServerPort();
1019
1561
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1020
1562
  const prevState = readState(root);
1021
- const isResuming = prevState?.step === 3;
1563
+ const isResuming = prevState?.step === 5;
1022
1564
  const now = new Date().toISOString();
1023
1565
  writeState(root, {
1024
1566
  feature,
1025
- step: 3,
1026
- label: STEP_LABELS[3],
1567
+ step: 5,
1568
+ label: STEP_LABELS[5],
1027
1569
  startedAt: isResuming ? prevState.startedAt : now,
1028
1570
  featureStartedAt: prevState?.featureStartedAt || now,
1571
+ appFormats: prevState?.appFormats,
1572
+ techStackId: prevState?.techStackId,
1029
1573
  });
1030
- logEvent(root, 'step', { step: 3, label: 'Confirm', feature });
1031
- stepHeader(3, 'Confirm', feature);
1574
+ logEvent(root, 'step', { step: 5, label: 'Confirm', feature });
1575
+ stepHeader(5, 'Confirm', feature);
1032
1576
  if (isResuming) {
1033
- printResumptionHeader(3);
1577
+ printResumptionHeader(5);
1034
1578
  }
1035
1579
  console.log('Summarize what was built and get user confirmation.');
1036
1580
  console.log();
1037
1581
  console.log(chalk.bold('Before presenting — verify everything works:'));
1038
1582
  checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
1039
- checkbox('Verify API routes return valid data (curl each route)');
1583
+ checkbox('Verify routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
1040
1584
  console.log();
1041
1585
  console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
1042
1586
  checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
@@ -1047,12 +1591,19 @@ function printStep3(root, feature) {
1047
1591
  console.log(chalk.dim(' If there are errors, fix the underlying issue before presenting.'));
1048
1592
  checkbox('Verify `hasContent=true` and `liveErrors=0` — do NOT ask the user to confirm if the preview is broken');
1049
1593
  console.log();
1594
+ console.log(chalk.bold('Verify the captured user prompt:'));
1595
+ checkbox("Read `.codeyam/editor-user-prompt.txt` — this is the user's original feature request");
1596
+ 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`');
1597
+ console.log(chalk.dim(" This must be the user's exact words, not a summary. It gets recorded in the journal."));
1598
+ console.log();
1050
1599
  console.log(chalk.bold('Then present to the user:'));
1051
1600
  checkbox('Summarize what was built (routes, components, data)');
1052
1601
  checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
1053
1602
  printDimensionGuidance(dim, dimNames);
1054
1603
  console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
1055
1604
  console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
1605
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
1606
+ console.log(chalk.red(' The preview only updates when you explicitly call `codeyam editor preview`. Verify, then describe.'));
1056
1607
  console.log();
1057
1608
  console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
1058
1609
  checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
@@ -1060,80 +1611,115 @@ function printStep3(root, feature) {
1060
1611
  checkbox('Ask the user: "Does everything work as expected?"');
1061
1612
  console.log();
1062
1613
  console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
1063
- console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 4'));
1614
+ console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 6'));
1064
1615
  console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
1065
- chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 3`'));
1616
+ chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 5`'));
1066
1617
  console.log();
1067
1618
  console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
1068
- stopGate(3, { confirm: true });
1619
+ console.log();
1620
+ checkboxHandoff();
1621
+ stopGate(5, { confirm: true });
1069
1622
  }
1070
- // ─── Step 4: Deconstruct ──────────────────────────────────────────────
1071
- function printStep4(root, feature) {
1623
+ // ─── Step 6: Deconstruct ──────────────────────────────────────────────
1624
+ function printStep6(root, feature) {
1072
1625
  const prevState = readState(root);
1073
- const isResuming = prevState?.step === 4;
1626
+ const isResuming = prevState?.step === 6;
1074
1627
  const now = new Date().toISOString();
1075
1628
  writeState(root, {
1076
1629
  feature,
1077
- step: 4,
1078
- label: STEP_LABELS[4],
1630
+ step: 6,
1631
+ label: STEP_LABELS[6],
1079
1632
  startedAt: isResuming ? prevState.startedAt : now,
1080
1633
  featureStartedAt: prevState?.featureStartedAt || now,
1634
+ appFormats: prevState?.appFormats,
1635
+ techStackId: prevState?.techStackId,
1081
1636
  });
1082
- logEvent(root, 'step', { step: 4, label: 'Deconstruct', feature });
1083
- stepHeader(4, 'Deconstruct', feature);
1637
+ logEvent(root, 'step', { step: 6, label: 'Deconstruct', feature });
1638
+ stepHeader(6, 'Deconstruct', feature);
1084
1639
  if (isResuming) {
1085
- printResumptionHeader(4);
1640
+ printResumptionHeader(6);
1086
1641
  }
1087
1642
  console.log(chalk.bold('Goal: pages contain ONLY components. Components contain ONLY sub-components.'));
1088
- console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 5.'));
1643
+ console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
1089
1644
  console.log();
1090
1645
  printExtractionPlanInstructions();
1091
- stopGate(4);
1646
+ console.log();
1647
+ checkboxHandoff();
1648
+ stopGate(6);
1092
1649
  }
1093
- // ─── Step 5: Extract ──────────────────────────────────────────────────
1094
- function printStep5(root, feature) {
1650
+ // ─── Step 7: Extract ──────────────────────────────────────────────────
1651
+ function printStep7(root, feature) {
1095
1652
  const port = getServerPort();
1096
1653
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1097
1654
  const prevState = readState(root);
1098
- const isResuming = prevState?.step === 5;
1655
+ const isResuming = prevState?.step === 7;
1099
1656
  const now = new Date().toISOString();
1100
1657
  writeState(root, {
1101
1658
  feature,
1102
- step: 5,
1103
- label: STEP_LABELS[5],
1659
+ step: 7,
1660
+ label: STEP_LABELS[7],
1104
1661
  startedAt: isResuming ? prevState.startedAt : now,
1105
1662
  featureStartedAt: prevState?.featureStartedAt || now,
1663
+ appFormats: prevState?.appFormats,
1664
+ techStackId: prevState?.techStackId,
1106
1665
  });
1107
- logEvent(root, 'step', { step: 5, label: 'Extract', feature });
1108
- stepHeader(5, 'Extract', feature);
1666
+ logEvent(root, 'step', { step: 7, label: 'Extract', feature });
1667
+ stepHeader(7, 'Extract', feature);
1109
1668
  if (isResuming) {
1110
- printResumptionHeader(5);
1669
+ printResumptionHeader(7);
1111
1670
  }
1112
- console.log('Execute your extraction plan from step 4.');
1671
+ const ctx = getTechStackContext(root);
1672
+ console.log('Execute your extraction plan from step 6.');
1113
1673
  console.log();
1114
1674
  console.log(chalk.bold('Components:'));
1115
1675
  checkbox('Extract each component from your plan into its own file');
1116
1676
  checkbox('Page/route files must contain ZERO direct JSX — only imported components');
1117
1677
  checkbox('Every component that renders multiple sections must be split into sub-components');
1118
- console.log(chalk.dim(' No tests needed — visual verification happens in step 7'));
1678
+ console.log(chalk.dim(' No tests needed — visual verification happens in step 9'));
1119
1679
  console.log();
1120
1680
  console.log(chalk.bold('Library functions AND hooks (TDD):'));
1121
1681
  checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
1122
1682
  console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
1123
1683
  console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
1684
+ console.log(chalk.dim(' EVERY conditional branch in the function MUST have a test that exercises it'));
1685
+ console.log(chalk.dim(' EVERY return path MUST be tested — if a function can return 3 different things, write 3+ tests'));
1686
+ console.log(chalk.dim(' Boundary values: 0, 1, -1, empty string, empty array, undefined, null'));
1124
1687
  console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
1125
- checkbox('Place test files next to source: `app/lib/drinks.ts` `app/lib/drinks.test.ts`');
1126
- console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 7 only captures component screenshots.'));
1688
+ console.log(chalk.dim(' Wrap all tests in a describe("FunctionName", ...) block — the audit matches on this name'));
1689
+ if (ctx.isExpo) {
1690
+ 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`');
1691
+ }
1692
+ else {
1693
+ checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
1694
+ }
1695
+ console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
1127
1696
  console.log();
1128
1697
  console.log(chalk.bold('Recursive pass:'));
1129
1698
  checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
1130
1699
  checkbox('Keep going until every file is a thin shell: just imports and composition');
1131
- console.log(chalk.yellow(' Check: does any file contain raw <div>, <span>, <h1>, <p>, <img>, or <ul>?'));
1700
+ console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
1132
1701
  console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
1133
1702
  console.log();
1703
+ console.log(chalk.bold('Decomposition pass (backend AND frontend):'));
1704
+ checkbox('Re-read EVERY file you created or modified. For each, extract a function/helper/sub-component for:');
1705
+ console.log(chalk.yellow(' — Conditionals (e.g. `isOverdue ? "red" : "green"` → `getStatusColor(date)`)'));
1706
+ console.log(chalk.yellow(' — Computed values (e.g. `items.filter(...).length` → `countActiveItems(items)`)'));
1707
+ console.log(chalk.yellow(' — Formatting (e.g. `${price.toFixed(2)}` → `formatPrice(price)`)'));
1708
+ console.log(chalk.yellow(' — Data transforms, validation, request/response shaping'));
1709
+ console.log(chalk.yellow(' — Anything doing more than one thing → split until each piece does one clearly defined thing'));
1710
+ checkbox('Then look at what you just extracted — can IT be broken down further? Keep going.');
1711
+ checkbox('Write tests for every extracted function (same TDD: failing tests FIRST)');
1712
+ console.log();
1713
+ console.log(chalk.bold.red('TEST QUALITY SELF-CHECK (before proceeding):'));
1714
+ checkbox('For EACH test file: count the test cases. Fewer than 3 tests for any function is a red flag.');
1715
+ checkbox('For EACH tested function: read the function, count the conditional branches. Every branch MUST have a test.');
1716
+ checkbox('For EACH tested function: verify you test at least one error/edge case, not just happy paths.');
1717
+ console.log(chalk.yellow(' If a function has an if/else, switch, or ternary — each path needs its own test case.'));
1718
+ console.log(chalk.yellow(' If a function handles null/undefined/empty — test those inputs explicitly.'));
1719
+ console.log();
1134
1720
  console.log(chalk.bold('Verify before proceeding:'));
1135
1721
  checkbox('Run all tests and verify they pass');
1136
- checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
1722
+ checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
1137
1723
  checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
1138
1724
  checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1139
1725
  printDimensionGuidance(dim, dimNames);
@@ -1141,93 +1727,117 @@ function printStep5(root, feature) {
1141
1727
  console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
1142
1728
  console.log();
1143
1729
  console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
1144
- stopGate(5);
1730
+ console.log();
1731
+ checkboxHandoff();
1732
+ stopGate(7);
1145
1733
  }
1146
- // ─── Step 6: Glossary ─────────────────────────────────────────────────
1147
- function printStep6(root, feature) {
1734
+ // ─── Step 8: Glossary ─────────────────────────────────────────────────
1735
+ function printStep8(root, feature) {
1148
1736
  const prevState = readState(root);
1149
- const isResuming = prevState?.step === 6;
1737
+ const isResuming = prevState?.step === 8;
1150
1738
  const now = new Date().toISOString();
1151
1739
  writeState(root, {
1152
1740
  feature,
1153
- step: 6,
1154
- label: STEP_LABELS[6],
1741
+ step: 8,
1742
+ label: STEP_LABELS[8],
1155
1743
  startedAt: isResuming ? prevState.startedAt : now,
1156
1744
  featureStartedAt: prevState?.featureStartedAt || now,
1745
+ appFormats: prevState?.appFormats,
1746
+ techStackId: prevState?.techStackId,
1157
1747
  });
1158
- logEvent(root, 'step', { step: 6, label: 'Glossary', feature });
1159
- stepHeader(6, 'Glossary', feature);
1748
+ logEvent(root, 'step', { step: 8, label: 'Glossary', feature });
1749
+ stepHeader(8, 'Glossary', feature);
1160
1750
  if (isResuming) {
1161
- printResumptionHeader(6);
1751
+ printResumptionHeader(8);
1162
1752
  }
1163
1753
  console.log('Record all new functions/components in `.codeyam/glossary.json`.');
1164
1754
  console.log();
1165
1755
  printGlossaryInstructions(feature);
1166
1756
  console.log();
1167
1757
  console.log(chalk.dim('Focus on updating the glossary. Application code and scenarios come in later steps.'));
1168
- stopGate(6);
1758
+ stopGate(8);
1169
1759
  }
1170
- // ─── Step 7: Analyze ──────────────────────────────────────────────────
1171
- function printStep7(root, feature) {
1760
+ // ─── Step 9: Analyze ──────────────────────────────────────────────────
1761
+ function printStep9(root, feature) {
1172
1762
  const port = getServerPort();
1173
1763
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1174
1764
  const prevState = readState(root);
1175
- const isResuming = prevState?.step === 7;
1765
+ const isResuming = prevState?.step === 9;
1176
1766
  const now = new Date().toISOString();
1177
1767
  writeState(root, {
1178
1768
  feature,
1179
- step: 7,
1180
- label: STEP_LABELS[7],
1769
+ step: 9,
1770
+ label: STEP_LABELS[9],
1181
1771
  startedAt: isResuming ? prevState.startedAt : now,
1182
1772
  featureStartedAt: prevState?.featureStartedAt || now,
1773
+ appFormats: prevState?.appFormats,
1774
+ techStackId: prevState?.techStackId,
1183
1775
  });
1184
- logEvent(root, 'step', { step: 7, label: 'Analyze', feature });
1185
- stepHeader(7, 'Analyze and Verify', feature);
1776
+ logEvent(root, 'step', { step: 9, label: 'Analyze', feature });
1777
+ stepHeader(9, 'Analyze and Verify', feature);
1186
1778
  if (isResuming) {
1187
- printResumptionHeader(7);
1779
+ printResumptionHeader(9);
1188
1780
  }
1189
1781
  console.log('Verify visual components (via isolation routes) and library functions (via tests).');
1190
1782
  console.log();
1191
1783
  console.log(chalk.bold('Visual Components — Component Isolation:'));
1192
- checkbox('List all files with new/modified visual components from step 5');
1784
+ checkbox('List all files with new/modified visual components from step 7');
1193
1785
  checkbox('Check existing scenarios: `codeyam editor scenarios`');
1194
1786
  console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
1195
1787
  console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
1196
1788
  console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
1197
- printComponentCaptureInstructions();
1789
+ const ctx9 = getTechStackContext(root);
1790
+ printComponentCaptureInstructions(root);
1198
1791
  console.log();
1199
1792
  console.log(chalk.bold('Library Functions — run tests:'));
1200
- checkbox('Run ALL test files created in step 5');
1201
- console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
1793
+ checkbox('Run ALL test files created in step 7');
1794
+ console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
1202
1795
  checkbox('Verify every test passes');
1203
1796
  checkbox('If any test fails, fix the source code and re-run');
1204
1797
  console.log();
1205
1798
  console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
1206
1799
  console.log();
1800
+ console.log(chalk.bold.red('DECOMPOSITION & COVERAGE REVIEW (before running audit):'));
1801
+ checkbox('Re-read every file. Extract any logic that could be its own function/helper/sub-component.');
1802
+ console.log(chalk.yellow(' Code either contains logic OR brings together smaller pieces.'));
1803
+ console.log(chalk.yellow(' Keep extracting until each piece does one clearly defined thing.'));
1804
+ console.log(chalk.yellow(' For each extraction: write failing test first, then extract, then make test pass.'));
1805
+ checkbox('Open each test file and verify: ≥3 test cases per function, happy path + edge case + error case');
1806
+ checkbox('If any function has fewer tests than conditional branches, add the missing tests NOW');
1807
+ checkbox('Update `.codeyam/glossary.json` with any new functions before running audit');
1808
+ console.log();
1207
1809
  checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
1208
- console.log(chalk.red.bold(' The audit is a HARD GATE — step 8 will refuse to run until the audit passes.'));
1209
- console.log(chalk.dim(' When audit passes, the import graph is built automatically for change tracking.'));
1810
+ console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
1811
+ console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
1812
+ console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
1813
+ console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
1814
+ console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
1815
+ console.log(chalk.dim(' If the audit reports "MANUAL ANALYSIS REQUIRED" for any entities,'));
1816
+ console.log(chalk.dim(' follow the manual analysis steps in the audit output.'));
1817
+ console.log(chalk.dim(' Do NOT re-run analyze-imports for those entities — it will fail again.'));
1210
1818
  console.log();
1211
- stopGate(7);
1819
+ stopGate(9);
1212
1820
  }
1213
- // ─── Step 8: App Scenarios ────────────────────────────────────────────
1214
- function printStep8(root, feature) {
1821
+ // ─── Step 10: App Scenarios ────────────────────────────────────────────
1822
+ function printStep10(root, feature) {
1215
1823
  const port = getServerPort();
1216
1824
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1217
1825
  const prevState = readState(root);
1218
- const isResuming = prevState?.step === 8;
1826
+ const isResuming = prevState?.step === 10;
1219
1827
  const now = new Date().toISOString();
1220
1828
  writeState(root, {
1221
1829
  feature,
1222
- step: 8,
1223
- label: STEP_LABELS[8],
1830
+ step: 10,
1831
+ label: STEP_LABELS[10],
1224
1832
  startedAt: isResuming ? prevState.startedAt : now,
1225
1833
  featureStartedAt: prevState?.featureStartedAt || now,
1834
+ appFormats: prevState?.appFormats,
1835
+ techStackId: prevState?.techStackId,
1226
1836
  });
1227
- logEvent(root, 'step', { step: 8, label: 'App Scenarios', feature });
1228
- stepHeader(8, 'App Scenarios', feature);
1837
+ logEvent(root, 'step', { step: 10, label: 'App Scenarios', feature });
1838
+ stepHeader(10, 'App Scenarios', feature);
1229
1839
  if (isResuming) {
1230
- printResumptionHeader(8);
1840
+ printResumptionHeader(10);
1231
1841
  }
1232
1842
  console.log('Create app-level scenarios with rich data that robustly demonstrates this feature.');
1233
1843
  console.log();
@@ -1241,6 +1851,7 @@ function printStep8(root, feature) {
1241
1851
  console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
1242
1852
  console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
1243
1853
  console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
1854
+ console.log(chalk.cyan(' • Use real image URLs (Unsplash, Pravatar) — not broken placeholders or empty strings'));
1244
1855
  console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
1245
1856
  console.log();
1246
1857
  console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
@@ -1249,13 +1860,19 @@ function printStep8(root, feature) {
1249
1860
  console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
1250
1861
  console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
1251
1862
  console.log();
1863
+ console.log(chalk.bold.cyan('Scenario naming — describe data states, not features:'));
1864
+ console.log(chalk.cyan(' • Name scenarios by what they represent: "Rich Data", "Empty", "Mobile", "Minimal"'));
1865
+ console.log(chalk.cyan(' • When enhancing a scenario with new feature data, update the name if it has grown beyond its original scope'));
1866
+ console.log(chalk.cyan(' • A single rich scenario exercising multiple features is more valuable than separate thin scenarios per feature'));
1867
+ console.log(chalk.cyan(' • Re-register with the same name to update; to rename, register with the new name'));
1868
+ console.log();
1252
1869
  checkbox('Ensure scenarios clearly demonstrate what changed in this session');
1253
1870
  console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
1254
1871
  console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
1255
1872
  console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
1256
1873
  printAppScenarioInstructions();
1257
1874
  console.log();
1258
- console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 10 if needed.'));
1875
+ console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 12 if needed.'));
1259
1876
  console.log();
1260
1877
  console.log(chalk.bold.cyan("Verify your work — screenshots don't lie:"));
1261
1878
  console.log(chalk.cyan(' • View every captured screenshot. Does it show what the scenario name promises?'));
@@ -1266,43 +1883,45 @@ function printStep8(root, feature) {
1266
1883
  console.log(chalk.bold.yellow('GATE: Before proceeding, run `codeyam editor scenario-coverage`'));
1267
1884
  console.log(chalk.yellow(' This checks which existing scenarios have stale screenshots.'));
1268
1885
  console.log(chalk.yellow(' Re-register every stale scenario listed until the check passes.'));
1269
- stopGate(8);
1886
+ stopGate(10);
1270
1887
  }
1271
- // ─── Step 9: User Scenarios ───────────────────────────────────────────
1272
- function printStep9(root, feature) {
1888
+ // ─── Step 11: User Scenarios ───────────────────────────────────────────
1889
+ function printStep11(root, feature) {
1273
1890
  const port = getServerPort();
1274
1891
  const prevState = readState(root);
1275
- const isResuming = prevState?.step === 9;
1892
+ const isResuming = prevState?.step === 11;
1276
1893
  const now = new Date().toISOString();
1277
1894
  writeState(root, {
1278
1895
  feature,
1279
- step: 9,
1280
- label: STEP_LABELS[9],
1896
+ step: 11,
1897
+ label: STEP_LABELS[11],
1281
1898
  startedAt: isResuming ? prevState.startedAt : now,
1282
1899
  featureStartedAt: prevState?.featureStartedAt || now,
1900
+ appFormats: prevState?.appFormats,
1901
+ techStackId: prevState?.techStackId,
1283
1902
  });
1284
- logEvent(root, 'step', { step: 9, label: 'User Scenarios', feature });
1285
- stepHeader(9, 'User Scenarios', feature);
1903
+ logEvent(root, 'step', { step: 11, label: 'User Scenarios', feature });
1904
+ stepHeader(11, 'User Scenarios', feature);
1286
1905
  if (isResuming) {
1287
- printResumptionHeader(9);
1906
+ printResumptionHeader(11);
1288
1907
  }
1289
- console.log('Create per-persona variations of existing scenarios. Skip to step 10 if no users.');
1908
+ console.log('Create per-persona variations of existing scenarios. Skip to step 12 if no users.');
1290
1909
  console.log();
1291
1910
  console.log(chalk.bold('If the app has NO users/auth:'));
1292
- console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
1911
+ console.log(chalk.dim(' Skip this step and proceed to step 12 (Verify).'));
1293
1912
  console.log();
1294
1913
  console.log(chalk.bold('If the app has users/auth:'));
1295
1914
  console.log();
1296
1915
  console.log(chalk.bold('Goal: Create persona variations of EXISTING app scenarios.'));
1297
1916
  console.log(chalk.dim(' Do NOT create standalone persona scenarios. Each user-persona scenario should be'));
1298
- console.log(chalk.dim(' a variation of an existing app scenario from step 8, with user-specific state layered on.'));
1917
+ console.log(chalk.dim(' a variation of an existing app scenario from step 10, with user-specific state layered on.'));
1299
1918
  console.log();
1300
1919
  console.log(chalk.bold('Checklist:'));
1301
1920
  checkbox('Run `codeyam editor scenarios` — list all existing app scenarios');
1302
1921
  checkbox('For EACH existing app scenario, create a logged-in variation:');
1303
1922
  console.log(chalk.dim(' Copy the scenario seed data and add "session":{"cookieValue":"sess_alice"} + user seed'));
1304
1923
  console.log(chalk.dim(' Name: "<Original Name> - Logged In" (e.g. "Full Catalog - Logged In")'));
1305
- console.log(chalk.dim(' Step 8 scenarios already serve as logged-out versions (no session cookie)'));
1924
+ console.log(chalk.dim(' Step 10 scenarios already serve as logged-out versions (no session cookie)'));
1306
1925
  checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
1307
1926
  console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
1308
1927
  checkbox('Include "dimensions" — inherit from the base app scenario, or override if the persona implies a different device');
@@ -1311,26 +1930,28 @@ function printStep9(root, feature) {
1311
1930
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
1312
1931
  console.log();
1313
1932
  console.log(chalk.dim('See FEATURE_PATTERNS.md and AUTH_PATTERNS.md for auth scenario guidance.'));
1314
- stopGate(9);
1933
+ stopGate(11);
1315
1934
  }
1316
- // ─── Step 10: Verify ──────────────────────────────────────────────────
1317
- function printStep10(root, feature) {
1935
+ // ─── Step 12: Verify ──────────────────────────────────────────────────
1936
+ function printStep12(root, feature) {
1318
1937
  const port = getServerPort();
1319
1938
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1320
1939
  const prevState = readState(root);
1321
- const isResuming = prevState?.step === 10;
1940
+ const isResuming = prevState?.step === 12;
1322
1941
  const now = new Date().toISOString();
1323
1942
  writeState(root, {
1324
1943
  feature,
1325
- step: 10,
1326
- label: STEP_LABELS[10],
1944
+ step: 12,
1945
+ label: STEP_LABELS[12],
1327
1946
  startedAt: isResuming ? prevState.startedAt : now,
1328
1947
  featureStartedAt: prevState?.featureStartedAt || now,
1948
+ appFormats: prevState?.appFormats,
1949
+ techStackId: prevState?.techStackId,
1329
1950
  });
1330
- logEvent(root, 'step', { step: 10, label: 'Verify', feature });
1331
- stepHeader(10, 'Verify', feature);
1951
+ logEvent(root, 'step', { step: 12, label: 'Verify', feature });
1952
+ stepHeader(12, 'Verify', feature);
1332
1953
  if (isResuming) {
1333
- printResumptionHeader(10);
1954
+ printResumptionHeader(12);
1334
1955
  }
1335
1956
  console.log('Verify component isolation screenshots, editor scenarios, and library tests.');
1336
1957
  console.log();
@@ -1353,63 +1974,67 @@ function printStep10(root, feature) {
1353
1974
  checkbox('Verify no broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
1354
1975
  console.log();
1355
1976
  console.log(chalk.bold('Library functions — test check:'));
1356
- checkbox('Re-run all test files from step 5 to confirm they still pass');
1977
+ checkbox('Re-run all test files from step 7 to confirm they still pass');
1357
1978
  console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
1358
1979
  checkbox('If any test fails, fix the source code and re-run');
1359
1980
  console.log();
1360
1981
  console.log(chalk.dim('Focus on fixing issues. All component screenshots, scenarios, and tests must be clean before proceeding.'));
1361
- stopGate(10);
1982
+ stopGate(12);
1362
1983
  }
1363
- // ─── Step 11: Journal ─────────────────────────────────────────────────
1364
- function printStep11(root, feature) {
1984
+ // ─── Step 13: Journal ─────────────────────────────────────────────────
1985
+ function printStep13(root, feature) {
1365
1986
  const port = getServerPort();
1366
1987
  const prevState = readState(root);
1367
- const isResuming = prevState?.step === 11;
1988
+ const isResuming = prevState?.step === 13;
1368
1989
  const now = new Date().toISOString();
1369
1990
  writeState(root, {
1370
1991
  feature,
1371
- step: 11,
1372
- label: STEP_LABELS[11],
1992
+ step: 13,
1993
+ label: STEP_LABELS[13],
1373
1994
  startedAt: isResuming ? prevState.startedAt : now,
1374
1995
  featureStartedAt: prevState?.featureStartedAt || now,
1996
+ appFormats: prevState?.appFormats,
1997
+ techStackId: prevState?.techStackId,
1375
1998
  });
1376
- logEvent(root, 'step', { step: 11, label: 'Journal', feature });
1377
- stepHeader(11, 'Journal', feature);
1999
+ logEvent(root, 'step', { step: 13, label: 'Journal', feature });
2000
+ stepHeader(13, 'Journal', feature);
1378
2001
  if (isResuming) {
1379
- printResumptionHeader(11);
2002
+ printResumptionHeader(13);
1380
2003
  }
1381
2004
  console.log('Create or update the journal entry for this feature.');
1382
2005
  console.log();
1383
2006
  console.log(chalk.bold('Checklist:'));
1384
2007
  checkbox('Write a concise description of what was built (2-3 sentences)');
1385
- checkbox(`First time at step 11 — create journal entry with ALL session screenshots:`);
2008
+ checkbox(`First time at step 13 — create journal entry with ALL session screenshots:`);
1386
2009
  console.log(chalk.dim(` codeyam editor journal '{"title":"...","type":"feature","description":"...","includeSessionScenarios":true}'`));
1387
2010
  console.log(chalk.dim(' includeSessionScenarios auto-discovers component + app + user persona screenshots'));
1388
- checkbox(`Returning to step 11 (before commit) — update the existing journal entry:`);
2011
+ checkbox(`Returning to step 13 (before commit) — update the existing journal entry:`);
1389
2012
  console.log(chalk.dim(` codeyam editor journal-update '{"time":"<journal entry time>","description":"<updated>","includeSessionScenarios":true}'`));
1390
2013
  console.log(chalk.dim(' Note: PATCH only works before the entry is committed. After commit, use POST to create a new entry.'));
1391
2014
  console.log();
1392
- console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step 13.'));
1393
- stopGate(11);
2015
+ console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step 15.'));
2016
+ stopGate(13);
1394
2017
  }
1395
- // ─── Step 12: Review ──────────────────────────────────────────────────
1396
- function printStep12(root, feature) {
2018
+ // ─── Step 14: Review ──────────────────────────────────────────────────
2019
+ function printStep14(root, feature) {
1397
2020
  const port = getServerPort();
1398
2021
  const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1399
2022
  const prevState = readState(root);
1400
- const isResuming = prevState?.step === 12;
2023
+ const isResuming = prevState?.step === 14;
1401
2024
  const now = new Date().toISOString();
1402
2025
  writeState(root, {
1403
2026
  feature,
1404
- step: 12,
1405
- label: STEP_LABELS[12],
2027
+ step: 14,
2028
+ label: STEP_LABELS[14],
1406
2029
  startedAt: isResuming ? prevState.startedAt : now,
1407
2030
  featureStartedAt: prevState?.featureStartedAt || now,
2031
+ appFormats: prevState?.appFormats,
2032
+ techStackId: prevState?.techStackId,
1408
2033
  });
1409
- logEvent(root, 'step', { step: 12, label: 'Review', feature });
1410
- stepHeader(12, 'Review', feature);
2034
+ logEvent(root, 'step', { step: 14, label: 'Review', feature });
2035
+ stepHeader(14, 'Review', feature);
1411
2036
  if (isResuming) {
1412
- printResumptionHeader(12);
2037
+ printResumptionHeader(14);
1413
2038
  }
1414
2039
  console.log('Verify all screenshots and checks pass before presenting to the user.');
1415
2040
  console.log();
@@ -1422,44 +2047,38 @@ function printStep12(root, feature) {
1422
2047
  checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
1423
2048
  checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
1424
2049
  checkbox('Fix or remove any broken images before continuing');
2050
+ checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
1425
2051
  checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
2052
+ checkbox('Verify tech stack is up to date: check for any new service imports (e.g. Stripe, Resend, Supabase) and ensure they appear in the tech stack with `envKeys`');
2053
+ console.log(chalk.dim(` Update via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"techStack": {...}}'`));
1426
2054
  checkbox('Do not proceed until all checks pass');
1427
- stopGate(12);
2055
+ stopGate(14);
1428
2056
  }
1429
- // ─── Step 13: Present ─────────────────────────────────────────────────
1430
- function printStep13(root, feature) {
1431
- const port = getServerPort();
1432
- const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
2057
+ // ─── Step 15: Present ─────────────────────────────────────────────────
2058
+ function printStep15(root, feature) {
1433
2059
  const prevState = readState(root);
1434
- const isResuming = prevState?.step === 13;
2060
+ const isResuming = prevState?.step === 15;
1435
2061
  const now = new Date().toISOString();
1436
2062
  writeState(root, {
1437
2063
  feature,
1438
- step: 13,
1439
- label: STEP_LABELS[13],
2064
+ step: 15,
2065
+ label: STEP_LABELS[15],
1440
2066
  startedAt: isResuming ? prevState.startedAt : now,
1441
2067
  featureStartedAt: prevState?.featureStartedAt || now,
2068
+ appFormats: prevState?.appFormats,
2069
+ techStackId: prevState?.techStackId,
1442
2070
  });
1443
- logEvent(root, 'step', { step: 13, label: 'Present', feature });
1444
- stepHeader(13, 'Present', feature);
2071
+ logEvent(root, 'step', { step: 15, label: 'Present', feature });
2072
+ stepHeader(15, 'Present', feature);
1445
2073
  if (isResuming) {
1446
- printResumptionHeader(13);
2074
+ printResumptionHeader(15);
1447
2075
  }
1448
2076
  console.log('Present the results to the user and get their approval.');
1449
2077
  console.log();
1450
2078
  console.log(chalk.bold('Checklist:'));
1451
- checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1452
- console.log(chalk.dim(' Each scenario has a `changeStatus` field: "new", "edited", "impacted", or null.'));
1453
- checkbox('Pick the best scenario to showcase from this working session:');
1454
- console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
1455
- console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
1456
- console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
1457
- checkbox('Switch the preview to that scenario using its `id` and `dimension`:');
1458
- console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1459
- console.log(chalk.dim(' Use the dimension that matches the scenario — check its registered dimensions.'));
1460
- printDimensionGuidance(dim, dimNames);
1461
2079
  checkbox(`Show the results panel: \`codeyam editor show-results\``);
1462
2080
  console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
2081
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1463
2082
  console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
1464
2083
  checkbox('Write a 1-2 sentence summary of what was built');
1465
2084
  checkbox('Report test count and audit status (one line)');
@@ -1471,7 +2090,7 @@ function printStep13(root, feature) {
1471
2090
  chalk.dim(' — describe changes, then re-verify'));
1472
2091
  console.log();
1473
2092
  console.log(chalk.bold('If the user chooses "Save & commit":'));
1474
- checkbox('Advance to the commit step: `codeyam editor 14`');
2093
+ checkbox('Advance to the commit step: `codeyam editor 16`');
1475
2094
  console.log();
1476
2095
  console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
1477
2096
  checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
@@ -1479,7 +2098,7 @@ function printStep13(root, feature) {
1479
2098
  checkbox(`Run: \`codeyam editor change "${feature}"\` — this gives you the change checklist`);
1480
2099
  checkbox('THEN make the requested changes and follow the checklist');
1481
2100
  console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
1482
- stopGate(13, { confirm: true });
2101
+ stopGate(15, { confirm: true });
1483
2102
  }
1484
2103
  // ─── Migration Mode ──────────────────────────────────────────────────
1485
2104
  /**
@@ -1676,7 +2295,7 @@ function printMigrateStep3(root) {
1676
2295
  console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
1677
2296
  console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
1678
2297
  console.log();
1679
- printComponentCaptureInstructions();
2298
+ printComponentCaptureInstructions(root);
1680
2299
  console.log();
1681
2300
  migrationStopGate(3, pageName, pageIndex, totalPages);
1682
2301
  }
@@ -1689,7 +2308,6 @@ function printMigrateStep4(root) {
1689
2308
  }
1690
2309
  const { pageName, pageIndex, totalPages } = pageInfo;
1691
2310
  writeMigrationStepState(root, 4, pageName, pageIndex, totalPages);
1692
- const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1693
2311
  console.log();
1694
2312
  console.log(chalk.bold.cyan(`━━━ Migration Step 4: Preview — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1695
2313
  console.log();
@@ -1699,11 +2317,8 @@ function printMigrateStep4(root) {
1699
2317
  checkbox('Review all scenario screenshots for this page — do they look correct?');
1700
2318
  checkbox('Check for client errors: `codeyam editor client-errors`');
1701
2319
  checkbox('If any issues: fix the scenario data, re-register affected scenarios');
1702
- checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1703
- checkbox('Pick the best scenario to showcase:');
1704
- console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1705
- printDimensionGuidance(dim, dimNames);
1706
2320
  checkbox('Show results: `codeyam editor show-results`');
2321
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1707
2322
  console.log();
1708
2323
  console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
1709
2324
  migrationStopGate(4, pageName, pageIndex, totalPages);
@@ -1815,7 +2430,7 @@ function printMigrateStep8(root) {
1815
2430
  console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
1816
2431
  console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
1817
2432
  checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
1818
- checkbox('Find all scenarios that use the real page route (not /codeyam-isolate/ paths)');
2433
+ checkbox('Find all scenarios that use the real page route (not /isolated-components/ paths)');
1819
2434
  checkbox('Re-register each one with the SAME name to update its screenshot');
1820
2435
  console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
1821
2436
  checkbox('After each registration, check the response for `clientErrors`');
@@ -1823,10 +2438,10 @@ function printMigrateStep8(root) {
1823
2438
  console.log();
1824
2439
  console.log(chalk.bold('Component Scenarios:'));
1825
2440
  console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
1826
- printComponentCaptureInstructions();
2441
+ printComponentCaptureInstructions(root);
1827
2442
  console.log();
1828
2443
  console.log(chalk.bold('Verify:'));
1829
- checkbox('Run `codeyam editor analyze-imports` to populate import graph');
2444
+ checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
1830
2445
  checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
1831
2446
  checkbox('Run `codeyam editor audit` to check completeness');
1832
2447
  checkbox('Check for client errors: `codeyam editor client-errors`');
@@ -1863,18 +2478,14 @@ function printMigrateStep10(root) {
1863
2478
  }
1864
2479
  const { pageName, pageIndex, totalPages } = pageInfo;
1865
2480
  writeMigrationStepState(root, 10, pageName, pageIndex, totalPages);
1866
- const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1867
2481
  console.log();
1868
2482
  console.log(chalk.bold.cyan(`━━━ Migration Step 10: Present — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
1869
2483
  console.log();
1870
2484
  console.log("Show results and commit this page's migration.");
1871
2485
  console.log();
1872
2486
  console.log(chalk.bold('Checklist:'));
1873
- checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1874
- checkbox('Pick the best scenario to showcase:');
1875
- console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1876
- printDimensionGuidance(dim, dimNames);
1877
2487
  checkbox('Show results: `codeyam editor show-results`');
2488
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1878
2489
  checkbox('Write a 1-2 sentence summary of what was migrated');
1879
2490
  console.log();
1880
2491
  console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
@@ -2094,47 +2705,52 @@ function handleMigrateCommand(root, subArg) {
2094
2705
  };
2095
2706
  stepFns[step](root);
2096
2707
  }
2097
- // ─── Step 14: Commit ─────────────────────────────────────────────────
2098
- function printStep14(root, feature) {
2708
+ // ─── Step 16: Commit ─────────────────────────────────────────────────
2709
+ function printStep16(root, feature) {
2099
2710
  const prevState = readState(root);
2100
- const isResuming = prevState?.step === 14;
2711
+ const isResuming = prevState?.step === 16;
2101
2712
  const now = new Date().toISOString();
2102
2713
  writeState(root, {
2103
2714
  feature,
2104
- step: 14,
2105
- label: STEP_LABELS[14],
2715
+ step: 16,
2716
+ label: STEP_LABELS[16],
2106
2717
  startedAt: isResuming ? prevState.startedAt : now,
2107
2718
  featureStartedAt: prevState?.featureStartedAt || now,
2719
+ appFormats: prevState?.appFormats,
2720
+ techStackId: prevState?.techStackId,
2108
2721
  });
2109
- logEvent(root, 'step', { step: 14, label: 'Commit', feature });
2110
- stepHeader(14, 'Commit', feature);
2722
+ logEvent(root, 'step', { step: 16, label: 'Commit', feature });
2723
+ stepHeader(16, 'Commit', feature);
2111
2724
  if (isResuming) {
2112
- printResumptionHeader(14);
2725
+ printResumptionHeader(16);
2113
2726
  }
2114
2727
  console.log('Commit all changes for this feature.');
2115
2728
  console.log();
2116
2729
  console.log(chalk.bold('Checklist:'));
2730
+ checkbox('Ensure all screenshots are fresh: `codeyam editor recapture-stale`');
2117
2731
  checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
2118
2732
  checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
2119
2733
  console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
2120
- stopGate(14);
2734
+ stopGate(16);
2121
2735
  }
2122
- // ─── Step 15: Finalize ───────────────────────────────────────────────
2123
- function printStep15(root, feature) {
2736
+ // ─── Step 17: Finalize ───────────────────────────────────────────────
2737
+ function printStep17(root, feature) {
2124
2738
  const prevState = readState(root);
2125
- const isResuming = prevState?.step === 15;
2739
+ const isResuming = prevState?.step === 17;
2126
2740
  const now = new Date().toISOString();
2127
2741
  writeState(root, {
2128
2742
  feature,
2129
- step: 15,
2130
- label: STEP_LABELS[15],
2743
+ step: 17,
2744
+ label: STEP_LABELS[17],
2131
2745
  startedAt: isResuming ? prevState.startedAt : now,
2132
2746
  featureStartedAt: prevState?.featureStartedAt || now,
2747
+ appFormats: prevState?.appFormats,
2748
+ techStackId: prevState?.techStackId,
2133
2749
  });
2134
- logEvent(root, 'step', { step: 15, label: 'Finalize', feature });
2135
- stepHeader(15, 'Finalize', feature);
2750
+ logEvent(root, 'step', { step: 17, label: 'Finalize', feature });
2751
+ stepHeader(17, 'Finalize', feature);
2136
2752
  if (isResuming) {
2137
- printResumptionHeader(15);
2753
+ printResumptionHeader(17);
2138
2754
  }
2139
2755
  console.log('Update the journal with the commit SHA and amend the commit.');
2140
2756
  console.log();
@@ -2142,24 +2758,26 @@ function printStep15(root, feature) {
2142
2758
  checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
2143
2759
  checkbox('Amend the commit to include the journal update: `git add .codeyam/journal/ && git commit --amend --no-edit`');
2144
2760
  console.log(chalk.dim(' The journal-update modifies journal files after the commit — amend to keep the tree clean.'));
2145
- stopGate(15);
2761
+ stopGate(17);
2146
2762
  }
2147
- // ─── Step 16: Push ───────────────────────────────────────────────────
2148
- function printStep16(root, feature) {
2763
+ // ─── Step 18: Push ───────────────────────────────────────────────────
2764
+ function printStep18(root, feature) {
2149
2765
  const prevState = readState(root);
2150
- const isResuming = prevState?.step === 16;
2766
+ const isResuming = prevState?.step === 18;
2151
2767
  const now = new Date().toISOString();
2152
2768
  writeState(root, {
2153
2769
  feature,
2154
- step: 16,
2155
- label: STEP_LABELS[16],
2770
+ step: 18,
2771
+ label: STEP_LABELS[18],
2156
2772
  startedAt: isResuming ? prevState.startedAt : now,
2157
2773
  featureStartedAt: prevState?.featureStartedAt || now,
2774
+ appFormats: prevState?.appFormats,
2775
+ techStackId: prevState?.techStackId,
2158
2776
  });
2159
- logEvent(root, 'step', { step: 16, label: 'Push', feature });
2160
- stepHeader(16, 'Push', feature);
2777
+ logEvent(root, 'step', { step: 18, label: 'Push', feature });
2778
+ stepHeader(18, 'Push', feature);
2161
2779
  if (isResuming) {
2162
- printResumptionHeader(16);
2780
+ printResumptionHeader(18);
2163
2781
  }
2164
2782
  console.log('Push the commit to the remote repository.');
2165
2783
  console.log();
@@ -2170,12 +2788,15 @@ function printStep16(root, feature) {
2170
2788
  console.log(chalk.dim(' If the user says yes, run: `git push`'));
2171
2789
  console.log(chalk.dim(' If NO remote exists, ask (AskUserQuestion): "This project doesn\'t have a git remote yet. Would you like help setting one up? I can walk you through creating a GitHub repository." with options "Yes, set up remote" and "No, skip"'));
2172
2790
  console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
2173
- checkbox('After the user responds, run: `codeyam editor steps` to start the next feature');
2791
+ console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
2174
2792
  console.log();
2175
- console.log(chalk.red.bold(' If the user reports a bug or requests a fix after committing:'));
2176
- console.log(chalk.red.bold(' You MUST still run `codeyam editor change` before making any changes.'));
2177
- console.log(chalk.red.bold(' The change workflow applies to ALL changes — post-commit fixes are not an exception.'));
2178
- stopGate(16, { confirm: true });
2793
+ const port = getServerPort();
2794
+ console.log(chalk.bold.yellow('IMPORTANT: After the push succeeds (or the user skips it), the feature is DONE.'));
2795
+ console.log(chalk.yellow(` Signal the UI by running: curl -s -X POST http://localhost:${port}/api/editor-feature-complete`));
2796
+ console.log(chalk.yellow(' Then STOP and wait. Do NOT ask the user what to build next — the UI handles the transition.'));
2797
+ console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
2798
+ console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
2799
+ stopGate(18, { confirm: true });
2179
2800
  }
2180
2801
  // ─── Command definition ───────────────────────────────────────────────
2181
2802
  // ─── Analyze-imports subcommand ────────────────────────────────────────
@@ -2195,7 +2816,7 @@ async function handleAnalyzeImports(options = {}) {
2195
2816
  return;
2196
2817
  }
2197
2818
  console.error(chalk.red('Error: .codeyam/glossary.json not found.'));
2198
- console.error(chalk.dim(' Run codeyam editor 6 to create the glossary first.'));
2819
+ console.error(chalk.dim(' Run codeyam editor 8 to create the glossary first.'));
2199
2820
  process.exit(1);
2200
2821
  }
2201
2822
  let glossaryEntries;
@@ -2204,10 +2825,14 @@ async function handleAnalyzeImports(options = {}) {
2204
2825
  glossaryEntries = sanitizeGlossaryEntries(parsed);
2205
2826
  }
2206
2827
  catch {
2828
+ if (options.silent)
2829
+ return;
2207
2830
  console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
2208
2831
  process.exit(1);
2209
2832
  }
2210
2833
  if (glossaryEntries.length === 0) {
2834
+ if (options.silent)
2835
+ return;
2211
2836
  console.error(chalk.red('Error: glossary.json is empty.'));
2212
2837
  process.exit(1);
2213
2838
  }
@@ -2249,25 +2874,137 @@ async function handleAnalyzeImports(options = {}) {
2249
2874
  // Non-fatal — scenario file paths just won't be included
2250
2875
  }
2251
2876
  const progress = new ProgressReporter();
2252
- // Run data-structure-only analysis for all entities (glossary + pages)
2877
+ // Filter to only files that actually need analysis avoids re-analyzing
2878
+ // ~120 files when only 2 are incomplete.
2879
+ let targetFilePaths = filePaths;
2880
+ try {
2881
+ const { getDatabase } = await import('../../../packages/database/index.js');
2882
+ const db = getDatabase();
2883
+ const { requireBranchAndProject: rbp } = await import('../utils/database.js');
2884
+ const { project } = await rbp(JSON.parse(fs.readFileSync(path.join(root, '.codeyam', 'config.json'), 'utf8')).projectSlug);
2885
+ const { filterToIncompleteFilePaths } = await import('../utils/editorAudit.js');
2886
+ const incomplete = await filterToIncompleteFilePaths(db, project.id, filePaths);
2887
+ if (incomplete.length < filePaths.length && incomplete.length > 0) {
2888
+ if (!options.silent) {
2889
+ console.log(chalk.dim(`Skipping ${filePaths.length - incomplete.length} already-analyzed files, analyzing ${incomplete.length} remaining...`));
2890
+ }
2891
+ targetFilePaths = incomplete;
2892
+ }
2893
+ else if (incomplete.length === 0) {
2894
+ if (!options.silent) {
2895
+ console.log(chalk.green('All entities already have analyses — nothing to do.'));
2896
+ }
2897
+ // Still need to run the import graph + backfill below
2898
+ targetFilePaths = [];
2899
+ }
2900
+ }
2901
+ catch {
2902
+ // Non-fatal — fall back to analyzing all files
2903
+ }
2904
+ // If specific file paths were requested, scope analysis to only those files.
2905
+ // The full filePaths set is still used for the import graph below.
2906
+ if (options.filePaths && options.filePaths.length > 0) {
2907
+ const requested = new Set(options.filePaths);
2908
+ targetFilePaths = targetFilePaths.filter((fp) => requested.has(fp));
2909
+ if (targetFilePaths.length === 0 && !options.silent) {
2910
+ console.log(chalk.dim('Requested file(s) already analyzed or not in glossary — skipping analysis.'));
2911
+ }
2912
+ }
2913
+ // Run data-structure-only analysis for entities that need it.
2253
2914
  // Don't pass entityNames — entities may not exist yet (fresh clone).
2254
2915
  // The analyzer will discover and create them from file paths.
2255
- progress.start('Running import analysis for all entities...');
2256
- try {
2257
- await runAnalysisForEntities({
2258
- projectRoot: root,
2259
- filePaths,
2260
- progress,
2261
- onlyDataStructure: true,
2262
- });
2916
+ if (targetFilePaths.length > 0) {
2917
+ progress.start(`Running import analysis for ${targetFilePaths.length} entit${targetFilePaths.length !== 1 ? 'ies' : 'y'}...`);
2918
+ try {
2919
+ await runAnalysisForEntities({
2920
+ projectRoot: root,
2921
+ filePaths: targetFilePaths,
2922
+ progress,
2923
+ onlyDataStructure: true,
2924
+ });
2925
+ }
2926
+ catch (err) {
2927
+ progress.fail('Analysis failed');
2928
+ const msg = err instanceof Error ? err.message : String(err);
2929
+ if (options.silent) {
2930
+ // Internal caller — don't kill the process, let the caller handle it
2931
+ return;
2932
+ }
2933
+ console.error(chalk.red(`Error: ${msg}`));
2934
+ process.exit(1);
2935
+ }
2936
+ progress.succeed('Analysis complete');
2263
2937
  }
2264
- catch (err) {
2265
- progress.fail('Analysis failed');
2266
- const msg = err instanceof Error ? err.message : String(err);
2267
- console.error(chalk.red(`Error: ${msg}`));
2268
- process.exit(1);
2938
+ // Surface analysis errors if any occurred
2939
+ const errorReportPath = path.join(root, '.codeyam', 'analysis-errors.txt');
2940
+ if (fs.existsSync(errorReportPath)) {
2941
+ if (!options.silent) {
2942
+ console.log(chalk.yellow('\nSome entity analyses failed. Full details in .codeyam/analysis-errors.txt'));
2943
+ console.log(chalk.yellow('Send this file to the CodeYam team for diagnosis.'));
2944
+ console.log(chalk.dim('Affected entities will be missing from the import graph.'));
2945
+ }
2946
+ // Write structured analysis-failures.json for the audit to detect.
2947
+ // Parse the error report to find which entities failed, and cross-reference
2948
+ // with the files we tried to analyze.
2949
+ try {
2950
+ const { readAnalysisFailures, writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
2951
+ const errorContent = fs.readFileSync(errorReportPath, 'utf8');
2952
+ const existingFailures = readAnalysisFailures(root);
2953
+ // Parse error messages to identify failed file paths.
2954
+ // Error format: "Error on step <StepName> for entity <Name> (<filePath>)"
2955
+ // or more generic "CodeYam Error: <message>"
2956
+ const entityErrorRegex = /(?:Error on step \w+ for entity (\w+))|(?:entity[: ]+["']?(\w+)["']?)/gi;
2957
+ const failedEntityNames = new Set();
2958
+ let match;
2959
+ while ((match = entityErrorRegex.exec(errorContent)) !== null) {
2960
+ const name = match[1] || match[2];
2961
+ if (name)
2962
+ failedEntityNames.add(name);
2963
+ }
2964
+ // Map failed entity names back to file paths from targetFilePaths
2965
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
2966
+ let glossary = [];
2967
+ try {
2968
+ glossary = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
2969
+ }
2970
+ catch {
2971
+ // Non-fatal
2972
+ }
2973
+ // Also: any targetFilePaths that didn't produce entities are failures
2974
+ const now = new Date().toISOString();
2975
+ const updatedFailures = { ...existingFailures };
2976
+ if (failedEntityNames.size > 0) {
2977
+ for (const name of failedEntityNames) {
2978
+ const entry = glossary.find((e) => e.name === name);
2979
+ if (entry) {
2980
+ updatedFailures[entry.filePath] = {
2981
+ entityName: name,
2982
+ error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
2983
+ failedAt: now,
2984
+ };
2985
+ }
2986
+ }
2987
+ }
2988
+ // When we can't parse specific entity names from the error, DON'T mark
2989
+ // all target files as failed. The error may be non-fatal (e.g., a cache
2990
+ // miss logged as "CodeYam Error") and blanket-marking every file as
2991
+ // permanently failed blocks future audits from resolving them.
2992
+ writeAnalysisFailures(root, updatedFailures);
2993
+ }
2994
+ catch {
2995
+ // Non-fatal — failure tracking is best-effort
2996
+ }
2997
+ }
2998
+ else {
2999
+ // No errors — clear any stale failure tracking
3000
+ try {
3001
+ const { writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
3002
+ writeAnalysisFailures(root, {});
3003
+ }
3004
+ catch {
3005
+ // Non-fatal
3006
+ }
2269
3007
  }
2270
- progress.succeed('Analysis complete');
2271
3008
  // Load entities WITH metadata from database
2272
3009
  progress.start('Loading entity metadata...');
2273
3010
  await initializeEnvironment();
@@ -2347,6 +3084,8 @@ async function handleAnalyzeImports(options = {}) {
2347
3084
  sha: e.sha,
2348
3085
  name: e.name,
2349
3086
  filePath: e.filePath || '',
3087
+ isDefaultExport: e.metadata?.notExported === false &&
3088
+ e.metadata?.namedExport === false,
2350
3089
  })));
2351
3090
  if (backfillResult.updated > 0 && !options.silent) {
2352
3091
  console.log(chalk.green(`Linked ${backfillResult.updated} scenario(s) to their entities.`));
@@ -2448,43 +3187,98 @@ async function handleDelete(scenarioId) {
2448
3187
  * Creates isolation route directories and the layout guard file.
2449
3188
  * This avoids brace-expansion permission prompts in the embedded terminal.
2450
3189
  */
3190
+ function handleDesignSystem(designSystemId) {
3191
+ const root = process.cwd();
3192
+ const ds = DESIGN_SYSTEMS.find((d) => d.id === designSystemId);
3193
+ if (!ds) {
3194
+ console.error(chalk.red(`Error: Unknown design system "${designSystemId}". Valid options: ${DESIGN_SYSTEMS.map((d) => d.id).join(', ')}`));
3195
+ process.exit(1);
3196
+ }
3197
+ const srcPath = path.join(__dirname, '..', '..', 'templates', 'design-systems', ds.fileName);
3198
+ if (!fs.existsSync(srcPath)) {
3199
+ console.error(chalk.red(`Error: Design system file not found: ${srcPath}`));
3200
+ process.exit(1);
3201
+ }
3202
+ const destDir = path.join(root, '.codeyam');
3203
+ fs.mkdirSync(destDir, { recursive: true });
3204
+ const destPath = path.join(destDir, 'design-system.md');
3205
+ fs.copyFileSync(srcPath, destPath);
3206
+ console.log(chalk.green(`Installed "${ds.name}" design system → .codeyam/design-system.md`));
3207
+ }
2451
3208
  function handleIsolate(componentNames) {
2452
3209
  const root = process.cwd();
3210
+ const ctx = getTechStackContext(root);
2453
3211
  if (componentNames.length === 0) {
2454
3212
  console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
2455
3213
  process.exit(1);
2456
3214
  }
2457
3215
  const isolateDir = path.join(root, 'app', 'isolated-components');
2458
- // Create layout.tsx with production guard if missing
2459
- const layoutPath = path.join(isolateDir, 'layout.tsx');
3216
+ // Create the framework-appropriate layout guard if missing.
3217
+ // Clean up wrong-framework layout from a previous CLI version.
3218
+ const layoutPath = path.join(isolateDir, ctx.isExpo ? '_layout.tsx' : 'layout.tsx');
3219
+ const wrongLayoutPath = path.join(isolateDir, ctx.isExpo ? 'layout.tsx' : '_layout.tsx');
3220
+ if (fs.existsSync(wrongLayoutPath)) {
3221
+ fs.unlinkSync(wrongLayoutPath);
3222
+ }
2460
3223
  if (!fs.existsSync(layoutPath)) {
2461
3224
  fs.mkdirSync(isolateDir, { recursive: true });
2462
- fs.writeFileSync(layoutPath, [
2463
- 'import { notFound } from "next/navigation";',
2464
- '',
2465
- 'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
2466
- ' if (process.env.NODE_ENV === "production") notFound();',
2467
- ' return <>{children}</>;',
2468
- '}',
2469
- '',
2470
- ].join('\n'), 'utf8');
2471
- console.log(chalk.green(`Created layout guard: app/isolated-components/layout.tsx`));
2472
- }
2473
- // Create a directory for each component
3225
+ if (ctx.isExpo) {
3226
+ fs.writeFileSync(layoutPath, [
3227
+ 'import { Redirect, Slot } from "expo-router";',
3228
+ '',
3229
+ 'export default function CaptureLayout() {',
3230
+ ' if (!__DEV__) return <Redirect href="/" />;',
3231
+ ' return <Slot />;',
3232
+ '}',
3233
+ '',
3234
+ ].join('\n'), 'utf8');
3235
+ }
3236
+ else {
3237
+ fs.writeFileSync(layoutPath, [
3238
+ 'import { notFound } from "next/navigation";',
3239
+ '',
3240
+ 'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
3241
+ ' if (process.env.NODE_ENV === "production") notFound();',
3242
+ ' return <>{children}</>;',
3243
+ '}',
3244
+ '',
3245
+ ].join('\n'), 'utf8');
3246
+ }
3247
+ console.log(chalk.green(`Created layout guard: app/isolated-components/${ctx.isExpo ? '_layout.tsx' : 'layout.tsx'}`));
3248
+ }
3249
+ // Create isolation route for each component.
3250
+ // Expo Router uses flat files (ComponentName.tsx), Next.js uses subdirectories (ComponentName/page.tsx).
2474
3251
  const created = [];
2475
3252
  const existed = [];
2476
3253
  for (const name of componentNames) {
2477
- const dir = path.join(isolateDir, name);
2478
- if (fs.existsSync(dir)) {
2479
- existed.push(name);
3254
+ if (ctx.isExpo) {
3255
+ const filePath = path.join(isolateDir, `${name}.tsx`);
3256
+ if (fs.existsSync(filePath)) {
3257
+ existed.push(name);
3258
+ }
3259
+ else {
3260
+ created.push(name);
3261
+ }
2480
3262
  }
2481
3263
  else {
2482
- fs.mkdirSync(dir, { recursive: true });
2483
- created.push(name);
3264
+ const dir = path.join(isolateDir, name);
3265
+ if (fs.existsSync(dir)) {
3266
+ existed.push(name);
3267
+ }
3268
+ else {
3269
+ fs.mkdirSync(dir, { recursive: true });
3270
+ created.push(name);
3271
+ }
2484
3272
  }
2485
3273
  }
2486
3274
  if (created.length > 0) {
2487
- console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
3275
+ if (ctx.isExpo) {
3276
+ console.log(chalk.green(`Ready for ${created.length} isolation route(s): ${created.map((n) => `app/isolated-components/${n}.tsx`).join(', ')}`));
3277
+ console.log(chalk.dim(' Create each file with useLocalSearchParams, a scenarios map, and nativeID="codeyam-capture"'));
3278
+ }
3279
+ else {
3280
+ console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
3281
+ }
2488
3282
  }
2489
3283
  if (existed.length > 0) {
2490
3284
  console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
@@ -2538,6 +3332,12 @@ function formatApiSubcommandResult(subcommand, data) {
2538
3332
  parts.push(`scenarioId="${data.scenarioId}"`);
2539
3333
  if (data.sessionsNotified != null)
2540
3334
  parts.push(`notified=${data.sessionsNotified}`);
3335
+ // Surface the HTTP health check from the dev server
3336
+ if (data.preview) {
3337
+ parts.push(`healthy=${data.preview.healthy}`);
3338
+ if (data.preview.error)
3339
+ parts.push(`error="${data.preview.error}"`);
3340
+ }
2541
3341
  return parts.join(' ');
2542
3342
  }
2543
3343
  case 'dev-server': {
@@ -2587,10 +3387,82 @@ function formatApiSubcommandResult(subcommand, data) {
2587
3387
  }
2588
3388
  return parts.join(' ');
2589
3389
  }
3390
+ case 'verify-routes': {
3391
+ const lines = [];
3392
+ lines.push(chalk.bold.yellow('━━━ Route Verification ━━━'));
3393
+ for (const [route, info] of Object.entries(data.routes || {})) {
3394
+ const r = info;
3395
+ if (r.ok) {
3396
+ lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}`));
3397
+ }
3398
+ else {
3399
+ lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
3400
+ }
3401
+ }
3402
+ for (const [route, info] of Object.entries(data.apiRoutes || {})) {
3403
+ const r = info;
3404
+ if (r.ok && r.isJSON) {
3405
+ lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}, valid JSON`));
3406
+ }
3407
+ else if (r.ok) {
3408
+ lines.push(chalk.yellow(` ⚠ ${route} — HTTP ${r.status}, not valid JSON`));
3409
+ }
3410
+ else {
3411
+ lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
3412
+ }
3413
+ }
3414
+ lines.push('');
3415
+ lines.push(data.ok ? chalk.green(data.summary) : chalk.red(data.summary));
3416
+ return lines.join('\n');
3417
+ }
2590
3418
  default:
2591
3419
  return null; // journal-list, show/hide-results: keep full JSON
2592
3420
  }
2593
3421
  }
3422
+ /**
3423
+ * `codeyam editor handoff '{"summary":"..."}'`
3424
+ *
3425
+ * Update the handoff summary in .codeyam/config.json for other AI providers.
3426
+ */
3427
+ function handleHandoff(jsonArg) {
3428
+ if (!jsonArg) {
3429
+ console.error(chalk.red('Error: JSON argument required.'));
3430
+ console.error(chalk.dim(' Usage: codeyam editor handoff \'{"summary":"Built the X component..."}\''));
3431
+ process.exit(1);
3432
+ }
3433
+ let summary;
3434
+ try {
3435
+ const parsed = JSON.parse(jsonArg);
3436
+ summary = parsed.summary;
3437
+ }
3438
+ catch {
3439
+ console.error(chalk.red('Error: Invalid JSON.'));
3440
+ process.exit(1);
3441
+ }
3442
+ if (!summary) {
3443
+ console.error(chalk.red('Error: "summary" field is required.'));
3444
+ process.exit(1);
3445
+ }
3446
+ const root = getProjectRoot();
3447
+ const configPath = path.join(root, '.codeyam', 'config.json');
3448
+ try {
3449
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
3450
+ const state = readState(root);
3451
+ const provider = config.provider || 'claude';
3452
+ config.handoff = {
3453
+ summary,
3454
+ lastProvider: provider,
3455
+ lastStep: state?.step,
3456
+ lastUpdated: new Date().toISOString(),
3457
+ };
3458
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
3459
+ console.log(chalk.green('✓ Handoff summary updated in .codeyam/config.json'));
3460
+ }
3461
+ catch (err) {
3462
+ console.error(chalk.red(`Error: Could not update config.json: ${err.message}`));
3463
+ process.exit(1);
3464
+ }
3465
+ }
2594
3466
  /**
2595
3467
  * `codeyam editor register '{"name":"...","componentName":"...",...}'`
2596
3468
  *
@@ -2615,11 +3487,15 @@ async function handleRegister(jsonArg) {
2615
3487
  }
2616
3488
  // Normalize to array for uniform handling
2617
3489
  const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
3490
+ const root = getProjectRoot();
2618
3491
  const port = getServerPort();
2619
3492
  const url = `http://localhost:${port}/api/editor-register-scenario`;
2620
3493
  const isBatch = items.length > 1;
2621
3494
  let succeeded = 0;
2622
3495
  let failed = 0;
3496
+ let warnings = 0;
3497
+ const issues = [];
3498
+ const screenshotEntries = [];
2623
3499
  for (let i = 0; i < items.length; i++) {
2624
3500
  const body = items[i];
2625
3501
  const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
@@ -2647,11 +3523,30 @@ async function handleRegister(jsonArg) {
2647
3523
  else {
2648
3524
  parts.push(`errors=0`);
2649
3525
  }
2650
- if (data.seedResult)
2651
- parts.push(`seed=${data.seedResult.success}`);
3526
+ if (data.visibleText) {
3527
+ const truncated = data.visibleText.length > 100
3528
+ ? data.visibleText.substring(0, 97) + '...'
3529
+ : data.visibleText;
3530
+ parts.push(`visibleText="${truncated}"`);
3531
+ }
3532
+ if (data.seedResult) {
3533
+ if (data.seedResult.success) {
3534
+ parts.push(`seed=ok`);
3535
+ }
3536
+ else {
3537
+ parts.push(chalk.red(`seed=FAILED${data.seedResult.error ? ` (${data.seedResult.error})` : ''}`));
3538
+ }
3539
+ }
2652
3540
  if (data.captureError)
2653
3541
  parts.push(chalk.yellow(`captureError="${data.captureError}"`));
2654
3542
  console.log(prefix + parts.join(' '));
3543
+ // Warn when application scenario is missing seed data
3544
+ if (data.missingSeedWarning) {
3545
+ console.log(chalk.red.bold('WARNING: No seed data in this application scenario!'));
3546
+ console.log(chalk.yellow(' This scenario has type "application" but no "seed" data was provided.'));
3547
+ console.log(chalk.yellow(' The page will render with an empty database — nothing will be visible.'));
3548
+ console.log(chalk.yellow(' Re-register with "seed":{...} containing the database rows this page needs.'));
3549
+ }
2655
3550
  // Surface client errors prominently so they can't be missed
2656
3551
  if (data.clientErrors && data.clientErrors.length > 0) {
2657
3552
  console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
@@ -2659,14 +3554,39 @@ async function handleRegister(jsonArg) {
2659
3554
  console.log(chalk.red(` → ${err}`));
2660
3555
  }
2661
3556
  console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
3557
+ // Provide actionable debugging hints for HTTP/API errors
3558
+ const hasApiResponseError = data.clientErrors.some((e) => e.includes('API response error:'));
3559
+ const hasHttpError = data.clientErrors.some((e) => e.includes('HTTP error:'));
3560
+ if (hasHttpError || hasApiResponseError) {
3561
+ console.log(chalk.yellow(' To debug: look at the "API response error" lines above — they show the exact API endpoint'));
3562
+ console.log(chalk.yellow(' that failed and what the server returned. Then check the scenario seed data to ensure'));
3563
+ console.log(chalk.yellow(' all IDs referenced in the URL exist in the seed, and that the route is correct.'));
3564
+ }
2662
3565
  console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
2663
3566
  }
3567
+ const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
2664
3568
  if (!res.ok) {
2665
3569
  console.error(chalk.dim(JSON.stringify(data, null, 2)));
2666
3570
  failed++;
2667
3571
  }
2668
3572
  else {
3573
+ if (resultIssues.length > 0)
3574
+ warnings++;
2669
3575
  succeeded++;
3576
+ // Collect screenshot paths for duplicate detection
3577
+ if (data.screenshotCaptured &&
3578
+ data.scenario?.screenshotPath &&
3579
+ data.scenario?.name) {
3580
+ const absPath = path.join(root, '.codeyam', 'editor-scenarios', data.scenario.screenshotPath);
3581
+ screenshotEntries.push({
3582
+ scenarioName: data.scenario.name,
3583
+ screenshotAbsPath: absPath,
3584
+ });
3585
+ }
3586
+ }
3587
+ // Collect issues from this registration
3588
+ for (const issue of resultIssues) {
3589
+ issues.push(`"${issue.scenarioName}": ${issue.message}`);
2670
3590
  }
2671
3591
  }
2672
3592
  catch (error) {
@@ -2677,12 +3597,99 @@ async function handleRegister(jsonArg) {
2677
3597
  failed++;
2678
3598
  }
2679
3599
  }
3600
+ // Detect duplicate screenshots in the batch
3601
+ if (isBatch && screenshotEntries.length > 1) {
3602
+ const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
3603
+ const hashEntries = screenshotEntries
3604
+ .map((e) => {
3605
+ const hash = computeScreenshotHash(e.screenshotAbsPath);
3606
+ return hash
3607
+ ? {
3608
+ scenarioName: e.scenarioName,
3609
+ screenshotPath: e.screenshotAbsPath,
3610
+ hash,
3611
+ }
3612
+ : null;
3613
+ })
3614
+ .filter((e) => e !== null);
3615
+ const duplicates = findDuplicateScreenshots(hashEntries);
3616
+ if (duplicates.size > 0) {
3617
+ console.log('');
3618
+ console.log(chalk.yellow.bold('WARNING: Identical screenshots detected:'));
3619
+ for (const [, names] of duplicates) {
3620
+ const quoted = names.map((n) => `"${n}"`);
3621
+ console.log(chalk.yellow(` ${quoted.join(' and ')} produced the same screenshot`));
3622
+ }
3623
+ console.log(chalk.yellow(" This usually means the app's view state depends on something scenarios can't control"));
3624
+ console.log(chalk.yellow(' (e.g., a hardcoded function return value, or identical localStorage not differentiating views).'));
3625
+ }
3626
+ }
2680
3627
  if (isBatch) {
2681
- console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
3628
+ console.log('');
3629
+ if (failed > 0 || warnings > 0) {
3630
+ console.log(chalk.red.bold(`ERROR: ${failed} failed, ${warnings} with warnings out of ${items.length} scenarios`));
3631
+ console.log('');
3632
+ console.log(chalk.red.bold('Issues that MUST be fixed:'));
3633
+ for (const issue of issues) {
3634
+ console.log(chalk.red(` ✗ ${issue}`));
3635
+ }
3636
+ console.log('');
3637
+ console.log(chalk.red('Do NOT skip these errors. Fix each issue and re-register the affected scenarios.'));
3638
+ }
3639
+ else {
3640
+ console.log(chalk.green(`✓ Batch complete: ${succeeded}/${items.length} succeeded with no issues`));
3641
+ }
3642
+ }
3643
+ if (failed > 0 || warnings > 0) {
3644
+ process.exit(1);
3645
+ }
3646
+ }
3647
+ // ─── Glossary-add subcommand ──────────────────────────────────────────
3648
+ /**
3649
+ * `codeyam editor glossary-add '{"name":"...", "filePath":"...", "description":"..."}'`
3650
+ *
3651
+ * Safely adds/updates entries in .codeyam/glossary.json via the CLI,
3652
+ * avoiding hand-editing that breaks on unicode characters.
3653
+ */
3654
+ async function handleGlossaryAdd(jsonArg) {
3655
+ if (!jsonArg) {
3656
+ console.error(chalk.red('Error: JSON argument required.'));
3657
+ console.error(chalk.dim(' Usage: codeyam editor glossary-add \'{"name":"DrinkCard","filePath":"app/components/DrinkCard.tsx","description":"Displays a drink item"}\''));
3658
+ console.error(chalk.dim(' For large payloads: codeyam editor glossary-add @.codeyam/tmp/entry.json'));
3659
+ process.exit(1);
2682
3660
  }
2683
- if (failed > 0) {
3661
+ const parsed = parseRegisterArg(jsonArg);
3662
+ if (parsed.error) {
3663
+ console.error(chalk.red(`Error: ${parsed.error}`));
3664
+ process.exit(1);
3665
+ }
3666
+ // Normalize to array
3667
+ const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
3668
+ // Read existing glossary
3669
+ const root = getProjectRoot();
3670
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
3671
+ let existing = [];
3672
+ try {
3673
+ const raw = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
3674
+ existing = sanitizeGlossaryEntries(raw);
3675
+ }
3676
+ catch {
3677
+ // Glossary doesn't exist yet or can't be parsed — start fresh
3678
+ }
3679
+ // Merge
3680
+ const { validateGlossaryEntry, mergeGlossaryEntries } = await import('../utils/glossaryAdd.js');
3681
+ const result = mergeGlossaryEntries(existing, items);
3682
+ // Report validation errors
3683
+ for (const err of result.errors) {
3684
+ console.error(chalk.red(`Error at index ${err.index}: ${err.message}`));
3685
+ }
3686
+ if (result.added === 0 && result.updated === 0 && result.errors.length > 0) {
2684
3687
  process.exit(1);
2685
3688
  }
3689
+ // Write back with utf8 encoding to safely handle unicode
3690
+ fs.mkdirSync(path.dirname(glossaryPath), { recursive: true });
3691
+ fs.writeFileSync(glossaryPath, JSON.stringify(result.entries, null, 2), 'utf8');
3692
+ console.log(`added=${result.added} updated=${result.updated} total=${result.entries.length}`);
2686
3693
  }
2687
3694
  // ─── Dependents subcommand ────────────────────────────────────────────
2688
3695
  /**
@@ -2805,7 +3812,7 @@ async function handleDependents(entityName) {
2805
3812
  *
2806
3813
  * Prints a condensed post-change checklist that guides Claude through
2807
3814
  * re-verifying after user-requested modifications. When called from
2808
- * step 13+, this loops back to step 13 (present). When called from an
3815
+ * step 15+, this loops back to step 15 (present). When called from an
2809
3816
  * earlier step, it returns to that step so the normal flow continues.
2810
3817
  */
2811
3818
  function handleChange(feature) {
@@ -2823,7 +3830,7 @@ function handleChange(feature) {
2823
3830
  process.exit(1);
2824
3831
  }
2825
3832
  }
2826
- const currentStep = state?.step ?? 13;
3833
+ const currentStep = state?.step ?? 15;
2827
3834
  const port = getServerPort();
2828
3835
  console.log();
2829
3836
  console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
@@ -2845,6 +3852,8 @@ function handleChange(feature) {
2845
3852
  console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
2846
3853
  printDimensionGuidance(dim, dimNames);
2847
3854
  console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
3855
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
3856
+ console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
2848
3857
  console.log();
2849
3858
  console.log(chalk.bold('0. Close the results panel:'));
2850
3859
  checkbox(`Hide results: \`codeyam editor hide-results\``);
@@ -2871,6 +3880,7 @@ function handleChange(feature) {
2871
3880
  console.log(chalk.dim(' re-register "Home - Default", "Catalog - Full", "Detail - WithReviews", etc.'));
2872
3881
  checkbox("Enrich existing scenario data to exercise the change — don't just re-register unchanged data");
2873
3882
  console.log(chalk.dim(' Add data that demonstrates what changed: new fields, relationships, states, content variety.'));
3883
+ console.log(chalk.dim(' If the enriched data makes the scenario name too narrow, rename it to reflect its broader coverage.'));
2874
3884
  checkbox('After each re-registration, view the screenshot to verify data is visible');
2875
3885
  console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
2876
3886
  console.log();
@@ -2879,6 +3889,7 @@ function handleChange(feature) {
2879
3889
  checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
2880
3890
  printDimensionGuidance(dim, dimNames);
2881
3891
  checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
3892
+ checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
2882
3893
  checkbox('Run `codeyam editor audit` — all checks must pass');
2883
3894
  checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
2884
3895
  console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
@@ -2893,10 +3904,10 @@ function handleChange(feature) {
2893
3904
  console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
2894
3905
  console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
2895
3906
  console.log();
2896
- // If the change was initiated from a step before 13, return to that step
2897
- // instead of jumping to step 13. The change workflow should only loop to
2898
- // step 13 when changes are requested FROM step 13.
2899
- if (currentStep < 13) {
3907
+ // If the change was initiated from a step before 15, return to that step
3908
+ // instead of jumping to step 15. The change workflow should only loop to
3909
+ // step 15 when changes are requested FROM step 15.
3910
+ if (currentStep < 15) {
2900
3911
  console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2901
3912
  console.log(chalk.red.bold(' REQUIRED: Return to current step'));
2902
3913
  console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
@@ -2907,7 +3918,7 @@ function handleChange(feature) {
2907
3918
  console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2908
3919
  console.log(chalk.red.bold(' REQUIRED: Show Results'));
2909
3920
  console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
2910
- chalk.bold(`codeyam editor 13`));
3921
+ chalk.bold(`codeyam editor 15`));
2911
3922
  console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
2912
3923
  console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2913
3924
  }
@@ -2918,10 +3929,11 @@ function handleChange(feature) {
2918
3929
  * Fetch the audit result from the server.
2919
3930
  * Returns null if the server is unreachable.
2920
3931
  */
2921
- async function fetchAuditResult() {
3932
+ async function fetchAuditResult(options) {
2922
3933
  const port = getServerPort();
3934
+ const params = options?.skipTests ? '?skipTests=true' : '';
2923
3935
  try {
2924
- const res = await fetch(`http://localhost:${port}/api/editor-audit`);
3936
+ const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
2925
3937
  if (!res.ok)
2926
3938
  return null;
2927
3939
  return await res.json();
@@ -2944,20 +3956,222 @@ async function checkAuditGate() {
2944
3956
  return true; // Server unreachable — don't block
2945
3957
  if (data.summary?.allPassing === true)
2946
3958
  return true;
2947
- // If incomplete entities are the only issue, auto-fix with analyze-imports (once)
2948
- const { isAutoRemediable } = await import('../utils/editorAudit.js');
2949
- if (isAutoRemediable(data.summary, false)) {
3959
+ // Lightweight auto-fix: backfill entity_sha (DB-only, fast).
3960
+ // Never run handleAnalyzeImports here it takes minutes on large projects.
3961
+ const { isOnlyIncompleteEntities, isOnlyPreExistingIncomplete } = await import('../utils/editorAudit.js');
3962
+ // If the only failures are pre-existing incomplete entities (not caused by
3963
+ // this session), don't block — the audit text already calls these "non-blocking".
3964
+ if (isOnlyPreExistingIncomplete(data.summary, data.incompleteEntities)) {
3965
+ return true;
3966
+ }
3967
+ if (isOnlyIncompleteEntities(data.summary)) {
3968
+ try {
3969
+ const entities = await loadEntities({});
3970
+ if (entities && entities.length > 0) {
3971
+ const { getDatabase } = await import('../../../packages/database/index.js');
3972
+ const db = getDatabase();
3973
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3974
+ sha: e.sha,
3975
+ name: e.name,
3976
+ filePath: e.filePath || '',
3977
+ isDefaultExport: e.metadata?.notExported === false &&
3978
+ e.metadata?.namedExport === false,
3979
+ })));
3980
+ }
3981
+ }
3982
+ catch {
3983
+ // Fall through
3984
+ }
3985
+ // Re-check after backfill — skip re-running tests (backfill is DB-only)
3986
+ const retry = await fetchAuditResult({ skipTests: true });
3987
+ if (retry?.summary?.allPassing === true)
3988
+ return true;
3989
+ }
3990
+ // Print specific failures so Claude knows what to fix without running audit separately
3991
+ printAuditGateFailures(data);
3992
+ return false;
3993
+ }
3994
+ /**
3995
+ * Print a concise summary of audit failures for the gate block message.
3996
+ * This gives Claude immediate context about what to fix without needing
3997
+ * to run `codeyam editor audit` as a separate step.
3998
+ */
3999
+ function printAuditGateFailures(data) {
4000
+ const s = data.summary;
4001
+ if (!s)
4002
+ return;
4003
+ const issues = [];
4004
+ if (s.componentsMissing > 0) {
4005
+ issues.push(`${s.componentsMissing} component(s) missing scenarios`);
4006
+ const missing = (data.components || []).filter((c) => c.status === 'missing');
4007
+ for (const c of missing) {
4008
+ issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
4009
+ if (c.hint)
4010
+ issues.push(` Fix: ${c.hint}`);
4011
+ }
4012
+ }
4013
+ if (s.componentsWithErrors > 0) {
4014
+ issues.push(`${s.componentsWithErrors} component(s) with client errors (browser API or runtime errors in captured scenarios)`);
4015
+ const withErrors = (data.components || []).filter((c) => c.status === 'has_errors');
4016
+ for (const c of withErrors) {
4017
+ issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
4018
+ }
4019
+ }
4020
+ if (s.functionsMissing > 0) {
4021
+ issues.push(`${s.functionsMissing} function(s) missing test files`);
4022
+ const missing = (data.functions || []).filter((f) => f.status === 'missing');
4023
+ for (const f of missing) {
4024
+ if (f.testFile) {
4025
+ issues.push(` → ${f.name} — test file missing: ${f.testFile}`);
4026
+ }
4027
+ else {
4028
+ issues.push(` → ${f.name} (${f.filePath}) — no testFile in glossary`);
4029
+ if (f.suggestedTestFile) {
4030
+ issues.push(` Fix: Create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to .codeyam/glossary.json`);
4031
+ }
4032
+ }
4033
+ }
4034
+ }
4035
+ if (s.functionsFailing > 0) {
4036
+ issues.push(`${s.functionsFailing} function(s) with failing tests`);
4037
+ const failing = (data.functions || []).filter((f) => f.status === 'failing');
4038
+ for (const f of failing) {
4039
+ issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
4040
+ }
4041
+ }
4042
+ if (s.functionsRunnerError > 0) {
4043
+ issues.push(`${s.functionsRunnerError} function(s) with test runner errors (the runner crashed — not a test failure)`);
4044
+ const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
4045
+ for (const f of runnerErrors) {
4046
+ issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
4047
+ if (f.hint)
4048
+ issues.push(` ${f.hint}`);
4049
+ else if (f.errorMessage)
4050
+ issues.push(` Error: ${f.errorMessage}`);
4051
+ }
4052
+ }
4053
+ if (s.functionsNameMismatch > 0) {
4054
+ issues.push(`${s.functionsNameMismatch} function(s) with test name mismatch`);
4055
+ const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
4056
+ for (const f of mismatch) {
4057
+ issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
4058
+ if (f.hint)
4059
+ issues.push(` Fix: ${f.hint}`);
4060
+ }
4061
+ }
4062
+ if (s.missingFromGlossary > 0) {
4063
+ issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
4064
+ const missingGlossary = data.missingFromGlossary || [];
4065
+ for (const m of missingGlossary) {
4066
+ issues.push(` → ${m.name} (${m.filePath})`);
4067
+ }
4068
+ const missingPaths = missingGlossary
4069
+ .map((m) => m.filePath)
4070
+ .filter(Boolean);
4071
+ if (missingPaths.length > 0) {
4072
+ issues.push(` Fix: Run \`codeyam editor analyze-imports ${missingPaths.join(' ')}\``);
4073
+ }
4074
+ else {
4075
+ issues.push(` Fix: Run \`codeyam editor analyze-imports\` to add these files to the glossary`);
4076
+ }
4077
+ }
4078
+ if (s.incompleteEntities > 0) {
4079
+ // Check for persistent analysis failures
4080
+ let analysisFailures = {};
2950
4081
  try {
2951
- await handleAnalyzeImports({ silent: true });
4082
+ const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
4083
+ if (fs.existsSync(failuresPath)) {
4084
+ analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
4085
+ }
2952
4086
  }
2953
4087
  catch {
2954
- return false;
4088
+ // Non-fatal
4089
+ }
4090
+ const preCount = s.preExistingIncompleteEntities || 0;
4091
+ const hasFailures = Object.keys(analysisFailures).length > 0;
4092
+ if (hasFailures) {
4093
+ issues.push(`${s.incompleteEntities} entity/entities — automated analysis failed, manual analysis required`);
4094
+ }
4095
+ else if (preCount > 0 && preCount === s.incompleteEntities) {
4096
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
4097
+ }
4098
+ else if (preCount > 0) {
4099
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis (${preCount} pre-existing — not from your changes)`);
4100
+ }
4101
+ else {
4102
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
4103
+ }
4104
+ const incomplete = data.incompleteEntities || [];
4105
+ for (const e of incomplete) {
4106
+ const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
4107
+ if (failureEntry) {
4108
+ issues.push(` → ${e.name} — MANUAL ANALYSIS REQUIRED (automated analysis failed)`);
4109
+ issues.push(` Run \`codeyam editor audit\` for step-by-step manual analysis instructions`);
4110
+ }
4111
+ else {
4112
+ issues.push(` → ${e.name} (${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''})${e.preExisting ? ' [pre-existing]' : ''}`);
4113
+ }
4114
+ }
4115
+ if (!hasFailures) {
4116
+ issues.push(` Fix: Run \`codeyam editor analyze-imports\` to build import graphs`);
2955
4117
  }
2956
- // Re-check after fix
2957
- const retry = await fetchAuditResult();
2958
- return retry?.summary?.allPassing === true;
2959
4118
  }
2960
- return false;
4119
+ if (s.unassociatedScenarios > 0) {
4120
+ const unassociated = data.unassociatedScenarios || [];
4121
+ const unassocPaths = unassociated
4122
+ .map((u) => u.filePath)
4123
+ .filter(Boolean);
4124
+ if (unassocPaths.length > 0) {
4125
+ issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports ${unassocPaths.join(' ')}\` then re-run audit`);
4126
+ }
4127
+ else {
4128
+ issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
4129
+ }
4130
+ for (const u of unassociated) {
4131
+ issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
4132
+ }
4133
+ }
4134
+ if (s.miscategorizedScenarios > 0) {
4135
+ issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
4136
+ const miscategorized = data.miscategorizedScenarios || [];
4137
+ for (const m of miscategorized) {
4138
+ issues.push(` → ${m.componentName} at ${m.url} (${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''})`);
4139
+ }
4140
+ issues.push(` Fix: Re-register these as app-level scenarios (with pageFilePath, no componentName) or use isolation routes`);
4141
+ }
4142
+ if (s.scenariosNeedingRecapture > 0) {
4143
+ issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
4144
+ const recapture = data.scenariosNeedingRecapture || [];
4145
+ for (const r of recapture) {
4146
+ issues.push(` → "${r.scenarioName}" (entity: ${r.entityName}, ${r.status?.status || 'changed'})`);
4147
+ }
4148
+ issues.push(` Fix: Re-capture affected scenarios to update screenshots after code changes`);
4149
+ }
4150
+ if (issues.length > 0) {
4151
+ console.error(chalk.yellow('\nAudit failures:'));
4152
+ for (const issue of issues) {
4153
+ console.error(chalk.yellow(` • ${issue}`));
4154
+ }
4155
+ }
4156
+ // Surface client error details inline — these are the most common cause of
4157
+ // Claude looping because the errors require code fixes, not audit re-runs.
4158
+ if (data.components) {
4159
+ const withErrors = data.components.filter((c) => c.status === 'has_errors' && c.clientErrors?.length > 0);
4160
+ if (withErrors.length > 0) {
4161
+ console.error(chalk.yellow('\nClient errors found:'));
4162
+ for (const c of withErrors) {
4163
+ console.error(chalk.red(` ${c.name}:`));
4164
+ for (const err of c.clientErrors.slice(0, 3)) {
4165
+ console.error(chalk.red(` → ${err}`));
4166
+ }
4167
+ if (c.clientErrors.length > 3) {
4168
+ console.error(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
4169
+ }
4170
+ }
4171
+ console.error(chalk.yellow('\nFix: Fix the code errors above, then re-capture the affected scenarios.'));
4172
+ }
4173
+ }
4174
+ console.error(chalk.dim('\nRun `codeyam editor audit` for full details.\n'));
2961
4175
  }
2962
4176
  // ─── Audit subcommand ────────────────────────────────────────────────
2963
4177
  /**
@@ -2967,59 +4181,137 @@ async function checkAuditGate() {
2967
4181
  * which glossary components have registered scenarios and which functions
2968
4182
  * have test files. Exits with code 1 if anything is missing.
2969
4183
  */
2970
- async function handleAudit() {
4184
+ async function handleAudit(options) {
2971
4185
  let data = await fetchAuditResult();
2972
4186
  if (!data) {
2973
4187
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
2974
4188
  process.exit(1);
2975
4189
  }
2976
- // Auto-fix incomplete entities — but only once.
2977
- // If analyze-imports runs and entities are STILL incomplete, report clearly
2978
- // instead of retrying. The analysis may have errors (e.g., stale entity
2979
- // references from refactoring) that can't be fixed by re-running.
4190
+ // Two-phase auto-fix for entity associations:
4191
+ // Phase 1: DB-only backfill fast, matches existing entities to scenarios.
4192
+ // Phase 2: If backfill fails, targeted analyze-imports for only the
4193
+ // specific unresolved files (1-3 files, not the full glossary scan).
2980
4194
  const incompleteBeforeFix = data.incompleteEntities || [];
4195
+ const unassociatedBeforeFix = data.unassociatedScenarios || [];
2981
4196
  let autoRemediationFailed = false;
2982
- if (incompleteBeforeFix.length > 0) {
2983
- console.log(chalk.dim(`Running import analysis for ${incompleteBeforeFix.length} incomplete entit${incompleteBeforeFix.length !== 1 ? 'ies' : 'y'}...`));
4197
+ if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
4198
+ const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
4199
+ console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
4200
+ // Backfill entity_sha on scenarios that were registered before entities existed
2984
4201
  try {
2985
- await handleAnalyzeImports({ silent: true });
4202
+ const entities = await loadEntities({});
4203
+ if (entities && entities.length > 0) {
4204
+ const { getDatabase } = await import('../../../packages/database/index.js');
4205
+ const db = getDatabase();
4206
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
4207
+ sha: e.sha,
4208
+ name: e.name,
4209
+ filePath: e.filePath || '',
4210
+ isDefaultExport: e.metadata?.notExported === false &&
4211
+ e.metadata?.namedExport === false,
4212
+ })));
4213
+ }
2986
4214
  }
2987
4215
  catch {
2988
- // Fall through — the audit will still report them as incomplete
4216
+ // Fall through — re-fetch will show remaining issues
2989
4217
  }
2990
- // Re-fetch audit results after the fix
2991
- data = await fetchAuditResult();
4218
+ // Re-fetch audit results after the backfill — skip re-running tests
4219
+ // since they haven't changed (backfill is DB-only).
4220
+ data = await fetchAuditResult({ skipTests: true });
2992
4221
  if (!data) {
2993
- console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
4222
+ console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
2994
4223
  process.exit(1);
2995
4224
  }
2996
- // If entities are still incomplete after running analyze-imports,
2997
- // flag it so we can show a clear message instead of looping
4225
+ // If issues persist after DB-only backfill, run targeted analyze-imports
4226
+ // for ONLY the specific files that need entities. This is fast (1-3 files)
4227
+ // unlike full analyze-imports which scans all glossary entries.
4228
+ const incompleteAfterBackfill = data.incompleteEntities || [];
4229
+ const unassociatedAfterBackfill = data.unassociatedScenarios || [];
4230
+ if (incompleteAfterBackfill.length > 0 ||
4231
+ unassociatedAfterBackfill.length > 0) {
4232
+ const { determineTargetedAnalysisPaths } = await import('../utils/editorAudit.js');
4233
+ const targetPaths = determineTargetedAnalysisPaths({
4234
+ unassociatedScenarios: unassociatedAfterBackfill,
4235
+ incompleteEntities: incompleteAfterBackfill,
4236
+ });
4237
+ if (targetPaths.length > 0) {
4238
+ console.log(chalk.dim(`Running targeted analysis for ${targetPaths.length} file${targetPaths.length !== 1 ? 's' : ''}...`));
4239
+ try {
4240
+ await handleAnalyzeImports({
4241
+ silent: true,
4242
+ filePaths: targetPaths,
4243
+ });
4244
+ // Retry backfill after analysis created entities
4245
+ const entities = await loadEntities({});
4246
+ if (entities && entities.length > 0) {
4247
+ const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4248
+ const freshDb = getDb();
4249
+ await backfillEntityShaOnScenarios(freshDb, entities.map((e) => ({
4250
+ sha: e.sha,
4251
+ name: e.name,
4252
+ filePath: e.filePath || '',
4253
+ isDefaultExport: e.metadata?.notExported === false &&
4254
+ e.metadata?.namedExport === false,
4255
+ })));
4256
+ }
4257
+ // Re-fetch audit results after targeted analysis
4258
+ data = await fetchAuditResult({ skipTests: true });
4259
+ if (!data) {
4260
+ console.error(chalk.red('Error: Could not reach the CodeYam server after analysis.'));
4261
+ process.exit(1);
4262
+ }
4263
+ }
4264
+ catch {
4265
+ // Targeted analysis failed — fall through to show remaining issues
4266
+ }
4267
+ }
4268
+ }
4269
+ // Check if issues persist after all remediation attempts
2998
4270
  const incompleteAfterFix = data.incompleteEntities || [];
2999
- if (incompleteAfterFix.length > 0) {
4271
+ const unassociatedAfterFix = data.unassociatedScenarios || [];
4272
+ if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
3000
4273
  autoRemediationFailed = true;
3001
4274
  }
3002
4275
  }
3003
- const { components, functions, summary } = data;
4276
+ let { components, functions, summary } = data;
3004
4277
  console.log();
3005
4278
  console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
3006
4279
  console.log();
3007
4280
  // Components
3008
4281
  if (components.length > 0) {
4282
+ // Build name frequency map for disambiguation
4283
+ const componentNameCounts = new Map();
4284
+ for (const c of components) {
4285
+ componentNameCounts.set(c.name, (componentNameCounts.get(c.name) || 0) + 1);
4286
+ }
3009
4287
  console.log(chalk.bold('Components (scenarios):'));
3010
4288
  for (const c of components) {
3011
- const icon = c.status === 'ok' ? chalk.green('✓') : chalk.red('✗');
4289
+ const icon = c.status === 'ok'
4290
+ ? chalk.green('✓')
4291
+ : c.status === 'needs_recapture'
4292
+ ? chalk.yellow('↻')
4293
+ : chalk.red('✗');
3012
4294
  let detail;
3013
4295
  if (c.status === 'has_errors') {
3014
4296
  detail = chalk.red(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
3015
4297
  }
4298
+ else if (c.status === 'needs_recapture') {
4299
+ detail = chalk.yellow(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''}, needs recapture`);
4300
+ }
3016
4301
  else if (c.status === 'ok') {
3017
4302
  detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
3018
4303
  }
3019
4304
  else {
3020
4305
  detail = chalk.red(' — no scenarios registered');
4306
+ if (c.hint) {
4307
+ detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
4308
+ }
3021
4309
  }
3022
- console.log(` ${icon} ${c.name}${detail}`);
4310
+ // Show file path for failing components always, for OK only when name is ambiguous
4311
+ const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
4312
+ const showPath = (c.status !== 'ok' && c.status !== 'needs_recapture') || isDuplicate;
4313
+ const pathSuffix = showPath && c.filePath ? chalk.dim(` (${c.filePath})`) : '';
4314
+ console.log(` ${icon} ${c.name}${pathSuffix}${detail}`);
3023
4315
  if (c.clientErrors && c.clientErrors.length > 0) {
3024
4316
  for (const err of c.clientErrors.slice(0, 3)) {
3025
4317
  console.log(chalk.red(` → ${err}`));
@@ -3039,7 +4331,21 @@ async function handleAudit() {
3039
4331
  let detail;
3040
4332
  switch (f.status) {
3041
4333
  case 'ok':
3042
- detail = chalk.dim(` (${f.testFile})`);
4334
+ if (f.testCaseCount !== undefined && f.testCaseCount < 3) {
4335
+ detail = chalk.yellow(` (${f.testFile}) — ⚠ only ${f.testCaseCount} test case${f.testCaseCount !== 1 ? 's' : ''}, consider adding more`);
4336
+ }
4337
+ else {
4338
+ detail = chalk.dim(` (${f.testFile}${f.testCaseCount !== undefined ? `, ${f.testCaseCount} tests` : ''})`);
4339
+ }
4340
+ break;
4341
+ case 'runner_error':
4342
+ detail = chalk.red(` — test runner crashed: ${f.testFile}`);
4343
+ if (f.errorMessage) {
4344
+ detail += `\n ${chalk.red(f.errorMessage)}`;
4345
+ detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
4346
+ const ctx = getTechStackContext(process.cwd());
4347
+ detail += `\n ${chalk.yellow('Try running the test manually: ' + ctx.testRunCommand + ' ' + f.testFile)}`;
4348
+ }
3043
4349
  break;
3044
4350
  case 'failing':
3045
4351
  detail = chalk.red(` — tests failing: ${f.testFile}`);
@@ -3049,13 +4355,23 @@ async function handleAudit() {
3049
4355
  break;
3050
4356
  case 'missing':
3051
4357
  default:
3052
- detail = f.testFile
3053
- ? chalk.red(` — test file missing: ${f.testFile}`)
3054
- : chalk.red(' — no test file specified');
4358
+ if (f.testFile) {
4359
+ detail = chalk.red(` — test file missing: ${f.testFile}`);
4360
+ }
4361
+ else {
4362
+ detail = chalk.red(` — no test file specified in glossary`);
4363
+ detail += chalk.dim(` (source: ${f.filePath})`);
4364
+ if (f.suggestedTestFile) {
4365
+ detail += `\n ${chalk.yellow(`Fix: Either create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to this entry in .codeyam/glossary.json`)}`;
4366
+ }
4367
+ }
3055
4368
  break;
3056
4369
  }
3057
4370
  console.log(` ${icon} ${f.name}${detail}`);
3058
4371
  }
4372
+ if (summary.functionsThinCoverage > 0) {
4373
+ console.log(chalk.yellow.bold(` ⚠ ${summary.functionsThinCoverage} function${summary.functionsThinCoverage !== 1 ? 's' : ''} with thin test coverage (< 3 test cases). Add more tests to cover all branches.`));
4374
+ }
3059
4375
  console.log();
3060
4376
  }
3061
4377
  // Missing from glossary
@@ -3065,24 +4381,85 @@ async function handleAudit() {
3065
4381
  for (const m of missingFromGlossary) {
3066
4382
  console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
3067
4383
  }
3068
- console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
4384
+ const mgPaths = missingFromGlossary
4385
+ .map((m) => m.filePath)
4386
+ .filter(Boolean);
4387
+ if (mgPaths.length > 0) {
4388
+ console.log(chalk.yellow(` Add these to .codeyam/glossary.json and run \`codeyam editor analyze-imports ${mgPaths.join(' ')}\``));
4389
+ }
4390
+ else {
4391
+ console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
4392
+ }
3069
4393
  console.log();
3070
4394
  }
3071
- // Incomplete entities (scenarios without analyses)
4395
+ // Incomplete entities (scenarios without analyses) — report with guidance.
4396
+ // We intentionally do NOT run analysis here: it starts the analyzer
4397
+ // template which is slow even for a few files. Users should run
4398
+ // `codeyam editor analyze-imports` separately if needed.
3072
4399
  const incompleteEntities = data.incompleteEntities || [];
3073
4400
  if (incompleteEntities.length > 0) {
4401
+ const { formatIncompleteEntityGuidance, formatManualAnalysisGuidance } = await import('../utils/editorAudit.js');
4402
+ const { readAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
4403
+ // Check for persistent analysis failures
4404
+ const analysisFailures = readAnalysisFailures(getProjectRoot());
3074
4405
  console.log(chalk.bold('Incomplete entities (need import analysis):'));
3075
4406
  for (const e of incompleteEntities) {
3076
- console.log(` ${chalk.red('✗')} ${e.name} ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
4407
+ // Check if this entity has a persistent failure
4408
+ const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
4409
+ if (failureEntry) {
4410
+ // Show manual analysis instructions instead of "run analyze-imports"
4411
+ const filePath = Object.entries(analysisFailures).find(([, f]) => f.entityName === e.name)?.[0];
4412
+ const guidance = formatManualAnalysisGuidance({
4413
+ name: e.name,
4414
+ filePath: filePath || '',
4415
+ scenarioCount: e.scenarioCount,
4416
+ error: failureEntry.error,
4417
+ });
4418
+ // Print each line with proper indentation
4419
+ for (const line of guidance.split('\n')) {
4420
+ console.log(` ${chalk.red('✗')} ${line}`);
4421
+ }
4422
+ }
4423
+ else {
4424
+ const guidance = formatIncompleteEntityGuidance(e);
4425
+ console.log(` ${chalk.red('✗')} ${guidance}`);
4426
+ }
4427
+ }
4428
+ const incompleteErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
4429
+ if (fs.existsSync(incompleteErrorReportPath)) {
4430
+ console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
4431
+ }
4432
+ console.log();
4433
+ }
4434
+ // Unassociated scenarios (NULL entity_sha with file paths)
4435
+ const unassociatedScenarios = data.unassociatedScenarios || [];
4436
+ if (unassociatedScenarios.length > 0) {
4437
+ console.log(chalk.bold('Unassociated scenarios (missing entity link):'));
4438
+ for (const u of unassociatedScenarios) {
4439
+ console.log(` ${chalk.red('✗')} ${u.name} ${chalk.dim(`(${u.filePath})`)} — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''} with no entity_sha`);
4440
+ for (const sn of u.scenarioNames.slice(0, 3)) {
4441
+ console.log(chalk.dim(` "${sn}"`));
4442
+ }
4443
+ if (u.scenarioNames.length > 3) {
4444
+ console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
4445
+ }
3077
4446
  }
4447
+ const uaPaths = unassociatedScenarios
4448
+ .map((u) => u.filePath)
4449
+ .filter(Boolean);
4450
+ const uaCmd = uaPaths.length > 0
4451
+ ? `codeyam editor analyze-imports ${uaPaths.join(' ')}`
4452
+ : 'codeyam editor analyze-imports';
3078
4453
  if (autoRemediationFailed) {
3079
- console.log(chalk.red(' analyze-imports was run automatically but these entities are STILL incomplete.'));
3080
- console.log(chalk.red(' This usually means the analysis had errors — check the output above for details.'));
3081
- console.log(chalk.yellow(' Common causes: stale entity references from refactoring, missing source files.'));
3082
- console.log(chalk.yellow(' Try: run `codeyam editor analyze-imports` manually to see the full error output.'));
4454
+ console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
4455
+ console.log(chalk.yellow(` Run \`${uaCmd}\` to see the full error output.`));
3083
4456
  }
3084
4457
  else {
3085
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
4458
+ console.log(chalk.yellow(` Run \`${uaCmd}\` to create entity records, then re-run audit to backfill.`));
4459
+ }
4460
+ const unassocErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
4461
+ if (fs.existsSync(unassocErrorReportPath)) {
4462
+ console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
3086
4463
  }
3087
4464
  console.log();
3088
4465
  }
@@ -3101,6 +4478,51 @@ async function handleAudit() {
3101
4478
  console.log(chalk.yellow(' or re-register with an isolation URL.'));
3102
4479
  console.log();
3103
4480
  }
4481
+ // Scenarios needing recapture (entity or dependency tree changed)
4482
+ const scenariosNeedingRecapture = data.scenariosNeedingRecapture || [];
4483
+ if (scenariosNeedingRecapture.length > 0) {
4484
+ console.log(chalk.bold('Scenarios needing recapture (dependency tree changed):'));
4485
+ for (const s of scenariosNeedingRecapture) {
4486
+ const reason = s.status.status === 'impacted' && s.status.impactedBy?.length
4487
+ ? `impacted by: ${s.status.impactedBy.map((d) => `${d.name} [${d.changeType}]`).join(', ')}`
4488
+ : `${s.status.status}`;
4489
+ console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
4490
+ }
4491
+ if (options?.fix) {
4492
+ // --fix: auto-recapture stale scenarios instead of just reporting them
4493
+ const { shouldAutoRecapture } = await import('../utils/editorAudit.js');
4494
+ if (shouldAutoRecapture({ fix: true, scenariosNeedingRecapture })) {
4495
+ console.log(chalk.cyan(' --fix: auto-recapturing stale scenarios...'));
4496
+ console.log();
4497
+ await handleRecaptureStale();
4498
+ // Re-fetch audit results so the summary and exit code reflect
4499
+ // the post-fix state, not the pre-fix state.
4500
+ const refreshed = await fetchAuditResult({ skipTests: true });
4501
+ if (refreshed) {
4502
+ data = refreshed;
4503
+ ({ components, functions, summary } = data);
4504
+ }
4505
+ }
4506
+ }
4507
+ else {
4508
+ console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
4509
+ console.log(chalk.dim(' Or: codeyam editor audit --fix (to auto-recapture)'));
4510
+ }
4511
+ console.log();
4512
+ }
4513
+ // Duplicate glossary names (warning, not a failure)
4514
+ const duplicateNames = data.duplicateNames || [];
4515
+ if (duplicateNames.length > 0) {
4516
+ console.log(chalk.bold('Duplicate names in glossary (confusing for audit):'));
4517
+ for (const dn of duplicateNames) {
4518
+ console.log(` ${chalk.yellow('⚠')} "${dn.name}" appears ${dn.filePaths.length} times:`);
4519
+ for (const fp of dn.filePaths) {
4520
+ console.log(` ${fp}`);
4521
+ }
4522
+ }
4523
+ console.log(chalk.yellow(' Fix: remove duplicate entries or rename them to be unique in .codeyam/glossary.json'));
4524
+ console.log();
4525
+ }
3104
4526
  // Summary
3105
4527
  const allOk = summary.allPassing;
3106
4528
  if (allOk) {
@@ -3121,6 +4543,9 @@ async function handleAudit() {
3121
4543
  if (summary.functionsFailing > 0) {
3122
4544
  parts.push(`${summary.functionsFailing} function${summary.functionsFailing !== 1 ? 's' : ''} with failing tests`);
3123
4545
  }
4546
+ if (summary.functionsRunnerError > 0) {
4547
+ parts.push(`${summary.functionsRunnerError} function${summary.functionsRunnerError !== 1 ? 's' : ''} with test runner errors (not test failures — see details above)`);
4548
+ }
3124
4549
  if (summary.functionsNameMismatch > 0) {
3125
4550
  parts.push(`${summary.functionsNameMismatch} function${summary.functionsNameMismatch !== 1 ? 's' : ''} with test name mismatch (missing top-level describe)`);
3126
4551
  }
@@ -3130,25 +4555,123 @@ async function handleAudit() {
3130
4555
  if (summary.incompleteEntities > 0) {
3131
4556
  parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
3132
4557
  }
4558
+ if (summary.unassociatedScenarios > 0) {
4559
+ parts.push(`${summary.unassociatedScenarios} component${summary.unassociatedScenarios !== 1 ? 's' : ''} with scenarios missing entity link`);
4560
+ }
3133
4561
  if (summary.miscategorizedScenarios > 0) {
3134
4562
  parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
3135
4563
  }
4564
+ if (summary.scenariosNeedingRecapture > 0) {
4565
+ parts.push(`${summary.scenariosNeedingRecapture} scenario${summary.scenariosNeedingRecapture !== 1 ? 's' : ''} need recapture`);
4566
+ }
3136
4567
  console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
3137
4568
  }
3138
4569
  console.log();
3139
4570
  if (!allOk) {
3140
4571
  process.exit(1);
3141
4572
  }
3142
- // Auto-run analyze-imports when audit passes — this builds the import graph
3143
- // so the App tab can show component/function dependencies for each page.
3144
- // Previously this was a manual step that Claude had to remember to run.
3145
- console.log(chalk.dim('Building import graph...'));
4573
+ }
4574
+ // ─── Recapture-stale subcommand ────────────────────────────────────────
4575
+ /**
4576
+ * `codeyam editor recapture-stale`
4577
+ *
4578
+ * Identifies all scenarios whose entity (or dependency) has changed but
4579
+ * whose screenshot hasn't been recaptured this session, then recaptures
4580
+ * them in batch.
4581
+ */
4582
+ async function handleRecaptureStale() {
4583
+ const port = getServerPort();
4584
+ const url = `http://localhost:${port}/api/editor-recapture-stale`;
4585
+ console.log();
4586
+ console.log(chalk.bold.cyan('━━━ Recapture Stale Scenarios ━━━'));
4587
+ console.log();
4588
+ let res;
3146
4589
  try {
3147
- await handleAnalyzeImports({ silent: true });
4590
+ res = await fetch(url, { method: 'POST' });
3148
4591
  }
3149
- catch {
3150
- // Non-fatal audit passed, import graph is a bonus
3151
- console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
4592
+ catch (err) {
4593
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
4594
+ console.error(chalk.dim(` ${err.message}`));
4595
+ process.exit(1);
4596
+ }
4597
+ if (!res.ok) {
4598
+ const body = await res.json().catch(() => null);
4599
+ console.error(chalk.red(`Error: Recapture endpoint returned ${res.status}`));
4600
+ if (body?.error)
4601
+ console.error(chalk.red(` ${body.error}`));
4602
+ process.exit(1);
4603
+ }
4604
+ // Handle JSON response (early returns like "no changes" / "all up to date")
4605
+ const contentType = res.headers.get('content-type') || '';
4606
+ if (contentType.includes('application/json')) {
4607
+ const data = await res.json();
4608
+ if (data.note) {
4609
+ console.log(chalk.dim(data.note));
4610
+ }
4611
+ else if (data.total === 0) {
4612
+ console.log(chalk.green('All scenarios are up to date.'));
4613
+ }
4614
+ console.log();
4615
+ return;
4616
+ }
4617
+ // Stream NDJSON progress events
4618
+ let total = 0;
4619
+ let recapturedCount = 0;
4620
+ let failedCount = 0;
4621
+ const reader = res.body?.getReader();
4622
+ if (!reader) {
4623
+ console.error(chalk.red('Error: No response body'));
4624
+ process.exit(1);
4625
+ }
4626
+ const decoder = new TextDecoder();
4627
+ let buffer = '';
4628
+ while (true) {
4629
+ const { done, value } = await reader.read();
4630
+ if (done)
4631
+ break;
4632
+ buffer += decoder.decode(value, { stream: true });
4633
+ const lines = buffer.split('\n');
4634
+ buffer = lines.pop() || ''; // Keep incomplete last line in buffer
4635
+ for (const line of lines) {
4636
+ if (!line.trim())
4637
+ continue;
4638
+ try {
4639
+ const event = JSON.parse(line);
4640
+ switch (event.type) {
4641
+ case 'start':
4642
+ total = event.total;
4643
+ console.log(`Found ${total} stale scenario(s). Recapturing...\n`);
4644
+ break;
4645
+ case 'capturing':
4646
+ process.stdout.write(chalk.dim(` … ${event.name}`));
4647
+ break;
4648
+ case 'success':
4649
+ // Clear the "capturing" line and print success
4650
+ process.stdout.write('\r\x1b[K');
4651
+ console.log(` ${chalk.green('✓')} ${event.name}`);
4652
+ recapturedCount++;
4653
+ break;
4654
+ case 'failure':
4655
+ process.stdout.write('\r\x1b[K');
4656
+ console.log(` ${chalk.red('✗')} ${event.name} — ${chalk.dim(event.error)}`);
4657
+ failedCount++;
4658
+ break;
4659
+ case 'done':
4660
+ // Final summary
4661
+ console.log();
4662
+ const color = failedCount > 0 ? chalk.yellow : chalk.green;
4663
+ console.log(color(`Recaptured ${recapturedCount}/${total} scenario(s).`));
4664
+ console.log();
4665
+ break;
4666
+ }
4667
+ }
4668
+ catch {
4669
+ // Skip unparseable lines
4670
+ }
4671
+ }
4672
+ }
4673
+ if (failedCount > 0) {
4674
+ process.exit(1);
3152
4675
  }
3153
4676
  }
3154
4677
  // ─── Scenarios subcommand ─────────────────────────────────────────────
@@ -3253,20 +4776,22 @@ async function handleScenarioCoverage() {
3253
4776
  // Safety net: heal any scenarios with null entity_sha before checking coverage
3254
4777
  try {
3255
4778
  const { getDatabase } = await import('../../../packages/database/index.js');
3256
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
4779
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
3257
4780
  const db = getDatabase();
3258
4781
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
3259
4782
  if (backfillCount > 0) {
3260
4783
  console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
3261
4784
  await handleAnalyzeImports({ silent: true });
3262
4785
  // Run backfill after analysis
3263
- const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
4786
+ const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
3264
4787
  const entities = await loadEntities({});
3265
4788
  if (entities && entities.length > 0) {
3266
4789
  await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3267
4790
  sha: e.sha,
3268
4791
  name: e.name,
3269
4792
  filePath: e.filePath || '',
4793
+ isDefaultExport: e.metadata?.notExported === false &&
4794
+ e.metadata?.namedExport === false,
3270
4795
  })));
3271
4796
  }
3272
4797
  }
@@ -3396,7 +4921,7 @@ async function handleTemplate() {
3396
4921
  console.log(chalk.green(' Git initialized.'));
3397
4922
  }
3398
4923
  // 4. Run codeyam init
3399
- console.log(chalk.bold('Running codeyam init...'));
4924
+ console.log(chalk.bold('Initializing project...'));
3400
4925
  await initCommand.handler({
3401
4926
  force: true,
3402
4927
  'keep-server': true,
@@ -3405,7 +4930,7 @@ async function handleTemplate() {
3405
4930
  _: [],
3406
4931
  });
3407
4932
  console.log(chalk.green(' CodeYam initialized.'));
3408
- // 5. Verify config has startCommand
4933
+ // 5. Verify config has startCommand and set format-specific defaults
3409
4934
  const configPath = path.join(root, '.codeyam', 'config.json');
3410
4935
  if (fs.existsSync(configPath)) {
3411
4936
  try {
@@ -3416,6 +4941,30 @@ async function handleTemplate() {
3416
4941
  console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
3417
4942
  console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
3418
4943
  }
4944
+ // Store appFormats from the tech stack so the editor UI can adapt
4945
+ if (stack?.supportedFormats) {
4946
+ config.appFormats = stack.supportedFormats;
4947
+ }
4948
+ // Pre-populate tech stack from template
4949
+ if (stack) {
4950
+ config.techStack = getTechStackForTemplate(stack.id);
4951
+ }
4952
+ // Set mobile-first defaults for mobile-app projects
4953
+ if (stack?.supportedFormats?.includes('mobile-app')) {
4954
+ config.defaultScreenSize = {
4955
+ name: 'iPhone 16',
4956
+ width: 393,
4957
+ height: 852,
4958
+ };
4959
+ config.screenSizes = {
4960
+ 'iPhone 16': { width: 393, height: 852 },
4961
+ 'iPhone 16 Pro Max': { width: 430, height: 932 },
4962
+ 'iPhone SE': { width: 375, height: 667 },
4963
+ 'Pixel 8': { width: 412, height: 915 },
4964
+ 'iPad mini': { width: 744, height: 1133 },
4965
+ };
4966
+ }
4967
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
3419
4968
  }
3420
4969
  catch {
3421
4970
  // Config parse error is non-fatal
@@ -3450,7 +4999,15 @@ async function handleTemplate() {
3450
4999
  }
3451
5000
  console.log();
3452
5001
  console.log(chalk.green.bold('Project scaffolded and ready!'));
3453
- console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
5002
+ if (stack?.id === 'expo-react-native') {
5003
+ console.log(chalk.dim('Next: Set up your data types, configure the theme in lib/theme.ts, and build your feature.'));
5004
+ }
5005
+ else if (stack?.id === 'chrome-extension-react') {
5006
+ console.log(chalk.dim('Next: Configure your extension manifest and build your feature.'));
5007
+ }
5008
+ else {
5009
+ console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
5010
+ }
3454
5011
  }
3455
5012
  // ─── Sync subcommand ─────────────────────────────────────────────────
3456
5013
  /**
@@ -3475,7 +5032,7 @@ async function handleSync() {
3475
5032
  // fall through
3476
5033
  }
3477
5034
  if (!projectSlug) {
3478
- console.error(chalk.red('Error: No project slug found. Run codeyam init first.'));
5035
+ console.error(chalk.red('Error: No project slug found. Run `codeyam editor template` to initialize the project.'));
3479
5036
  process.exit(1);
3480
5037
  }
3481
5038
  const connectionOk = await withoutSpinner(() => testEnvironment());
@@ -3659,7 +5216,7 @@ function handleEditorDebug(args) {
3659
5216
  scenarios.push({
3660
5217
  id: 'overview-with-state',
3661
5218
  title: 'Cycle overview (project, with state)',
3662
- render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(4, feature)))),
5219
+ render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(6, feature)))),
3663
5220
  });
3664
5221
  }
3665
5222
  const stepFns = {
@@ -3679,8 +5236,10 @@ function handleEditorDebug(args) {
3679
5236
  14: printStep14,
3680
5237
  15: printStep15,
3681
5238
  16: printStep16,
5239
+ 17: printStep17,
5240
+ 18: printStep18,
3682
5241
  };
3683
- for (let step = 1; step <= 16; step++) {
5242
+ for (let step = 1; step <= 18; step++) {
3684
5243
  const stepId = `step-${step}`;
3685
5244
  if (!wants(stepId))
3686
5245
  continue;
@@ -3700,7 +5259,7 @@ function handleEditorDebug(args) {
3700
5259
  if (step === 2) {
3701
5260
  scenarios.push({
3702
5261
  id: 'step-2-scaffold',
3703
- title: 'Step 2 (Prototype) — scaffold flow (no project)',
5262
+ title: 'Step 2 (Prepare) — scaffold flow (no project)',
3704
5263
  render: () => withTempRoot(false, (tempRoot) => captureOutput(() => printStep2(tempRoot, feature))),
3705
5264
  });
3706
5265
  }
@@ -3753,14 +5312,205 @@ function handleEditorDebug(args) {
3753
5312
  console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
3754
5313
  console.log();
3755
5314
  }
5315
+ // ─── Manual entity analysis ───────────────────────────────────────────
5316
+ /**
5317
+ * `codeyam editor manual-entity <JSON|@file>`
5318
+ *
5319
+ * Creates entity and analysis records from Claude-provided metadata when
5320
+ * automated analyze-imports fails. This unblocks the audit gate without
5321
+ * needing the analyzer template to parse the source file.
5322
+ */
5323
+ async function handleManualEntity(jsonArg) {
5324
+ const root = getProjectRoot();
5325
+ // Parse JSON input (supports @file.json convention)
5326
+ const parsed = parseRegisterArg(jsonArg);
5327
+ if (parsed.error || !parsed.body) {
5328
+ console.error(chalk.red(`Error: ${parsed.error || 'Invalid input'}`));
5329
+ console.error(chalk.dim(' Usage: codeyam editor manual-entity \'{"name":"Header","filePath":"src/components/Header.tsx","entityType":"visual",...}\''));
5330
+ console.error(chalk.dim(' Or: codeyam editor manual-entity @.codeyam/tmp/manual-entity.json'));
5331
+ process.exit(1);
5332
+ }
5333
+ const input = parsed.body;
5334
+ // Validate input
5335
+ const { validateManualEntityInput, convertTypeInfoToDataForMocks, readAnalysisFailures, clearFailureForPath, writeAnalysisFailures, } = await import('../utils/manualEntityAnalysis.js');
5336
+ // Read glossary for validation
5337
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
5338
+ let glossaryEntries = [];
5339
+ try {
5340
+ glossaryEntries = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
5341
+ }
5342
+ catch {
5343
+ // Empty glossary — validation will still check file existence
5344
+ }
5345
+ const errors = validateManualEntityInput(input, glossaryEntries, {
5346
+ fileExists: (p) => fs.existsSync(path.join(root, p)),
5347
+ });
5348
+ if (errors.length > 0) {
5349
+ console.error(chalk.red('Validation errors:'));
5350
+ for (const err of errors) {
5351
+ console.error(chalk.red(` • ${err}`));
5352
+ }
5353
+ process.exit(1);
5354
+ }
5355
+ // Read source file and compute SHA
5356
+ const sourceContent = fs.readFileSync(path.join(root, input.filePath), 'utf8');
5357
+ const { generateSha } = await import('../../../packages/database/index.js');
5358
+ const entitySha = generateSha(input.filePath, input.name, sourceContent);
5359
+ // Get project and branch
5360
+ await initializeEnvironment();
5361
+ const configPath = path.join(root, '.codeyam', 'config.json');
5362
+ let projectSlug;
5363
+ try {
5364
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
5365
+ projectSlug = config.projectSlug;
5366
+ }
5367
+ catch {
5368
+ console.error(chalk.red('Error: .codeyam/config.json not found or invalid.'));
5369
+ process.exit(1);
5370
+ }
5371
+ const { project, branch } = await requireBranchAndProject(projectSlug);
5372
+ const { getDatabase } = await import('../../../packages/database/index.js');
5373
+ const db = getDatabase();
5374
+ // Convert type info to dataForMocks format
5375
+ const dataForMocks = convertTypeInfoToDataForMocks(input.typeInfo);
5376
+ // Create entity record
5377
+ const entityMetadata = {
5378
+ importedExports: (input.importedExports || []).map((ie) => ({
5379
+ name: ie.name,
5380
+ filePath: ie.filePath,
5381
+ })),
5382
+ manuallyAnalyzed: true,
5383
+ };
5384
+ // Check if entity already exists
5385
+ const existingEntity = await db
5386
+ .selectFrom('entities')
5387
+ .select('sha')
5388
+ .where('sha', '=', entitySha)
5389
+ .executeTakeFirst();
5390
+ if (!existingEntity) {
5391
+ await db
5392
+ .insertInto('entities')
5393
+ .values({
5394
+ sha: entitySha,
5395
+ project_id: project.id,
5396
+ name: input.name,
5397
+ entity_type: input.entityType,
5398
+ file_path: input.filePath,
5399
+ metadata: JSON.stringify(entityMetadata),
5400
+ })
5401
+ .onConflict((oc) => oc.column('sha').doNothing())
5402
+ .execute();
5403
+ console.log(chalk.dim(`Created entity record: ${input.name} (${entitySha.slice(0, 8)}...)`));
5404
+ }
5405
+ else {
5406
+ // Update metadata on existing entity
5407
+ await db
5408
+ .updateTable('entities')
5409
+ .set({
5410
+ metadata: JSON.stringify(entityMetadata),
5411
+ })
5412
+ .where('sha', '=', entitySha)
5413
+ .execute();
5414
+ console.log(chalk.dim(`Updated existing entity: ${input.name} (${entitySha.slice(0, 8)}...)`));
5415
+ }
5416
+ // Create entity_branch record
5417
+ await db
5418
+ .insertInto('entity_branches')
5419
+ .values({
5420
+ entity_sha: entitySha,
5421
+ branch_id: branch.id,
5422
+ active: true,
5423
+ })
5424
+ .onConflict((oc) => oc.columns(['entity_sha', 'branch_id']).doUpdateSet({ active: true }))
5425
+ .execute();
5426
+ // Create analysis record
5427
+ const { randomUUID } = await import('crypto');
5428
+ const analysisId = randomUUID();
5429
+ const now = new Date().toISOString();
5430
+ const analysisMetadata = {
5431
+ scenariosDataStructure: {
5432
+ dataForMocks: Object.keys(dataForMocks).length > 0 ? dataForMocks : null,
5433
+ },
5434
+ mergedDataStructure: {
5435
+ dependencySchemas: null,
5436
+ },
5437
+ manuallyAnalyzed: true,
5438
+ };
5439
+ const analysisStatus = {
5440
+ startedAt: now,
5441
+ finishedAt: now,
5442
+ steps: [],
5443
+ errors: [],
5444
+ };
5445
+ // Check if analysis already exists for this entity
5446
+ const existingAnalysis = await db
5447
+ .selectFrom('analyses')
5448
+ .select('id')
5449
+ .where('entity_sha', '=', entitySha)
5450
+ .executeTakeFirst();
5451
+ if (!existingAnalysis) {
5452
+ await db
5453
+ .insertInto('analyses')
5454
+ .values({
5455
+ id: analysisId,
5456
+ project_id: project.id,
5457
+ entity_sha: entitySha,
5458
+ entity_name: input.name,
5459
+ entity_type: input.entityType,
5460
+ file_path: input.filePath,
5461
+ status: JSON.stringify(analysisStatus),
5462
+ metadata: JSON.stringify(analysisMetadata),
5463
+ })
5464
+ .execute();
5465
+ console.log(chalk.dim(`Created analysis record: ${analysisId.slice(0, 8)}...`));
5466
+ }
5467
+ else {
5468
+ // Update existing analysis with manual metadata
5469
+ await db
5470
+ .updateTable('analyses')
5471
+ .set({
5472
+ metadata: JSON.stringify(analysisMetadata),
5473
+ status: JSON.stringify(analysisStatus),
5474
+ })
5475
+ .where('entity_sha', '=', entitySha)
5476
+ .execute();
5477
+ console.log(chalk.dim(`Updated existing analysis for ${input.name}`));
5478
+ }
5479
+ // Backfill entity_sha on scenarios
5480
+ const backfillResult = await backfillEntityShaOnScenarios(db, [
5481
+ {
5482
+ sha: entitySha,
5483
+ name: input.name,
5484
+ filePath: input.filePath,
5485
+ isDefaultExport: false,
5486
+ },
5487
+ ]);
5488
+ if (backfillResult.updated > 0) {
5489
+ console.log(chalk.dim(`Linked ${backfillResult.updated} scenario(s) to entity`));
5490
+ }
5491
+ // Clear from analysis failures tracker
5492
+ const failures = readAnalysisFailures(root);
5493
+ if (failures[input.filePath]) {
5494
+ const updated = clearFailureForPath(failures, input.filePath);
5495
+ writeAnalysisFailures(root, updated);
5496
+ console.log(chalk.dim(`Cleared analysis failure for ${input.filePath}`));
5497
+ }
5498
+ console.log();
5499
+ console.log(chalk.green.bold(`✓ Manual entity analysis complete: ${input.name}`));
5500
+ console.log(chalk.dim(` Entity SHA: ${entitySha.slice(0, 12)}...`));
5501
+ console.log(chalk.dim(` Imports: ${(input.importedExports || []).length} glossary entities`));
5502
+ console.log(chalk.dim(` Type info: ${Object.keys(dataForMocks).length} fields`));
5503
+ console.log();
5504
+ console.log(chalk.dim('Re-run `codeyam editor audit` to verify.'));
5505
+ }
3756
5506
  // ─── Command definition ───────────────────────────────────────────────
3757
5507
  const editorCommand = {
3758
5508
  command: 'editor [step] [json]',
3759
5509
  describe: 'Editor mode guided workflow',
3760
5510
  builder: (yargs) => {
3761
5511
  const stepDescription = IS_INTERNAL_BUILD
3762
- ? '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)'
3763
- : '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)';
5512
+ ? '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)'
5513
+ : '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)';
3764
5514
  let builder = yargs
3765
5515
  .positional('step', {
3766
5516
  type: 'string',
@@ -3791,12 +5541,17 @@ const editorCommand = {
3791
5541
  alias: 'p',
3792
5542
  describe: 'Port to run the web server on',
3793
5543
  default: 3111,
5544
+ })
5545
+ .option('fix', {
5546
+ type: 'boolean',
5547
+ describe: 'For audit: also recapture stale scenarios after displaying results',
5548
+ default: false,
3794
5549
  });
3795
5550
  if (IS_INTERNAL_BUILD) {
3796
5551
  builder = builder
3797
5552
  .option('target', {
3798
5553
  type: 'string',
3799
- describe: 'Debug target (setup, overview, overview-with-state, step-1..step-16, or comma-separated list)',
5554
+ describe: 'Debug target (setup, overview, overview-with-state, step-1..step-18, or comma-separated list)',
3800
5555
  })
3801
5556
  .option('resume', {
3802
5557
  type: 'boolean',
@@ -3812,13 +5567,27 @@ const editorCommand = {
3812
5567
  describe: 'Debug: output directory for the bundle',
3813
5568
  });
3814
5569
  }
3815
- return builder;
5570
+ // Allow extra positional args for subcommands like `isolate A B C`
5571
+ // without yargs rejecting them as unknown.
5572
+ return builder.strict(false);
3816
5573
  },
3817
5574
  handler: async (argv) => {
3818
5575
  const root = getProjectRoot();
3819
5576
  // API subcommands: preview, show-results, hide-results, commit,
3820
5577
  // journal, journal-update, dev-server, client-errors
3821
5578
  if (argv.step && EDITOR_API_SUBCOMMANDS.includes(argv.step)) {
5579
+ // Guard: commit requires step 16 to have been run first.
5580
+ // Without this, Claude shortcuts the process by running
5581
+ // `codeyam editor commit` directly from step 15, skipping
5582
+ // steps 16-18 entirely.
5583
+ if (argv.step === 'commit') {
5584
+ const state = readState(root);
5585
+ if (state && state.step !== 16) {
5586
+ console.error(chalk.red('Error: Run `codeyam editor 16` before committing.'));
5587
+ console.error(chalk.dim(` Current step: ${state.step} (${state.label || 'unknown'}). Step 16 (Commit) must be run first.`));
5588
+ process.exit(1);
5589
+ }
5590
+ }
3822
5591
  const port = getServerPort();
3823
5592
  try {
3824
5593
  const request = buildEditorApiRequest(argv.step, argv.json || undefined);
@@ -3838,6 +5607,12 @@ const editorCommand = {
3838
5607
  console.log(JSON.stringify(result.data, null, 2));
3839
5608
  }
3840
5609
  }
5610
+ // After a successful commit, remind Claude to continue to step 17
5611
+ if (argv.step === 'commit' && result.ok) {
5612
+ console.log();
5613
+ console.log(chalk.green('Commit done. Now run: ') +
5614
+ chalk.bold('codeyam editor 17'));
5615
+ }
3841
5616
  }
3842
5617
  catch (err) {
3843
5618
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
@@ -3846,14 +5621,73 @@ const editorCommand = {
3846
5621
  }
3847
5622
  return;
3848
5623
  }
5624
+ // Subcommand: codeyam editor handoff '{"summary":"..."}'
5625
+ if (argv.step === 'handoff') {
5626
+ await handleHandoff(argv.json || '');
5627
+ return;
5628
+ }
3849
5629
  // Subcommand: codeyam editor register '{"name":"..."}'
3850
5630
  if (argv.step === 'register') {
3851
5631
  await handleRegister(argv.json || '');
3852
5632
  return;
3853
5633
  }
3854
- // Subcommand: codeyam editor analyze-imports
5634
+ // Subcommand: codeyam editor glossary-add '{"name":"...", ...}'
5635
+ if (argv.step === 'glossary-add') {
5636
+ await handleGlossaryAdd(argv.json || '');
5637
+ return;
5638
+ }
5639
+ // Subcommand: codeyam editor task-ontrack
5640
+ // Corrective command when Claude advanced without creating a task.
5641
+ if (argv.step === 'task-ontrack') {
5642
+ const state = readState(root);
5643
+ if (!state?.step) {
5644
+ console.error(chalk.red('No editor state found. Run `codeyam editor 1` to start.'));
5645
+ process.exit(1);
5646
+ }
5647
+ const currentLabel = STEP_LABELS[state.step] || `Step ${state.step}`;
5648
+ const totalSteps = Object.keys(STEP_LABELS).length;
5649
+ const nextLabel = state.step < totalSteps ? STEP_LABELS[state.step + 1] : undefined;
5650
+ console.log();
5651
+ console.log(chalk.bold.yellow('━━━ GETTING BACK ON TRACK ━━━'));
5652
+ console.log();
5653
+ console.log(chalk.yellow('You went off-track by not creating a task. Create the task below to continue.'));
5654
+ console.log();
5655
+ // Print the TASK directive for the current step
5656
+ let taskTitle;
5657
+ if (state.step < totalSteps) {
5658
+ taskTitle = `Complete codeyam editor step ${state.step}: '${currentLabel}' and move on to step ${state.step + 1}: '${nextLabel}'`;
5659
+ }
5660
+ else {
5661
+ taskTitle =
5662
+ 'Ask user what to build next and restart codeyam editor workflow';
5663
+ }
5664
+ console.log(chalk.bold.cyan('━━━ TASK ━━━'));
5665
+ console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
5666
+ console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
5667
+ console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
5668
+ console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
5669
+ console.log();
5670
+ console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
5671
+ console.log();
5672
+ console.log(chalk.green(`After creating the task, re-run: codeyam editor ${state.step + 1}`));
5673
+ console.log();
5674
+ // Mark task as expected-created so the next step can proceed.
5675
+ // The hook will set taskCreated=true when it sees the actual TaskCreate call.
5676
+ // But we also allow advancement now since task-ontrack itself is the corrective action.
5677
+ const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
5678
+ fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
5679
+ return;
5680
+ }
5681
+ // Subcommand: codeyam editor analyze-imports [file1.tsx file2.tsx ...]
3855
5682
  if (argv.step === 'analyze-imports') {
3856
- await handleAnalyzeImports();
5683
+ const { parseAnalyzeImportsArgs } = await import('./editorAnalyzeImportsArgs.js');
5684
+ const requestedPaths = parseAnalyzeImportsArgs(argv.json, argv._);
5685
+ await handleAnalyzeImports(requestedPaths.length > 0 ? { filePaths: requestedPaths } : {});
5686
+ return;
5687
+ }
5688
+ // Subcommand: codeyam editor manual-entity <JSON|@file>
5689
+ if (argv.step === 'manual-entity') {
5690
+ await handleManualEntity(argv.json || '');
3857
5691
  return;
3858
5692
  }
3859
5693
  // Subcommand: codeyam editor dependents <EntityName>
@@ -3861,9 +5695,9 @@ const editorCommand = {
3861
5695
  await handleDependents(argv.json || '');
3862
5696
  return;
3863
5697
  }
3864
- // Subcommand: codeyam editor audit
5698
+ // Subcommand: codeyam editor audit [--fix]
3865
5699
  if (argv.step === 'audit') {
3866
- await handleAudit();
5700
+ await handleAudit({ fix: argv.fix || false });
3867
5701
  return;
3868
5702
  }
3869
5703
  // Subcommand: codeyam editor scenarios
@@ -3876,6 +5710,11 @@ const editorCommand = {
3876
5710
  await handleScenarioCoverage();
3877
5711
  return;
3878
5712
  }
5713
+ // Subcommand: codeyam editor recapture-stale
5714
+ if (argv.step === 'recapture-stale') {
5715
+ await handleRecaptureStale();
5716
+ return;
5717
+ }
3879
5718
  // Subcommand: codeyam editor change <feature>
3880
5719
  if (argv.step === 'change') {
3881
5720
  handleChange(argv.json || '');
@@ -3891,19 +5730,21 @@ const editorCommand = {
3891
5730
  await handleValidateSeed(argv.json || '');
3892
5731
  return;
3893
5732
  }
5733
+ // Subcommand: codeyam editor design-system <id>
5734
+ if (argv.step === 'design-system') {
5735
+ handleDesignSystem(argv.json || '');
5736
+ return;
5737
+ }
3894
5738
  // Subcommand: codeyam editor delete <scenarioId>
3895
5739
  if (argv.step === 'delete') {
3896
5740
  await handleDelete(argv.json || '');
3897
5741
  return;
3898
5742
  }
3899
5743
  // Subcommand: codeyam editor isolate "StarRating CategoryBadge DrinkCard"
5744
+ // Also supports: codeyam editor isolate StarRating CategoryBadge DrinkCard
3900
5745
  if (argv.step === 'isolate') {
3901
- const names = [];
3902
- if (argv.json)
3903
- names.push(...argv.json.split(/[\s,]+/).filter(Boolean));
3904
- // Collect any extra positional args (yargs puts them in argv._)
3905
- const extras = argv._.filter((a) => typeof a === 'string' && a !== 'editor');
3906
- names.push(...extras);
5746
+ const { parseIsolateArgs } = await import('./editorIsolateArgs.js');
5747
+ const names = parseIsolateArgs(argv.json, argv._);
3907
5748
  handleIsolate(names);
3908
5749
  return;
3909
5750
  }
@@ -3938,18 +5779,13 @@ const editorCommand = {
3938
5779
  }
3939
5780
  else {
3940
5781
  const state = readState(root);
3941
- // Clear prompt file when feature is done (step 16) so the hook
3942
- // can capture the next feature request from the user.
3943
- if (state?.step === 16) {
3944
- clearEditorUserPrompt(root);
3945
- }
3946
5782
  printCycleOverview(root, state);
3947
5783
  }
3948
5784
  return;
3949
5785
  }
3950
5786
  const step = argv.step ? parseInt(argv.step, 10) : undefined;
3951
- if (step != null && (isNaN(step) || step < 1 || step > 16)) {
3952
- console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-16.`));
5787
+ if (step != null && (isNaN(step) || step < 1 || step > 18)) {
5788
+ console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-18.`));
3953
5789
  process.exit(1);
3954
5790
  }
3955
5791
  if (step == null) {
@@ -3972,10 +5808,18 @@ const editorCommand = {
3972
5808
  try {
3973
5809
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
3974
5810
  const { projectSlug } = config;
5811
+ // Detect provider switch and inform the user
5812
+ const handoff = config.handoff;
5813
+ const currentProvider = config.provider || 'claude';
5814
+ if (handoff?.lastProvider && handoff.lastProvider !== currentProvider) {
5815
+ console.log(chalk.yellow(` Provider changed: ${handoff.lastProvider} → ${currentProvider}`));
5816
+ console.log(chalk.dim(' The editor will offer to continue with handoff context.'));
5817
+ }
3975
5818
  if (!projectSlug) {
3976
- errorLog('Missing project slug. Try reinitializing with: codeyam init --force');
5819
+ errorLog('Missing project slug. Try reinitializing with: `codeyam editor template`');
3977
5820
  return;
3978
5821
  }
5822
+ console.log(chalk.dim(` CodeYam Editor v${getDisplayVersion()}`));
3979
5823
  const connectionOk = await withoutSpinner(() => testEnvironment());
3980
5824
  if (!connectionOk) {
3981
5825
  errorLog('Environment validation failed');
@@ -4091,6 +5935,52 @@ const editorCommand = {
4091
5935
  catch {
4092
5936
  // Non-fatal — migration failure shouldn't block editor startup
4093
5937
  }
5938
+ // Auto-seed on fresh clone: if no scenario has ever been activated
5939
+ // (active-scenario.json doesn't exist), seed the application database
5940
+ // with the first application scenario that has seed data.
5941
+ const activeScenarioPath = path.join(projectRoot, '.codeyam', 'active-scenario.json');
5942
+ if (!fs.existsSync(activeScenarioPath)) {
5943
+ try {
5944
+ const { getDatabase: getDb } = await import('../../../packages/database/index.js');
5945
+ const seedDb = getDb();
5946
+ // Prefer the home page scenario (url "/") since the App tab
5947
+ // sorts "Home" first — fall back to any application scenario.
5948
+ const homeScenario = await seedDb
5949
+ .selectFrom('editor_scenarios')
5950
+ .select(['id', 'name', 'type'])
5951
+ .where('project_id', '=', project.id)
5952
+ .where('url', '=', '/')
5953
+ .where('component_name', 'is', null)
5954
+ .orderBy('created_at', 'asc')
5955
+ .executeTakeFirst();
5956
+ const appScenario = homeScenario ||
5957
+ (await seedDb
5958
+ .selectFrom('editor_scenarios')
5959
+ .select(['id', 'name', 'type'])
5960
+ .where('project_id', '=', project.id)
5961
+ .where('type', '=', 'application')
5962
+ .orderBy('created_at', 'asc')
5963
+ .executeTakeFirst());
5964
+ if (appScenario) {
5965
+ const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
5966
+ if (fs.existsSync(seedFile)) {
5967
+ const { switchActiveScenario } = await import('../utils/editorScenarioSwitch.js');
5968
+ const seedResult = await switchActiveScenario({
5969
+ scenarioId: appScenario.id,
5970
+ scenarioName: appScenario.name || undefined,
5971
+ scenarioType: appScenario.type || undefined,
5972
+ projectRoot,
5973
+ });
5974
+ if (seedResult.seedResult?.success) {
5975
+ console.log(chalk.green(` Auto-seeded database with scenario: ${appScenario.name || appScenario.id}`));
5976
+ }
5977
+ }
5978
+ }
5979
+ }
5980
+ catch {
5981
+ // Non-fatal — auto-seed is best-effort
5982
+ }
5983
+ }
4094
5984
  // `codeyam editor` (no step) always implies editor mode.
4095
5985
  // The empty-folder heuristic is no longer needed here — running this
4096
5986
  // command IS the signal. We still detect empty folders so that
@@ -4213,7 +6103,7 @@ const editorCommand = {
4213
6103
  if (!needsAnalysis) {
4214
6104
  try {
4215
6105
  const { getDatabase } = await import('../../../packages/database/index.js');
4216
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
6106
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
4217
6107
  const db = getDatabase();
4218
6108
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
4219
6109
  if (backfillCount > 0) {
@@ -4235,6 +6125,48 @@ const editorCommand = {
4235
6125
  }
4236
6126
  }
4237
6127
  }
6128
+ // Backfill page_file_path for application scenarios that have a URL but
6129
+ // no page_file_path. This resolves the file from the URL using Next.js
6130
+ // routing conventions, enabling syncScenarioEntityShas to link them to
6131
+ // entities. Covers fresh clones where JSON files lack pageFilePath.
6132
+ try {
6133
+ const { getDatabase: getDb } = await import('../../../packages/database/index.js');
6134
+ const pfpDb = getDb();
6135
+ const unresolved = await pfpDb
6136
+ .selectFrom('editor_scenarios')
6137
+ .select(['id', 'url'])
6138
+ .where('project_id', '=', project.id)
6139
+ .where('component_name', 'is', null)
6140
+ .where('page_file_path', 'is', null)
6141
+ .where('url', 'is not', null)
6142
+ .execute();
6143
+ if (unresolved.length > 0) {
6144
+ const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
6145
+ const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
6146
+ const { allFiles: pfpFiles } = scanPfp(projectRoot);
6147
+ let pfpResolved = 0;
6148
+ for (const row of unresolved) {
6149
+ const r = row;
6150
+ if (!r.url)
6151
+ continue;
6152
+ const matched = matchUrlToPageFile(r.url, pfpFiles);
6153
+ if (matched) {
6154
+ await pfpDb
6155
+ .updateTable('editor_scenarios')
6156
+ .set({ page_file_path: matched })
6157
+ .where('id', '=', r.id)
6158
+ .execute();
6159
+ pfpResolved++;
6160
+ }
6161
+ }
6162
+ if (pfpResolved > 0) {
6163
+ console.log(chalk.green(` Resolved page_file_path for ${pfpResolved} scenario(s) from URL`));
6164
+ }
6165
+ }
6166
+ }
6167
+ catch {
6168
+ /* Non-fatal — page_file_path backfill from URL */
6169
+ }
4238
6170
  // Backfill entity_sha on scenarios that were synced before entities existed.
4239
6171
  // This runs independently of analyze-imports so fresh clones and file-synced
4240
6172
  // scenarios get linked to their entities even when auto-analyze doesn't trigger.
@@ -4247,6 +6179,8 @@ const editorCommand = {
4247
6179
  sha: e.sha,
4248
6180
  name: e.name,
4249
6181
  filePath: e.filePath || '',
6182
+ isDefaultExport: e.metadata?.notExported === false &&
6183
+ e.metadata?.namedExport === false,
4250
6184
  })));
4251
6185
  if (result.updated > 0) {
4252
6186
  console.log(chalk.green(` Linked ${result.updated} scenario(s) to their entities`));
@@ -4291,12 +6225,15 @@ const editorCommand = {
4291
6225
  // Step 1 is planning-only and may not persist state (no --feature flag).
4292
6226
  const skipValidation = step === 2 && argv.feature;
4293
6227
  if (!skipValidation) {
4294
- const stepError = validateStepTransition(step, state?.step ?? null);
6228
+ const stepError = validateStepTransition(step, state?.step ?? null, state?.startedAt, root);
4295
6229
  if (stepError) {
4296
6230
  console.error(chalk.red(`Error: ${stepError}`));
4297
6231
  process.exit(1);
4298
6232
  }
4299
6233
  }
6234
+ printHandoffContext(root);
6235
+ // Track step progress automatically for provider handoff
6236
+ updateHandoffProgress(root, step);
4300
6237
  switch (step) {
4301
6238
  case 1: {
4302
6239
  const feature = argv.feature || undefined;
@@ -4331,18 +6268,20 @@ const editorCommand = {
4331
6268
  case 13:
4332
6269
  case 14:
4333
6270
  case 15:
4334
- case 16: {
6271
+ case 16:
6272
+ case 17:
6273
+ case 18: {
4335
6274
  const feature = argv.feature || state?.feature;
4336
6275
  if (!feature) {
4337
6276
  console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
4338
6277
  process.exit(1);
4339
6278
  }
4340
- // Hard gate: steps 8+ require audit to have passed
4341
- if (step >= 8) {
6279
+ // Hard gate: steps 10+ require audit to have passed
6280
+ if (step >= 10) {
4342
6281
  const auditOk = await checkAuditGate();
4343
6282
  if (!auditOk) {
4344
- console.error(chalk.red.bold('BLOCKED: The audit has not passed. Run `codeyam editor audit` and fix all issues before proceeding.'));
4345
- console.error(chalk.yellow('Every function and hook must have a test file. Every component must have scenarios.'));
6283
+ // checkAuditGate() already printed specific failure details above
6284
+ console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
4346
6285
  process.exit(1);
4347
6286
  }
4348
6287
  }
@@ -4361,6 +6300,8 @@ const editorCommand = {
4361
6300
  14: printStep14,
4362
6301
  15: printStep15,
4363
6302
  16: printStep16,
6303
+ 17: printStep17,
6304
+ 18: printStep18,
4364
6305
  };
4365
6306
  stepFns[step](root, feature);
4366
6307
  break;