@codeyam/codeyam-cli 0.1.0-staging.50df560 → 0.1.0-staging.535b4e1

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 (327) 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 +3 -3
  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 +1 -0
  14. package/analyzer-template/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.ts +21 -0
  15. package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +82 -10
  16. package/analyzer-template/packages/analyze/src/lib/files/analyzeChange.ts +4 -0
  17. package/analyzer-template/packages/analyze/src/lib/files/analyzeInitial.ts +4 -0
  18. package/analyzer-template/packages/analyze/src/lib/files/analyzeNextRoute.ts +8 -3
  19. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +239 -58
  20. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +1684 -1462
  21. package/analyzer-template/packages/aws/package.json +6 -6
  22. package/analyzer-template/packages/database/package.json +1 -1
  23. package/analyzer-template/packages/database/src/lib/client/loadAnalysis_Client.ts +3 -1
  24. package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +7 -1
  25. package/analyzer-template/packages/database/src/lib/loadEntity.ts +11 -4
  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 +4 -4
  32. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js.map +1 -1
  33. package/analyzer-template/packages/github/dist/utils/src/lib/awsLog.js +1 -1
  34. package/analyzer-template/packages/github/dist/utils/src/lib/awsLog.js.map +1 -1
  35. package/analyzer-template/packages/utils/dist/utils/src/lib/awsLog.js +1 -1
  36. package/analyzer-template/packages/utils/dist/utils/src/lib/awsLog.js.map +1 -1
  37. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts +3 -1
  38. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
  39. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +22 -1
  40. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  41. package/analyzer-template/packages/utils/src/lib/awsLog.ts +1 -1
  42. package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +27 -0
  43. package/analyzer-template/project/runMultiScenarioServer.ts +26 -3
  44. package/background/src/lib/virtualized/project/runMultiScenarioServer.js +23 -3
  45. package/background/src/lib/virtualized/project/runMultiScenarioServer.js.map +1 -1
  46. package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js +47 -0
  47. package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js.map +1 -0
  48. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js +71 -0
  49. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
  50. package/codeyam-cli/src/commands/__tests__/editor.designSystem.test.js +30 -0
  51. package/codeyam-cli/src/commands/__tests__/editor.designSystem.test.js.map +1 -0
  52. package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js +55 -0
  53. package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js.map +1 -0
  54. package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +39 -3
  55. package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
  56. package/codeyam-cli/src/commands/editor.js +1438 -205
  57. package/codeyam-cli/src/commands/editor.js.map +1 -1
  58. package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js +23 -0
  59. package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js.map +1 -0
  60. package/codeyam-cli/src/commands/init.js +20 -0
  61. package/codeyam-cli/src/commands/init.js.map +1 -1
  62. package/codeyam-cli/src/data/designSystems.js +27 -0
  63. package/codeyam-cli/src/data/designSystems.js.map +1 -0
  64. package/codeyam-cli/src/data/techStacks.js +1 -1
  65. package/codeyam-cli/src/utils/__tests__/devServerState.test.js +93 -1
  66. package/codeyam-cli/src/utils/__tests__/devServerState.test.js.map +1 -1
  67. package/codeyam-cli/src/utils/__tests__/editorApi.test.js +44 -0
  68. package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -1
  69. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +1969 -761
  70. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  71. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
  72. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
  73. package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +98 -1
  74. package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -1
  75. package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js +1108 -0
  76. package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js.map +1 -0
  77. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +120 -0
  78. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -1
  79. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +140 -1
  80. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  81. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +134 -1
  82. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
  83. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +33 -1
  84. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  85. package/codeyam-cli/src/utils/__tests__/envFile.test.js +125 -0
  86. package/codeyam-cli/src/utils/__tests__/envFile.test.js.map +1 -0
  87. package/codeyam-cli/src/utils/__tests__/handoffContext.test.js +500 -0
  88. package/codeyam-cli/src/utils/__tests__/handoffContext.test.js.map +1 -0
  89. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
  90. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
  91. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js +127 -0
  92. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js.map +1 -0
  93. package/codeyam-cli/src/utils/__tests__/testRunner.test.js +216 -0
  94. package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
  95. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +6 -0
  96. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -1
  97. package/codeyam-cli/src/utils/analysisRunner.js +28 -1
  98. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  99. package/codeyam-cli/src/utils/analyzer.js +11 -1
  100. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  101. package/codeyam-cli/src/utils/designSystemShowcase.js +810 -0
  102. package/codeyam-cli/src/utils/designSystemShowcase.js.map +1 -0
  103. package/codeyam-cli/src/utils/devServerState.js +32 -0
  104. package/codeyam-cli/src/utils/devServerState.js.map +1 -1
  105. package/codeyam-cli/src/utils/editorApi.js +16 -0
  106. package/codeyam-cli/src/utils/editorApi.js.map +1 -1
  107. package/codeyam-cli/src/utils/editorAudit.js +275 -21
  108. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  109. package/codeyam-cli/src/utils/editorPreview.js +5 -3
  110. package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
  111. package/codeyam-cli/src/utils/editorRoadmap.js +574 -0
  112. package/codeyam-cli/src/utils/editorRoadmap.js.map +1 -0
  113. package/codeyam-cli/src/utils/editorScenarioSwitch.js +27 -12
  114. package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -1
  115. package/codeyam-cli/src/utils/editorScenarios.js +71 -0
  116. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  117. package/codeyam-cli/src/utils/editorSeedAdapter.js +69 -16
  118. package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
  119. package/codeyam-cli/src/utils/entityChangeStatus.server.js +31 -0
  120. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  121. package/codeyam-cli/src/utils/envFile.js +90 -0
  122. package/codeyam-cli/src/utils/envFile.js.map +1 -0
  123. package/codeyam-cli/src/utils/handoffContext.js +257 -0
  124. package/codeyam-cli/src/utils/handoffContext.js.map +1 -0
  125. package/codeyam-cli/src/utils/install-skills.js +36 -6
  126. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  127. package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
  128. package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
  129. package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js +159 -0
  130. package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js.map +1 -0
  131. package/codeyam-cli/src/utils/queue/job.js +29 -3
  132. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  133. package/codeyam-cli/src/utils/registerScenarioResult.js +52 -0
  134. package/codeyam-cli/src/utils/registerScenarioResult.js.map +1 -0
  135. package/codeyam-cli/src/utils/scenariosManifest.js +8 -2
  136. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  137. package/codeyam-cli/src/utils/techStackConfig.js +38 -0
  138. package/codeyam-cli/src/utils/techStackConfig.js.map +1 -0
  139. package/codeyam-cli/src/utils/techStackConfig.test.js +85 -0
  140. package/codeyam-cli/src/utils/techStackConfig.test.js.map +1 -0
  141. package/codeyam-cli/src/utils/testResultCache.js +53 -0
  142. package/codeyam-cli/src/utils/testResultCache.js.map +1 -0
  143. package/codeyam-cli/src/utils/testResultCache.server.js +81 -0
  144. package/codeyam-cli/src/utils/testResultCache.server.js.map +1 -0
  145. package/codeyam-cli/src/utils/testResultCache.server.test.js +187 -0
  146. package/codeyam-cli/src/utils/testResultCache.server.test.js.map +1 -0
  147. package/codeyam-cli/src/utils/testResultCache.test.js +230 -0
  148. package/codeyam-cli/src/utils/testResultCache.test.js.map +1 -0
  149. package/codeyam-cli/src/utils/testRunner.js +193 -1
  150. package/codeyam-cli/src/utils/testRunner.js.map +1 -1
  151. package/codeyam-cli/src/utils/webappDetection.js +4 -2
  152. package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
  153. package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js +99 -0
  154. package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js.map +1 -0
  155. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +119 -1
  156. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -1
  157. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +384 -12
  158. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
  159. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +67 -0
  160. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
  161. package/codeyam-cli/src/webserver/app/lib/clientErrors.js +3 -0
  162. package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
  163. package/codeyam-cli/src/webserver/app/lib/database.js.map +1 -1
  164. package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js +34 -0
  165. package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js.map +1 -0
  166. package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-CLe80MMu.js → CopyButton-DTBZZfSk.js} +1 -1
  167. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-Crt_KN_U.js → EntityItem-BxclONWq.js} +1 -1
  168. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CD7lGABo.js → EntityTypeIcon-BsnEOJZ_.js} +1 -1
  169. package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-CgTNOhnu.js → InlineSpinner-ByaELMbv.js} +1 -1
  170. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-CKeQT5Ty.js → InteractivePreview-6WjVfhxX.js} +2 -2
  171. package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-D3s1MFkb.js → LibraryFunctionPreview-ChX-Hp7W.js} +1 -1
  172. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-CM5zg40N.js → LogViewer-C-9zQdXg.js} +1 -1
  173. package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-Bs2_Oua4.js +36 -0
  174. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-C2PLkej3.js → ReportIssueModal-DQsceHVv.js} +1 -1
  175. package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-DanvyBPb.js → SafeScreenshot-DThcm_9M.js} +1 -1
  176. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-DUMfcNVK.js → ScenarioViewer-Cl4oOA3A.js} +1 -1
  177. package/codeyam-cli/src/webserver/build/client/assets/Spinner-CIil5-gb.js +34 -0
  178. package/codeyam-cli/src/webserver/build/client/assets/{ViewportInspectBar-BA_Ry-rs.js → ViewportInspectBar-BqkA9zyZ.js} +1 -1
  179. package/codeyam-cli/src/webserver/build/client/assets/{_index-BAWd-Xjf.js → _index-DnOgyseQ.js} +1 -1
  180. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BOARiB-g.js → activity.(_tab)-DqM9hbNE.js} +1 -1
  181. package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-CHx25PAe.js → addon-web-links-C58dYPwR.js} +1 -1
  182. package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-Bg3e7q4S.js → agent-transcripts-B8NCeOrm.js} +1 -1
  183. package/codeyam-cli/src/webserver/build/client/assets/api.editor-database-verify-l0sNRNKZ.js +1 -0
  184. package/codeyam-cli/src/webserver/build/client/assets/api.editor-github-verify-l0sNRNKZ.js +1 -0
  185. package/codeyam-cli/src/webserver/build/client/assets/api.editor-handoff-l0sNRNKZ.js +1 -0
  186. package/codeyam-cli/src/webserver/build/client/assets/api.editor-hosting-verify-l0sNRNKZ.js +1 -0
  187. package/codeyam-cli/src/webserver/build/client/assets/api.editor-roadmap-l0sNRNKZ.js +1 -0
  188. package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-scenario-data-l0sNRNKZ.js +1 -0
  189. package/codeyam-cli/src/webserver/build/client/assets/api.editor-schema-l0sNRNKZ.js +1 -0
  190. package/codeyam-cli/src/webserver/build/client/assets/api.editor-verify-routes-l0sNRNKZ.js +1 -0
  191. package/codeyam-cli/src/webserver/build/client/assets/api.interactive-switch-scenario-l0sNRNKZ.js +1 -0
  192. package/codeyam-cli/src/webserver/build/client/assets/{book-open-CL-lMgHh.js → book-open-BFSIqZgO.js} +1 -1
  193. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-GmAjGS9-.js → chevron-down-B9fDzFVh.js} +1 -1
  194. package/codeyam-cli/src/webserver/build/client/assets/chunk-UVKPFVEO-Bmq2apuh.js +43 -0
  195. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-DFcQkN5j.js → circle-check-DLPObLUx.js} +1 -1
  196. package/codeyam-cli/src/webserver/build/client/assets/{copy-C6iF61Xs.js → copy-DXEmO0TD.js} +1 -1
  197. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-4ImjHTVC.js → createLucideIcon-BwyFiRot.js} +1 -1
  198. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-Coe5NhbS.js +1 -0
  199. package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CCKUIm0S.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
  200. package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-C8y4mmyv.js → dev.empty-iRhRIFlp.js} +1 -1
  201. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-BZPBzV73.js +1 -0
  202. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DhtVC4aI.js +161 -0
  203. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-C6fEYHrh.js +41 -0
  204. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-Blfy9UlN.js → entity._sha._-pc-vc6wO.js} +13 -12
  205. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-KTQuL0aj.js → entity._sha.scenarios._scenarioId.dev-C8AyYgYT.js} +1 -1
  206. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js → entity._sha.scenarios._scenarioId.fullscreen-DziaVQX1.js} +1 -1
  207. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-DQM8E7L4.js → entity._sha_.create-scenario-BTcpgIpC.js} +1 -1
  208. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-CAoXLsQr.js → entity._sha_.edit._scenarioId-D_O_ajfZ.js} +1 -1
  209. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-SuW9syRS.js → entry.client-j1Vi0bco.js} +6 -6
  210. package/codeyam-cli/src/webserver/build/client/assets/{files-D-xGrg29.js → files-kuny2Q_s.js} +1 -1
  211. package/codeyam-cli/src/webserver/build/client/assets/{git-Bq_fbXP5.js → git-DgCZPMie.js} +1 -1
  212. package/codeyam-cli/src/webserver/build/client/assets/globals-L-aUIeux.css +1 -0
  213. package/codeyam-cli/src/webserver/build/client/assets/{index-Bp1l4hSv.js → index-BliGSSpl.js} +1 -1
  214. package/codeyam-cli/src/webserver/build/client/assets/{index-DE3jI_dv.js → index-SqjQKTdH.js} +1 -1
  215. package/codeyam-cli/src/webserver/build/client/assets/{index-CWV9XZiG.js → index-vyrZD2g4.js} +1 -1
  216. package/codeyam-cli/src/webserver/build/client/assets/{labs-B_IX45ih.js → labs-c3yLxSEp.js} +1 -1
  217. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-De-7qQ2u.js → loader-circle-D-q28GLF.js} +1 -1
  218. package/codeyam-cli/src/webserver/build/client/assets/manifest-79d0d81a.js +1 -0
  219. package/codeyam-cli/src/webserver/build/client/assets/{memory-Cx2xEx7s.js → memory-CEWIUC4t.js} +1 -1
  220. package/codeyam-cli/src/webserver/build/client/assets/{pause-CFxEKL1u.js → pause-BP6fitdh.js} +1 -1
  221. package/codeyam-cli/src/webserver/build/client/assets/root-L2V0jea7.js +80 -0
  222. package/codeyam-cli/src/webserver/build/client/assets/{search-BdBb5aqc.js → search-BooqacKS.js} +1 -1
  223. package/codeyam-cli/src/webserver/build/client/assets/{settings-DdE-Untf.js → settings-BM0nbryO.js} +1 -1
  224. package/codeyam-cli/src/webserver/build/client/assets/{simulations-DSCdE99u.js → simulations-ovy6FjRY.js} +1 -1
  225. package/codeyam-cli/src/webserver/build/client/assets/{terminal-CrplD4b1.js → terminal-DHemCJIs.js} +1 -1
  226. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-DqJ0j69l.js → triangle-alert-D87ekDl8.js} +1 -1
  227. package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-DhXHbEjP.js → useCustomSizes-Dk0Tciqg.js} +1 -1
  228. package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-C8QvIe05.js +2 -0
  229. package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-Cy5Qg_UR.js → useReportContext-jkCytuYz.js} +1 -1
  230. package/codeyam-cli/src/webserver/build/client/assets/{useToast-5HR2j9ZE.js → useToast-BgqkixU9.js} +1 -1
  231. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-shqfY320.js +16 -0
  232. package/codeyam-cli/src/webserver/build/server/assets/{index-CjLhfz6Z.js → index-BsBA3Ez3.js} +1 -1
  233. package/codeyam-cli/src/webserver/build/server/assets/init-BBYQUAjy.js +14 -0
  234. package/codeyam-cli/src/webserver/build/server/assets/server-build-DFPAE5Ud.js +853 -0
  235. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  236. package/codeyam-cli/src/webserver/build-info.json +5 -5
  237. package/codeyam-cli/src/webserver/editorProxy.js +310 -23
  238. package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
  239. package/codeyam-cli/src/webserver/idleDetector.js +27 -3
  240. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
  241. package/codeyam-cli/src/webserver/server.js +67 -0
  242. package/codeyam-cli/src/webserver/server.js.map +1 -1
  243. package/codeyam-cli/src/webserver/terminalServer.js +122 -17
  244. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  245. package/codeyam-cli/templates/codeyam-editor-codex.md +61 -0
  246. package/codeyam-cli/templates/codeyam-editor-gemini.md +59 -0
  247. package/codeyam-cli/templates/codeyam-editor-reference.md +8 -6
  248. package/codeyam-cli/templates/design-systems/clean-dashboard-design-system.md +255 -0
  249. package/codeyam-cli/templates/design-systems/editorial-design-system.md +267 -0
  250. package/codeyam-cli/templates/design-systems/mono-brutalist-design-system.md +256 -0
  251. package/codeyam-cli/templates/design-systems/neo-brutalist-design-system.md +294 -0
  252. package/codeyam-cli/templates/editor-step-hook.py +4 -4
  253. package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +204 -5
  254. package/codeyam-cli/templates/expo-react-native/__tests__/.gitkeep +0 -0
  255. package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +6 -3
  256. package/codeyam-cli/templates/expo-react-native/app/index.tsx +36 -0
  257. package/codeyam-cli/templates/expo-react-native/app.json +11 -0
  258. package/codeyam-cli/templates/expo-react-native/babel.config.js +1 -0
  259. package/codeyam-cli/templates/expo-react-native/gitignore +2 -0
  260. package/codeyam-cli/templates/expo-react-native/global.css +7 -0
  261. package/codeyam-cli/templates/expo-react-native/lib/theme.ts +73 -0
  262. package/codeyam-cli/templates/expo-react-native/package.json +32 -16
  263. package/codeyam-cli/templates/expo-react-native/patches/expo-modules-autolinking+3.0.24.patch +29 -0
  264. package/codeyam-cli/templates/isolation-route/expo-router.tsx.template +54 -0
  265. package/codeyam-cli/templates/nextjs-prisma-sqlite/gitignore +1 -0
  266. package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +47 -34
  267. package/codeyam-cli/templates/seed-adapters/supabase.ts +271 -78
  268. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +17 -2
  269. package/package.json +1 -1
  270. package/packages/ai/src/lib/astScopes/methodSemantics.js +99 -0
  271. package/packages/ai/src/lib/astScopes/methodSemantics.js.map +1 -1
  272. package/packages/ai/src/lib/astScopes/nodeToSource.js +16 -0
  273. package/packages/ai/src/lib/astScopes/nodeToSource.js.map +1 -1
  274. package/packages/ai/src/lib/astScopes/paths.js +12 -3
  275. package/packages/ai/src/lib/astScopes/paths.js.map +1 -1
  276. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +27 -10
  277. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  278. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
  279. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
  280. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
  281. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  282. package/packages/analyze/index.js +1 -1
  283. package/packages/analyze/index.js.map +1 -1
  284. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
  285. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  286. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
  287. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  288. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +1 -0
  289. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
  290. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
  291. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
  292. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
  293. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
  294. package/packages/analyze/src/lib/files/analyzeChange.js +1 -0
  295. package/packages/analyze/src/lib/files/analyzeChange.js.map +1 -1
  296. package/packages/analyze/src/lib/files/analyzeInitial.js +1 -0
  297. package/packages/analyze/src/lib/files/analyzeInitial.js.map +1 -1
  298. package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
  299. package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
  300. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +120 -28
  301. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  302. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +1368 -1193
  303. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  304. package/packages/database/src/lib/loadAnalysis.js +7 -1
  305. package/packages/database/src/lib/loadAnalysis.js.map +1 -1
  306. package/packages/database/src/lib/loadEntity.js +4 -4
  307. package/packages/database/src/lib/loadEntity.js.map +1 -1
  308. package/packages/utils/src/lib/awsLog.js +1 -1
  309. package/packages/utils/src/lib/awsLog.js.map +1 -1
  310. package/packages/utils/src/lib/fs/rsyncCopy.js +22 -1
  311. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  312. package/codeyam-cli/src/webserver/build/client/assets/Spinner-D0LgAaSa.js +0 -34
  313. package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-BAdwhyCx.js +0 -43
  314. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DcX-ZS3p.js +0 -1
  315. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-Gbk_i5Js.js +0 -1
  316. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DII1pg_z.js +0 -58
  317. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-oepecPae.js +0 -41
  318. package/codeyam-cli/src/webserver/build/client/assets/globals-Yn9W3zp3.css +0 -1
  319. package/codeyam-cli/src/webserver/build/client/assets/manifest-cdf2c0a7.js +0 -1
  320. package/codeyam-cli/src/webserver/build/client/assets/root-BxUQigda.js +0 -67
  321. package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-BNd5hYuW.js +0 -2
  322. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-B_PsTAb1.js +0 -13
  323. package/codeyam-cli/src/webserver/build/server/assets/init-BEqlbI84.js +0 -10
  324. package/codeyam-cli/src/webserver/build/server/assets/server-build-YI63xTu4.js +0 -553
  325. package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +0 -33
  326. package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +0 -12
  327. package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +0 -12
