@codeyam/codeyam-cli 0.1.0-staging.30dc541 → 0.1.0-staging.39719f5

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 (159) hide show
  1. package/analyzer-template/.build-info.json +7 -7
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +2 -2
  4. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +36 -9
  5. package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.ts +10 -3
  6. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +16 -6
  7. package/analyzer-template/packages/analyze/index.ts +4 -1
  8. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +28 -2
  9. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +5 -36
  10. package/analyzer-template/packages/analyze/src/lib/files/analyze/findOrCreateEntity.ts +10 -6
  11. package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +9 -12
  12. package/analyzer-template/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.ts +21 -0
  13. package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +82 -10
  14. package/analyzer-template/packages/analyze/src/lib/files/analyzeChange.ts +4 -0
  15. package/analyzer-template/packages/analyze/src/lib/files/analyzeInitial.ts +4 -0
  16. package/analyzer-template/packages/analyze/src/lib/files/analyzeNextRoute.ts +8 -3
  17. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +235 -58
  18. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +170 -26
  19. package/analyzer-template/packages/aws/package.json +1 -1
  20. package/analyzer-template/packages/database/package.json +1 -1
  21. package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +19 -15
  22. package/analyzer-template/packages/database/src/lib/loadEntity.ts +19 -8
  23. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.d.ts.map +1 -1
  24. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js +1 -1
  25. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js.map +1 -1
  26. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts +4 -1
  27. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts.map +1 -1
  28. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js +5 -5
  29. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js.map +1 -1
  30. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts +3 -1
  31. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
  32. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +22 -1
  33. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  34. package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +27 -0
  35. package/analyzer-template/project/analyzeFileEntities.ts +26 -0
  36. package/background/src/lib/virtualized/project/analyzeFileEntities.js +22 -0
  37. package/background/src/lib/virtualized/project/analyzeFileEntities.js.map +1 -1
  38. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js +63 -0
  39. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
  40. package/codeyam-cli/src/commands/editor.js +775 -125
  41. package/codeyam-cli/src/commands/editor.js.map +1 -1
  42. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +1248 -16
  43. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  44. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
  45. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
  46. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +20 -0
  47. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  48. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +33 -1
  49. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  50. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
  51. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
  52. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js +127 -0
  53. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js.map +1 -0
  54. package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js +84 -0
  55. package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js.map +1 -0
  56. package/codeyam-cli/src/utils/__tests__/testRunner.test.js +217 -0
  57. package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
  58. package/codeyam-cli/src/utils/analysisRunner.js +36 -7
  59. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  60. package/codeyam-cli/src/utils/analyzer.js +11 -1
  61. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  62. package/codeyam-cli/src/utils/editorAudit.js +280 -15
  63. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  64. package/codeyam-cli/src/utils/editorPreview.js +5 -3
  65. package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
  66. package/codeyam-cli/src/utils/editorScenarios.js +36 -4
  67. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  68. package/codeyam-cli/src/utils/entityChangeStatus.server.js +16 -0
  69. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  70. package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
  71. package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
  72. package/codeyam-cli/src/utils/queue/job.js +26 -5
  73. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  74. package/codeyam-cli/src/utils/registerScenarioResult.js +52 -0
  75. package/codeyam-cli/src/utils/registerScenarioResult.js.map +1 -0
  76. package/codeyam-cli/src/utils/screenshotHash.js +26 -0
  77. package/codeyam-cli/src/utils/screenshotHash.js.map +1 -0
  78. package/codeyam-cli/src/utils/testRunner.js +199 -1
  79. package/codeyam-cli/src/utils/testRunner.js.map +1 -1
  80. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +28 -1
  81. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -1
  82. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +57 -2
  83. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
  84. package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js +57 -1
  85. package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js.map +1 -1
  86. package/codeyam-cli/src/webserver/app/lib/clientErrors.js +15 -0
  87. package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
  88. package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-CQENLSrF.js +36 -0
  89. package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-scenario-data-l0sNRNKZ.js +1 -0
  90. package/codeyam-cli/src/webserver/build/client/assets/api.editor-schema-l0sNRNKZ.js +1 -0
  91. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-Coe5NhbS.js +1 -0
  92. package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CCKUIm0S.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
  93. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-aIHKLB-m.js +96 -0
  94. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-CluPkvXJ.js +41 -0
  95. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-Blfy9UlN.js → entity._sha._-ByHz6rAQ.js} +13 -12
  96. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-KTQuL0aj.js → entity._sha.scenarios._scenarioId.dev-CmLO432x.js} +1 -1
  97. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js → entity._sha.scenarios._scenarioId.fullscreen-Bz9sCUF_.js} +1 -1
  98. package/codeyam-cli/src/webserver/build/client/assets/globals-oyPmV37k.css +1 -0
  99. package/codeyam-cli/src/webserver/build/client/assets/manifest-bcbb3d49.js +1 -0
  100. package/codeyam-cli/src/webserver/build/client/assets/{root-BxUQigda.js → root-D2_tktnk.js} +26 -13
  101. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-DjF-soOH.js +16 -0
  102. package/codeyam-cli/src/webserver/build/server/assets/{index-Cd-ufawF.js → index-nAvHGWbz.js} +1 -1
  103. package/codeyam-cli/src/webserver/build/server/assets/{init-CzeBGOto.js → init-XhpIt-OT.js} +1 -1
  104. package/codeyam-cli/src/webserver/build/server/assets/server-build-DVwiibFu.js +644 -0
  105. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  106. package/codeyam-cli/src/webserver/build-info.json +5 -5
  107. package/codeyam-cli/src/webserver/idleDetector.js +15 -0
  108. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
  109. package/codeyam-cli/src/webserver/scripts/journalCapture.ts +17 -0
  110. package/codeyam-cli/src/webserver/server.js +52 -14
  111. package/codeyam-cli/src/webserver/server.js.map +1 -1
  112. package/codeyam-cli/src/webserver/terminalServer.js +69 -15
  113. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  114. package/codeyam-cli/templates/editor-step-hook.py +21 -0
  115. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +19 -1
  116. package/package.json +1 -1
  117. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +27 -10
  118. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  119. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
  120. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
  121. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
  122. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  123. package/packages/analyze/index.js +1 -1
  124. package/packages/analyze/index.js.map +1 -1
  125. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
  126. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  127. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
  128. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  129. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +3 -2
  130. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
  131. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +9 -7
  132. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
  133. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
  134. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
  135. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
  136. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
  137. package/packages/analyze/src/lib/files/analyzeChange.js +1 -0
  138. package/packages/analyze/src/lib/files/analyzeChange.js.map +1 -1
  139. package/packages/analyze/src/lib/files/analyzeInitial.js +1 -0
  140. package/packages/analyze/src/lib/files/analyzeInitial.js.map +1 -1
  141. package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
  142. package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
  143. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +116 -28
  144. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  145. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +139 -24
  146. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  147. package/packages/database/src/lib/loadAnalysis.js +1 -1
  148. package/packages/database/src/lib/loadAnalysis.js.map +1 -1
  149. package/packages/database/src/lib/loadEntity.js +5 -5
  150. package/packages/database/src/lib/loadEntity.js.map +1 -1
  151. package/packages/utils/src/lib/fs/rsyncCopy.js +22 -1
  152. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  153. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DcX-ZS3p.js +0 -1
  154. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-CGzKlIHg.js +0 -58
  155. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-oepecPae.js +0 -41
  156. package/codeyam-cli/src/webserver/build/client/assets/globals-Yn9W3zp3.css +0 -1
  157. package/codeyam-cli/src/webserver/build/client/assets/manifest-2ef99f38.js +0 -1
  158. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-BPmOG9bE.js +0 -13
  159. package/codeyam-cli/src/webserver/build/server/assets/server-build-Dht7CKXY.js +0 -552
@@ -21,6 +21,7 @@ import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } f
21
21
  import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
22
22
  import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
23
23
  import { parseRegisterArg } from "../utils/parseRegisterArg.js";
24
+ import { classifyRegistrationResult } from "../utils/registerScenarioResult.js";
24
25
  import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
25
26
  import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
26
27
  const __filename = fileURLToPath(import.meta.url);
@@ -121,6 +122,13 @@ function writeState(root, state) {
121
122
  const dir = path.dirname(statePath);
122
123
  fs.mkdirSync(dir, { recursive: true });
123
124
  fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
125
+ // Write task tracking file — marks that a task is expected for this step.
126
+ // The step hook sets taskCreated=true when it sees a TaskCreate tool call.
127
+ // Steps 1 and below don't require tasks (feature not named yet).
128
+ if (state.step && state.step >= 2) {
129
+ const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
130
+ fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: false }, null, 2), 'utf8');
131
+ }
124
132
  }
