@codeyam/codeyam-cli 0.1.0-staging.50df560 → 0.1.0-staging.5370992

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 (219) hide show
  1. package/analyzer-template/.build-info.json +7 -7
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +1 -1
  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 +1 -1
  22. package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +7 -1
  23. package/analyzer-template/packages/database/src/lib/loadEntity.ts +11 -4
  24. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.d.ts.map +1 -1
  25. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js +7 -1
  26. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js.map +1 -1
  27. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts +4 -1
  28. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts.map +1 -1
  29. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js +4 -4
  30. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js.map +1 -1
  31. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts +3 -1
  32. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
  33. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +22 -1
  34. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  35. package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +27 -0
  36. package/analyzer-template/project/runMultiScenarioServer.ts +26 -3
  37. package/background/src/lib/virtualized/project/runMultiScenarioServer.js +23 -3
  38. package/background/src/lib/virtualized/project/runMultiScenarioServer.js.map +1 -1
  39. package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js +47 -0
  40. package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js.map +1 -0
  41. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js +71 -0
  42. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
  43. package/codeyam-cli/src/commands/__tests__/editor.designSystem.test.js +30 -0
  44. package/codeyam-cli/src/commands/__tests__/editor.designSystem.test.js.map +1 -0
  45. package/codeyam-cli/src/commands/editor.js +1014 -194
  46. package/codeyam-cli/src/commands/editor.js.map +1 -1
  47. package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js +23 -0
  48. package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js.map +1 -0
  49. package/codeyam-cli/src/data/designSystems.js +27 -0
  50. package/codeyam-cli/src/data/designSystems.js.map +1 -0
  51. package/codeyam-cli/src/utils/__tests__/editorApi.test.js +44 -0
  52. package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -1
  53. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +1092 -30
  54. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  55. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
  56. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
  57. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +140 -1
  58. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  59. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +50 -1
  60. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
  61. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +33 -1
  62. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  63. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
  64. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
  65. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js +127 -0
  66. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js.map +1 -0
  67. package/codeyam-cli/src/utils/__tests__/testRunner.test.js +217 -0
  68. package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
  69. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +6 -0
  70. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -1
  71. package/codeyam-cli/src/utils/analysisRunner.js +28 -1
  72. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  73. package/codeyam-cli/src/utils/analyzer.js +11 -1
  74. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  75. package/codeyam-cli/src/utils/editorApi.js +16 -0
  76. package/codeyam-cli/src/utils/editorApi.js.map +1 -1
  77. package/codeyam-cli/src/utils/editorAudit.js +248 -16
  78. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  79. package/codeyam-cli/src/utils/editorPreview.js +5 -3
  80. package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
  81. package/codeyam-cli/src/utils/editorScenarios.js +61 -0
  82. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  83. package/codeyam-cli/src/utils/editorSeedAdapter.js +42 -2
  84. package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
  85. package/codeyam-cli/src/utils/entityChangeStatus.server.js +31 -0
  86. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  87. package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
  88. package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
  89. package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js +159 -0
  90. package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js.map +1 -0
  91. package/codeyam-cli/src/utils/queue/job.js +29 -3
  92. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  93. package/codeyam-cli/src/utils/registerScenarioResult.js +52 -0
  94. package/codeyam-cli/src/utils/registerScenarioResult.js.map +1 -0
  95. package/codeyam-cli/src/utils/scenariosManifest.js +8 -2
  96. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  97. package/codeyam-cli/src/utils/testResultCache.js +53 -0
  98. package/codeyam-cli/src/utils/testResultCache.js.map +1 -0
  99. package/codeyam-cli/src/utils/testResultCache.server.js +81 -0
  100. package/codeyam-cli/src/utils/testResultCache.server.js.map +1 -0
  101. package/codeyam-cli/src/utils/testResultCache.server.test.js +187 -0
  102. package/codeyam-cli/src/utils/testResultCache.server.test.js.map +1 -0
  103. package/codeyam-cli/src/utils/testResultCache.test.js +230 -0
  104. package/codeyam-cli/src/utils/testResultCache.test.js.map +1 -0
  105. package/codeyam-cli/src/utils/testRunner.js +199 -1
  106. package/codeyam-cli/src/utils/testRunner.js.map +1 -1
  107. package/codeyam-cli/src/utils/webappDetection.js +4 -2
  108. package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
  109. package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js +98 -0
  110. package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js.map +1 -0
  111. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +30 -11
  112. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
  113. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +67 -0
  114. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
  115. package/codeyam-cli/src/webserver/app/lib/clientErrors.js +3 -0
  116. package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
  117. package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js +34 -0
  118. package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js.map +1 -0
  119. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-CKeQT5Ty.js → InteractivePreview-DtYTSPL2.js} +1 -1
  120. package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-CQENLSrF.js +36 -0
  121. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-DUMfcNVK.js → ScenarioViewer-CefgqbCr.js} +1 -1
  122. package/codeyam-cli/src/webserver/build/client/assets/Spinner-Bc8BG-Lw.js +34 -0
  123. package/codeyam-cli/src/webserver/build/client/assets/{_index-BAWd-Xjf.js → _index-C1YkzTAV.js} +1 -1
  124. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BOARiB-g.js → activity.(_tab)-yH46LLUz.js} +1 -1
  125. package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-scenario-data-l0sNRNKZ.js +1 -0
  126. package/codeyam-cli/src/webserver/build/client/assets/api.editor-schema-l0sNRNKZ.js +1 -0
  127. package/codeyam-cli/src/webserver/build/client/assets/api.editor-verify-routes-l0sNRNKZ.js +1 -0
  128. package/codeyam-cli/src/webserver/build/client/assets/api.interactive-switch-scenario-l0sNRNKZ.js +1 -0
  129. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-Coe5NhbS.js +1 -0
  130. package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CCKUIm0S.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
  131. package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-C8y4mmyv.js → dev.empty-CRepiabR.js} +1 -1
  132. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DLM1-ZMt.js +96 -0
  133. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-CluPkvXJ.js +41 -0
  134. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-Blfy9UlN.js → entity._sha._-DYJRGiDI.js} +13 -12
  135. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-KTQuL0aj.js → entity._sha.scenarios._scenarioId.dev-wdiwx5-Z.js} +1 -1
  136. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js → entity._sha.scenarios._scenarioId.fullscreen-BrkN-40Y.js} +1 -1
  137. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-DQM8E7L4.js → entity._sha_.create-scenario-DxfhekTZ.js} +1 -1
  138. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-CAoXLsQr.js → entity._sha_.edit._scenarioId-CRXJWmpB.js} +1 -1
  139. package/codeyam-cli/src/webserver/build/client/assets/globals-9EkC9j9I.css +1 -0
  140. package/codeyam-cli/src/webserver/build/client/assets/manifest-7e749098.js +1 -0
  141. package/codeyam-cli/src/webserver/build/client/assets/{root-BxUQigda.js → root-DGtly3mb.js} +26 -13
  142. package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-D9QZKaLJ.js +2 -0
  143. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-CO8xocj3.js +16 -0
  144. package/codeyam-cli/src/webserver/build/server/assets/{index-CjLhfz6Z.js → index-QKPqlUgg.js} +1 -1
  145. package/codeyam-cli/src/webserver/build/server/assets/{init-BEqlbI84.js → init-DlspChIk.js} +1 -1
  146. package/codeyam-cli/src/webserver/build/server/assets/server-build-ChzicV-B.js +689 -0
  147. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  148. package/codeyam-cli/src/webserver/build-info.json +5 -5
  149. package/codeyam-cli/src/webserver/editorProxy.js +55 -3
  150. package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
  151. package/codeyam-cli/src/webserver/idleDetector.js +27 -3
  152. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
  153. package/codeyam-cli/src/webserver/terminalServer.js +22 -8
  154. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  155. package/codeyam-cli/templates/codeyam-editor-reference.md +8 -6
  156. package/codeyam-cli/templates/design-systems/clean-dashboard-design-system.md +255 -0
  157. package/codeyam-cli/templates/design-systems/editorial-design-system.md +267 -0
  158. package/codeyam-cli/templates/design-systems/mono-brutalist-design-system.md +256 -0
  159. package/codeyam-cli/templates/design-systems/neo-brutalist-design-system.md +294 -0
  160. package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +115 -0
  161. package/codeyam-cli/templates/expo-react-native/__tests__/.gitkeep +0 -0
  162. package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +3 -2
  163. package/codeyam-cli/templates/expo-react-native/global.css +7 -0
  164. package/codeyam-cli/templates/expo-react-native/lib/theme.ts +73 -0
  165. package/codeyam-cli/templates/expo-react-native/package.json +16 -6
  166. package/codeyam-cli/templates/isolation-route/expo-router.tsx.template +54 -0
  167. package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +47 -34
  168. package/codeyam-cli/templates/seed-adapters/supabase.ts +14 -5
  169. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +17 -2
  170. package/package.json +1 -1
  171. package/packages/ai/src/lib/astScopes/methodSemantics.js +99 -0
  172. package/packages/ai/src/lib/astScopes/methodSemantics.js.map +1 -1
  173. package/packages/ai/src/lib/astScopes/nodeToSource.js +16 -0
  174. package/packages/ai/src/lib/astScopes/nodeToSource.js.map +1 -1
  175. package/packages/ai/src/lib/astScopes/paths.js +12 -3
  176. package/packages/ai/src/lib/astScopes/paths.js.map +1 -1
  177. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +27 -10
  178. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  179. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
  180. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
  181. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
  182. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  183. package/packages/analyze/index.js +1 -1
  184. package/packages/analyze/index.js.map +1 -1
  185. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
  186. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  187. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
  188. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  189. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +1 -0
  190. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
  191. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
  192. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
  193. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
  194. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
  195. package/packages/analyze/src/lib/files/analyzeChange.js +1 -0
  196. package/packages/analyze/src/lib/files/analyzeChange.js.map +1 -1
  197. package/packages/analyze/src/lib/files/analyzeInitial.js +1 -0
  198. package/packages/analyze/src/lib/files/analyzeInitial.js.map +1 -1
  199. package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
  200. package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
  201. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +120 -28
  202. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  203. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +1368 -1193
  204. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  205. package/packages/database/src/lib/loadAnalysis.js +7 -1
  206. package/packages/database/src/lib/loadAnalysis.js.map +1 -1
  207. package/packages/database/src/lib/loadEntity.js +4 -4
  208. package/packages/database/src/lib/loadEntity.js.map +1 -1
  209. package/packages/utils/src/lib/fs/rsyncCopy.js +22 -1
  210. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  211. package/codeyam-cli/src/webserver/build/client/assets/Spinner-D0LgAaSa.js +0 -34
  212. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DcX-ZS3p.js +0 -1
  213. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DII1pg_z.js +0 -58
  214. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-oepecPae.js +0 -41
  215. package/codeyam-cli/src/webserver/build/client/assets/globals-Yn9W3zp3.css +0 -1
  216. package/codeyam-cli/src/webserver/build/client/assets/manifest-cdf2c0a7.js +0 -1
  217. package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-BNd5hYuW.js +0 -2
  218. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-B_PsTAb1.js +0 -13
  219. package/codeyam-cli/src/webserver/build/server/assets/server-build-YI63xTu4.js +0 -553