@@ -13,6 +13,7 @@ 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";
@@ -21,8 +22,11 @@ import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } f
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 = {
@@ -187,6 +191,181 @@ function getProjectDimensions(root) {
187
191
  return { defaultName: 'Desktop', names: [] };
188
192
  }
189
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
+ }
190
369
  /**
191
370
  * Print dimension guidance when the project has multiple screen sizes.
192
371
  * Tells Claude to pick the right dimension for the content being previewed
@@ -210,6 +389,86 @@ function checkbox(text) {
210
389
  const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
211
390
  console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
212
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
+ }
213
472
  /**
214
473
  * Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
215
474
  * Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
@@ -237,6 +496,10 @@ function printAppScenarioInstructions(pageName, route) {
237
496
  console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
238
497
  }
239
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();
240
503
  checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
241
504
  console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
242
505
  console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
@@ -251,7 +514,12 @@ function printAppScenarioInstructions(pageName, route) {
251
514
  console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
252
515
  }
253
516
  else {
517
+ const appCtx = getTechStackContext(root);
254
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
+ }
255
523
  console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
256
524
  console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
257
525
  }
@@ -299,6 +567,12 @@ function printExtractionPlanInstructions() {
299
567
  console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
300
568
  console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
301
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();
302
576
  console.log(chalk.bold('Checklist:'));
303
577
  checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
304
578
  checkbox('Read EVERY file used by this page/feature');
@@ -309,6 +583,12 @@ function printExtractionPlanInstructions() {
309
583
  console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
310
584
  console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
311
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'));
312
592
  console.log();
313
593
  checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
314
594
  console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
@@ -319,6 +599,15 @@ function printExtractionPlanInstructions() {
319
599
  console.log(chalk.yellow(' — What it is (component, function, hook)'));
320
600
  console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
321
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'));
604
+ console.log();
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.'));
322
611
  console.log();
323
612
  console.log(chalk.dim('Present the numbered plan, then proceed to step 7 to execute it.'));
324
613
  }
@@ -326,10 +615,18 @@ function printExtractionPlanInstructions() {
326
615
  * Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
327
616
  * Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
328
617
  */