125
133
  /**
126
134
  * Clear the editor state (for starting a new feature).
@@ -203,6 +211,34 @@ function checkbox(text) {
203
211
  const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
204
212
  console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
205
213
  }
214
+ /**
215
+ * Instructions for creating/updating .codeyam/data-structure.json.
216
+ * Only prints creation instructions if the file doesn't exist yet.
217
+ * If it exists, reminds Claude to update it if data models changed.
218
+ */
219
+ function printDataStructureInstructions() {
220
+ const root = getProjectRoot();
221
+ const dsPath = path.join(root, '.codeyam', 'data-structure.json');
222
+ const exists = fs.existsSync(dsPath);
223
+ if (!exists) {
224
+ console.log(chalk.bold('Create the data structure config:'));
225
+ checkbox('Create `.codeyam/data-structure.json` describing all data sources');
226
+ console.log(chalk.dim(" This file tells the editor what data the app uses and how it's stored."));
227
+ console.log(chalk.dim(' The file is a JSON array. Each entry describes one data source:'));
228
+ console.log(chalk.dim(' { "name": "Drink", "description": "...", "category": "datastore", "order": 1,'));
229
+ console.log(chalk.dim(' "fields": [{ "name": "id", "type": "number", "isId": true, "required": true }, ...] }'));
230
+ console.log();
231
+ console.log(chalk.dim(' category: "datastore" for persisted data (DB, localStorage)'));
232
+ console.log(chalk.dim(' "mock-api" for mocked external API responses'));
233
+ console.log(chalk.dim(' order: 1 = primary data store, 2 = secondary, etc.'));
234
+ console.log(chalk.dim(' Do NOT include types only used as component props.'));
235
+ }
236
+ else {
237
+ checkbox('If this feature changes data models, update `.codeyam/data-structure.json`');
238
+ console.log(chalk.dim(' Add new tables, update fields, or change descriptions to match.'));
239
+ }
240
+ console.log();
241
+ }
206
242
  /**
207
243
  * Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
208
244
  * Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
@@ -248,11 +284,15 @@ function printAppScenarioInstructions(pageName, route) {
248
284
  console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
249
285
  console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
250
286
  }
251
- checkbox('For large seed/mock data, write JSON to a temp file and use @ prefix:');
252
- console.log(chalk.dim(' Write JSON to .codeyam/tmp/scenario.json then register with:'));
253
- console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
287
+ console.log();
288
+ console.log(chalk.bold('Register ALL scenarios at once (bulk registration):'));
289
+ console.log(chalk.dim(' Write an array of scenario objects to a temp file:'));
290
+ console.log(chalk.dim(' [{"name":"...","type":"application","url":"/","seed":{...}}, {"name":"...","url":"/other",...}]'));
291
+ console.log(chalk.dim(' Then register all at once: codeyam editor register @.codeyam/tmp/scenarios.json'));
292
+ console.log(chalk.yellow(' Bulk registration is preferred — faster and avoids repeated capture overhead.'));
293
+ console.log();
254
294
  checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
255
- checkbox('After each registration, check the response for `clientErrors`');
295
+ checkbox('After registration, check the response for `clientErrors`');
256
296
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
257
297
  console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
258
298
  }
@@ -317,7 +357,7 @@ function printExtractionPlanInstructions() {
317
357
  */