@@ -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,6 +22,7 @@ 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";
25
27
  import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
26
28
  const __filename = fileURLToPath(import.meta.url);
@@ -187,6 +189,44 @@ function getProjectDimensions(root) {
187
189
  return { defaultName: 'Desktop', names: [] };
188
190
  }
189
191
  }
192
+ function getTechStackContext(root) {
193
+ const state = readState(root);
194
+ const techStackId = state?.techStackId || '';
195
+ const stack = TECH_STACKS.find((s) => s.id === techStackId);
196
+ const isExpo = techStackId === 'expo-react-native';
197
+ const isChromeExt = techStackId === 'chrome-extension-react';
198
+ const isNextjs = techStackId.startsWith('nextjs-') || (!isExpo && !isChromeExt);
199
+ return {
200
+ id: techStackId || 'nextjs-prisma-sqlite',
201
+ isExpo,
202
+ isNextjs,
203
+ isChromeExt,
204
+ hasDatabase: isNextjs,
205
+ testRunner: isNextjs ? 'vitest' : 'jest',
206
+ testRunCommand: isNextjs ? 'npx vitest run' : 'npx jest',
207
+ storageType: isExpo
208
+ ? 'asyncStorage'
209
+ : isChromeExt
210
+ ? 'chromeStorage'
211
+ : 'prisma',
212
+ routerImport: isExpo
213
+ ? 'expo-router'
214
+ : isChromeExt
215
+ ? 'react-router-dom'
216
+ : 'next/navigation',
217
+ componentPrimitives: isExpo
218
+ ? '<View>, <Text>, <ScrollView>'
219
+ : '<div>, <span>, <h1>',
220
+ rawPrimitivesList: isExpo
221
+ ? '<View>, <Text>, <Image>, <ScrollView>, <FlatList>'
222
+ : '<div>, <span>, <h1>, <p>, <img>, <ul>',
223
+ patternsFile: isExpo
224
+ ? 'MOBILE_SETUP.md'
225
+ : isChromeExt
226
+ ? 'EXTENSION_SETUP.md'
227
+ : 'FEATURE_PATTERNS.md',
228
+ };
229
+ }
190
230
  /**
191
231
  * Print dimension guidance when the project has multiple screen sizes.
192
232
  * Tells Claude to pick the right dimension for the content being previewed
@@ -210,6 +250,34 @@ function checkbox(text) {
210
250
  const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
211
251
  console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
212
252
  }
253
+ /**
254
+ * Instructions for creating/updating .codeyam/data-structure.json.
255
+ * Only prints creation instructions if the file doesn't exist yet.
256
+ * If it exists, reminds Claude to update it if data models changed.
257
+ */
258
+ function printDataStructureInstructions() {
259
+ const root = getProjectRoot();
260
+ const dsPath = path.join(root, '.codeyam', 'data-structure.json');
261
+ const exists = fs.existsSync(dsPath);
262
+ if (!exists) {
263
+ console.log(chalk.bold('Create the data structure config:'));
264
+ checkbox('Create `.codeyam/data-structure.json` describing all data sources');
265
+ console.log(chalk.dim(" This file tells the editor what data the app uses and how it's stored."));
266
+ console.log(chalk.dim(' The file is a JSON array. Each entry describes one data source:'));
267
+ console.log(chalk.dim(' { "name": "Drink", "description": "...", "category": "datastore", "order": 1,'));
268
+ console.log(chalk.dim(' "fields": [{ "name": "id", "type": "number", "isId": true, "required": true }, ...] }'));
269
+ console.log();
270
+ console.log(chalk.dim(' category: "datastore" for persisted data (DB, localStorage)'));
271
+ console.log(chalk.dim(' "mock-api" for mocked external API responses'));
272
+ console.log(chalk.dim(' order: 1 = primary data store, 2 = secondary, etc.'));
273
+ console.log(chalk.dim(' Do NOT include types only used as component props.'));
274
+ }
275
+ else {
276
+ checkbox('If this feature changes data models, update `.codeyam/data-structure.json`');
277
+ console.log(chalk.dim(' Add new tables, update fields, or change descriptions to match.'));
278
+ }
279
+ console.log();
280
+ }
213
281
  /**
214
282
  * Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
215
283
  * Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
@@ -251,7 +319,12 @@ function printAppScenarioInstructions(pageName, route) {
251
319
  console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
252
320
  }
253
321
  else {
322
+ const appCtx = getTechStackContext(root);
254
323
  checkbox('Include data in every app scenario — without it the page will be empty:');
324
+ if (appCtx.isExpo) {
325
+ console.log(chalk.dim(' Use "localStorage":{"items":"[...]"} to pre-populate AsyncStorage (values are JSON strings)'));
326
+ console.log(chalk.dim(" AsyncStorage uses localStorage on web — CodeYam's injection works automatically."));
327
+ }
255
328
  console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
256
329
  console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
257
330
  }
@@ -326,9 +399,11 @@ function printExtractionPlanInstructions() {
326
399
  * Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
327
400
  * Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
328
401
  */