329
- function printComponentCaptureInstructions() {
330
- checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
331
- console.log(chalk.dim(' This creates app/codeyam-isolate/layout.tsx (with production notFound() guard) and'));
332
- 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
+ }
333
630
  checkbox('For each visual component:');
334
631
  console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
335
632
  console.log(chalk.dim(' — Props/interface'));
@@ -340,23 +637,37 @@ function printComponentCaptureInstructions() {
340
637
  console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
341
638
  console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
342
639
  console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
343
- console.log(chalk.dim(' Remix: app/routes/codeyam-isolate.ComponentName.tsx → /codeyam-isolate/ComponentName'));
344
- 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
+ }
345
648
  console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
346
649
  console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
347
- console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
348
- console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
349
- console.log(chalk.dim(' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
350
- 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'));
351
662
  console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
352
663
  console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
353
664
  console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
354
665
  console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
355
666
  console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
356
667
  console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
357
- console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
668
+ console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
358
669
  console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
359
- 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/...).'));
360
671
  console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
361
672
  console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
362
673
  console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
@@ -366,7 +677,7 @@ function printComponentCaptureInstructions() {
366
677
  console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
367
678
  console.log();
368
679
  console.log(chalk.bold(' Bulk registration: write all component scenarios to a temp file as an array:'));
369
- console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/codeyam-isolate/Comp?s=Default"}, ...]'));
680
+ console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
370
681
  console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
371
682
  }
372
683
  /**
@@ -419,7 +730,9 @@ function stopGate(current, opts) {
419
730
  // Skip step 1 (no feature name yet — task starts at step 2)
420
731
  if (current >= 2) {
421
732
  console.log(chalk.bold.cyan('━━━ TASK ━━━'));
422
- console.log(chalk.cyan('Mark your current task (if any) as complete.'));
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'));
423
736
  console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
424
737
  console.log();
425
738
  let taskTitle;
@@ -683,11 +996,17 @@ function printSetup(root) {
683
996
  console.log();
684
997
  // ── Design System ────────────────────────────────────────────────
685
998
  console.log(chalk.bold('Design System (ask FIRST):'));
686
- 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."'));
687
1000
  console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
688
- 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"'));
689
1008
  console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
690
- console.log(chalk.yellow(' Option 2 label: "No, use sensible defaults"'));
1009
+ console.log(chalk.yellow(' Option label: "Skip — use sensible defaults"'));
691
1010
  console.log(chalk.dim(' → Skip'));
692
1011
  console.log();
693
1012
  console.log(chalk.bold('Checklist:'));
@@ -708,8 +1027,8 @@ function printSetup(root) {
708
1027
  console.log(chalk.bold('Tech Stack Selection:'));
709
1028
  console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
710
1029
  console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
711
- console.log(chalk.dim(' Show ALL matching stacks even if there is only one. Mark the recommended option.'));
712
- 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.'));
713
1032
  console.log();
714
1033
  console.log(chalk.bold(' Available Tech Stacks:'));
715
1034
  for (const stack of TECH_STACKS) {
@@ -742,7 +1061,7 @@ function printSetup(root) {
742
1061
  console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
743
1062
  console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
744
1063
  console.log();
745
- 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).'));
746
1065
  console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
747
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}}'`));
748
1067
  console.log();
@@ -901,6 +1220,15 @@ function printStep1(root, feature, options, userPrompt) {
901
1220
  console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
902
1221
  console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
903
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();
904
1232
  const designSystem = readDesignSystem(root);
905
1233
  if (designSystem) {
906
1234
  console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
@@ -914,6 +1242,8 @@ function printStep1(root, feature, options, userPrompt) {
914
1242
  console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
915
1243
  chalk.dim(' — user describes changes, you revise the plan, then re-present'));
916
1244
  console.log();
1245
+ checkboxHandoff();
1246
+ console.log();
917
1247
  console.log(chalk.dim('This step is for understanding user goals and getting buy-in. Code comes in Step 2.'));
918
1248
  console.log();
919
1249
  console.log(chalk.bold.red('━━━ STOP ━━━'));
@@ -951,33 +1281,80 @@ function printStep2(root, feature) {
951
1281
  }
952
1282
  console.log('Get the project ready to build.');
953
1283
  console.log();
1284
+ const ctx = getTechStackContext(root);
954
1285
  // If no project exists yet, include scaffolding instructions first
955
1286
  if (!projectExists) {
956
1287
  console.log(chalk.bold('Scaffold the project:'));
957
1288
  checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
958
- 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
+ }
959
1298
  console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
960
1299
  console.log();
961
- checkbox('Define your data models in `prisma/schema.prisma`');
962
- console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
963
- console.log();
964
- checkbox('Push schema and seed the database');
965
- console.log(chalk.dim(' npm run db:push'));
966
- console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
967
- console.log(chalk.dim(' npm run db:seed'));
968
- console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
969
- console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
970
- console.log();
971
- console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
972
- console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
973
- console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
974
- console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
975
- console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
976
- console.log();
977
- console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
978
- console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
979
- console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
980
- console.log();
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();
981
1358
  }
982
1359
  else {
983
1360
  console.log(chalk.bold('Prepare the database for this feature:'));
@@ -992,7 +1369,12 @@ function printStep2(root, feature) {
992
1369
  console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
993
1370
  console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
994
1371
  console.log();
1372
+ printDataStructureInstructions();
995
1373
  }
1374
+ console.log();
1375
+ printServiceRecordingReminder(port);
1376
+ console.log();
1377
+ checkboxHandoff();
996
1378
  stopGate(2);
997
1379
  }
998
1380
  // ─── Step 3: Prototype ────────────────────────────────────────────────
@@ -1017,26 +1399,42 @@ function printStep3(root, feature) {
1017
1399
  if (isResuming) {
1018
1400
  printResumptionHeader(3);
1019
1401
  }
1402
+ const ctx = getTechStackContext(root);
1020
1403
  console.log('Build fast with real data. Prioritize speed over quality.');
1021
1404
  console.log();
1022
1405
  console.log(chalk.bold('Checklist:'));
1023
- checkbox('Create API routes that read from the database via Prisma');
1024
- if (!projectExists) {
1025
- checkbox('Seed the database with demo data');
1026
- checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
1027
- console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
1028
- console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
1029
- console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
1030
- console.log();
1031
- console.log(chalk.bold.cyan('Make seed data visually rich:'));
1032
- console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
1033
- console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
1034
- console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1035
- console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1036
- console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
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
+ }
1037
1435
  }
1038
1436
  checkbox('Verify the dev server shows the changes');
1039
- 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}`);
1040
1438
  // Responsive design guidance when building a mobile-responsive web app
1041
1439
  if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
1042
1440
  console.log();
@@ -1053,16 +1451,33 @@ function printStep3(root, feature) {
1053
1451
  console.log();
1054
1452
  console.log(designSystem);
1055
1453
  console.log();
1056
- checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
1057
- console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
1058
- console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
1059
- console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
1060
- console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
1061
- console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
1062
- checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
1063
- console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
1064
- console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
1065
- 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
+ }
1066
1481
  }
1067
1482
  console.log();
1068
1483
  console.log(chalk.bold.cyan('Keep the preview moving:'));
@@ -1076,6 +1491,9 @@ function printStep3(root, feature) {
1076
1491
  console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
1077
1492
  console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
1078
1493
  console.log();
1494
+ printServiceRecordingReminder(port);
1495
+ console.log();
1496
+ checkboxHandoff();
1079
1497
  stopGate(3);
1080
1498
  }
1081
1499
  // ─── Step 4: Verify Prototype ─────────────────────────────────────────
@@ -1090,6 +1508,8 @@ function printStep4(root, feature) {
1090
1508
  label: STEP_LABELS[4],
1091
1509
  startedAt: isResuming ? prevState.startedAt : now,
1092
1510
  featureStartedAt: prevState?.featureStartedAt || now,
1511
+ appFormats: prevState?.appFormats,
1512
+ techStackId: prevState?.techStackId,
1093
1513
  });
1094
1514
  logEvent(root, 'step', { step: 4, label: 'Verify Prototype', feature });
1095
1515
  stepHeader(4, 'Verify Prototype', feature);
@@ -1099,14 +1519,13 @@ function printStep4(root, feature) {
1099
1519
  console.log('Verify everything works before presenting the prototype.');
1100
1520
  console.log();
1101
1521
  console.log(chalk.bold('Verify the dev server:'));
1102
- console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
1103
- console.log(chalk.dim(' # Check page loads: curl -s -o /dev/null -w "%{http_code}" http://localhost:<dev-port>'));
1104
- 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"]}'`));
1105
1524
  console.log();
1106
1525
  console.log(chalk.bold('Verify before proceeding:'));
1107
1526
  console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
1108
- checkbox('Verify the page loads: curl the dev server URL and confirm HTTP 200 (not an error page)');
1109
- 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.'));
1110
1529
  checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
1111
1530
  console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
1112
1531
  console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
@@ -1117,6 +1536,14 @@ function printStep4(root, feature) {
1117
1536
  console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
1118
1537
  console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
1119
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
+ }
1120
1547
  console.log(chalk.bold('Update README and setup script:'));
1121
1548
  checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
1122
1549
  checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
@@ -1124,6 +1551,8 @@ function printStep4(root, feature) {
1124
1551
  console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
1125
1552
  console.log();
1126
1553
  console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
1554
+ console.log();
1555
+ checkboxHandoff();
1127
1556
  stopGate(4);
1128
1557
  }
1129
1558
  // ─── Step 5: Confirm ──────────────────────────────────────────────────
@@ -1139,6 +1568,8 @@ function printStep5(root, feature) {
1139
1568
  label: STEP_LABELS[5],
1140
1569
  startedAt: isResuming ? prevState.startedAt : now,
1141
1570
  featureStartedAt: prevState?.featureStartedAt || now,
1571
+ appFormats: prevState?.appFormats,
1572
+ techStackId: prevState?.techStackId,
1142
1573
  });
1143
1574
  logEvent(root, 'step', { step: 5, label: 'Confirm', feature });
1144
1575
  stepHeader(5, 'Confirm', feature);
@@ -1149,7 +1580,7 @@ function printStep5(root, feature) {
1149
1580
  console.log();
1150
1581
  console.log(chalk.bold('Before presenting — verify everything works:'));
1151
1582
  checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
1152
- checkbox('Verify API routes return valid data (curl each route)');
1583
+ checkbox('Verify routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
1153
1584
  console.log();
1154
1585
  console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
1155
1586
  checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
@@ -1185,6 +1616,8 @@ function printStep5(root, feature) {
1185
1616
  chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 5`'));
1186
1617
  console.log();
1187
1618
  console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
1619
+ console.log();
1620
+ checkboxHandoff();
1188
1621
  stopGate(5, { confirm: true });
1189
1622
  }
1190
1623
  // ─── Step 6: Deconstruct ──────────────────────────────────────────────
@@ -1198,6 +1631,8 @@ function printStep6(root, feature) {
1198
1631
  label: STEP_LABELS[6],
1199
1632
  startedAt: isResuming ? prevState.startedAt : now,
1200
1633
  featureStartedAt: prevState?.featureStartedAt || now,
1634
+ appFormats: prevState?.appFormats,
1635
+ techStackId: prevState?.techStackId,
1201
1636
  });
1202
1637
  logEvent(root, 'step', { step: 6, label: 'Deconstruct', feature });
1203
1638
  stepHeader(6, 'Deconstruct', feature);
@@ -1208,6 +1643,8 @@ function printStep6(root, feature) {
1208
1643
  console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
1209
1644
  console.log();
1210
1645
  printExtractionPlanInstructions();
1646
+ console.log();
1647
+ checkboxHandoff();
1211
1648
  stopGate(6);
1212
1649
  }
1213
1650
  // ─── Step 7: Extract ──────────────────────────────────────────────────
@@ -1223,12 +1660,15 @@ function printStep7(root, feature) {
1223
1660
  label: STEP_LABELS[7],
1224
1661
  startedAt: isResuming ? prevState.startedAt : now,
1225
1662
  featureStartedAt: prevState?.featureStartedAt || now,
1663
+ appFormats: prevState?.appFormats,
1664
+ techStackId: prevState?.techStackId,
1226
1665
  });
1227
1666
  logEvent(root, 'step', { step: 7, label: 'Extract', feature });
1228
1667
  stepHeader(7, 'Extract', feature);
1229
1668
  if (isResuming) {
1230
1669
  printResumptionHeader(7);
1231
1670
  }
1671
+ const ctx = getTechStackContext(root);
1232
1672
  console.log('Execute your extraction plan from step 6.');
1233
1673
  console.log();
1234
1674
  console.log(chalk.bold('Components:'));
@@ -1241,19 +1681,45 @@ function printStep7(root, feature) {
1241
1681
  checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
1242
1682
  console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
1243
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'));
1244
1687
  console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
1245
- checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
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
+ }
1246
1695
  console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
1247
1696
  console.log();
1248
1697
  console.log(chalk.bold('Recursive pass:'));
1249
1698
  checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
1250
1699
  checkbox('Keep going until every file is a thin shell: just imports and composition');
1251
- 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}?`));
1252
1701
  console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
1253
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();
1254
1720
  console.log(chalk.bold('Verify before proceeding:'));
1255
1721
  checkbox('Run all tests and verify they pass');
1256
- 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'}`);
1257
1723
  checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
1258
1724
  checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1259
1725
  printDimensionGuidance(dim, dimNames);
@@ -1261,6 +1727,8 @@ function printStep7(root, feature) {
1261
1727
  console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
1262
1728
  console.log();
1263
1729
  console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
1730
+ console.log();
1731
+ checkboxHandoff();
1264
1732
  stopGate(7);
1265
1733
  }
1266
1734
  // ─── Step 8: Glossary ─────────────────────────────────────────────────
@@ -1274,6 +1742,8 @@ function printStep8(root, feature) {
1274
1742
  label: STEP_LABELS[8],
1275
1743
  startedAt: isResuming ? prevState.startedAt : now,
1276
1744
  featureStartedAt: prevState?.featureStartedAt || now,
1745
+ appFormats: prevState?.appFormats,
1746
+ techStackId: prevState?.techStackId,
1277
1747
  });
1278
1748
  logEvent(root, 'step', { step: 8, label: 'Glossary', feature });
1279
1749
  stepHeader(8, 'Glossary', feature);
@@ -1300,6 +1770,8 @@ function printStep9(root, feature) {
1300
1770
  label: STEP_LABELS[9],
1301
1771
  startedAt: isResuming ? prevState.startedAt : now,
1302
1772
  featureStartedAt: prevState?.featureStartedAt || now,
1773
+ appFormats: prevState?.appFormats,
1774
+ techStackId: prevState?.techStackId,
1303
1775
  });
1304
1776
  logEvent(root, 'step', { step: 9, label: 'Analyze', feature });
1305
1777
  stepHeader(9, 'Analyze and Verify', feature);
@@ -1314,19 +1786,35 @@ function printStep9(root, feature) {
1314
1786
  console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
1315
1787
  console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
1316
1788
  console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
1317
- printComponentCaptureInstructions();
1789
+ const ctx9 = getTechStackContext(root);
1790
+ printComponentCaptureInstructions(root);
1318
1791
  console.log();
1319
1792
  console.log(chalk.bold('Library Functions — run tests:'));
1320
1793
  checkbox('Run ALL test files created in step 7');
1321
- console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
1794
+ console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
1322
1795
  checkbox('Verify every test passes');
1323
1796
  checkbox('If any test fails, fix the source code and re-run');
1324
1797
  console.log();
1325
1798
  console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
1326
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();
1327
1809
  checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
1328
1810
  console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
1329
- console.log(chalk.dim(' When audit passes, the import graph is built automatically for change tracking.'));
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.'));
1330
1818
  console.log();
1331
1819
  stopGate(9);
1332
1820
  }
@@ -1343,6 +1831,8 @@ function printStep10(root, feature) {
1343
1831
  label: STEP_LABELS[10],
1344
1832
  startedAt: isResuming ? prevState.startedAt : now,
1345
1833
  featureStartedAt: prevState?.featureStartedAt || now,
1834
+ appFormats: prevState?.appFormats,
1835
+ techStackId: prevState?.techStackId,
1346
1836
  });
1347
1837
  logEvent(root, 'step', { step: 10, label: 'App Scenarios', feature });
1348
1838
  stepHeader(10, 'App Scenarios', feature);
@@ -1407,6 +1897,8 @@ function printStep11(root, feature) {
1407
1897
  label: STEP_LABELS[11],
1408
1898
  startedAt: isResuming ? prevState.startedAt : now,
1409
1899
  featureStartedAt: prevState?.featureStartedAt || now,
1900
+ appFormats: prevState?.appFormats,
1901
+ techStackId: prevState?.techStackId,
1410
1902
  });
1411
1903
  logEvent(root, 'step', { step: 11, label: 'User Scenarios', feature });
1412
1904
  stepHeader(11, 'User Scenarios', feature);
@@ -1453,6 +1945,8 @@ function printStep12(root, feature) {
1453
1945
  label: STEP_LABELS[12],
1454
1946
  startedAt: isResuming ? prevState.startedAt : now,
1455
1947
  featureStartedAt: prevState?.featureStartedAt || now,
1948
+ appFormats: prevState?.appFormats,
1949
+ techStackId: prevState?.techStackId,
1456
1950
  });
1457
1951
  logEvent(root, 'step', { step: 12, label: 'Verify', feature });
1458
1952
  stepHeader(12, 'Verify', feature);
@@ -1499,6 +1993,8 @@ function printStep13(root, feature) {
1499
1993
  label: STEP_LABELS[13],
1500
1994
  startedAt: isResuming ? prevState.startedAt : now,
1501
1995
  featureStartedAt: prevState?.featureStartedAt || now,
1996
+ appFormats: prevState?.appFormats,
1997
+ techStackId: prevState?.techStackId,
1502
1998
  });
1503
1999
  logEvent(root, 'step', { step: 13, label: 'Journal', feature });
1504
2000
  stepHeader(13, 'Journal', feature);
@@ -1532,6 +2028,8 @@ function printStep14(root, feature) {
1532
2028
  label: STEP_LABELS[14],
1533
2029
  startedAt: isResuming ? prevState.startedAt : now,
1534
2030
  featureStartedAt: prevState?.featureStartedAt || now,
2031
+ appFormats: prevState?.appFormats,
2032
+ techStackId: prevState?.techStackId,
1535
2033
  });
1536
2034
  logEvent(root, 'step', { step: 14, label: 'Review', feature });
1537
2035
  stepHeader(14, 'Review', feature);
@@ -1551,6 +2049,8 @@ function printStep14(root, feature) {
1551
2049
  checkbox('Fix or remove any broken images before continuing');
1552
2050
  checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
1553
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": {...}}'`));
1554
2054
  checkbox('Do not proceed until all checks pass');
1555
2055
  stopGate(14);
1556
2056
  }
@@ -1565,6 +2065,8 @@ function printStep15(root, feature) {
1565
2065
  label: STEP_LABELS[15],
1566
2066
  startedAt: isResuming ? prevState.startedAt : now,
1567
2067
  featureStartedAt: prevState?.featureStartedAt || now,
2068
+ appFormats: prevState?.appFormats,
2069
+ techStackId: prevState?.techStackId,
1568
2070
  });
1569
2071
  logEvent(root, 'step', { step: 15, label: 'Present', feature });
1570
2072
  stepHeader(15, 'Present', feature);
@@ -1793,7 +2295,7 @@ function printMigrateStep3(root) {
1793
2295
  console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
1794
2296
  console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
1795
2297
  console.log();
1796
- printComponentCaptureInstructions();
2298
+ printComponentCaptureInstructions(root);
1797
2299
  console.log();
1798
2300
  migrationStopGate(3, pageName, pageIndex, totalPages);
1799
2301
  }
@@ -1928,7 +2430,7 @@ function printMigrateStep8(root) {
1928
2430
  console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
1929
2431
  console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
1930
2432
  checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
1931
- 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)');
1932
2434
  checkbox('Re-register each one with the SAME name to update its screenshot');
1933
2435
  console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
1934
2436
  checkbox('After each registration, check the response for `clientErrors`');
@@ -1936,10 +2438,10 @@ function printMigrateStep8(root) {
1936
2438
  console.log();
1937
2439
  console.log(chalk.bold('Component Scenarios:'));
1938
2440
  console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
1939
- printComponentCaptureInstructions();
2441
+ printComponentCaptureInstructions(root);
1940
2442
  console.log();
1941
2443
  console.log(chalk.bold('Verify:'));
1942
- 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)');
1943
2445
  checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
1944
2446
  checkbox('Run `codeyam editor audit` to check completeness');
1945
2447
  checkbox('Check for client errors: `codeyam editor client-errors`');
@@ -2214,6 +2716,8 @@ function printStep16(root, feature) {
2214
2716
  label: STEP_LABELS[16],
2215
2717
  startedAt: isResuming ? prevState.startedAt : now,
2216
2718
  featureStartedAt: prevState?.featureStartedAt || now,
2719
+ appFormats: prevState?.appFormats,
2720
+ techStackId: prevState?.techStackId,
2217
2721
  });
2218
2722
  logEvent(root, 'step', { step: 16, label: 'Commit', feature });
2219
2723
  stepHeader(16, 'Commit', feature);
@@ -2240,6 +2744,8 @@ function printStep17(root, feature) {
2240
2744
  label: STEP_LABELS[17],
2241
2745
  startedAt: isResuming ? prevState.startedAt : now,
2242
2746
  featureStartedAt: prevState?.featureStartedAt || now,
2747
+ appFormats: prevState?.appFormats,
2748
+ techStackId: prevState?.techStackId,
2243
2749
  });
2244
2750
  logEvent(root, 'step', { step: 17, label: 'Finalize', feature });
2245
2751
  stepHeader(17, 'Finalize', feature);
@@ -2265,6 +2771,8 @@ function printStep18(root, feature) {
2265
2771
  label: STEP_LABELS[18],
2266
2772
  startedAt: isResuming ? prevState.startedAt : now,
2267
2773
  featureStartedAt: prevState?.featureStartedAt || now,
2774
+ appFormats: prevState?.appFormats,
2775
+ techStackId: prevState?.techStackId,
2268
2776
  });
2269
2777
  logEvent(root, 'step', { step: 18, label: 'Push', feature });
2270
2778
  stepHeader(18, 'Push', feature);
@@ -2282,11 +2790,11 @@ function printStep18(root, feature) {
2282
2790
  console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
2283
2791
  console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
2284
2792
  console.log();
2285
- console.log(chalk.bold.yellow('IMPORTANT: After this step, the current feature is DONE.'));
2286
- console.log(chalk.yellow(' Any new user request — even if it sounds related — is a NEW feature.'));
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.'));
2287
2797
  console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
2288
- console.log(chalk.yellow(' Ask the user what they want to build. Once they tell you, run `codeyam editor 1` yourself.'));
2289
- console.log(chalk.yellow(' NEVER tell the user to run `codeyam editor` commands — these are internal. Just ask what to build.'));
2290
2798
  console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
2291
2799
  stopGate(18, { confirm: true });
2292
2800
  }
@@ -2317,10 +2825,14 @@ async function handleAnalyzeImports(options = {}) {
2317
2825
  glossaryEntries = sanitizeGlossaryEntries(parsed);
2318
2826
  }
2319
2827
  catch {
2828
+ if (options.silent)
2829
+ return;
2320
2830
  console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
2321
2831
  process.exit(1);
2322
2832
  }
2323
2833
  if (glossaryEntries.length === 0) {
2834
+ if (options.silent)
2835
+ return;
2324
2836
  console.error(chalk.red('Error: glossary.json is empty.'));
2325
2837
  process.exit(1);
2326
2838
  }
@@ -2362,25 +2874,137 @@ async function handleAnalyzeImports(options = {}) {
2362
2874
  // Non-fatal — scenario file paths just won't be included
2363
2875
  }
2364
2876
  const progress = new ProgressReporter();
2365
- // 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.
2366
2914
  // Don't pass entityNames — entities may not exist yet (fresh clone).
2367
2915
  // The analyzer will discover and create them from file paths.
2368
- progress.start('Running import analysis for all entities...');
2369
- try {
2370
- await runAnalysisForEntities({
2371
- projectRoot: root,
2372
- filePaths,
2373
- progress,
2374
- onlyDataStructure: true,
2375
- });
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');
2376
2937
  }
2377
- catch (err) {
2378
- progress.fail('Analysis failed');
2379
- const msg = err instanceof Error ? err.message : String(err);
2380
- console.error(chalk.red(`Error: ${msg}`));
2381
- 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
+ }
2382
3007
  }
2383
- progress.succeed('Analysis complete');
2384
3008
  // Load entities WITH metadata from database
2385
3009
  progress.start('Loading entity metadata...');
2386
3010
  await initializeEnvironment();
@@ -2563,43 +3187,98 @@ async function handleDelete(scenarioId) {
2563
3187
  * Creates isolation route directories and the layout guard file.
2564
3188
  * This avoids brace-expansion permission prompts in the embedded terminal.
2565
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
+ }
2566
3208
  function handleIsolate(componentNames) {
2567
3209
  const root = process.cwd();
3210
+ const ctx = getTechStackContext(root);
2568
3211
  if (componentNames.length === 0) {
2569
3212
  console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
2570
3213
  process.exit(1);
2571
3214
  }
2572
3215
  const isolateDir = path.join(root, 'app', 'isolated-components');
2573
- // Create layout.tsx with production guard if missing
2574
- 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
+ }
2575
3223
  if (!fs.existsSync(layoutPath)) {
2576
3224
  fs.mkdirSync(isolateDir, { recursive: true });
2577
- fs.writeFileSync(layoutPath, [
2578
- 'import { notFound } from "next/navigation";',
2579
- '',
2580
- 'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
2581
- ' if (process.env.NODE_ENV === "production") notFound();',
2582
- ' return <>{children}</>;',
2583
- '}',
2584
- '',
2585
- ].join('\n'), 'utf8');
2586
- console.log(chalk.green(`Created layout guard: app/isolated-components/layout.tsx`));
2587
- }
2588
- // 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).
2589
3251
  const created = [];
2590
3252
  const existed = [];
2591
3253
  for (const name of componentNames) {
2592
- const dir = path.join(isolateDir, name);
2593
- if (fs.existsSync(dir)) {
2594
- 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
+ }
2595
3262
  }
2596
3263
  else {
2597
- fs.mkdirSync(dir, { recursive: true });
2598
- 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
+ }
2599
3272
  }
2600
3273
  }
2601
3274
  if (created.length > 0) {
2602
- 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
+ }
2603
3282
  }
2604
3283
  if (existed.length > 0) {
2605
3284
  console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
@@ -2653,6 +3332,12 @@ function formatApiSubcommandResult(subcommand, data) {
2653
3332
  parts.push(`scenarioId="${data.scenarioId}"`);
2654
3333
  if (data.sessionsNotified != null)
2655
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
+ }
2656
3341
  return parts.join(' ');
2657
3342
  }
2658
3343
  case 'dev-server': {
@@ -2702,10 +3387,82 @@ function formatApiSubcommandResult(subcommand, data) {
2702
3387
  }
2703
3388
  return parts.join(' ');
2704
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
+ }
2705
3418
  default:
2706
3419
  return null; // journal-list, show/hide-results: keep full JSON
2707
3420
  }
2708
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
+ }
2709
3466
  /**
2710
3467
  * `codeyam editor register '{"name":"...","componentName":"...",...}'`
2711
3468
  *
@@ -2736,6 +3493,8 @@ async function handleRegister(jsonArg) {
2736
3493
  const isBatch = items.length > 1;
2737
3494
  let succeeded = 0;
2738
3495
  let failed = 0;
3496
+ let warnings = 0;
3497
+ const issues = [];
2739
3498
  const screenshotEntries = [];
2740
3499
  for (let i = 0; i < items.length; i++) {
2741
3500
  const body = items[i];
@@ -2805,11 +3564,14 @@ async function handleRegister(jsonArg) {
2805
3564
  }
2806
3565
  console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
2807
3566
  }
3567
+ const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
2808
3568
  if (!res.ok) {
2809
3569
  console.error(chalk.dim(JSON.stringify(data, null, 2)));
2810
3570
  failed++;
2811
3571
  }
2812
3572
  else {
3573
+ if (resultIssues.length > 0)
3574
+ warnings++;
2813
3575
  succeeded++;
2814
3576
  // Collect screenshot paths for duplicate detection
2815
3577
  if (data.screenshotCaptured &&
@@ -2822,6 +3584,10 @@ async function handleRegister(jsonArg) {
2822
3584
  });
2823
3585
  }
2824
3586
  }
3587
+ // Collect issues from this registration
3588
+ for (const issue of resultIssues) {
3589
+ issues.push(`"${issue.scenarioName}": ${issue.message}`);
3590
+ }
2825
3591
  }
2826
3592
  catch (error) {
2827
3593
  const msg = error instanceof Error ? error.message : String(error);
@@ -2833,7 +3599,7 @@ async function handleRegister(jsonArg) {
2833
3599
  }
2834
3600
  // Detect duplicate screenshots in the batch
2835
3601
  if (isBatch && screenshotEntries.length > 1) {
2836
- const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash');
3602
+ const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
2837
3603
  const hashEntries = screenshotEntries
2838
3604
  .map((e) => {
2839
3605
  const hash = computeScreenshotHash(e.screenshotAbsPath);
@@ -2859,9 +3625,22 @@ async function handleRegister(jsonArg) {
2859
3625
  }
2860
3626
  }
2861
3627
  if (isBatch) {
2862
- 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
+ }
2863
3642
  }
2864
- if (failed > 0) {
3643
+ if (failed > 0 || warnings > 0) {
2865
3644
  process.exit(1);
2866
3645
  }
2867
3646
  }
@@ -3150,10 +3929,11 @@ function handleChange(feature) {
3150
3929
  * Fetch the audit result from the server.
3151
3930
  * Returns null if the server is unreachable.
3152
3931
  */
3153
- async function fetchAuditResult() {
3932
+ async function fetchAuditResult(options) {
3154
3933
  const port = getServerPort();
3934
+ const params = options?.skipTests ? '?skipTests=true' : '';
3155
3935
  try {
3156
- const res = await fetch(`http://localhost:${port}/api/editor-audit`);
3936
+ const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
3157
3937
  if (!res.ok)
3158
3938
  return null;
3159
3939
  return await res.json();
@@ -3176,17 +3956,15 @@ async function checkAuditGate() {
3176
3956
  return true; // Server unreachable — don't block
3177
3957
  if (data.summary?.allPassing === true)
3178
3958
  return true;
3179
- // If incomplete entities / unassociated scenarios are the only issue,
3180
- // auto-fix with analyze-imports + entity SHA backfill (once)
3181
- const { isAutoRemediable } = await import('../utils/editorAudit.js');
3182
- if (isAutoRemediable(data.summary, false)) {
3183
- try {
3184
- await handleAnalyzeImports({ silent: true });
3185
- }
3186
- catch {
3187
- // Fall through to backfill attempt
3188
- }
3189
- // Backfill entity_sha on scenarios registered before entities existed
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)) {
3190
3968
  try {
3191
3969
  const entities = await loadEntities({});
3192
3970
  if (entities && entities.length > 0) {
@@ -3204,8 +3982,8 @@ async function checkAuditGate() {
3204
3982
  catch {
3205
3983
  // Fall through
3206
3984
  }
3207
- // Re-check after fix
3208
- const retry = await fetchAuditResult();
3985
+ // Re-check after backfill — skip re-running tests (backfill is DB-only)
3986
+ const retry = await fetchAuditResult({ skipTests: true });
3209
3987
  if (retry?.summary?.allPassing === true)
3210
3988
  return true;
3211
3989
  }
@@ -3228,6 +4006,8 @@ function printAuditGateFailures(data) {
3228
4006
  const missing = (data.components || []).filter((c) => c.status === 'missing');
3229
4007
  for (const c of missing) {
3230
4008
  issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
4009
+ if (c.hint)
4010
+ issues.push(` Fix: ${c.hint}`);
3231
4011
  }
3232
4012
  }
3233
4013
  if (s.componentsWithErrors > 0) {
@@ -3241,7 +4021,15 @@ function printAuditGateFailures(data) {
3241
4021
  issues.push(`${s.functionsMissing} function(s) missing test files`);
3242
4022
  const missing = (data.functions || []).filter((f) => f.status === 'missing');
3243
4023
  for (const f of missing) {
3244
- issues.push(` → ${f.name}${f.filePath ? ` (${f.filePath})` : ''}`);
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
+ }
3245
4033
  }
3246
4034
  }
3247
4035
  if (s.functionsFailing > 0) {
@@ -3256,6 +4044,10 @@ function printAuditGateFailures(data) {
3256
4044
  const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
3257
4045
  for (const f of runnerErrors) {
3258
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}`);
3259
4051
  }
3260
4052
  }
3261
4053
  if (s.functionsNameMismatch > 0) {
@@ -3263,13 +4055,44 @@ function printAuditGateFailures(data) {
3263
4055
  const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
3264
4056
  for (const f of mismatch) {
3265
4057
  issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
4058
+ if (f.hint)
4059
+ issues.push(` Fix: ${f.hint}`);
3266
4060
  }
3267
4061
  }
3268
- if (s.missingFromGlossary > 0)
4062
+ if (s.missingFromGlossary > 0) {
3269
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
+ }
3270
4078
  if (s.incompleteEntities > 0) {
4079
+ // Check for persistent analysis failures
4080
+ let analysisFailures = {};
4081
+ try {
4082
+ const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
4083
+ if (fs.existsSync(failuresPath)) {
4084
+ analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
4085
+ }
4086
+ }
4087
+ catch {
4088
+ // Non-fatal
4089
+ }
3271
4090
  const preCount = s.preExistingIncompleteEntities || 0;
3272
- if (preCount > 0 && preCount === s.incompleteEntities) {
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) {
3273
4096
  issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
3274
4097
  }
3275
4098
  else if (preCount > 0) {
@@ -3278,18 +4101,52 @@ function printAuditGateFailures(data) {
3278
4101
  else {
3279
4102
  issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
3280
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`);
4117
+ }
3281
4118
  }
3282
4119
  if (s.unassociatedScenarios > 0) {
3283
- issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
3284
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
+ }
3285
4130
  for (const u of unassociated) {
3286
4131
  issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
3287
4132
  }
3288
4133
  }
3289
- if (s.miscategorizedScenarios > 0)
4134
+ if (s.miscategorizedScenarios > 0) {
3290
4135
  issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
3291
- if (s.scenariosNeedingRecapture > 0)
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) {
3292
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
+ }
3293
4150
  if (issues.length > 0) {
3294
4151
  console.error(chalk.yellow('\nAudit failures:'));
3295
4152
  for (const issue of issues) {
@@ -3324,28 +4181,22 @@ function printAuditGateFailures(data) {
3324
4181
  * which glossary components have registered scenarios and which functions
3325
4182
  * have test files. Exits with code 1 if anything is missing.
3326
4183
  */
3327
- async function handleAudit() {
4184
+ async function handleAudit(options) {
3328
4185
  let data = await fetchAuditResult();
3329
4186
  if (!data) {
3330
4187
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3331
4188
  process.exit(1);
3332
4189
  }
3333
- // Auto-fix incomplete entities and unassociated scenarios — but only once.
3334
- // If analyze-imports + entity SHA sync runs and issues persist, report clearly
3335
- // instead of retrying. The analysis may have errors (e.g., stale entity
3336
- // 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).
3337
4194
  const incompleteBeforeFix = data.incompleteEntities || [];
3338
4195
  const unassociatedBeforeFix = data.unassociatedScenarios || [];
3339
4196
  let autoRemediationFailed = false;
3340
4197
  if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
3341
4198
  const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
3342
- console.log(chalk.dim(`Auto-fixing ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
3343
- try {
3344
- await handleAnalyzeImports({ silent: true });
3345
- }
3346
- catch {
3347
- // Fall through — the audit will still report them
3348
- }
4199
+ console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
3349
4200
  // Backfill entity_sha on scenarios that were registered before entities existed
3350
4201
  try {
3351
4202
  const entities = await loadEntities({});
@@ -3364,20 +4215,65 @@ async function handleAudit() {
3364
4215
  catch {
3365
4216
  // Fall through — re-fetch will show remaining issues
3366
4217
  }
3367
- // Re-fetch audit results after the fix
3368
- 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 });
3369
4221
  if (!data) {
3370
- console.error(chalk.red('Error: Could not reach the CodeYam server after auto-fix.'));
4222
+ console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
3371
4223
  process.exit(1);
3372
4224
  }
3373
- // If issues persist after auto-fix, flag it so we show clear guidance
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
3374
4270
  const incompleteAfterFix = data.incompleteEntities || [];
3375
4271
  const unassociatedAfterFix = data.unassociatedScenarios || [];
3376
4272
  if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
3377
4273
  autoRemediationFailed = true;
3378
4274
  }
3379
4275
  }
3380
- const { components, functions, summary } = data;
4276
+ let { components, functions, summary } = data;
3381
4277
  console.log();
3382
4278
  console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
3383
4279
  console.log();
@@ -3407,6 +4303,9 @@ async function handleAudit() {
3407
4303
  }
3408
4304
  else {
3409
4305
  detail = chalk.red(' — no scenarios registered');
4306
+ if (c.hint) {
4307
+ detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
4308
+ }
3410
4309
  }
3411
4310
  // Show file path for failing components always, for OK only when name is ambiguous
3412
4311
  const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
@@ -3432,14 +4331,20 @@ async function handleAudit() {
3432
4331
  let detail;
3433
4332
  switch (f.status) {
3434
4333
  case 'ok':
3435
- 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
+ }
3436
4340
  break;
3437
4341
  case 'runner_error':
3438
4342
  detail = chalk.red(` — test runner crashed: ${f.testFile}`);
3439
4343
  if (f.errorMessage) {
3440
4344
  detail += `\n ${chalk.red(f.errorMessage)}`;
3441
4345
  detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
3442
- detail += `\n ${chalk.yellow('Try running the test manually: npx vitest run ' + f.testFile)}`;
4346
+ const ctx = getTechStackContext(process.cwd());
4347
+ detail += `\n ${chalk.yellow('Try running the test manually: ' + ctx.testRunCommand + ' ' + f.testFile)}`;
3443
4348
  }
3444
4349
  break;
3445
4350
  case 'failing':
@@ -3450,13 +4355,23 @@ async function handleAudit() {
3450
4355
  break;
3451
4356
  case 'missing':
3452
4357
  default:
3453
- detail = f.testFile
3454
- ? chalk.red(` — test file missing: ${f.testFile}`)
3455
- : 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
+ }
3456
4368
  break;
3457
4369
  }
3458
4370
  console.log(` ${icon} ${f.name}${detail}`);
3459
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
+ }
3460
4375
  console.log();
3461
4376
  }
3462
4377
  // Missing from glossary
@@ -3466,22 +4381,53 @@ async function handleAudit() {
3466
4381
  for (const m of missingFromGlossary) {
3467
4382
  console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
3468
4383
  }
3469
- 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
+ }
3470
4393
  console.log();
3471
4394
  }
3472
- // 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.
3473
4399
  const incompleteEntities = data.incompleteEntities || [];
3474
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());
3475
4405
  console.log(chalk.bold('Incomplete entities (need import analysis):'));
3476
4406
  for (const e of incompleteEntities) {
3477
- console.log(` ${chalk.red('✗')} ${e.name} — ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
3478
- }
3479
- if (autoRemediationFailed) {
3480
- console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
3481
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to see the full error output.'));
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
+ }
3482
4427
  }
3483
- else {
3484
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities.'));
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.'));
3485
4431
  }
3486
4432
  console.log();
3487
4433
  }
@@ -3498,12 +4444,22 @@ async function handleAudit() {
3498
4444
  console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
3499
4445
  }
3500
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';
3501
4453
  if (autoRemediationFailed) {
3502
4454
  console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
3503
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to see the full error output.'));
4455
+ console.log(chalk.yellow(` Run \`${uaCmd}\` to see the full error output.`));
3504
4456
  }
3505
4457
  else {
3506
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to create entity records, then re-run audit to backfill.'));
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.'));
3507
4463
  }
3508
4464
  console.log();
3509
4465
  }
@@ -3532,7 +4488,26 @@ async function handleAudit() {
3532
4488
  : `${s.status.status}`;
3533
4489
  console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
3534
4490
  }
3535
- console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
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
+ }
3536
4511
  console.log();
3537
4512
  }
3538
4513
  // Duplicate glossary names (warning, not a failure)
@@ -3595,17 +4570,6 @@ async function handleAudit() {
3595
4570
  if (!allOk) {
3596
4571
  process.exit(1);
3597
4572
  }
3598
- // Auto-run analyze-imports when audit passes — this builds the import graph
3599
- // so the App tab can show component/function dependencies for each page.
3600
- // Previously this was a manual step that Claude had to remember to run.
3601
- console.log(chalk.dim('Building import graph...'));
3602
- try {
3603
- await handleAnalyzeImports({ silent: true });
3604
- }
3605
- catch {
3606
- // Non-fatal — audit passed, import graph is a bonus
3607
- console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
3608
- }
3609
4573
  }
3610
4574
  // ─── Recapture-stale subcommand ────────────────────────────────────────
3611
4575
  /**
@@ -3812,14 +4776,14 @@ async function handleScenarioCoverage() {
3812
4776
  // Safety net: heal any scenarios with null entity_sha before checking coverage
3813
4777
  try {
3814
4778
  const { getDatabase } = await import('../../../packages/database/index.js');
3815
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
4779
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
3816
4780
  const db = getDatabase();
3817
4781
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
3818
4782
  if (backfillCount > 0) {
3819
4783
  console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
3820
4784
  await handleAnalyzeImports({ silent: true });
3821
4785
  // Run backfill after analysis
3822
- const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
4786
+ const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
3823
4787
  const entities = await loadEntities({});
3824
4788
  if (entities && entities.length > 0) {
3825
4789
  await backfillEntityShaOnScenarios(db, entities.map((e) => ({
@@ -3966,7 +4930,7 @@ async function handleTemplate() {
3966
4930
  _: [],
3967
4931
  });
3968
4932
  console.log(chalk.green(' CodeYam initialized.'));
3969
- // 5. Verify config has startCommand
4933
+ // 5. Verify config has startCommand and set format-specific defaults
3970
4934
  const configPath = path.join(root, '.codeyam', 'config.json');
3971
4935
  if (fs.existsSync(configPath)) {
3972
4936
  try {
@@ -3977,6 +4941,30 @@ async function handleTemplate() {
3977
4941
  console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
3978
4942
  console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
3979
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));
3980
4968
  }
3981
4969
  catch {
3982
4970
  // Config parse error is non-fatal
@@ -4011,7 +4999,15 @@ async function handleTemplate() {
4011
4999
  }
4012
5000
  console.log();
4013
5001
  console.log(chalk.green.bold('Project scaffolded and ready!'));
4014
- 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
+ }
4015
5011
  }
4016
5012
  // ─── Sync subcommand ─────────────────────────────────────────────────
4017
5013
  /**
@@ -4316,14 +5312,205 @@ function handleEditorDebug(args) {
4316
5312
  console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
4317
5313
  console.log();
4318
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
+ }
4319
5506
  // ─── Command definition ───────────────────────────────────────────────
4320
5507
  const editorCommand = {
4321
5508
  command: 'editor [step] [json]',
4322
5509
  describe: 'Editor mode guided workflow',
4323
5510
  builder: (yargs) => {
4324
5511
  const stepDescription = IS_INTERNAL_BUILD
4325
- ? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)'
4326
- : 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)';
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)';
4327
5514
  let builder = yargs
4328
5515
  .positional('step', {
4329
5516
  type: 'string',
@@ -4354,6 +5541,11 @@ const editorCommand = {
4354
5541
  alias: 'p',
4355
5542
  describe: 'Port to run the web server on',
4356
5543
  default: 3111,
5544
+ })
5545
+ .option('fix', {
5546
+ type: 'boolean',
5547
+ describe: 'For audit: also recapture stale scenarios after displaying results',
5548
+ default: false,
4357
5549
  });
4358
5550
  if (IS_INTERNAL_BUILD) {
4359
5551
  builder = builder
@@ -4429,6 +5621,11 @@ const editorCommand = {
4429
5621
  }
4430
5622
  return;
4431
5623
  }
5624
+ // Subcommand: codeyam editor handoff '{"summary":"..."}'
5625
+ if (argv.step === 'handoff') {
5626
+ await handleHandoff(argv.json || '');
5627
+ return;
5628
+ }
4432
5629
  // Subcommand: codeyam editor register '{"name":"..."}'
4433
5630
  if (argv.step === 'register') {
4434
5631
  await handleRegister(argv.json || '');
@@ -4465,7 +5662,9 @@ const editorCommand = {
4465
5662
  'Ask user what to build next and restart codeyam editor workflow';
4466
5663
  }
4467
5664
  console.log(chalk.bold.cyan('━━━ TASK ━━━'));
4468
- console.log(chalk.cyan('Mark your current task (if any) as complete.'));
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'));
4469
5668
  console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
4470
5669
  console.log();
4471
5670
  console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
@@ -4479,9 +5678,16 @@ const editorCommand = {
4479
5678
  fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
4480
5679
  return;
4481
5680
  }
4482
- // Subcommand: codeyam editor analyze-imports
5681
+ // Subcommand: codeyam editor analyze-imports [file1.tsx file2.tsx ...]
4483
5682
  if (argv.step === 'analyze-imports') {
4484
- 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 || '');
4485
5691
  return;
4486
5692
  }
4487
5693
  // Subcommand: codeyam editor dependents <EntityName>
@@ -4489,9 +5695,9 @@ const editorCommand = {
4489
5695
  await handleDependents(argv.json || '');
4490
5696
  return;
4491
5697
  }
4492
- // Subcommand: codeyam editor audit
5698
+ // Subcommand: codeyam editor audit [--fix]
4493
5699
  if (argv.step === 'audit') {
4494
- await handleAudit();
5700
+ await handleAudit({ fix: argv.fix || false });
4495
5701
  return;
4496
5702
  }
4497
5703
  // Subcommand: codeyam editor scenarios
@@ -4524,6 +5730,11 @@ const editorCommand = {
4524
5730
  await handleValidateSeed(argv.json || '');
4525
5731
  return;
4526
5732
  }
5733
+ // Subcommand: codeyam editor design-system <id>
5734
+ if (argv.step === 'design-system') {
5735
+ handleDesignSystem(argv.json || '');
5736
+ return;
5737
+ }
4527
5738
  // Subcommand: codeyam editor delete <scenarioId>
4528
5739
  if (argv.step === 'delete') {
4529
5740
  await handleDelete(argv.json || '');
@@ -4597,10 +5808,18 @@ const editorCommand = {
4597
5808
  try {
4598
5809
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
4599
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
+ }
4600
5818
  if (!projectSlug) {
4601
5819
  errorLog('Missing project slug. Try reinitializing with: `codeyam editor template`');
4602
5820
  return;
4603
5821
  }
5822
+ console.log(chalk.dim(` CodeYam Editor v${getDisplayVersion()}`));
4604
5823
  const connectionOk = await withoutSpinner(() => testEnvironment());
4605
5824
  if (!connectionOk) {
4606
5825
  errorLog('Environment validation failed');
@@ -4724,13 +5943,24 @@ const editorCommand = {
4724
5943
  try {
4725
5944
  const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4726
5945
  const seedDb = getDb();
4727
- const appScenario = await seedDb
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
4728
5949
  .selectFrom('editor_scenarios')
4729
5950
  .select(['id', 'name', 'type'])
4730
5951
  .where('project_id', '=', project.id)
4731
- .where('type', '=', 'application')
5952
+ .where('url', '=', '/')
5953
+ .where('component_name', 'is', null)
4732
5954
  .orderBy('created_at', 'asc')
4733
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());
4734
5964
  if (appScenario) {
4735
5965
  const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
4736
5966
  if (fs.existsSync(seedFile)) {
@@ -4873,7 +6103,7 @@ const editorCommand = {
4873
6103
  if (!needsAnalysis) {
4874
6104
  try {
4875
6105
  const { getDatabase } = await import('../../../packages/database/index.js');
4876
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
6106
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
4877
6107
  const db = getDatabase();
4878
6108
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
4879
6109
  if (backfillCount > 0) {
@@ -4912,7 +6142,7 @@ const editorCommand = {
4912
6142
  .execute();
4913
6143
  if (unresolved.length > 0) {
4914
6144
  const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
4915
- const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
6145
+ const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
4916
6146
  const { allFiles: pfpFiles } = scanPfp(projectRoot);
4917
6147
  let pfpResolved = 0;
4918
6148
  for (const row of unresolved) {
@@ -5001,6 +6231,9 @@ const editorCommand = {
5001
6231
  process.exit(1);
5002
6232
  }
5003
6233
  }
6234
+ printHandoffContext(root);
6235
+ // Track step progress automatically for provider handoff
6236
+ updateHandoffProgress(root, step);
5004
6237
  switch (step) {
5005
6238
  case 1: {
5006
6239
  const feature = argv.feature || undefined;