318
358
  function printComponentCaptureInstructions() {
319
359
  checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
320
- console.log(chalk.dim(' This creates app/codeyam-isolate/layout.tsx (with production notFound() guard) and'));
360
+ console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
321
361
  console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
322
362
  checkbox('For each visual component:');
323
363
  console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
@@ -329,8 +369,8 @@ function printComponentCaptureInstructions() {
329
369
  console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
330
370
  console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
331
371
  console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
332
- console.log(chalk.dim(' Remix: app/routes/codeyam-isolate.ComponentName.tsx → /codeyam-isolate/ComponentName'));
333
- console.log(chalk.dim(' Next.js: app/codeyam-isolate/ComponentName/page.tsx → /codeyam-isolate/ComponentName'));
372
+ console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
373
+ console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
334
374
  console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
335
375
  console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
336
376
  console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
@@ -343,9 +383,9 @@ function printComponentCaptureInstructions() {
343
383
  console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
344
384
  console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
345
385
  console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
346
- console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
386
+ console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
347
387
  console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
348
- console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/codeyam-isolate/... or /isolated-components/...).'));
388
+ console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/isolated-components/...).'));
349
389
  console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
350
390
  console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
351
391
  console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
@@ -353,6 +393,10 @@ function printComponentCaptureInstructions() {
353
393
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
354
394
  console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
355
395
  console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
396
+ console.log();
397
+ console.log(chalk.bold(' Bulk registration: write all component scenarios to a temp file as an array:'));
398
+ console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
399
+ console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
356
400
  }
357
401
  /**
358
402
  * Print a section header.
@@ -393,9 +437,33 @@ function printProgressTracker(current) {
393
437
  *
394
438
  * Options:
395
439
  * - confirm: true → step requires user confirmation before proceeding (steps 1, 3, 11)
440
+ * - feature: string → feature name for task directive
396
441
  */
397
442
  function stopGate(current, opts) {
398
- console.log();
443
+ const totalSteps = Object.keys(STEP_LABELS).length;
444
+ const currentLabel = STEP_LABELS[current] || `Step ${current}`;
445
+ const nextLabel = current < totalSteps ? STEP_LABELS[current + 1] : undefined;
446
+ console.log();
447
+ // ━━━ TASK ━━━ — deterministic task lifecycle directive
448
+ // Skip step 1 (no feature name yet — task starts at step 2)
449
+ if (current >= 2) {
450
+ console.log(chalk.bold.cyan('━━━ TASK ━━━'));
451
+ console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
452
+ console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
453
+ console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
454
+ console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
455
+ console.log();
456
+ let taskTitle;
457
+ if (current < totalSteps) {
458
+ taskTitle = `Complete codeyam editor step ${current}: '${currentLabel}' and move on to step ${current + 1}: '${nextLabel}'`;
459
+ }
460
+ else {
461
+ taskTitle =
462
+ 'Ask user what to build next and restart codeyam editor workflow';
463
+ }
464
+ console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
465
+ console.log();
466
+ }
399
467
  console.log(chalk.bold.red('━━━ STOP ━━━'));
400
468
  console.log();
401
469
  console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
@@ -404,20 +472,12 @@ function stopGate(current, opts) {
404
472
  console.log(chalk.yellow('Wait for user confirmation before moving on.'));
405
473
  }
406
474
  console.log();
407
- console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
408
- printProgressTracker(current);
409
- console.log();
410
- console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
411
- console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
412
- console.log();
413
- if (current < 18) {
475
+ if (current < totalSteps) {
414
476
  console.log(chalk.green('When done, run: ') +
415
477
  chalk.bold(`codeyam editor ${current + 1}`));
416
478
  }
417
479
  else {
418
- console.log(chalk.green('Feature complete! Run: ') +
419
- chalk.bold('codeyam editor steps') +
420
- chalk.green(' to start the next feature'));
480
+ console.log(chalk.green('Feature complete! Ask the user what to build next, then run: ') + chalk.bold('codeyam editor 1'));
421
481
  }
422
482
  console.log();
423
483
  }
@@ -785,9 +845,7 @@ function printCycleOverview(root, state) {
785
845
  console.log();
786
846
  console.log(chalk.green('Continue with: ') +
787
847
  chalk.bold(`codeyam editor ${state.step}`));
788
- console.log(chalk.dim('Or run ') +
789
- chalk.bold('codeyam editor 1') +
790
- chalk.dim(' to start a new feature'));
848
+ console.log(chalk.dim('Or ask the user what to build next, then run `codeyam editor 1` yourself (never expose this command to the user)'));
791
849
  console.log();
792
850
  console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
793
851
  chalk.bold('codeyam editor change'));
@@ -815,7 +873,8 @@ function printCycleOverview(root, state) {
815
873
  console.log(` ${chalk.bold.yellow('17')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
816
874
  console.log(` ${chalk.bold.yellow('18')} ${chalk.bold('Push')} — Push to remote`);
817
875
  console.log();
818
- console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
876
+ console.log(chalk.green('Start now: ') +
877
+ 'Ask the user what they want to build, then run `codeyam editor 1` (never expose this command to the user)');
819
878
  console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
820
879
  }
821
880
  console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
@@ -894,12 +953,6 @@ function printStep1(root, feature, options, userPrompt) {
894
953
  console.log();
895
954
  console.log(chalk.yellow('Wait for user confirmation before moving on.'));
896
955
  console.log();
897
- console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
898
- printProgressTracker(1);
899
- console.log();
900
- console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
901
- console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
902
- console.log();
903
956
  console.log(chalk.green('When done, run: ') +
904
957
  chalk.bold('codeyam editor 2 --feature "Feature Name"'));
905
958
  console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
@@ -927,13 +980,6 @@ function printStep2(root, feature) {
927
980
  if (isResuming) {
928
981
  printResumptionHeader(2);
929
982
  }
930
- console.log(chalk.bold.red('━━━ MANDATORY: CREATE TASK NOW ━━━'));
931
- console.log();
932
- console.log(chalk.red(`Run TaskCreate with title: "Use \`codeyam editor\` to work on ${feature}"`));
933
- console.log(chalk.red('DO NOT read files, scaffold, write code, or do ANYTHING else until this task exists.'));
934
- console.log(chalk.red('This is not optional. Create the task, then continue below.'));
935
- console.log(chalk.bold.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
936
- console.log();
937
983
  console.log('Get the project ready to build.');
938
984
  console.log();
939
985
  // If no project exists yet, include scaffolding instructions first
@@ -963,6 +1009,7 @@ function printStep2(root, feature) {
963
1009
  console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
964
1010
  console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
965
1011
  console.log();
1012
+ printDataStructureInstructions();
966
1013
  }
967
1014
  else {
968
1015
  console.log(chalk.bold('Prepare the database for this feature:'));
@@ -977,6 +1024,7 @@ function printStep2(root, feature) {
977
1024
  console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
978
1025
  console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
979
1026
  console.log();
1027
+ printDataStructureInstructions();
980
1028
  }
981
1029
  stopGate(2);
982
1030
  }
@@ -1012,6 +1060,13 @@ function printStep3(root, feature) {
1012
1060
  console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
1013
1061
  console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
1014
1062
  console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
1063
+ console.log();
1064
+ console.log(chalk.bold.cyan('Make seed data visually rich:'));
1065
+ console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
1066
+ console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
1067
+ console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1068
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1069
+ console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
1015
1070
  }
1016
1071
  checkbox('Verify the dev server shows the changes');
1017
1072
  checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
@@ -1051,6 +1106,8 @@ function printStep3(root, feature) {
1051
1106
  console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
1052
1107
  console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
1053
1108
  printDimensionGuidance(dim, dimNames);
1109
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
1110
+ console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
1054
1111
  console.log();
1055
1112
  stopGate(3);
1056
1113
  }
@@ -1147,6 +1204,8 @@ function printStep5(root, feature) {
1147
1204
  printDimensionGuidance(dim, dimNames);
1148
1205
  console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
1149
1206
  console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
1207
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
1208
+ console.log(chalk.red(' The preview only updates when you explicitly call `codeyam editor preview`. Verify, then describe.'));
1150
1209
  console.log();
1151
1210
  console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
1152
1211
  checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
@@ -1300,7 +1359,13 @@ function printStep9(root, feature) {
1300
1359
  console.log();
1301
1360
  checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
1302
1361
  console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
1303
- console.log(chalk.dim(' When audit passes, the import graph is built automatically for change tracking.'));
1362
+ console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
1363
+ console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
1364
+ console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
1365
+ console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
1366
+ console.log(chalk.dim(' If the audit reports "MANUAL ANALYSIS REQUIRED" for any entities,'));
1367
+ console.log(chalk.dim(' follow the manual analysis steps in the audit output.'));
1368
+ console.log(chalk.dim(' Do NOT re-run analyze-imports for those entities — it will fail again.'));
1304
1369
  console.log();
1305
1370
  stopGate(9);
1306
1371
  }
@@ -1335,6 +1400,7 @@ function printStep10(root, feature) {
1335
1400
  console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
1336
1401
  console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
1337
1402
  console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
1403
+ console.log(chalk.cyan(' • Use real image URLs (Unsplash, Pravatar) — not broken placeholders or empty strings'));
1338
1404
  console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
1339
1405
  console.log();
1340
1406
  console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
@@ -1549,7 +1615,7 @@ function printStep15(root, feature) {
1549
1615
  console.log(chalk.bold('Checklist:'));
1550
1616
  checkbox(`Show the results panel: \`codeyam editor show-results\``);
1551
1617
  console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
1552
- console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
1618
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1553
1619
  console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
1554
1620
  checkbox('Write a 1-2 sentence summary of what was built');
1555
1621
  checkbox('Report test count and audit status (one line)');
@@ -1789,7 +1855,7 @@ function printMigrateStep4(root) {
1789
1855
  checkbox('Check for client errors: `codeyam editor client-errors`');
1790
1856
  checkbox('If any issues: fix the scenario data, re-register affected scenarios');
1791
1857
  checkbox('Show results: `codeyam editor show-results`');
1792
- console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
1858
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1793
1859
  console.log();
1794
1860
  console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
1795
1861
  migrationStopGate(4, pageName, pageIndex, totalPages);
@@ -1901,7 +1967,7 @@ function printMigrateStep8(root) {
1901
1967
  console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
1902
1968
  console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
1903
1969
  checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
1904
- checkbox('Find all scenarios that use the real page route (not /codeyam-isolate/ paths)');
1970
+ checkbox('Find all scenarios that use the real page route (not /isolated-components/ paths)');
1905
1971
  checkbox('Re-register each one with the SAME name to update its screenshot');
1906
1972
  console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
1907
1973
  checkbox('After each registration, check the response for `clientErrors`');
@@ -1956,7 +2022,7 @@ function printMigrateStep10(root) {
1956
2022
  console.log();
1957
2023
  console.log(chalk.bold('Checklist:'));
1958
2024
  checkbox('Show results: `codeyam editor show-results`');
1959
- console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
2025
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1960
2026
  checkbox('Write a 1-2 sentence summary of what was migrated');
1961
2027
  console.log();
1962
2028
  console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
@@ -2253,15 +2319,13 @@ function printStep18(root, feature) {
2253
2319
  console.log(chalk.dim(' If the user says yes, run: `git push`'));
2254
2320
  console.log(chalk.dim(' If NO remote exists, ask (AskUserQuestion): "This project doesn\'t have a git remote yet. Would you like help setting one up? I can walk you through creating a GitHub repository." with options "Yes, set up remote" and "No, skip"'));
2255
2321
  console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
2256
- checkbox('Mark the current build task as complete');
2257
- checkbox('Create a new task with EXACTLY this title: "Ask the user what to build next, then run `codeyam editor 1` to start the next feature"');
2258
- console.log(chalk.dim(' There must ALWAYS be an active task. Never leave the task list empty.'));
2259
- console.log(chalk.dim(' This task reminds you to use the editor workflow for the next feature.'));
2322
+ console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
2260
2323
  console.log();
2261
2324
  console.log(chalk.bold.yellow('IMPORTANT: After this step, the current feature is DONE.'));
2262
2325
  console.log(chalk.yellow(' Any new user request — even if it sounds related — is a NEW feature.'));
2263
2326
  console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
2264
- console.log(chalk.yellow(' The next feature MUST start with `codeyam editor 1`.'));
2327
+ console.log(chalk.yellow(' Ask the user what they want to build. Once they tell you, run `codeyam editor 1` yourself.'));
2328
+ console.log(chalk.yellow(' NEVER tell the user to run `codeyam editor` commands — these are internal. Just ask what to build.'));
2265
2329
  console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
2266
2330
  stopGate(18, { confirm: true });
2267
2331
  }
@@ -2337,25 +2401,132 @@ async function handleAnalyzeImports(options = {}) {
2337
2401
  // Non-fatal — scenario file paths just won't be included
2338
2402
  }
2339
2403
  const progress = new ProgressReporter();
2340
- // Run data-structure-only analysis for all entities (glossary + pages)
2404
+ // Filter to only files that actually need analysis avoids re-analyzing
2405
+ // ~120 files when only 2 are incomplete.
2406
+ let targetFilePaths = filePaths;
2407
+ try {
2408
+ const { getDatabase } = await import('../../../packages/database/index.js');
2409
+ const db = getDatabase();
2410
+ const { requireBranchAndProject: rbp } = await import('../utils/database.js');
2411
+ const { project } = await rbp(JSON.parse(fs.readFileSync(path.join(root, '.codeyam', 'config.json'), 'utf8')).projectSlug);
2412
+ const { filterToIncompleteFilePaths } = await import('../utils/editorAudit.js');
2413
+ const incomplete = await filterToIncompleteFilePaths(db, project.id, filePaths);
2414
+ if (incomplete.length < filePaths.length && incomplete.length > 0) {
2415
+ if (!options.silent) {
2416
+ console.log(chalk.dim(`Skipping ${filePaths.length - incomplete.length} already-analyzed files, analyzing ${incomplete.length} remaining...`));
2417
+ }
2418
+ targetFilePaths = incomplete;
2419
+ }
2420
+ else if (incomplete.length === 0) {
2421
+ if (!options.silent) {
2422
+ console.log(chalk.green('All entities already have analyses — nothing to do.'));
2423
+ }
2424
+ // Still need to run the import graph + backfill below
2425
+ targetFilePaths = [];
2426
+ }
2427
+ }
2428
+ catch {
2429
+ // Non-fatal — fall back to analyzing all files
2430
+ }
2431
+ // Run data-structure-only analysis for entities that need it.
2341
2432
  // Don't pass entityNames — entities may not exist yet (fresh clone).
2342
2433
  // The analyzer will discover and create them from file paths.
2343
- progress.start('Running import analysis for all entities...');
2344
- try {
2345
- await runAnalysisForEntities({
2346
- projectRoot: root,
2347
- filePaths,
2348
- progress,
2349
- onlyDataStructure: true,
2350
- });
2434
+ if (targetFilePaths.length > 0) {
2435
+ progress.start(`Running import analysis for ${targetFilePaths.length} entit${targetFilePaths.length !== 1 ? 'ies' : 'y'}...`);
2436
+ try {
2437
+ await runAnalysisForEntities({
2438
+ projectRoot: root,
2439
+ filePaths: targetFilePaths,
2440
+ progress,
2441
+ onlyDataStructure: true,
2442
+ });
2443
+ }
2444
+ catch (err) {
2445
+ progress.fail('Analysis failed');
2446
+ const msg = err instanceof Error ? err.message : String(err);
2447
+ console.error(chalk.red(`Error: ${msg}`));
2448
+ process.exit(1);
2449
+ }
2450
+ progress.succeed('Analysis complete');
2351
2451
  }
2352
- catch (err) {
2353
- progress.fail('Analysis failed');
2354
- const msg = err instanceof Error ? err.message : String(err);
2355
- console.error(chalk.red(`Error: ${msg}`));
2356
- process.exit(1);
2452
+ // Surface analysis errors if any occurred
2453
+ const errorReportPath = path.join(root, '.codeyam', 'analysis-errors.txt');
2454
+ if (fs.existsSync(errorReportPath)) {
2455
+ if (!options.silent) {
2456
+ console.log(chalk.yellow('\nSome entity analyses failed. Full details in .codeyam/analysis-errors.txt'));
2457
+ console.log(chalk.yellow('Send this file to the CodeYam team for diagnosis.'));
2458
+ console.log(chalk.dim('Affected entities will be missing from the import graph.'));
2459
+ }
2460
+ // Write structured analysis-failures.json for the audit to detect.
2461
+ // Parse the error report to find which entities failed, and cross-reference
2462
+ // with the files we tried to analyze.
2463
+ try {
2464
+ const { readAnalysisFailures, writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
2465
+ const errorContent = fs.readFileSync(errorReportPath, 'utf8');
2466
+ const existingFailures = readAnalysisFailures(root);
2467
+ // Parse error messages to identify failed file paths.
2468
+ // Error format: "Error on step <StepName> for entity <Name> (<filePath>)"
2469
+ // or more generic "CodeYam Error: <message>"
2470
+ const entityErrorRegex = /(?:Error on step \w+ for entity (\w+))|(?:entity[: ]+["']?(\w+)["']?)/gi;
2471
+ const failedEntityNames = new Set();
2472
+ let match;
2473
+ while ((match = entityErrorRegex.exec(errorContent)) !== null) {
2474
+ const name = match[1] || match[2];
2475
+ if (name)
2476
+ failedEntityNames.add(name);
2477
+ }
2478
+ // Map failed entity names back to file paths from targetFilePaths
2479
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
2480
+ let glossary = [];
2481
+ try {
2482
+ glossary = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
2483
+ }
2484
+ catch {
2485
+ // Non-fatal
2486
+ }
2487
+ // Also: any targetFilePaths that didn't produce entities are failures
2488
+ const now = new Date().toISOString();
2489
+ const updatedFailures = { ...existingFailures };
2490
+ if (failedEntityNames.size > 0) {
2491
+ for (const name of failedEntityNames) {
2492
+ const entry = glossary.find((e) => e.name === name);
2493
+ if (entry) {
2494
+ updatedFailures[entry.filePath] = {
2495
+ entityName: name,
2496
+ error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
2497
+ failedAt: now,
2498
+ };
2499
+ }
2500
+ }
2501
+ }
2502
+ else if (targetFilePaths.length > 0) {
2503
+ // Couldn't parse specific entity names — mark all target files
2504
+ // that we attempted to analyze as potentially failed
2505
+ for (const fp of targetFilePaths) {
2506
+ const entry = glossary.find((e) => e.filePath === fp);
2507
+ updatedFailures[fp] = {
2508
+ entityName: entry?.name || path.basename(fp, path.extname(fp)),
2509
+ error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
2510
+ failedAt: now,
2511
+ };
2512
+ }
2513
+ }
2514
+ writeAnalysisFailures(root, updatedFailures);
2515
+ }
2516
+ catch {
2517
+ // Non-fatal — failure tracking is best-effort
2518
+ }
2519
+ }
2520
+ else {
2521
+ // No errors — clear any stale failure tracking
2522
+ try {
2523
+ const { writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
2524
+ writeAnalysisFailures(root, {});
2525
+ }
2526
+ catch {
2527
+ // Non-fatal
2528
+ }
2357
2529
  }
2358
- progress.succeed('Analysis complete');
2359
2530
  // Load entities WITH metadata from database
2360
2531
  progress.start('Loading entity metadata...');
2361
2532
  await initializeEnvironment();
@@ -2705,11 +2876,15 @@ async function handleRegister(jsonArg) {
2705
2876
  }
2706
2877
  // Normalize to array for uniform handling
2707
2878
  const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
2879
+ const root = getProjectRoot();
2708
2880
  const port = getServerPort();
2709
2881
  const url = `http://localhost:${port}/api/editor-register-scenario`;
2710
2882
  const isBatch = items.length > 1;
2711
2883
  let succeeded = 0;
2712
2884
  let failed = 0;
2885
+ let warnings = 0;
2886
+ const issues = [];
2887
+ const screenshotEntries = [];
2713
2888
  for (let i = 0; i < items.length; i++) {
2714
2889
  const body = items[i];
2715
2890
  const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
@@ -2737,6 +2912,12 @@ async function handleRegister(jsonArg) {
2737
2912
  else {
2738
2913
  parts.push(`errors=0`);
2739
2914
  }
2915
+ if (data.visibleText) {
2916
+ const truncated = data.visibleText.length > 100
2917
+ ? data.visibleText.substring(0, 97) + '...'
2918
+ : data.visibleText;
2919
+ parts.push(`visibleText="${truncated}"`);
2920
+ }
2740
2921
  if (data.seedResult) {
2741
2922
  if (data.seedResult.success) {
2742
2923
  parts.push(`seed=ok`);
@@ -2772,12 +2953,29 @@ async function handleRegister(jsonArg) {
2772
2953
  }
2773
2954
  console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
2774
2955
  }
2956
+ const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
2775
2957
  if (!res.ok) {
2776
2958
  console.error(chalk.dim(JSON.stringify(data, null, 2)));
2777
2959
  failed++;
2778
2960
  }
2779
2961
  else {
2962
+ if (resultIssues.length > 0)
2963
+ warnings++;
2780
2964
  succeeded++;
2965
+ // Collect screenshot paths for duplicate detection
2966
+ if (data.screenshotCaptured &&
2967
+ data.scenario?.screenshotPath &&
2968
+ data.scenario?.name) {
2969
+ const absPath = path.join(root, '.codeyam', 'editor-scenarios', data.scenario.screenshotPath);
2970
+ screenshotEntries.push({
2971
+ scenarioName: data.scenario.name,
2972
+ screenshotAbsPath: absPath,
2973
+ });
2974
+ }
2975
+ }
2976
+ // Collect issues from this registration
2977
+ for (const issue of resultIssues) {
2978
+ issues.push(`"${issue.scenarioName}": ${issue.message}`);
2781
2979
  }
2782
2980
  }
2783
2981
  catch (error) {
@@ -2788,10 +2986,50 @@ async function handleRegister(jsonArg) {
2788
2986
  failed++;
2789
2987
  }
2790
2988
  }
2989
+ // Detect duplicate screenshots in the batch
2990
+ if (isBatch && screenshotEntries.length > 1) {
2991
+ const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
2992
+ const hashEntries = screenshotEntries
2993
+ .map((e) => {
2994
+ const hash = computeScreenshotHash(e.screenshotAbsPath);
2995
+ return hash
2996
+ ? {
2997
+ scenarioName: e.scenarioName,
2998
+ screenshotPath: e.screenshotAbsPath,
2999
+ hash,
3000
+ }
3001
+ : null;
3002
+ })
3003
+ .filter((e) => e !== null);
3004
+ const duplicates = findDuplicateScreenshots(hashEntries);
3005
+ if (duplicates.size > 0) {
3006
+ console.log('');
3007
+ console.log(chalk.yellow.bold('WARNING: Identical screenshots detected:'));
3008
+ for (const [, names] of duplicates) {
3009
+ const quoted = names.map((n) => `"${n}"`);
3010
+ console.log(chalk.yellow(` ${quoted.join(' and ')} produced the same screenshot`));
3011
+ }
3012
+ console.log(chalk.yellow(" This usually means the app's view state depends on something scenarios can't control"));
3013
+ console.log(chalk.yellow(' (e.g., a hardcoded function return value, or identical localStorage not differentiating views).'));
3014
+ }
3015
+ }
2791
3016
  if (isBatch) {
2792
- console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
3017
+ console.log('');
3018
+ if (failed > 0 || warnings > 0) {
3019
+ console.log(chalk.red.bold(`ERROR: ${failed} failed, ${warnings} with warnings out of ${items.length} scenarios`));
3020
+ console.log('');
3021
+ console.log(chalk.red.bold('Issues that MUST be fixed:'));
3022
+ for (const issue of issues) {
3023
+ console.log(chalk.red(` ✗ ${issue}`));
3024
+ }
3025
+ console.log('');
3026
+ console.log(chalk.red('Do NOT skip these errors. Fix each issue and re-register the affected scenarios.'));
3027
+ }
3028
+ else {
3029
+ console.log(chalk.green(`✓ Batch complete: ${succeeded}/${items.length} succeeded with no issues`));
3030
+ }
2793
3031
  }
2794
- if (failed > 0) {
3032
+ if (failed > 0 || warnings > 0) {
2795
3033
  process.exit(1);
2796
3034
  }
2797
3035
  }
@@ -3003,6 +3241,8 @@ function handleChange(feature) {
3003
3241
  console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
3004
3242
  printDimensionGuidance(dim, dimNames);
3005
3243
  console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
3244
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
3245
+ console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
3006
3246
  console.log();
3007
3247
  console.log(chalk.bold('0. Close the results panel:'));
3008
3248
  checkbox(`Hide results: \`codeyam editor hide-results\``);
@@ -3078,10 +3318,11 @@ function handleChange(feature) {
3078
3318
  * Fetch the audit result from the server.
3079
3319
  * Returns null if the server is unreachable.
3080
3320
  */
3081
- async function fetchAuditResult() {
3321
+ async function fetchAuditResult(options) {
3082
3322
  const port = getServerPort();
3323
+ const params = options?.skipTests ? '?skipTests=true' : '';
3083
3324
  try {
3084
- const res = await fetch(`http://localhost:${port}/api/editor-audit`);
3325
+ const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
3085
3326
  if (!res.ok)
3086
3327
  return null;
3087
3328
  return await res.json();
@@ -3104,17 +3345,34 @@ async function checkAuditGate() {
3104
3345
  return true; // Server unreachable — don't block
3105
3346
  if (data.summary?.allPassing === true)
3106
3347
  return true;
3107
- // If incomplete entities are the only issue, auto-fix with analyze-imports (once)
3108
- const { isAutoRemediable } = await import('../utils/editorAudit.js');
3109
- if (isAutoRemediable(data.summary, false)) {
3348
+ // Lightweight auto-fix: backfill entity_sha (DB-only, fast).
3349
+ // Never run handleAnalyzeImports here it takes minutes on large projects.
3350
+ const { isOnlyIncompleteEntities, isOnlyPreExistingIncomplete } = await import('../utils/editorAudit.js');
3351
+ // If the only failures are pre-existing incomplete entities (not caused by
3352
+ // this session), don't block — the audit text already calls these "non-blocking".
3353
+ if (isOnlyPreExistingIncomplete(data.summary, data.incompleteEntities)) {
3354
+ return true;
3355
+ }
3356
+ if (isOnlyIncompleteEntities(data.summary)) {
3110
3357
  try {
3111
- await handleAnalyzeImports({ silent: true });
3358
+ const entities = await loadEntities({});
3359
+ if (entities && entities.length > 0) {
3360
+ const { getDatabase } = await import('../../../packages/database/index.js');
3361
+ const db = getDatabase();
3362
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3363
+ sha: e.sha,
3364
+ name: e.name,
3365
+ filePath: e.filePath || '',
3366
+ isDefaultExport: e.metadata?.notExported === false &&
3367
+ e.metadata?.namedExport === false,
3368
+ })));
3369
+ }
3112
3370
  }
3113
3371
  catch {
3114
- return false;
3372
+ // Fall through
3115
3373
  }
3116
- // Re-check after fix
3117
- const retry = await fetchAuditResult();
3374
+ // Re-check after backfill — skip re-running tests (backfill is DB-only)
3375
+ const retry = await fetchAuditResult({ skipTests: true });
3118
3376
  if (retry?.summary?.allPassing === true)
3119
3377
  return true;
3120
3378
  }
@@ -3137,6 +3395,8 @@ function printAuditGateFailures(data) {
3137
3395
  const missing = (data.components || []).filter((c) => c.status === 'missing');
3138
3396
  for (const c of missing) {
3139
3397
  issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
3398
+ if (c.hint)
3399
+ issues.push(` Fix: ${c.hint}`);
3140
3400
  }
3141
3401
  }
3142
3402
  if (s.componentsWithErrors > 0) {
@@ -3150,7 +3410,15 @@ function printAuditGateFailures(data) {
3150
3410
  issues.push(`${s.functionsMissing} function(s) missing test files`);
3151
3411
  const missing = (data.functions || []).filter((f) => f.status === 'missing');
3152
3412
  for (const f of missing) {
3153
- issues.push(` → ${f.name}${f.filePath ? ` (${f.filePath})` : ''}`);
3413
+ if (f.testFile) {
3414
+ issues.push(` → ${f.name} — test file missing: ${f.testFile}`);
3415
+ }
3416
+ else {
3417
+ issues.push(` → ${f.name} (${f.filePath}) — no testFile in glossary`);
3418
+ if (f.suggestedTestFile) {
3419
+ issues.push(` Fix: Create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to .codeyam/glossary.json`);
3420
+ }
3421
+ }
3154
3422
  }
3155
3423
  }
3156
3424
  if (s.functionsFailing > 0) {
@@ -3165,6 +3433,10 @@ function printAuditGateFailures(data) {
3165
3433
  const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
3166
3434
  for (const f of runnerErrors) {
3167
3435
  issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3436
+ if (f.hint)
3437
+ issues.push(` ${f.hint}`);
3438
+ else if (f.errorMessage)
3439
+ issues.push(` Error: ${f.errorMessage}`);
3168
3440
  }
3169
3441
  }
3170
3442
  if (s.functionsNameMismatch > 0) {
@@ -3172,13 +3444,36 @@ function printAuditGateFailures(data) {
3172
3444
  const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
3173
3445
  for (const f of mismatch) {
3174
3446
  issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3447
+ if (f.hint)
3448
+ issues.push(` Fix: ${f.hint}`);
3175
3449
  }
3176
3450
  }
3177
- if (s.missingFromGlossary > 0)
3451
+ if (s.missingFromGlossary > 0) {
3178
3452
  issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
3453
+ const missingGlossary = data.missingFromGlossary || [];
3454
+ for (const m of missingGlossary) {
3455
+ issues.push(` → ${m.name} (${m.filePath})`);
3456
+ }
3457
+ issues.push(` Fix: Run \`codeyam editor analyze-imports\` to add these files to the glossary`);
3458
+ }
3179
3459
  if (s.incompleteEntities > 0) {
3460
+ // Check for persistent analysis failures
3461
+ let analysisFailures = {};
3462
+ try {
3463
+ const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
3464
+ if (fs.existsSync(failuresPath)) {
3465
+ analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
3466
+ }
3467
+ }
3468
+ catch {
3469
+ // Non-fatal
3470
+ }
3180
3471
  const preCount = s.preExistingIncompleteEntities || 0;
3181
- if (preCount > 0 && preCount === s.incompleteEntities) {
3472
+ const hasFailures = Object.keys(analysisFailures).length > 0;
3473
+ if (hasFailures) {
3474
+ issues.push(`${s.incompleteEntities} entity/entities — automated analysis failed, manual analysis required`);
3475
+ }
3476
+ else if (preCount > 0 && preCount === s.incompleteEntities) {
3182
3477
  issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
3183
3478
  }
3184
3479
  else if (preCount > 0) {
@@ -3187,11 +3482,44 @@ function printAuditGateFailures(data) {
3187
3482
  else {
3188
3483
  issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
3189
3484
  }
3485
+ const incomplete = data.incompleteEntities || [];
3486
+ for (const e of incomplete) {
3487
+ const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
3488
+ if (failureEntry) {
3489
+ issues.push(` → ${e.name} — MANUAL ANALYSIS REQUIRED (automated analysis failed)`);
3490
+ issues.push(` Run \`codeyam editor audit\` for step-by-step manual analysis instructions`);
3491
+ }
3492
+ else {
3493
+ issues.push(` → ${e.name} (${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''})${e.preExisting ? ' [pre-existing]' : ''}`);
3494
+ }
3495
+ }
3496
+ if (!hasFailures) {
3497
+ issues.push(` Fix: Run \`codeyam editor analyze-imports\` to build import graphs`);
3498
+ }
3499
+ }
3500
+ if (s.unassociatedScenarios > 0) {
3501
+ issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
3502
+ const unassociated = data.unassociatedScenarios || [];
3503
+ for (const u of unassociated) {
3504
+ issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
3505
+ }
3190
3506
  }
3191
- if (s.miscategorizedScenarios > 0)
3507
+ if (s.miscategorizedScenarios > 0) {
3192
3508
  issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
3193
- if (s.scenariosNeedingRecapture > 0)
3509
+ const miscategorized = data.miscategorizedScenarios || [];
3510
+ for (const m of miscategorized) {
3511
+ issues.push(` → ${m.componentName} at ${m.url} (${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''})`);
3512
+ }
3513
+ issues.push(` Fix: Re-register these as app-level scenarios (with pageFilePath, no componentName) or use isolation routes`);
3514
+ }
3515
+ if (s.scenariosNeedingRecapture > 0) {
3194
3516
  issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
3517
+ const recapture = data.scenariosNeedingRecapture || [];
3518
+ for (const r of recapture) {
3519
+ issues.push(` → "${r.scenarioName}" (entity: ${r.entityName}, ${r.status?.status || 'changed'})`);
3520
+ }
3521
+ issues.push(` Fix: Re-capture affected scenarios to update screenshots after code changes`);
3522
+ }
3195
3523
  if (issues.length > 0) {
3196
3524
  console.error(chalk.yellow('\nAudit failures:'));
3197
3525
  for (const issue of issues) {
@@ -3232,30 +3560,45 @@ async function handleAudit() {
3232
3560
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3233
3561
  process.exit(1);
3234
3562
  }
3235
- // Auto-fix incomplete entities but only once.
3236
- // If analyze-imports runs and entities are STILL incomplete, report clearly
3237
- // instead of retrying. The analysis may have errors (e.g., stale entity
3238
- // references from refactoring) that can't be fixed by re-running.
3563
+ // Lightweight auto-fix: backfill entity_sha on scenarios that were
3564
+ // registered before entity records existed. This is fast (DB-only).
3565
+ // Import analysis is NOT triggered here it scans all files and takes
3566
+ // minutes on large projects. Users should run it separately if needed.
3239
3567
  const incompleteBeforeFix = data.incompleteEntities || [];
3568
+ const unassociatedBeforeFix = data.unassociatedScenarios || [];
3240
3569
  let autoRemediationFailed = false;
3241
- if (incompleteBeforeFix.length > 0) {
3242
- console.log(chalk.dim(`Running import analysis for ${incompleteBeforeFix.length} incomplete entit${incompleteBeforeFix.length !== 1 ? 'ies' : 'y'}...`));
3570
+ if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
3571
+ const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
3572
+ console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
3573
+ // Backfill entity_sha on scenarios that were registered before entities existed
3243
3574
  try {
3244
- await handleAnalyzeImports({ silent: true });
3575
+ const entities = await loadEntities({});
3576
+ if (entities && entities.length > 0) {
3577
+ const { getDatabase } = await import('../../../packages/database/index.js');
3578
+ const db = getDatabase();
3579
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3580
+ sha: e.sha,
3581
+ name: e.name,
3582
+ filePath: e.filePath || '',
3583
+ isDefaultExport: e.metadata?.notExported === false &&
3584
+ e.metadata?.namedExport === false,
3585
+ })));
3586
+ }
3245
3587
  }
3246
3588
  catch {
3247
- // Fall through — the audit will still report them as incomplete
3589
+ // Fall through — re-fetch will show remaining issues
3248
3590
  }
3249
- // Re-fetch audit results after the fix
3250
- data = await fetchAuditResult();
3591
+ // Re-fetch audit results after the backfill — skip re-running tests
3592
+ // since they haven't changed (backfill is DB-only).
3593
+ data = await fetchAuditResult({ skipTests: true });
3251
3594
  if (!data) {
3252
- console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
3595
+ console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
3253
3596
  process.exit(1);
3254
3597
  }
3255
- // If entities are still incomplete after running analyze-imports,
3256
- // flag it so we can show a clear message instead of looping
3598
+ // If issues persist after backfill, flag it so we show clear guidance
3257
3599
  const incompleteAfterFix = data.incompleteEntities || [];
3258
- if (incompleteAfterFix.length > 0) {
3600
+ const unassociatedAfterFix = data.unassociatedScenarios || [];
3601
+ if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
3259
3602
  autoRemediationFailed = true;
3260
3603
  }
3261
3604
  }
@@ -3289,6 +3632,9 @@ async function handleAudit() {
3289
3632
  }
3290
3633
  else {
3291
3634
  detail = chalk.red(' — no scenarios registered');
3635
+ if (c.hint) {
3636
+ detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
3637
+ }
3292
3638
  }
3293
3639
  // Show file path for failing components always, for OK only when name is ambiguous
3294
3640
  const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
@@ -3332,9 +3678,16 @@ async function handleAudit() {
3332
3678
  break;
3333
3679
  case 'missing':
3334
3680
  default:
3335
- detail = f.testFile
3336
- ? chalk.red(` — test file missing: ${f.testFile}`)
3337
- : chalk.red(' — no test file specified');
3681
+ if (f.testFile) {
3682
+ detail = chalk.red(` — test file missing: ${f.testFile}`);
3683
+ }
3684
+ else {
3685
+ detail = chalk.red(` — no test file specified in glossary`);
3686
+ detail += chalk.dim(` (source: ${f.filePath})`);
3687
+ if (f.suggestedTestFile) {
3688
+ detail += `\n ${chalk.yellow(`Fix: Either create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to this entry in .codeyam/glossary.json`)}`;
3689
+ }
3690
+ }
3338
3691
  break;
3339
3692
  }
3340
3693
  console.log(` ${icon} ${f.name}${detail}`);
@@ -3351,23 +3704,68 @@ async function handleAudit() {
3351
3704
  console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
3352
3705
  console.log();
3353
3706
  }
3354
- // Incomplete entities (scenarios without analyses)
3707
+ // Incomplete entities (scenarios without analyses) — report with guidance.
3708
+ // We intentionally do NOT run analysis here: it starts the analyzer
3709
+ // template which is slow even for a few files. Users should run
3710
+ // `codeyam editor analyze-imports` separately if needed.
3355
3711
  const incompleteEntities = data.incompleteEntities || [];
3356
3712
  if (incompleteEntities.length > 0) {
3713
+ const { formatIncompleteEntityGuidance, formatManualAnalysisGuidance } = await import('../utils/editorAudit.js');
3714
+ const { readAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
3715
+ // Check for persistent analysis failures
3716
+ const analysisFailures = readAnalysisFailures(getProjectRoot());
3357
3717
  console.log(chalk.bold('Incomplete entities (need import analysis):'));
3358
3718
  for (const e of incompleteEntities) {
3359
- console.log(` ${chalk.red('✗')} ${e.name} ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
3719
+ // Check if this entity has a persistent failure
3720
+ const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
3721
+ if (failureEntry) {
3722
+ // Show manual analysis instructions instead of "run analyze-imports"
3723
+ const filePath = Object.entries(analysisFailures).find(([, f]) => f.entityName === e.name)?.[0];
3724
+ const guidance = formatManualAnalysisGuidance({
3725
+ name: e.name,
3726
+ filePath: filePath || '',
3727
+ scenarioCount: e.scenarioCount,
3728
+ error: failureEntry.error,
3729
+ });
3730
+ // Print each line with proper indentation
3731
+ for (const line of guidance.split('\n')) {
3732
+ console.log(` ${chalk.red('✗')} ${line}`);
3733
+ }
3734
+ }
3735
+ else {
3736
+ const guidance = formatIncompleteEntityGuidance(e);
3737
+ console.log(` ${chalk.red('✗')} ${guidance}`);
3738
+ }
3739
+ }
3740
+ const incompleteErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
3741
+ if (fs.existsSync(incompleteErrorReportPath)) {
3742
+ console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
3743
+ }
3744
+ console.log();
3745
+ }
3746
+ // Unassociated scenarios (NULL entity_sha with file paths)
3747
+ const unassociatedScenarios = data.unassociatedScenarios || [];
3748
+ if (unassociatedScenarios.length > 0) {
3749
+ console.log(chalk.bold('Unassociated scenarios (missing entity link):'));
3750
+ for (const u of unassociatedScenarios) {
3751
+ console.log(` ${chalk.red('✗')} ${u.name} ${chalk.dim(`(${u.filePath})`)} — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''} with no entity_sha`);
3752
+ for (const sn of u.scenarioNames.slice(0, 3)) {
3753
+ console.log(chalk.dim(` "${sn}"`));
3754
+ }
3755
+ if (u.scenarioNames.length > 3) {
3756
+ console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
3757
+ }
3360
3758
  }
3361
3759
  if (autoRemediationFailed) {
3362
- console.log(chalk.red(' analyze-imports was run automatically but these entities are STILL incomplete.'));
3363
- console.log(chalk.red(' DO NOT re-run analyze-imports or the audit in a loop — the result will be the same.'));
3364
- console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
3365
- console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
3366
- console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
3367
- console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
3760
+ console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
3761
+ console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to see the full error output.'));
3368
3762
  }
3369
3763
  else {
3370
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
3764
+ console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to create entity records, then re-run audit to backfill.'));
3765
+ }
3766
+ const unassocErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
3767
+ if (fs.existsSync(unassocErrorReportPath)) {
3768
+ console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
3371
3769
  }
3372
3770
  console.log();
3373
3771
  }
@@ -3444,6 +3842,9 @@ async function handleAudit() {
3444
3842
  if (summary.incompleteEntities > 0) {
3445
3843
  parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
3446
3844
  }
3845
+ if (summary.unassociatedScenarios > 0) {
3846
+ parts.push(`${summary.unassociatedScenarios} component${summary.unassociatedScenarios !== 1 ? 's' : ''} with scenarios missing entity link`);
3847
+ }
3447
3848
  if (summary.miscategorizedScenarios > 0) {
3448
3849
  parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
3449
3850
  }
@@ -3456,17 +3857,6 @@ async function handleAudit() {
3456
3857
  if (!allOk) {
3457
3858
  process.exit(1);
3458
3859
  }
3459
- // Auto-run analyze-imports when audit passes — this builds the import graph
3460
- // so the App tab can show component/function dependencies for each page.
3461
- // Previously this was a manual step that Claude had to remember to run.
3462
- console.log(chalk.dim('Building import graph...'));
3463
- try {
3464
- await handleAnalyzeImports({ silent: true });
3465
- }
3466
- catch {
3467
- // Non-fatal — audit passed, import graph is a bonus
3468
- console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
3469
- }
3470
3860
  }
3471
3861
  // ─── Recapture-stale subcommand ────────────────────────────────────────
3472
3862
  /**
@@ -3673,14 +4063,14 @@ async function handleScenarioCoverage() {
3673
4063
  // Safety net: heal any scenarios with null entity_sha before checking coverage
3674
4064
  try {
3675
4065
  const { getDatabase } = await import('../../../packages/database/index.js');
3676
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
4066
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
3677
4067
  const db = getDatabase();
3678
4068
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
3679
4069
  if (backfillCount > 0) {
3680
4070
  console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
3681
4071
  await handleAnalyzeImports({ silent: true });
3682
4072
  // Run backfill after analysis
3683
- const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
4073
+ const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
3684
4074
  const entities = await loadEntities({});
3685
4075
  if (entities && entities.length > 0) {
3686
4076
  await backfillEntityShaOnScenarios(db, entities.map((e) => ({
@@ -4177,14 +4567,205 @@ function handleEditorDebug(args) {
4177
4567
  console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
4178
4568
  console.log();
4179
4569
  }
4570
+ // ─── Manual entity analysis ───────────────────────────────────────────
4571
+ /**
4572
+ * `codeyam editor manual-entity <JSON|@file>`
4573
+ *
4574
+ * Creates entity and analysis records from Claude-provided metadata when
4575
+ * automated analyze-imports fails. This unblocks the audit gate without
4576
+ * needing the analyzer template to parse the source file.
4577
+ */
4578
+ async function handleManualEntity(jsonArg) {
4579
+ const root = getProjectRoot();
4580
+ // Parse JSON input (supports @file.json convention)
4581
+ const parsed = parseRegisterArg(jsonArg);
4582
+ if (parsed.error || !parsed.body) {
4583
+ console.error(chalk.red(`Error: ${parsed.error || 'Invalid input'}`));
4584
+ console.error(chalk.dim(' Usage: codeyam editor manual-entity \'{"name":"Header","filePath":"src/components/Header.tsx","entityType":"visual",...}\''));
4585
+ console.error(chalk.dim(' Or: codeyam editor manual-entity @.codeyam/tmp/manual-entity.json'));
4586
+ process.exit(1);
4587
+ }
4588
+ const input = parsed.body;
4589
+ // Validate input
4590
+ const { validateManualEntityInput, convertTypeInfoToDataForMocks, readAnalysisFailures, clearFailureForPath, writeAnalysisFailures, } = await import('../utils/manualEntityAnalysis.js');
4591
+ // Read glossary for validation
4592
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
4593
+ let glossaryEntries = [];
4594
+ try {
4595
+ glossaryEntries = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
4596
+ }
4597
+ catch {
4598
+ // Empty glossary — validation will still check file existence
4599
+ }
4600
+ const errors = validateManualEntityInput(input, glossaryEntries, {
4601
+ fileExists: (p) => fs.existsSync(path.join(root, p)),
4602
+ });
4603
+ if (errors.length > 0) {
4604
+ console.error(chalk.red('Validation errors:'));
4605
+ for (const err of errors) {
4606
+ console.error(chalk.red(` • ${err}`));
4607
+ }
4608
+ process.exit(1);
4609
+ }
4610
+ // Read source file and compute SHA
4611
+ const sourceContent = fs.readFileSync(path.join(root, input.filePath), 'utf8');
4612
+ const { generateSha } = await import('../../../packages/database/index.js');
4613
+ const entitySha = generateSha(input.filePath, input.name, sourceContent);
4614
+ // Get project and branch
4615
+ await initializeEnvironment();
4616
+ const configPath = path.join(root, '.codeyam', 'config.json');
4617
+ let projectSlug;
4618
+ try {
4619
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
4620
+ projectSlug = config.projectSlug;
4621
+ }
4622
+ catch {
4623
+ console.error(chalk.red('Error: .codeyam/config.json not found or invalid.'));
4624
+ process.exit(1);
4625
+ }
4626
+ const { project, branch } = await requireBranchAndProject(projectSlug);
4627
+ const { getDatabase } = await import('../../../packages/database/index.js');
4628
+ const db = getDatabase();
4629
+ // Convert type info to dataForMocks format
4630
+ const dataForMocks = convertTypeInfoToDataForMocks(input.typeInfo);
4631
+ // Create entity record
4632
+ const entityMetadata = {
4633
+ importedExports: (input.importedExports || []).map((ie) => ({
4634
+ name: ie.name,
4635
+ filePath: ie.filePath,
4636
+ })),
4637
+ manuallyAnalyzed: true,
4638
+ };
4639
+ // Check if entity already exists
4640
+ const existingEntity = await db
4641
+ .selectFrom('entities')
4642
+ .select('sha')
4643
+ .where('sha', '=', entitySha)
4644
+ .executeTakeFirst();
4645
+ if (!existingEntity) {
4646
+ await db
4647
+ .insertInto('entities')
4648
+ .values({
4649
+ sha: entitySha,
4650
+ project_id: project.id,
4651
+ name: input.name,
4652
+ entity_type: input.entityType,
4653
+ file_path: input.filePath,
4654
+ metadata: JSON.stringify(entityMetadata),
4655
+ })
4656
+ .onConflict((oc) => oc.column('sha').doNothing())
4657
+ .execute();
4658
+ console.log(chalk.dim(`Created entity record: ${input.name} (${entitySha.slice(0, 8)}...)`));
4659
+ }
4660
+ else {
4661
+ // Update metadata on existing entity
4662
+ await db
4663
+ .updateTable('entities')
4664
+ .set({
4665
+ metadata: JSON.stringify(entityMetadata),
4666
+ })
4667
+ .where('sha', '=', entitySha)
4668
+ .execute();
4669
+ console.log(chalk.dim(`Updated existing entity: ${input.name} (${entitySha.slice(0, 8)}...)`));
4670
+ }
4671
+ // Create entity_branch record
4672
+ await db
4673
+ .insertInto('entity_branches')
4674
+ .values({
4675
+ entity_sha: entitySha,
4676
+ branch_id: branch.id,
4677
+ active: true,
4678
+ })
4679
+ .onConflict((oc) => oc.columns(['entity_sha', 'branch_id']).doUpdateSet({ active: true }))
4680
+ .execute();
4681
+ // Create analysis record
4682
+ const { randomUUID } = await import('crypto');
4683
+ const analysisId = randomUUID();
4684
+ const now = new Date().toISOString();
4685
+ const analysisMetadata = {
4686
+ scenariosDataStructure: {
4687
+ dataForMocks: Object.keys(dataForMocks).length > 0 ? dataForMocks : null,
4688
+ },
4689
+ mergedDataStructure: {
4690
+ dependencySchemas: null,
4691
+ },
4692
+ manuallyAnalyzed: true,
4693
+ };
4694
+ const analysisStatus = {
4695
+ startedAt: now,
4696
+ finishedAt: now,
4697
+ steps: [],
4698
+ errors: [],
4699
+ };
4700
+ // Check if analysis already exists for this entity
4701
+ const existingAnalysis = await db
4702
+ .selectFrom('analyses')
4703
+ .select('id')
4704
+ .where('entity_sha', '=', entitySha)
4705
+ .executeTakeFirst();
4706
+ if (!existingAnalysis) {
4707
+ await db
4708
+ .insertInto('analyses')
4709
+ .values({
4710
+ id: analysisId,
4711
+ project_id: project.id,
4712
+ entity_sha: entitySha,
4713
+ entity_name: input.name,
4714
+ entity_type: input.entityType,
4715
+ file_path: input.filePath,
4716
+ status: JSON.stringify(analysisStatus),
4717
+ metadata: JSON.stringify(analysisMetadata),
4718
+ })
4719
+ .execute();
4720
+ console.log(chalk.dim(`Created analysis record: ${analysisId.slice(0, 8)}...`));
4721
+ }
4722
+ else {
4723
+ // Update existing analysis with manual metadata
4724
+ await db
4725
+ .updateTable('analyses')
4726
+ .set({
4727
+ metadata: JSON.stringify(analysisMetadata),
4728
+ status: JSON.stringify(analysisStatus),
4729
+ })
4730
+ .where('entity_sha', '=', entitySha)
4731
+ .execute();
4732
+ console.log(chalk.dim(`Updated existing analysis for ${input.name}`));
4733
+ }
4734
+ // Backfill entity_sha on scenarios
4735
+ const backfillResult = await backfillEntityShaOnScenarios(db, [
4736
+ {
4737
+ sha: entitySha,
4738
+ name: input.name,
4739
+ filePath: input.filePath,
4740
+ isDefaultExport: false,
4741
+ },
4742
+ ]);
4743
+ if (backfillResult.updated > 0) {
4744
+ console.log(chalk.dim(`Linked ${backfillResult.updated} scenario(s) to entity`));
4745
+ }
4746
+ // Clear from analysis failures tracker
4747
+ const failures = readAnalysisFailures(root);
4748
+ if (failures[input.filePath]) {
4749
+ const updated = clearFailureForPath(failures, input.filePath);
4750
+ writeAnalysisFailures(root, updated);
4751
+ console.log(chalk.dim(`Cleared analysis failure for ${input.filePath}`));
4752
+ }
4753
+ console.log();
4754
+ console.log(chalk.green.bold(`✓ Manual entity analysis complete: ${input.name}`));
4755
+ console.log(chalk.dim(` Entity SHA: ${entitySha.slice(0, 12)}...`));
4756
+ console.log(chalk.dim(` Imports: ${(input.importedExports || []).length} glossary entities`));
4757
+ console.log(chalk.dim(` Type info: ${Object.keys(dataForMocks).length} fields`));
4758
+ console.log();
4759
+ console.log(chalk.dim('Re-run `codeyam editor audit` to verify.'));
4760
+ }
4180
4761
  // ─── Command definition ───────────────────────────────────────────────
4181
4762
  const editorCommand = {
4182
4763
  command: 'editor [step] [json]',
4183
4764
  describe: 'Editor mode guided workflow',
4184
4765
  builder: (yargs) => {
4185
4766
  const stepDescription = IS_INTERNAL_BUILD
4186
- ? '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)'
4187
- : '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)';
4767
+ ? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, 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)'
4768
+ : 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, 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)';
4188
4769
  let builder = yargs
4189
4770
  .positional('step', {
4190
4771
  type: 'string',
@@ -4245,6 +4826,18 @@ const editorCommand = {
4245
4826
  // API subcommands: preview, show-results, hide-results, commit,
4246
4827
  // journal, journal-update, dev-server, client-errors
4247
4828
  if (argv.step && EDITOR_API_SUBCOMMANDS.includes(argv.step)) {
4829
+ // Guard: commit requires step 16 to have been run first.
4830
+ // Without this, Claude shortcuts the process by running
4831
+ // `codeyam editor commit` directly from step 15, skipping
4832
+ // steps 16-18 entirely.
4833
+ if (argv.step === 'commit') {
4834
+ const state = readState(root);
4835
+ if (state && state.step !== 16) {
4836
+ console.error(chalk.red('Error: Run `codeyam editor 16` before committing.'));
4837
+ console.error(chalk.dim(` Current step: ${state.step} (${state.label || 'unknown'}). Step 16 (Commit) must be run first.`));
4838
+ process.exit(1);
4839
+ }
4840
+ }
4248
4841
  const port = getServerPort();
4249
4842
  try {
4250
4843
  const request = buildEditorApiRequest(argv.step, argv.json || undefined);
@@ -4288,11 +4881,58 @@ const editorCommand = {
4288
4881
  await handleGlossaryAdd(argv.json || '');
4289
4882
  return;
4290
4883
  }
4884
+ // Subcommand: codeyam editor task-ontrack
4885
+ // Corrective command when Claude advanced without creating a task.
4886
+ if (argv.step === 'task-ontrack') {
4887
+ const state = readState(root);
4888
+ if (!state?.step) {
4889
+ console.error(chalk.red('No editor state found. Run `codeyam editor 1` to start.'));
4890
+ process.exit(1);
4891
+ }
4892
+ const currentLabel = STEP_LABELS[state.step] || `Step ${state.step}`;
4893
+ const totalSteps = Object.keys(STEP_LABELS).length;
4894
+ const nextLabel = state.step < totalSteps ? STEP_LABELS[state.step + 1] : undefined;
4895
+ console.log();
4896
+ console.log(chalk.bold.yellow('━━━ GETTING BACK ON TRACK ━━━'));
4897
+ console.log();
4898
+ console.log(chalk.yellow('You went off-track by not creating a task. Create the task below to continue.'));
4899
+ console.log();
4900
+ // Print the TASK directive for the current step
4901
+ let taskTitle;
4902
+ if (state.step < totalSteps) {
4903
+ taskTitle = `Complete codeyam editor step ${state.step}: '${currentLabel}' and move on to step ${state.step + 1}: '${nextLabel}'`;
4904
+ }
4905
+ else {
4906
+ taskTitle =
4907
+ 'Ask user what to build next and restart codeyam editor workflow';
4908
+ }
4909
+ console.log(chalk.bold.cyan('━━━ TASK ━━━'));
4910
+ console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
4911
+ console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
4912
+ console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
4913
+ console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
4914
+ console.log();
4915
+ console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
4916
+ console.log();
4917
+ console.log(chalk.green(`After creating the task, re-run: codeyam editor ${state.step + 1}`));
4918
+ console.log();
4919
+ // Mark task as expected-created so the next step can proceed.
4920
+ // The hook will set taskCreated=true when it sees the actual TaskCreate call.
4921
+ // But we also allow advancement now since task-ontrack itself is the corrective action.
4922
+ const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
4923
+ fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
4924
+ return;
4925
+ }
4291
4926
  // Subcommand: codeyam editor analyze-imports
4292
4927
  if (argv.step === 'analyze-imports') {
4293
4928
  await handleAnalyzeImports();
4294
4929
  return;
4295
4930
  }
4931
+ // Subcommand: codeyam editor manual-entity <JSON|@file>
4932
+ if (argv.step === 'manual-entity') {
4933
+ await handleManualEntity(argv.json || '');
4934
+ return;
4935
+ }
4296
4936
  // Subcommand: codeyam editor dependents <EntityName>
4297
4937
  if (argv.step === 'dependents') {
4298
4938
  await handleDependents(argv.json || '');
@@ -4533,13 +5173,24 @@ const editorCommand = {
4533
5173
  try {
4534
5174
  const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4535
5175
  const seedDb = getDb();
4536
- const appScenario = await seedDb
5176
+ // Prefer the home page scenario (url "/") since the App tab
5177
+ // sorts "Home" first — fall back to any application scenario.
5178
+ const homeScenario = await seedDb
4537
5179
  .selectFrom('editor_scenarios')
4538
5180
  .select(['id', 'name', 'type'])
4539
5181
  .where('project_id', '=', project.id)
4540
- .where('type', '=', 'application')
5182
+ .where('url', '=', '/')
5183
+ .where('component_name', 'is', null)
4541
5184
  .orderBy('created_at', 'asc')
4542
5185
  .executeTakeFirst();
5186
+ const appScenario = homeScenario ||
5187
+ (await seedDb
5188
+ .selectFrom('editor_scenarios')
5189
+ .select(['id', 'name', 'type'])
5190
+ .where('project_id', '=', project.id)
5191
+ .where('type', '=', 'application')
5192
+ .orderBy('created_at', 'asc')
5193
+ .executeTakeFirst());
4543
5194
  if (appScenario) {
4544
5195
  const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
4545
5196
  if (fs.existsSync(seedFile)) {
@@ -4682,7 +5333,7 @@ const editorCommand = {
4682
5333
  if (!needsAnalysis) {
4683
5334
  try {
4684
5335
  const { getDatabase } = await import('../../../packages/database/index.js');
4685
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
5336
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
4686
5337
  const db = getDatabase();
4687
5338
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
4688
5339
  if (backfillCount > 0) {
@@ -4721,7 +5372,7 @@ const editorCommand = {
4721
5372
  .execute();
4722
5373
  if (unresolved.length > 0) {
4723
5374
  const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
4724
- const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
5375
+ const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
4725
5376
  const { allFiles: pfpFiles } = scanPfp(projectRoot);
4726
5377
  let pfpResolved = 0;
4727
5378
  for (const row of unresolved) {
@@ -4804,7 +5455,7 @@ const editorCommand = {
4804
5455
  // Step 1 is planning-only and may not persist state (no --feature flag).
4805
5456
  const skipValidation = step === 2 && argv.feature;
4806
5457
  if (!skipValidation) {
4807
- const stepError = validateStepTransition(step, state?.step ?? null);
5458
+ const stepError = validateStepTransition(step, state?.step ?? null, state?.startedAt, root);
4808
5459
  if (stepError) {
4809
5460
  console.error(chalk.red(`Error: ${stepError}`));
4810
5461
  process.exit(1);
@@ -4858,7 +5509,6 @@ const editorCommand = {
4858
5509
  if (!auditOk) {
4859
5510
  // checkAuditGate() already printed specific failure details above
4860
5511
  console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
4861
- console.error(chalk.yellow('DO NOT re-run the audit in a loop — fix the underlying issues first.'));
4862
5512
  process.exit(1);
4863
5513
  }
4864
5514
  }