329
- function printComponentCaptureInstructions() {
402
+ function printComponentCaptureInstructions(root) {
403
+ const ctx = root ? getTechStackContext(root) : undefined;
404
+ const isExpo = ctx?.isExpo ?? false;
330
405
  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'));
406
+ console.log(chalk.dim(` This creates app/isolated-components/layout.tsx (with production ${isExpo ? '__DEV__' : 'notFound()'} guard) and`));
332
407
  console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
333
408
  checkbox('For each visual component:');
334
409
  console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
@@ -340,23 +415,37 @@ function printComponentCaptureInstructions() {
340
415
  console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
341
416
  console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
342
417
  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'));
418
+ if (isExpo) {
419
+ console.log(chalk.dim(' Expo: app/isolated-components/ComponentName.tsx → /isolated-components/ComponentName'));
420
+ console.log(chalk.dim(' Use useLocalSearchParams() from expo-router to read ?s=ScenarioName.'));
421
+ }
422
+ else {
423
+ console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
424
+ console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
425
+ }
345
426
  console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
346
427
  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'));
428
+ if (isExpo) {
429
+ console.log(chalk.dim(' Wrap the component in a capture container with nativeID="codeyam-capture":'));
430
+ console.log(chalk.dim(' <View nativeID="codeyam-capture" style={{ display:"flex" }}>'));
431
+ }
432
+ else {
433
+ console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
434
+ console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
435
+ }
436
+ console.log(chalk.dim(isExpo
437
+ ? ' <View style={{ width:"100%", maxWidth:... }}> ← match the app\'s container width'
438
+ : ' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
439
+ console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth: 384, full-width component → omit maxWidth'));
351
440
  console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
352
441
  console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
353
442
  console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
354
443
  console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
355
444
  console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
356
445
  console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
357
- console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
446
+ console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
358
447
  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/...).'));
448
+ console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/isolated-components/...).'));
360
449
  console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
361
450
  console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
362
451
  console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
@@ -366,7 +455,7 @@ function printComponentCaptureInstructions() {
366
455
  console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
367
456
  console.log();
368
457
  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"}, ...]'));
458
+ console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
370
459
  console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
371
460
  }
372
461
  /**
@@ -419,7 +508,9 @@ function stopGate(current, opts) {
419
508
  // Skip step 1 (no feature name yet — task starts at step 2)
420
509
  if (current >= 2) {
421
510
  console.log(chalk.bold.cyan('━━━ TASK ━━━'));
422
- console.log(chalk.cyan('Mark your current task (if any) as complete.'));
511
+ console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
512
+ console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
513
+ console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
423
514
  console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
424
515
  console.log();
425
516
  let taskTitle;
@@ -683,11 +774,17 @@ function printSetup(root) {
683
774
  console.log();
684
775
  // ── Design System ────────────────────────────────────────────────
685
776
  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?"'));
777
+ console.log(chalk.dim(' Ask: "What visual style do you want? Pick a built-in design system or bring your own."'));
687
778
  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"'));
779
+ console.log();
780
+ for (const ds of DESIGN_SYSTEMS) {
781
+ console.log(chalk.yellow(` Option label: "${ds.name}"`) +
782
+ chalk.dim(` — ${ds.description}`));
783
+ console.log(chalk.dim(` → Run: codeyam editor design-system ${ds.id}`));
784
+ }
785
+ console.log(chalk.yellow(' Option label: "I\'ll paste my own design system"'));
689
786
  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"'));
787
+ console.log(chalk.yellow(' Option label: "Skip use sensible defaults"'));
691
788
  console.log(chalk.dim(' → Skip'));
692
789
  console.log();
693
790
  console.log(chalk.bold('Checklist:'));
@@ -742,7 +839,7 @@ function printSetup(root) {
742
839
  console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
743
840
  console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
744
841
  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).'));
842
+ 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
843
  console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
747
844
  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
845
  console.log();
@@ -951,33 +1048,65 @@ function printStep2(root, feature) {
951
1048
  }
952
1049
  console.log('Get the project ready to build.');
953
1050
  console.log();
1051
+ const ctx = getTechStackContext(root);
954
1052
  // If no project exists yet, include scaffolding instructions first
955
1053
  if (!projectExists) {
956
1054
  console.log(chalk.bold('Scaffold the project:'));
957
1055
  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,'));
1056
+ if (ctx.isExpo) {
1057
+ console.log(chalk.dim(' This copies the Expo + React Native template, runs npm install,'));
1058
+ }
1059
+ else if (ctx.isChromeExt) {
1060
+ console.log(chalk.dim(' This copies the Chrome Extension + React template, runs npm install,'));
1061
+ }
1062
+ else {
1063
+ console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
1064
+ }
959
1065
  console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
960
1066
  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();
1067
+ if (ctx.isExpo) {
1068
+ // Expo: no database, use AsyncStorage + theme
1069
+ checkbox('Define your data types in `lib/types.ts`');
1070
+ console.log(chalk.dim(" Create TypeScript interfaces for your app's data models"));
1071
+ console.log();
1072
+ checkbox('Set up initial data using the storage helper in `lib/storage.ts`');
1073
+ console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
1074
+ console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "First item" }]);'));
1075
+ console.log();
1076
+ console.log(chalk.dim(' Read MOBILE_SETUP.md for data storage, navigation, and testing patterns.'));
1077
+ console.log();
1078
+ }
1079
+ else if (ctx.isChromeExt) {
1080
+ // Chrome Extension: use chrome.storage
1081
+ checkbox('Set up data storage using chrome.storage (with localStorage fallback for dev)');
1082
+ console.log();
1083
+ console.log(chalk.dim(' Read EXTENSION_SETUP.md for storage, messaging, and manifest patterns.'));
1084
+ console.log();
1085
+ }
1086
+ else {
1087
+ // Next.js: Prisma + database
1088
+ checkbox('Define your data models in `prisma/schema.prisma`');
1089
+ console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
1090
+ console.log();
1091
+ checkbox('Push schema and seed the database');
1092
+ console.log(chalk.dim(' npm run db:push'));
1093
+ console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
1094
+ console.log(chalk.dim(' npm run db:seed'));
1095
+ console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
1096
+ console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
1097
+ console.log();
1098
+ console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
1099
+ console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
1100
+ console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
1101
+ console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
1102
+ console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
1103
+ console.log();
1104
+ console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
1105
+ console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
1106
+ console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
1107
+ console.log();
1108
+ }
1109
+ printDataStructureInstructions();
981
1110
  }
982
1111
  else {
983
1112
  console.log(chalk.bold('Prepare the database for this feature:'));
@@ -992,6 +1121,7 @@ function printStep2(root, feature) {
992
1121
  console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
993
1122
  console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
994
1123
  console.log();
1124
+ printDataStructureInstructions();
995
1125
  }
996
1126
  stopGate(2);
997
1127
  }
@@ -1017,26 +1147,42 @@ function printStep3(root, feature) {
1017
1147
  if (isResuming) {
1018
1148
  printResumptionHeader(3);
1019
1149
  }
1150
+ const ctx = getTechStackContext(root);
1020
1151
  console.log('Build fast with real data. Prioritize speed over quality.');
1021
1152
  console.log();
1022
1153
  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'));
1154
+ if (ctx.isExpo) {
1155
+ checkbox('Build screens that read from AsyncStorage via `lib/storage.ts` or fetch from APIs');
1156
+ if (!projectExists) {
1157
+ checkbox('Populate initial data in AsyncStorage for development');
1158
+ console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
1159
+ console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "Buy groceries" }]);'));
1160
+ console.log();
1161
+ console.log(chalk.bold.cyan('Make data visually rich:'));
1162
+ console.log(chalk.cyan(' Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1163
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1164
+ console.log(chalk.cyan(' • Rich data makes the prototype look real and surfaces layout issues early'));
1165
+ }
1166
+ }
1167
+ else {
1168
+ checkbox('Create API routes that read from the database via Prisma');
1169
+ if (!projectExists) {
1170
+ checkbox('Seed the database with demo data');
1171
+ checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
1172
+ console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
1173
+ console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
1174
+ console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
1175
+ console.log();
1176
+ console.log(chalk.bold.cyan('Make seed data visually rich:'));
1177
+ console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
1178
+ console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
1179
+ console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1180
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1181
+ console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
1182
+ }
1037
1183
  }
1038
1184
  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');
1185
+ checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
1040
1186
  // Responsive design guidance when building a mobile-responsive web app
1041
1187
  if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
1042
1188
  console.log();
@@ -1053,16 +1199,29 @@ function printStep3(root, feature) {
1053
1199
  console.log();
1054
1200
  console.log(designSystem);
1055
1201
  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.'));
1202
+ if (ctx.isExpo) {
1203
+ checkbox('Define ALL design tokens in `lib/theme.ts` — this is the single source of truth');
1204
+ console.log(chalk.dim(' Colors: theme.colors.bgSurface, theme.colors.textPrimary, etc.'));
1205
+ console.log(chalk.dim(' Typography: theme.fontSize.sm, theme.fontSize.lg, theme.fontFamily.mono'));
1206
+ console.log(chalk.dim(' Spacing: theme.spacing.sm, theme.spacing.md, theme.spacing.lg, etc.'));
1207
+ console.log(chalk.dim(' Border radius: theme.borderRadius.sm, theme.borderRadius.lg, etc.'));
1208
+ checkbox('Import theme in every component — ZERO hardcoded color strings or pixel values');
1209
+ console.log(chalk.dim(' Bad: color: "#333", fontSize: 14, padding: 12'));
1210
+ console.log(chalk.dim(' Good: color: theme.colors.textPrimary, fontSize: theme.fontSize.sm, padding: theme.spacing.md'));
1211
+ console.log(chalk.dim(' Do NOT use CSS custom properties (var(--token)) they do not work in React Native.'));
1212
+ }
1213
+ else {
1214
+ checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
1215
+ console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
1216
+ console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
1217
+ console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
1218
+ console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
1219
+ console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
1220
+ checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
1221
+ console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
1222
+ console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
1223
+ console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
1224
+ }
1066
1225
  }
1067
1226
  console.log();
1068
1227
  console.log(chalk.bold.cyan('Keep the preview moving:'));
@@ -1099,14 +1258,13 @@ function printStep4(root, feature) {
1099
1258
  console.log('Verify everything works before presenting the prototype.');
1100
1259
  console.log();
1101
1260
  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'));
1261
+ console.log(chalk.dim(' # Verify pages and API routes load:'));
1262
+ console.log(chalk.dim(` codeyam editor verify-routes '{"paths":["/your-page"],"apiRoutes":["/api/your-route"]}'`));
1105
1263
  console.log();
1106
1264
  console.log(chalk.bold('Verify before proceeding:'));
1107
1265
  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');
1266
+ checkbox('Verify page and API routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
1267
+ console.log(chalk.dim(' Include ALL page paths you built and ALL API routes they depend on.'));
1110
1268
  checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
1111
1269
  console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
1112
1270
  console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
@@ -1117,6 +1275,14 @@ function printStep4(root, feature) {
1117
1275
  console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
1118
1276
  console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
1119
1277
  console.log();
1278
+ const ctx4 = getTechStackContext(root);
1279
+ if (ctx4.isExpo) {
1280
+ console.log(chalk.magenta.bold(' EXPO WEB PREVIEW NOTE:'));
1281
+ console.log(chalk.magenta(' The preview renders via react-native-web in a browser. Some differences'));
1282
+ console.log(chalk.magenta(' from native devices are expected (fonts, SafeAreaView, shadows, Platform.OS).'));
1283
+ console.log(chalk.magenta(' The preview is for layout and data verification. Test final polish on device.'));
1284
+ console.log();
1285
+ }
1120
1286
  console.log(chalk.bold('Update README and setup script:'));
1121
1287
  checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
1122
1288
  checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
@@ -1149,7 +1315,7 @@ function printStep5(root, feature) {
1149
1315
  console.log();
1150
1316
  console.log(chalk.bold('Before presenting — verify everything works:'));
1151
1317
  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)');
1318
+ checkbox('Verify routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
1153
1319
  console.log();
1154
1320
  console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
1155
1321
  checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
@@ -1229,6 +1395,7 @@ function printStep7(root, feature) {
1229
1395
  if (isResuming) {
1230
1396
  printResumptionHeader(7);
1231
1397
  }
1398
+ const ctx = getTechStackContext(root);
1232
1399
  console.log('Execute your extraction plan from step 6.');
1233
1400
  console.log();
1234
1401
  console.log(chalk.bold('Components:'));
@@ -1242,18 +1409,23 @@ function printStep7(root, feature) {
1242
1409
  console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
1243
1410
  console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
1244
1411
  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`');
1412
+ if (ctx.isExpo) {
1413
+ 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`');
1414
+ }
1415
+ else {
1416
+ checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
1417
+ }
1246
1418
  console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
1247
1419
  console.log();
1248
1420
  console.log(chalk.bold('Recursive pass:'));
1249
1421
  checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
1250
1422
  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>?'));
1423
+ console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
1252
1424
  console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
1253
1425
  console.log();
1254
1426
  console.log(chalk.bold('Verify before proceeding:'));
1255
1427
  checkbox('Run all tests and verify they pass');
1256
- checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
1428
+ checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
1257
1429
  checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
1258
1430
  checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1259
1431
  printDimensionGuidance(dim, dimNames);
@@ -1314,11 +1486,12 @@ function printStep9(root, feature) {
1314
1486
  console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
1315
1487
  console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
1316
1488
  console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
1317
- printComponentCaptureInstructions();
1489
+ const ctx9 = getTechStackContext(root);
1490
+ printComponentCaptureInstructions(root);
1318
1491
  console.log();
1319
1492
  console.log(chalk.bold('Library Functions — run tests:'));
1320
1493
  checkbox('Run ALL test files created in step 7');
1321
- console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
1494
+ console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
1322
1495
  checkbox('Verify every test passes');
1323
1496
  checkbox('If any test fails, fix the source code and re-run');
1324
1497
  console.log();
@@ -1326,7 +1499,13 @@ function printStep9(root, feature) {
1326
1499
  console.log();
1327
1500
  checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
1328
1501
  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.'));
1502
+ console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
1503
+ console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
1504
+ console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
1505
+ console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
1506
+ console.log(chalk.dim(' If the audit reports "MANUAL ANALYSIS REQUIRED" for any entities,'));
1507
+ console.log(chalk.dim(' follow the manual analysis steps in the audit output.'));
1508
+ console.log(chalk.dim(' Do NOT re-run analyze-imports for those entities — it will fail again.'));
1330
1509
  console.log();
1331
1510
  stopGate(9);
1332
1511
  }
@@ -1793,7 +1972,7 @@ function printMigrateStep3(root) {
1793
1972
  console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
1794
1973
  console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
1795
1974
  console.log();
1796
- printComponentCaptureInstructions();
1975
+ printComponentCaptureInstructions(root);
1797
1976
  console.log();
1798
1977
  migrationStopGate(3, pageName, pageIndex, totalPages);
1799
1978
  }
@@ -1928,7 +2107,7 @@ function printMigrateStep8(root) {
1928
2107
  console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
1929
2108
  console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
1930
2109
  checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
1931
- checkbox('Find all scenarios that use the real page route (not /codeyam-isolate/ paths)');
2110
+ checkbox('Find all scenarios that use the real page route (not /isolated-components/ paths)');
1932
2111
  checkbox('Re-register each one with the SAME name to update its screenshot');
1933
2112
  console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
1934
2113
  checkbox('After each registration, check the response for `clientErrors`');
@@ -1936,10 +2115,10 @@ function printMigrateStep8(root) {
1936
2115
  console.log();
1937
2116
  console.log(chalk.bold('Component Scenarios:'));
1938
2117
  console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
1939
- printComponentCaptureInstructions();
2118
+ printComponentCaptureInstructions(root);
1940
2119
  console.log();
1941
2120
  console.log(chalk.bold('Verify:'));
1942
- checkbox('Run `codeyam editor analyze-imports` to populate import graph');
2121
+ checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
1943
2122
  checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
1944
2123
  checkbox('Run `codeyam editor audit` to check completeness');
1945
2124
  checkbox('Check for client errors: `codeyam editor client-errors`');
@@ -2317,10 +2496,14 @@ async function handleAnalyzeImports(options = {}) {
2317
2496
  glossaryEntries = sanitizeGlossaryEntries(parsed);
2318
2497
  }
2319
2498
  catch {
2499
+ if (options.silent)
2500
+ return;
2320
2501
  console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
2321
2502
  process.exit(1);
2322
2503
  }
2323
2504
  if (glossaryEntries.length === 0) {
2505
+ if (options.silent)
2506
+ return;
2324
2507
  console.error(chalk.red('Error: glossary.json is empty.'));
2325
2508
  process.exit(1);
2326
2509
  }
@@ -2362,25 +2545,137 @@ async function handleAnalyzeImports(options = {}) {
2362
2545
  // Non-fatal — scenario file paths just won't be included
2363
2546
  }
2364
2547
  const progress = new ProgressReporter();
2365
- // Run data-structure-only analysis for all entities (glossary + pages)
2548
+ // Filter to only files that actually need analysis avoids re-analyzing
2549
+ // ~120 files when only 2 are incomplete.
2550
+ let targetFilePaths = filePaths;
2551
+ try {
2552
+ const { getDatabase } = await import('../../../packages/database/index.js');
2553
+ const db = getDatabase();
2554
+ const { requireBranchAndProject: rbp } = await import('../utils/database.js');
2555
+ const { project } = await rbp(JSON.parse(fs.readFileSync(path.join(root, '.codeyam', 'config.json'), 'utf8')).projectSlug);
2556
+ const { filterToIncompleteFilePaths } = await import('../utils/editorAudit.js');
2557
+ const incomplete = await filterToIncompleteFilePaths(db, project.id, filePaths);
2558
+ if (incomplete.length < filePaths.length && incomplete.length > 0) {
2559
+ if (!options.silent) {
2560
+ console.log(chalk.dim(`Skipping ${filePaths.length - incomplete.length} already-analyzed files, analyzing ${incomplete.length} remaining...`));
2561
+ }
2562
+ targetFilePaths = incomplete;
2563
+ }
2564
+ else if (incomplete.length === 0) {
2565
+ if (!options.silent) {
2566
+ console.log(chalk.green('All entities already have analyses — nothing to do.'));
2567
+ }
2568
+ // Still need to run the import graph + backfill below
2569
+ targetFilePaths = [];
2570
+ }
2571
+ }
2572
+ catch {
2573
+ // Non-fatal — fall back to analyzing all files
2574
+ }
2575
+ // If specific file paths were requested, scope analysis to only those files.
2576
+ // The full filePaths set is still used for the import graph below.
2577
+ if (options.filePaths && options.filePaths.length > 0) {
2578
+ const requested = new Set(options.filePaths);
2579
+ targetFilePaths = targetFilePaths.filter((fp) => requested.has(fp));
2580
+ if (targetFilePaths.length === 0 && !options.silent) {
2581
+ console.log(chalk.dim('Requested file(s) already analyzed or not in glossary — skipping analysis.'));
2582
+ }
2583
+ }
2584
+ // Run data-structure-only analysis for entities that need it.
2366
2585
  // Don't pass entityNames — entities may not exist yet (fresh clone).
2367
2586
  // 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
- });
2587
+ if (targetFilePaths.length > 0) {
2588
+ progress.start(`Running import analysis for ${targetFilePaths.length} entit${targetFilePaths.length !== 1 ? 'ies' : 'y'}...`);
2589
+ try {
2590
+ await runAnalysisForEntities({
2591
+ projectRoot: root,
2592
+ filePaths: targetFilePaths,
2593
+ progress,
2594
+ onlyDataStructure: true,
2595
+ });
2596
+ }
2597
+ catch (err) {
2598
+ progress.fail('Analysis failed');
2599
+ const msg = err instanceof Error ? err.message : String(err);
2600
+ if (options.silent) {
2601
+ // Internal caller — don't kill the process, let the caller handle it
2602
+ return;
2603
+ }
2604
+ console.error(chalk.red(`Error: ${msg}`));
2605
+ process.exit(1);
2606
+ }
2607
+ progress.succeed('Analysis complete');
2376
2608
  }
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);
2609
+ // Surface analysis errors if any occurred
2610
+ const errorReportPath = path.join(root, '.codeyam', 'analysis-errors.txt');
2611
+ if (fs.existsSync(errorReportPath)) {
2612
+ if (!options.silent) {
2613
+ console.log(chalk.yellow('\nSome entity analyses failed. Full details in .codeyam/analysis-errors.txt'));
2614
+ console.log(chalk.yellow('Send this file to the CodeYam team for diagnosis.'));
2615
+ console.log(chalk.dim('Affected entities will be missing from the import graph.'));
2616
+ }
2617
+ // Write structured analysis-failures.json for the audit to detect.
2618
+ // Parse the error report to find which entities failed, and cross-reference
2619
+ // with the files we tried to analyze.
2620
+ try {
2621
+ const { readAnalysisFailures, writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
2622
+ const errorContent = fs.readFileSync(errorReportPath, 'utf8');
2623
+ const existingFailures = readAnalysisFailures(root);
2624
+ // Parse error messages to identify failed file paths.
2625
+ // Error format: "Error on step <StepName> for entity <Name> (<filePath>)"
2626
+ // or more generic "CodeYam Error: <message>"
2627
+ const entityErrorRegex = /(?:Error on step \w+ for entity (\w+))|(?:entity[: ]+["']?(\w+)["']?)/gi;
2628
+ const failedEntityNames = new Set();
2629
+ let match;
2630
+ while ((match = entityErrorRegex.exec(errorContent)) !== null) {
2631
+ const name = match[1] || match[2];
2632
+ if (name)
2633
+ failedEntityNames.add(name);
2634
+ }
2635
+ // Map failed entity names back to file paths from targetFilePaths
2636
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
2637
+ let glossary = [];
2638
+ try {
2639
+ glossary = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
2640
+ }
2641
+ catch {
2642
+ // Non-fatal
2643
+ }
2644
+ // Also: any targetFilePaths that didn't produce entities are failures
2645
+ const now = new Date().toISOString();
2646
+ const updatedFailures = { ...existingFailures };
2647
+ if (failedEntityNames.size > 0) {
2648
+ for (const name of failedEntityNames) {
2649
+ const entry = glossary.find((e) => e.name === name);
2650
+ if (entry) {
2651
+ updatedFailures[entry.filePath] = {
2652
+ entityName: name,
2653
+ error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
2654
+ failedAt: now,
2655
+ };
2656
+ }
2657
+ }
2658
+ }
2659
+ // When we can't parse specific entity names from the error, DON'T mark
2660
+ // all target files as failed. The error may be non-fatal (e.g., a cache
2661
+ // miss logged as "CodeYam Error") and blanket-marking every file as
2662
+ // permanently failed blocks future audits from resolving them.
2663
+ writeAnalysisFailures(root, updatedFailures);
2664
+ }
2665
+ catch {
2666
+ // Non-fatal — failure tracking is best-effort
2667
+ }
2668
+ }
2669
+ else {
2670
+ // No errors — clear any stale failure tracking
2671
+ try {
2672
+ const { writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
2673
+ writeAnalysisFailures(root, {});
2674
+ }
2675
+ catch {
2676
+ // Non-fatal
2677
+ }
2382
2678
  }
2383
- progress.succeed('Analysis complete');
2384
2679
  // Load entities WITH metadata from database
2385
2680
  progress.start('Loading entity metadata...');
2386
2681
  await initializeEnvironment();
@@ -2563,43 +2858,87 @@ async function handleDelete(scenarioId) {
2563
2858
  * Creates isolation route directories and the layout guard file.
2564
2859
  * This avoids brace-expansion permission prompts in the embedded terminal.
2565
2860
  */
2861
+ function handleDesignSystem(designSystemId) {
2862
+ const root = process.cwd();
2863
+ const ds = DESIGN_SYSTEMS.find((d) => d.id === designSystemId);
2864
+ if (!ds) {
2865
+ console.error(chalk.red(`Error: Unknown design system "${designSystemId}". Valid options: ${DESIGN_SYSTEMS.map((d) => d.id).join(', ')}`));
2866
+ process.exit(1);
2867
+ }
2868
+ const srcPath = path.join(__dirname, '..', '..', 'templates', 'design-systems', ds.fileName);
2869
+ if (!fs.existsSync(srcPath)) {
2870
+ console.error(chalk.red(`Error: Design system file not found: ${srcPath}`));
2871
+ process.exit(1);
2872
+ }
2873
+ const destDir = path.join(root, '.codeyam');
2874
+ fs.mkdirSync(destDir, { recursive: true });
2875
+ const destPath = path.join(destDir, 'design-system.md');
2876
+ fs.copyFileSync(srcPath, destPath);
2877
+ console.log(chalk.green(`Installed "${ds.name}" design system → .codeyam/design-system.md`));
2878
+ }
2566
2879
  function handleIsolate(componentNames) {
2567
2880
  const root = process.cwd();
2881
+ const ctx = getTechStackContext(root);
2568
2882
  if (componentNames.length === 0) {
2569
2883
  console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
2570
2884
  process.exit(1);
2571
2885
  }
2572
2886
  const isolateDir = path.join(root, 'app', 'isolated-components');
2573
2887
  // Create layout.tsx with production guard if missing
2574
- const layoutPath = path.join(isolateDir, 'layout.tsx');
2888
+ const layoutPath = path.join(isolateDir, ctx.isExpo ? '_layout.tsx' : 'layout.tsx');
2575
2889
  if (!fs.existsSync(layoutPath)) {
2576
2890
  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
2891
+ if (ctx.isExpo) {
2892
+ fs.writeFileSync(layoutPath, [
2893
+ 'import { Redirect, Slot } from "expo-router";',
2894
+ '',
2895
+ 'export default function CaptureLayout() {',
2896
+ ' if (!__DEV__) return <Redirect href="/" />;',
2897
+ ' return <Slot />;',
2898
+ '}',
2899
+ '',
2900
+ ].join('\n'), 'utf8');
2901
+ }
2902
+ else {
2903
+ fs.writeFileSync(layoutPath, [
2904
+ 'import { notFound } from "next/navigation";',
2905
+ '',
2906
+ 'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
2907
+ ' if (process.env.NODE_ENV === "production") notFound();',
2908
+ ' return <>{children}</>;',
2909
+ '}',
2910
+ '',
2911
+ ].join('\n'), 'utf8');
2912
+ }
2913
+ console.log(chalk.green(`Created layout guard: app/isolated-components/${ctx.isExpo ? '_layout.tsx' : 'layout.tsx'}`));
2914
+ }
2915
+ // Create isolation route for each component.
2916
+ // Expo Router uses flat files (ComponentName.tsx), Next.js uses subdirectories (ComponentName/page.tsx).
2589
2917
  const created = [];
2590
2918
  const existed = [];
2591
2919
  for (const name of componentNames) {
2592
- const dir = path.join(isolateDir, name);
2593
- if (fs.existsSync(dir)) {
2594
- existed.push(name);
2920
+ if (ctx.isExpo) {
2921
+ const filePath = path.join(isolateDir, `${name}.tsx`);
2922
+ if (fs.existsSync(filePath)) {
2923
+ existed.push(name);
2924
+ }
2925
+ else {
2926
+ created.push(name);
2927
+ }
2595
2928
  }
2596
2929
  else {
2597
- fs.mkdirSync(dir, { recursive: true });
2598
- created.push(name);
2930
+ const dir = path.join(isolateDir, name);
2931
+ if (fs.existsSync(dir)) {
2932
+ existed.push(name);
2933
+ }
2934
+ else {
2935
+ fs.mkdirSync(dir, { recursive: true });
2936
+ created.push(name);
2937
+ }
2599
2938
  }
2600
2939
  }
2601
2940
  if (created.length > 0) {
2602
- console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
2941
+ console.log(chalk.green(`Created ${created.length} isolation route(s): ${created.join(', ')}`));
2603
2942
  }
2604
2943
  if (existed.length > 0) {
2605
2944
  console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
@@ -2653,6 +2992,12 @@ function formatApiSubcommandResult(subcommand, data) {
2653
2992
  parts.push(`scenarioId="${data.scenarioId}"`);
2654
2993
  if (data.sessionsNotified != null)
2655
2994
  parts.push(`notified=${data.sessionsNotified}`);
2995
+ // Surface the HTTP health check from the dev server
2996
+ if (data.preview) {
2997
+ parts.push(`healthy=${data.preview.healthy}`);
2998
+ if (data.preview.error)
2999
+ parts.push(`error="${data.preview.error}"`);
3000
+ }
2656
3001
  return parts.join(' ');
2657
3002
  }
2658
3003
  case 'dev-server': {
@@ -2702,6 +3047,34 @@ function formatApiSubcommandResult(subcommand, data) {
2702
3047
  }
2703
3048
  return parts.join(' ');
2704
3049
  }
3050
+ case 'verify-routes': {
3051
+ const lines = [];
3052
+ lines.push(chalk.bold.yellow('━━━ Route Verification ━━━'));
3053
+ for (const [route, info] of Object.entries(data.routes || {})) {
3054
+ const r = info;
3055
+ if (r.ok) {
3056
+ lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}`));
3057
+ }
3058
+ else {
3059
+ lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
3060
+ }
3061
+ }
3062
+ for (const [route, info] of Object.entries(data.apiRoutes || {})) {
3063
+ const r = info;
3064
+ if (r.ok && r.isJSON) {
3065
+ lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}, valid JSON`));
3066
+ }
3067
+ else if (r.ok) {
3068
+ lines.push(chalk.yellow(` ⚠ ${route} — HTTP ${r.status}, not valid JSON`));
3069
+ }
3070
+ else {
3071
+ lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
3072
+ }
3073
+ }
3074
+ lines.push('');
3075
+ lines.push(data.ok ? chalk.green(data.summary) : chalk.red(data.summary));
3076
+ return lines.join('\n');
3077
+ }
2705
3078
  default:
2706
3079
  return null; // journal-list, show/hide-results: keep full JSON
2707
3080
  }
@@ -2736,6 +3109,8 @@ async function handleRegister(jsonArg) {
2736
3109
  const isBatch = items.length > 1;
2737
3110
  let succeeded = 0;
2738
3111
  let failed = 0;
3112
+ let warnings = 0;
3113
+ const issues = [];
2739
3114
  const screenshotEntries = [];
2740
3115
  for (let i = 0; i < items.length; i++) {
2741
3116
  const body = items[i];
@@ -2805,11 +3180,14 @@ async function handleRegister(jsonArg) {
2805
3180
  }
2806
3181
  console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
2807
3182
  }
3183
+ const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
2808
3184
  if (!res.ok) {
2809
3185
  console.error(chalk.dim(JSON.stringify(data, null, 2)));
2810
3186
  failed++;
2811
3187
  }
2812
3188
  else {
3189
+ if (resultIssues.length > 0)
3190
+ warnings++;
2813
3191
  succeeded++;
2814
3192
  // Collect screenshot paths for duplicate detection
2815
3193
  if (data.screenshotCaptured &&
@@ -2822,6 +3200,10 @@ async function handleRegister(jsonArg) {
2822
3200
  });
2823
3201
  }
2824
3202
  }
3203
+ // Collect issues from this registration
3204
+ for (const issue of resultIssues) {
3205
+ issues.push(`"${issue.scenarioName}": ${issue.message}`);
3206
+ }
2825
3207
  }
2826
3208
  catch (error) {
2827
3209
  const msg = error instanceof Error ? error.message : String(error);
@@ -2833,7 +3215,7 @@ async function handleRegister(jsonArg) {
2833
3215
  }
2834
3216
  // Detect duplicate screenshots in the batch
2835
3217
  if (isBatch && screenshotEntries.length > 1) {
2836
- const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash');
3218
+ const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
2837
3219
  const hashEntries = screenshotEntries
2838
3220
  .map((e) => {
2839
3221
  const hash = computeScreenshotHash(e.screenshotAbsPath);
@@ -2859,9 +3241,22 @@ async function handleRegister(jsonArg) {
2859
3241
  }
2860
3242
  }
2861
3243
  if (isBatch) {
2862
- console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
3244
+ console.log('');
3245
+ if (failed > 0 || warnings > 0) {
3246
+ console.log(chalk.red.bold(`ERROR: ${failed} failed, ${warnings} with warnings out of ${items.length} scenarios`));
3247
+ console.log('');
3248
+ console.log(chalk.red.bold('Issues that MUST be fixed:'));
3249
+ for (const issue of issues) {
3250
+ console.log(chalk.red(` ✗ ${issue}`));
3251
+ }
3252
+ console.log('');
3253
+ console.log(chalk.red('Do NOT skip these errors. Fix each issue and re-register the affected scenarios.'));
3254
+ }
3255
+ else {
3256
+ console.log(chalk.green(`✓ Batch complete: ${succeeded}/${items.length} succeeded with no issues`));
3257
+ }
2863
3258
  }
2864
- if (failed > 0) {
3259
+ if (failed > 0 || warnings > 0) {
2865
3260
  process.exit(1);
2866
3261
  }
2867
3262
  }
@@ -3150,10 +3545,11 @@ function handleChange(feature) {
3150
3545
  * Fetch the audit result from the server.
3151
3546
  * Returns null if the server is unreachable.
3152
3547
  */
3153
- async function fetchAuditResult() {
3548
+ async function fetchAuditResult(options) {
3154
3549
  const port = getServerPort();
3550
+ const params = options?.skipTests ? '?skipTests=true' : '';
3155
3551
  try {
3156
- const res = await fetch(`http://localhost:${port}/api/editor-audit`);
3552
+ const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
3157
3553
  if (!res.ok)
3158
3554
  return null;
3159
3555
  return await res.json();
@@ -3176,17 +3572,15 @@ async function checkAuditGate() {
3176
3572
  return true; // Server unreachable — don't block
3177
3573
  if (data.summary?.allPassing === true)
3178
3574
  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
3575
+ // Lightweight auto-fix: backfill entity_sha (DB-only, fast).
3576
+ // Never run handleAnalyzeImports here it takes minutes on large projects.
3577
+ const { isOnlyIncompleteEntities, isOnlyPreExistingIncomplete } = await import('../utils/editorAudit.js');
3578
+ // If the only failures are pre-existing incomplete entities (not caused by
3579
+ // this session), don't block — the audit text already calls these "non-blocking".
3580
+ if (isOnlyPreExistingIncomplete(data.summary, data.incompleteEntities)) {
3581
+ return true;
3582
+ }
3583
+ if (isOnlyIncompleteEntities(data.summary)) {
3190
3584
  try {
3191
3585
  const entities = await loadEntities({});
3192
3586
  if (entities && entities.length > 0) {
@@ -3204,8 +3598,8 @@ async function checkAuditGate() {
3204
3598
  catch {
3205
3599
  // Fall through
3206
3600
  }
3207
- // Re-check after fix
3208
- const retry = await fetchAuditResult();
3601
+ // Re-check after backfill — skip re-running tests (backfill is DB-only)
3602
+ const retry = await fetchAuditResult({ skipTests: true });
3209
3603
  if (retry?.summary?.allPassing === true)
3210
3604
  return true;
3211
3605
  }
@@ -3228,6 +3622,8 @@ function printAuditGateFailures(data) {
3228
3622
  const missing = (data.components || []).filter((c) => c.status === 'missing');
3229
3623
  for (const c of missing) {
3230
3624
  issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
3625
+ if (c.hint)
3626
+ issues.push(` Fix: ${c.hint}`);
3231
3627
  }
3232
3628
  }
3233
3629
  if (s.componentsWithErrors > 0) {
@@ -3241,7 +3637,15 @@ function printAuditGateFailures(data) {
3241
3637
  issues.push(`${s.functionsMissing} function(s) missing test files`);
3242
3638
  const missing = (data.functions || []).filter((f) => f.status === 'missing');
3243
3639
  for (const f of missing) {
3244
- issues.push(` → ${f.name}${f.filePath ? ` (${f.filePath})` : ''}`);
3640
+ if (f.testFile) {
3641
+ issues.push(` → ${f.name} — test file missing: ${f.testFile}`);
3642
+ }
3643
+ else {
3644
+ issues.push(` → ${f.name} (${f.filePath}) — no testFile in glossary`);
3645
+ if (f.suggestedTestFile) {
3646
+ issues.push(` Fix: Create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to .codeyam/glossary.json`);
3647
+ }
3648
+ }
3245
3649
  }
3246
3650
  }
3247
3651
  if (s.functionsFailing > 0) {
@@ -3256,6 +3660,10 @@ function printAuditGateFailures(data) {
3256
3660
  const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
3257
3661
  for (const f of runnerErrors) {
3258
3662
  issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3663
+ if (f.hint)
3664
+ issues.push(` ${f.hint}`);
3665
+ else if (f.errorMessage)
3666
+ issues.push(` Error: ${f.errorMessage}`);
3259
3667
  }
3260
3668
  }
3261
3669
  if (s.functionsNameMismatch > 0) {
@@ -3263,13 +3671,44 @@ function printAuditGateFailures(data) {
3263
3671
  const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
3264
3672
  for (const f of mismatch) {
3265
3673
  issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3674
+ if (f.hint)
3675
+ issues.push(` Fix: ${f.hint}`);
3266
3676
  }
3267
3677
  }
3268
- if (s.missingFromGlossary > 0)
3678
+ if (s.missingFromGlossary > 0) {
3269
3679
  issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
3680
+ const missingGlossary = data.missingFromGlossary || [];
3681
+ for (const m of missingGlossary) {
3682
+ issues.push(` → ${m.name} (${m.filePath})`);
3683
+ }
3684
+ const missingPaths = missingGlossary
3685
+ .map((m) => m.filePath)
3686
+ .filter(Boolean);
3687
+ if (missingPaths.length > 0) {
3688
+ issues.push(` Fix: Run \`codeyam editor analyze-imports ${missingPaths.join(' ')}\``);
3689
+ }
3690
+ else {
3691
+ issues.push(` Fix: Run \`codeyam editor analyze-imports\` to add these files to the glossary`);
3692
+ }
3693
+ }
3270
3694
  if (s.incompleteEntities > 0) {
3695
+ // Check for persistent analysis failures
3696
+ let analysisFailures = {};
3697
+ try {
3698
+ const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
3699
+ if (fs.existsSync(failuresPath)) {
3700
+ analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
3701
+ }
3702
+ }
3703
+ catch {
3704
+ // Non-fatal
3705
+ }
3271
3706
  const preCount = s.preExistingIncompleteEntities || 0;
3272
- if (preCount > 0 && preCount === s.incompleteEntities) {
3707
+ const hasFailures = Object.keys(analysisFailures).length > 0;
3708
+ if (hasFailures) {
3709
+ issues.push(`${s.incompleteEntities} entity/entities — automated analysis failed, manual analysis required`);
3710
+ }
3711
+ else if (preCount > 0 && preCount === s.incompleteEntities) {
3273
3712
  issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
3274
3713
  }
3275
3714
  else if (preCount > 0) {
@@ -3278,18 +3717,52 @@ function printAuditGateFailures(data) {
3278
3717
  else {
3279
3718
  issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
3280
3719
  }
3720
+ const incomplete = data.incompleteEntities || [];
3721
+ for (const e of incomplete) {
3722
+ const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
3723
+ if (failureEntry) {
3724
+ issues.push(` → ${e.name} — MANUAL ANALYSIS REQUIRED (automated analysis failed)`);
3725
+ issues.push(` Run \`codeyam editor audit\` for step-by-step manual analysis instructions`);
3726
+ }
3727
+ else {
3728
+ issues.push(` → ${e.name} (${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''})${e.preExisting ? ' [pre-existing]' : ''}`);
3729
+ }
3730
+ }
3731
+ if (!hasFailures) {
3732
+ issues.push(` Fix: Run \`codeyam editor analyze-imports\` to build import graphs`);
3733
+ }
3281
3734
  }
3282
3735
  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
3736
  const unassociated = data.unassociatedScenarios || [];
3737
+ const unassocPaths = unassociated
3738
+ .map((u) => u.filePath)
3739
+ .filter(Boolean);
3740
+ if (unassocPaths.length > 0) {
3741
+ issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports ${unassocPaths.join(' ')}\` then re-run audit`);
3742
+ }
3743
+ else {
3744
+ issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
3745
+ }
3285
3746
  for (const u of unassociated) {
3286
3747
  issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
3287
3748
  }
3288
3749
  }
3289
- if (s.miscategorizedScenarios > 0)
3750
+ if (s.miscategorizedScenarios > 0) {
3290
3751
  issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
3291
- if (s.scenariosNeedingRecapture > 0)
3752
+ const miscategorized = data.miscategorizedScenarios || [];
3753
+ for (const m of miscategorized) {
3754
+ issues.push(` → ${m.componentName} at ${m.url} (${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''})`);
3755
+ }
3756
+ issues.push(` Fix: Re-register these as app-level scenarios (with pageFilePath, no componentName) or use isolation routes`);
3757
+ }
3758
+ if (s.scenariosNeedingRecapture > 0) {
3292
3759
  issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
3760
+ const recapture = data.scenariosNeedingRecapture || [];
3761
+ for (const r of recapture) {
3762
+ issues.push(` → "${r.scenarioName}" (entity: ${r.entityName}, ${r.status?.status || 'changed'})`);
3763
+ }
3764
+ issues.push(` Fix: Re-capture affected scenarios to update screenshots after code changes`);
3765
+ }
3293
3766
  if (issues.length > 0) {
3294
3767
  console.error(chalk.yellow('\nAudit failures:'));
3295
3768
  for (const issue of issues) {
@@ -3324,28 +3797,22 @@ function printAuditGateFailures(data) {
3324
3797
  * which glossary components have registered scenarios and which functions
3325
3798
  * have test files. Exits with code 1 if anything is missing.
3326
3799
  */
3327
- async function handleAudit() {
3800
+ async function handleAudit(options) {
3328
3801
  let data = await fetchAuditResult();
3329
3802
  if (!data) {
3330
3803
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3331
3804
  process.exit(1);
3332
3805
  }
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.
3806
+ // Two-phase auto-fix for entity associations:
3807
+ // Phase 1: DB-only backfill fast, matches existing entities to scenarios.
3808
+ // Phase 2: If backfill fails, targeted analyze-imports for only the
3809
+ // specific unresolved files (1-3 files, not the full glossary scan).
3337
3810
  const incompleteBeforeFix = data.incompleteEntities || [];
3338
3811
  const unassociatedBeforeFix = data.unassociatedScenarios || [];
3339
3812
  let autoRemediationFailed = false;
3340
3813
  if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
3341
3814
  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
- }
3815
+ console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
3349
3816
  // Backfill entity_sha on scenarios that were registered before entities existed
3350
3817
  try {
3351
3818
  const entities = await loadEntities({});
@@ -3364,20 +3831,65 @@ async function handleAudit() {
3364
3831
  catch {
3365
3832
  // Fall through — re-fetch will show remaining issues
3366
3833
  }
3367
- // Re-fetch audit results after the fix
3368
- data = await fetchAuditResult();
3834
+ // Re-fetch audit results after the backfill — skip re-running tests
3835
+ // since they haven't changed (backfill is DB-only).
3836
+ data = await fetchAuditResult({ skipTests: true });
3369
3837
  if (!data) {
3370
- console.error(chalk.red('Error: Could not reach the CodeYam server after auto-fix.'));
3838
+ console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
3371
3839
  process.exit(1);
3372
3840
  }
3373
- // If issues persist after auto-fix, flag it so we show clear guidance
3841
+ // If issues persist after DB-only backfill, run targeted analyze-imports
3842
+ // for ONLY the specific files that need entities. This is fast (1-3 files)
3843
+ // unlike full analyze-imports which scans all glossary entries.
3844
+ const incompleteAfterBackfill = data.incompleteEntities || [];
3845
+ const unassociatedAfterBackfill = data.unassociatedScenarios || [];
3846
+ if (incompleteAfterBackfill.length > 0 ||
3847
+ unassociatedAfterBackfill.length > 0) {
3848
+ const { determineTargetedAnalysisPaths } = await import('../utils/editorAudit.js');
3849
+ const targetPaths = determineTargetedAnalysisPaths({
3850
+ unassociatedScenarios: unassociatedAfterBackfill,
3851
+ incompleteEntities: incompleteAfterBackfill,
3852
+ });
3853
+ if (targetPaths.length > 0) {
3854
+ console.log(chalk.dim(`Running targeted analysis for ${targetPaths.length} file${targetPaths.length !== 1 ? 's' : ''}...`));
3855
+ try {
3856
+ await handleAnalyzeImports({
3857
+ silent: true,
3858
+ filePaths: targetPaths,
3859
+ });
3860
+ // Retry backfill after analysis created entities
3861
+ const entities = await loadEntities({});
3862
+ if (entities && entities.length > 0) {
3863
+ const { getDatabase: getDb } = await import('../../../packages/database/index.js');
3864
+ const freshDb = getDb();
3865
+ await backfillEntityShaOnScenarios(freshDb, entities.map((e) => ({
3866
+ sha: e.sha,
3867
+ name: e.name,
3868
+ filePath: e.filePath || '',
3869
+ isDefaultExport: e.metadata?.notExported === false &&
3870
+ e.metadata?.namedExport === false,
3871
+ })));
3872
+ }
3873
+ // Re-fetch audit results after targeted analysis
3874
+ data = await fetchAuditResult({ skipTests: true });
3875
+ if (!data) {
3876
+ console.error(chalk.red('Error: Could not reach the CodeYam server after analysis.'));
3877
+ process.exit(1);
3878
+ }
3879
+ }
3880
+ catch {
3881
+ // Targeted analysis failed — fall through to show remaining issues
3882
+ }
3883
+ }
3884
+ }
3885
+ // Check if issues persist after all remediation attempts
3374
3886
  const incompleteAfterFix = data.incompleteEntities || [];
3375
3887
  const unassociatedAfterFix = data.unassociatedScenarios || [];
3376
3888
  if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
3377
3889
  autoRemediationFailed = true;
3378
3890
  }
3379
3891
  }
3380
- const { components, functions, summary } = data;
3892
+ let { components, functions, summary } = data;
3381
3893
  console.log();
3382
3894
  console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
3383
3895
  console.log();
@@ -3407,6 +3919,9 @@ async function handleAudit() {
3407
3919
  }
3408
3920
  else {
3409
3921
  detail = chalk.red(' — no scenarios registered');
3922
+ if (c.hint) {
3923
+ detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
3924
+ }
3410
3925
  }
3411
3926
  // Show file path for failing components always, for OK only when name is ambiguous
3412
3927
  const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
@@ -3450,9 +3965,16 @@ async function handleAudit() {
3450
3965
  break;
3451
3966
  case 'missing':
3452
3967
  default:
3453
- detail = f.testFile
3454
- ? chalk.red(` — test file missing: ${f.testFile}`)
3455
- : chalk.red(' — no test file specified');
3968
+ if (f.testFile) {
3969
+ detail = chalk.red(` — test file missing: ${f.testFile}`);
3970
+ }
3971
+ else {
3972
+ detail = chalk.red(` — no test file specified in glossary`);
3973
+ detail += chalk.dim(` (source: ${f.filePath})`);
3974
+ if (f.suggestedTestFile) {
3975
+ detail += `\n ${chalk.yellow(`Fix: Either create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to this entry in .codeyam/glossary.json`)}`;
3976
+ }
3977
+ }
3456
3978
  break;
3457
3979
  }
3458
3980
  console.log(` ${icon} ${f.name}${detail}`);
@@ -3466,22 +3988,53 @@ async function handleAudit() {
3466
3988
  for (const m of missingFromGlossary) {
3467
3989
  console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
3468
3990
  }
3469
- console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
3991
+ const mgPaths = missingFromGlossary
3992
+ .map((m) => m.filePath)
3993
+ .filter(Boolean);
3994
+ if (mgPaths.length > 0) {
3995
+ console.log(chalk.yellow(` Add these to .codeyam/glossary.json and run \`codeyam editor analyze-imports ${mgPaths.join(' ')}\``));
3996
+ }
3997
+ else {
3998
+ console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
3999
+ }
3470
4000
  console.log();
3471
4001
  }
3472
- // Incomplete entities (scenarios without analyses)
4002
+ // Incomplete entities (scenarios without analyses) — report with guidance.
4003
+ // We intentionally do NOT run analysis here: it starts the analyzer
4004
+ // template which is slow even for a few files. Users should run
4005
+ // `codeyam editor analyze-imports` separately if needed.
3473
4006
  const incompleteEntities = data.incompleteEntities || [];
3474
4007
  if (incompleteEntities.length > 0) {
4008
+ const { formatIncompleteEntityGuidance, formatManualAnalysisGuidance } = await import('../utils/editorAudit.js');
4009
+ const { readAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
4010
+ // Check for persistent analysis failures
4011
+ const analysisFailures = readAnalysisFailures(getProjectRoot());
3475
4012
  console.log(chalk.bold('Incomplete entities (need import analysis):'));
3476
4013
  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.'));
4014
+ // Check if this entity has a persistent failure
4015
+ const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
4016
+ if (failureEntry) {
4017
+ // Show manual analysis instructions instead of "run analyze-imports"
4018
+ const filePath = Object.entries(analysisFailures).find(([, f]) => f.entityName === e.name)?.[0];
4019
+ const guidance = formatManualAnalysisGuidance({
4020
+ name: e.name,
4021
+ filePath: filePath || '',
4022
+ scenarioCount: e.scenarioCount,
4023
+ error: failureEntry.error,
4024
+ });
4025
+ // Print each line with proper indentation
4026
+ for (const line of guidance.split('\n')) {
4027
+ console.log(` ${chalk.red('✗')} ${line}`);
4028
+ }
4029
+ }
4030
+ else {
4031
+ const guidance = formatIncompleteEntityGuidance(e);
4032
+ console.log(` ${chalk.red('✗')} ${guidance}`);
4033
+ }
3482
4034
  }
3483
- else {
3484
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities.'));
4035
+ const incompleteErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
4036
+ if (fs.existsSync(incompleteErrorReportPath)) {
4037
+ console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
3485
4038
  }
3486
4039
  console.log();
3487
4040
  }
@@ -3498,12 +4051,22 @@ async function handleAudit() {
3498
4051
  console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
3499
4052
  }
3500
4053
  }
4054
+ const uaPaths = unassociatedScenarios
4055
+ .map((u) => u.filePath)
4056
+ .filter(Boolean);
4057
+ const uaCmd = uaPaths.length > 0
4058
+ ? `codeyam editor analyze-imports ${uaPaths.join(' ')}`
4059
+ : 'codeyam editor analyze-imports';
3501
4060
  if (autoRemediationFailed) {
3502
4061
  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.'));
4062
+ console.log(chalk.yellow(` Run \`${uaCmd}\` to see the full error output.`));
3504
4063
  }
3505
4064
  else {
3506
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to create entity records, then re-run audit to backfill.'));
4065
+ console.log(chalk.yellow(` Run \`${uaCmd}\` to create entity records, then re-run audit to backfill.`));
4066
+ }
4067
+ const unassocErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
4068
+ if (fs.existsSync(unassocErrorReportPath)) {
4069
+ console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
3507
4070
  }
3508
4071
  console.log();
3509
4072
  }
@@ -3532,7 +4095,26 @@ async function handleAudit() {
3532
4095
  : `${s.status.status}`;
3533
4096
  console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
3534
4097
  }
3535
- console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
4098
+ if (options?.fix) {
4099
+ // --fix: auto-recapture stale scenarios instead of just reporting them
4100
+ const { shouldAutoRecapture } = await import('../utils/editorAudit.js');
4101
+ if (shouldAutoRecapture({ fix: true, scenariosNeedingRecapture })) {
4102
+ console.log(chalk.cyan(' --fix: auto-recapturing stale scenarios...'));
4103
+ console.log();
4104
+ await handleRecaptureStale();
4105
+ // Re-fetch audit results so the summary and exit code reflect
4106
+ // the post-fix state, not the pre-fix state.
4107
+ const refreshed = await fetchAuditResult({ skipTests: true });
4108
+ if (refreshed) {
4109
+ data = refreshed;
4110
+ ({ components, functions, summary } = data);
4111
+ }
4112
+ }
4113
+ }
4114
+ else {
4115
+ console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
4116
+ console.log(chalk.dim(' Or: codeyam editor audit --fix (to auto-recapture)'));
4117
+ }
3536
4118
  console.log();
3537
4119
  }
3538
4120
  // Duplicate glossary names (warning, not a failure)
@@ -3595,17 +4177,6 @@ async function handleAudit() {
3595
4177
  if (!allOk) {
3596
4178
  process.exit(1);
3597
4179
  }
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
4180
  }
3610
4181
  // ─── Recapture-stale subcommand ────────────────────────────────────────
3611
4182
  /**
@@ -3812,14 +4383,14 @@ async function handleScenarioCoverage() {
3812
4383
  // Safety net: heal any scenarios with null entity_sha before checking coverage
3813
4384
  try {
3814
4385
  const { getDatabase } = await import('../../../packages/database/index.js');
3815
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
4386
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
3816
4387
  const db = getDatabase();
3817
4388
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
3818
4389
  if (backfillCount > 0) {
3819
4390
  console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
3820
4391
  await handleAnalyzeImports({ silent: true });
3821
4392
  // Run backfill after analysis
3822
- const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
4393
+ const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
3823
4394
  const entities = await loadEntities({});
3824
4395
  if (entities && entities.length > 0) {
3825
4396
  await backfillEntityShaOnScenarios(db, entities.map((e) => ({
@@ -3966,7 +4537,7 @@ async function handleTemplate() {
3966
4537
  _: [],
3967
4538
  });
3968
4539
  console.log(chalk.green(' CodeYam initialized.'));
3969
- // 5. Verify config has startCommand
4540
+ // 5. Verify config has startCommand and set format-specific defaults
3970
4541
  const configPath = path.join(root, '.codeyam', 'config.json');
3971
4542
  if (fs.existsSync(configPath)) {
3972
4543
  try {
@@ -3977,6 +4548,26 @@ async function handleTemplate() {
3977
4548
  console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
3978
4549
  console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
3979
4550
  }
4551
+ // Store appFormats from the tech stack so the editor UI can adapt
4552
+ if (stack?.supportedFormats) {
4553
+ config.appFormats = stack.supportedFormats;
4554
+ }
4555
+ // Set mobile-first defaults for mobile-app projects
4556
+ if (stack?.supportedFormats?.includes('mobile-app')) {
4557
+ config.defaultScreenSize = {
4558
+ name: 'iPhone 16',
4559
+ width: 393,
4560
+ height: 852,
4561
+ };
4562
+ config.screenSizes = {
4563
+ 'iPhone 16': { width: 393, height: 852 },
4564
+ 'iPhone 16 Pro Max': { width: 430, height: 932 },
4565
+ 'iPhone SE': { width: 375, height: 667 },
4566
+ 'Pixel 8': { width: 412, height: 915 },
4567
+ 'iPad mini': { width: 744, height: 1133 },
4568
+ };
4569
+ }
4570
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
3980
4571
  }
3981
4572
  catch {
3982
4573
  // Config parse error is non-fatal
@@ -4011,7 +4602,15 @@ async function handleTemplate() {
4011
4602
  }
4012
4603
  console.log();
4013
4604
  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.'));
4605
+ if (stack?.id === 'expo-react-native') {
4606
+ console.log(chalk.dim('Next: Set up your data types, configure the theme in lib/theme.ts, and build your feature.'));
4607
+ }
4608
+ else if (stack?.id === 'chrome-extension-react') {
4609
+ console.log(chalk.dim('Next: Configure your extension manifest and build your feature.'));
4610
+ }
4611
+ else {
4612
+ console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
4613
+ }
4015
4614
  }
4016
4615
  // ─── Sync subcommand ─────────────────────────────────────────────────
4017
4616
  /**
@@ -4316,14 +4915,205 @@ function handleEditorDebug(args) {
4316
4915
  console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
4317
4916
  console.log();
4318
4917
  }
4918
+ // ─── Manual entity analysis ───────────────────────────────────────────
4919
+ /**
4920
+ * `codeyam editor manual-entity <JSON|@file>`
4921
+ *
4922
+ * Creates entity and analysis records from Claude-provided metadata when
4923
+ * automated analyze-imports fails. This unblocks the audit gate without
4924
+ * needing the analyzer template to parse the source file.
4925
+ */
4926
+ async function handleManualEntity(jsonArg) {
4927
+ const root = getProjectRoot();
4928
+ // Parse JSON input (supports @file.json convention)
4929
+ const parsed = parseRegisterArg(jsonArg);
4930
+ if (parsed.error || !parsed.body) {
4931
+ console.error(chalk.red(`Error: ${parsed.error || 'Invalid input'}`));
4932
+ console.error(chalk.dim(' Usage: codeyam editor manual-entity \'{"name":"Header","filePath":"src/components/Header.tsx","entityType":"visual",...}\''));
4933
+ console.error(chalk.dim(' Or: codeyam editor manual-entity @.codeyam/tmp/manual-entity.json'));
4934
+ process.exit(1);
4935
+ }
4936
+ const input = parsed.body;
4937
+ // Validate input
4938
+ const { validateManualEntityInput, convertTypeInfoToDataForMocks, readAnalysisFailures, clearFailureForPath, writeAnalysisFailures, } = await import('../utils/manualEntityAnalysis.js');
4939
+ // Read glossary for validation
4940
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
4941
+ let glossaryEntries = [];
4942
+ try {
4943
+ glossaryEntries = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
4944
+ }
4945
+ catch {
4946
+ // Empty glossary — validation will still check file existence
4947
+ }
4948
+ const errors = validateManualEntityInput(input, glossaryEntries, {
4949
+ fileExists: (p) => fs.existsSync(path.join(root, p)),
4950
+ });
4951
+ if (errors.length > 0) {
4952
+ console.error(chalk.red('Validation errors:'));
4953
+ for (const err of errors) {
4954
+ console.error(chalk.red(` • ${err}`));
4955
+ }
4956
+ process.exit(1);
4957
+ }
4958
+ // Read source file and compute SHA
4959
+ const sourceContent = fs.readFileSync(path.join(root, input.filePath), 'utf8');
4960
+ const { generateSha } = await import('../../../packages/database/index.js');
4961
+ const entitySha = generateSha(input.filePath, input.name, sourceContent);
4962
+ // Get project and branch
4963
+ await initializeEnvironment();
4964
+ const configPath = path.join(root, '.codeyam', 'config.json');
4965
+ let projectSlug;
4966
+ try {
4967
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
4968
+ projectSlug = config.projectSlug;
4969
+ }
4970
+ catch {
4971
+ console.error(chalk.red('Error: .codeyam/config.json not found or invalid.'));
4972
+ process.exit(1);
4973
+ }
4974
+ const { project, branch } = await requireBranchAndProject(projectSlug);
4975
+ const { getDatabase } = await import('../../../packages/database/index.js');
4976
+ const db = getDatabase();
4977
+ // Convert type info to dataForMocks format
4978
+ const dataForMocks = convertTypeInfoToDataForMocks(input.typeInfo);
4979
+ // Create entity record
4980
+ const entityMetadata = {
4981
+ importedExports: (input.importedExports || []).map((ie) => ({
4982
+ name: ie.name,
4983
+ filePath: ie.filePath,
4984
+ })),
4985
+ manuallyAnalyzed: true,
4986
+ };
4987
+ // Check if entity already exists
4988
+ const existingEntity = await db
4989
+ .selectFrom('entities')
4990
+ .select('sha')
4991
+ .where('sha', '=', entitySha)
4992
+ .executeTakeFirst();
4993
+ if (!existingEntity) {
4994
+ await db
4995
+ .insertInto('entities')
4996
+ .values({
4997
+ sha: entitySha,
4998
+ project_id: project.id,
4999
+ name: input.name,
5000
+ entity_type: input.entityType,
5001
+ file_path: input.filePath,
5002
+ metadata: JSON.stringify(entityMetadata),
5003
+ })
5004
+ .onConflict((oc) => oc.column('sha').doNothing())
5005
+ .execute();
5006
+ console.log(chalk.dim(`Created entity record: ${input.name} (${entitySha.slice(0, 8)}...)`));
5007
+ }
5008
+ else {
5009
+ // Update metadata on existing entity
5010
+ await db
5011
+ .updateTable('entities')
5012
+ .set({
5013
+ metadata: JSON.stringify(entityMetadata),
5014
+ })
5015
+ .where('sha', '=', entitySha)
5016
+ .execute();
5017
+ console.log(chalk.dim(`Updated existing entity: ${input.name} (${entitySha.slice(0, 8)}...)`));
5018
+ }
5019
+ // Create entity_branch record
5020
+ await db
5021
+ .insertInto('entity_branches')
5022
+ .values({
5023
+ entity_sha: entitySha,
5024
+ branch_id: branch.id,
5025
+ active: true,
5026
+ })
5027
+ .onConflict((oc) => oc.columns(['entity_sha', 'branch_id']).doUpdateSet({ active: true }))
5028
+ .execute();
5029
+ // Create analysis record
5030
+ const { randomUUID } = await import('crypto');
5031
+ const analysisId = randomUUID();
5032
+ const now = new Date().toISOString();
5033
+ const analysisMetadata = {
5034
+ scenariosDataStructure: {
5035
+ dataForMocks: Object.keys(dataForMocks).length > 0 ? dataForMocks : null,
5036
+ },
5037
+ mergedDataStructure: {
5038
+ dependencySchemas: null,
5039
+ },
5040
+ manuallyAnalyzed: true,
5041
+ };
5042
+ const analysisStatus = {
5043
+ startedAt: now,
5044
+ finishedAt: now,
5045
+ steps: [],
5046
+ errors: [],
5047
+ };
5048
+ // Check if analysis already exists for this entity
5049
+ const existingAnalysis = await db
5050
+ .selectFrom('analyses')
5051
+ .select('id')
5052
+ .where('entity_sha', '=', entitySha)
5053
+ .executeTakeFirst();
5054
+ if (!existingAnalysis) {
5055
+ await db
5056
+ .insertInto('analyses')
5057
+ .values({
5058
+ id: analysisId,
5059
+ project_id: project.id,
5060
+ entity_sha: entitySha,
5061
+ entity_name: input.name,
5062
+ entity_type: input.entityType,
5063
+ file_path: input.filePath,
5064
+ status: JSON.stringify(analysisStatus),
5065
+ metadata: JSON.stringify(analysisMetadata),
5066
+ })
5067
+ .execute();
5068
+ console.log(chalk.dim(`Created analysis record: ${analysisId.slice(0, 8)}...`));
5069
+ }
5070
+ else {
5071
+ // Update existing analysis with manual metadata
5072
+ await db
5073
+ .updateTable('analyses')
5074
+ .set({
5075
+ metadata: JSON.stringify(analysisMetadata),
5076
+ status: JSON.stringify(analysisStatus),
5077
+ })
5078
+ .where('entity_sha', '=', entitySha)
5079
+ .execute();
5080
+ console.log(chalk.dim(`Updated existing analysis for ${input.name}`));
5081
+ }
5082
+ // Backfill entity_sha on scenarios
5083
+ const backfillResult = await backfillEntityShaOnScenarios(db, [
5084
+ {
5085
+ sha: entitySha,
5086
+ name: input.name,
5087
+ filePath: input.filePath,
5088
+ isDefaultExport: false,
5089
+ },
5090
+ ]);
5091
+ if (backfillResult.updated > 0) {
5092
+ console.log(chalk.dim(`Linked ${backfillResult.updated} scenario(s) to entity`));
5093
+ }
5094
+ // Clear from analysis failures tracker
5095
+ const failures = readAnalysisFailures(root);
5096
+ if (failures[input.filePath]) {
5097
+ const updated = clearFailureForPath(failures, input.filePath);
5098
+ writeAnalysisFailures(root, updated);
5099
+ console.log(chalk.dim(`Cleared analysis failure for ${input.filePath}`));
5100
+ }
5101
+ console.log();
5102
+ console.log(chalk.green.bold(`✓ Manual entity analysis complete: ${input.name}`));
5103
+ console.log(chalk.dim(` Entity SHA: ${entitySha.slice(0, 12)}...`));
5104
+ console.log(chalk.dim(` Imports: ${(input.importedExports || []).length} glossary entities`));
5105
+ console.log(chalk.dim(` Type info: ${Object.keys(dataForMocks).length} fields`));
5106
+ console.log();
5107
+ console.log(chalk.dim('Re-run `codeyam editor audit` to verify.'));
5108
+ }
4319
5109
  // ─── Command definition ───────────────────────────────────────────────
4320
5110
  const editorCommand = {
4321
5111
  command: 'editor [step] [json]',
4322
5112
  describe: 'Editor mode guided workflow',
4323
5113
  builder: (yargs) => {
4324
5114
  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)';
5115
+ ? '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)'
5116
+ : '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
5117
  let builder = yargs
4328
5118
  .positional('step', {
4329
5119
  type: 'string',
@@ -4354,6 +5144,11 @@ const editorCommand = {
4354
5144
  alias: 'p',
4355
5145
  describe: 'Port to run the web server on',
4356
5146
  default: 3111,
5147
+ })
5148
+ .option('fix', {
5149
+ type: 'boolean',
5150
+ describe: 'For audit: also recapture stale scenarios after displaying results',
5151
+ default: false,
4357
5152
  });
4358
5153
  if (IS_INTERNAL_BUILD) {
4359
5154
  builder = builder
@@ -4465,7 +5260,9 @@ const editorCommand = {
4465
5260
  'Ask user what to build next and restart codeyam editor workflow';
4466
5261
  }
4467
5262
  console.log(chalk.bold.cyan('━━━ TASK ━━━'));
4468
- console.log(chalk.cyan('Mark your current task (if any) as complete.'));
5263
+ console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
5264
+ console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
5265
+ console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
4469
5266
  console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
4470
5267
  console.log();
4471
5268
  console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
@@ -4479,9 +5276,16 @@ const editorCommand = {
4479
5276
  fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
4480
5277
  return;
4481
5278
  }
4482
- // Subcommand: codeyam editor analyze-imports
5279
+ // Subcommand: codeyam editor analyze-imports [file1.tsx file2.tsx ...]
4483
5280
  if (argv.step === 'analyze-imports') {
4484
- await handleAnalyzeImports();
5281
+ const { parseAnalyzeImportsArgs } = await import('./editorAnalyzeImportsArgs.js');
5282
+ const requestedPaths = parseAnalyzeImportsArgs(argv.json, argv._);
5283
+ await handleAnalyzeImports(requestedPaths.length > 0 ? { filePaths: requestedPaths } : {});
5284
+ return;
5285
+ }
5286
+ // Subcommand: codeyam editor manual-entity <JSON|@file>
5287
+ if (argv.step === 'manual-entity') {
5288
+ await handleManualEntity(argv.json || '');
4485
5289
  return;
4486
5290
  }
4487
5291
  // Subcommand: codeyam editor dependents <EntityName>
@@ -4489,9 +5293,9 @@ const editorCommand = {
4489
5293
  await handleDependents(argv.json || '');
4490
5294
  return;
4491
5295
  }
4492
- // Subcommand: codeyam editor audit
5296
+ // Subcommand: codeyam editor audit [--fix]
4493
5297
  if (argv.step === 'audit') {
4494
- await handleAudit();
5298
+ await handleAudit({ fix: argv.fix || false });
4495
5299
  return;
4496
5300
  }
4497
5301
  // Subcommand: codeyam editor scenarios
@@ -4524,6 +5328,11 @@ const editorCommand = {
4524
5328
  await handleValidateSeed(argv.json || '');
4525
5329
  return;
4526
5330
  }
5331
+ // Subcommand: codeyam editor design-system <id>
5332
+ if (argv.step === 'design-system') {
5333
+ handleDesignSystem(argv.json || '');
5334
+ return;
5335
+ }
4527
5336
  // Subcommand: codeyam editor delete <scenarioId>
4528
5337
  if (argv.step === 'delete') {
4529
5338
  await handleDelete(argv.json || '');
@@ -4724,13 +5533,24 @@ const editorCommand = {
4724
5533
  try {
4725
5534
  const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4726
5535
  const seedDb = getDb();
4727
- const appScenario = await seedDb
5536
+ // Prefer the home page scenario (url "/") since the App tab
5537
+ // sorts "Home" first — fall back to any application scenario.
5538
+ const homeScenario = await seedDb
4728
5539
  .selectFrom('editor_scenarios')
4729
5540
  .select(['id', 'name', 'type'])
4730
5541
  .where('project_id', '=', project.id)
4731
- .where('type', '=', 'application')
5542
+ .where('url', '=', '/')
5543
+ .where('component_name', 'is', null)
4732
5544
  .orderBy('created_at', 'asc')
4733
5545
  .executeTakeFirst();
5546
+ const appScenario = homeScenario ||
5547
+ (await seedDb
5548
+ .selectFrom('editor_scenarios')
5549
+ .select(['id', 'name', 'type'])
5550
+ .where('project_id', '=', project.id)
5551
+ .where('type', '=', 'application')
5552
+ .orderBy('created_at', 'asc')
5553
+ .executeTakeFirst());
4734
5554
  if (appScenario) {
4735
5555
  const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
4736
5556
  if (fs.existsSync(seedFile)) {
@@ -4873,7 +5693,7 @@ const editorCommand = {
4873
5693
  if (!needsAnalysis) {
4874
5694
  try {
4875
5695
  const { getDatabase } = await import('../../../packages/database/index.js');
4876
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
5696
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
4877
5697
  const db = getDatabase();
4878
5698
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
4879
5699
  if (backfillCount > 0) {
@@ -4912,7 +5732,7 @@ const editorCommand = {
4912
5732
  .execute();
4913
5733
  if (unresolved.length > 0) {
4914
5734
  const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
4915
- const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
5735
+ const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
4916
5736
  const { allFiles: pfpFiles } = scanPfp(projectRoot);
4917
5737
  let pfpResolved = 0;
4918
5738
  for (const row of unresolved) {