@codeyam/codeyam-cli 0.1.19 → 0.1.21

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 (107) 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 +5 -1
  5. package/analyzer-template/packages/analyze/src/lib/files/analyze/findOrCreateEntity.ts +10 -6
  6. package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +9 -12
  7. package/analyzer-template/packages/analyze/src/lib/files/analyzeChange.ts +4 -0
  8. package/analyzer-template/packages/analyze/src/lib/files/analyzeInitial.ts +4 -0
  9. package/analyzer-template/packages/aws/package.json +1 -1
  10. package/analyzer-template/packages/database/package.json +1 -1
  11. package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +19 -15
  12. package/analyzer-template/packages/database/src/lib/loadEntity.ts +19 -8
  13. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.d.ts.map +1 -1
  14. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js +1 -1
  15. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js.map +1 -1
  16. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts +4 -1
  17. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts.map +1 -1
  18. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js +5 -5
  19. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js.map +1 -1
  20. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts +3 -1
  21. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
  22. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +22 -1
  23. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  24. package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +27 -0
  25. package/analyzer-template/project/analyzeFileEntities.ts +26 -0
  26. package/background/src/lib/virtualized/project/analyzeFileEntities.js +22 -0
  27. package/background/src/lib/virtualized/project/analyzeFileEntities.js.map +1 -1
  28. package/codeyam-cli/src/commands/editor.js +412 -96
  29. package/codeyam-cli/src/commands/editor.js.map +1 -1
  30. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +895 -16
  31. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  32. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +20 -0
  33. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  34. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js +127 -0
  35. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js.map +1 -0
  36. package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js +84 -0
  37. package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js.map +1 -0
  38. package/codeyam-cli/src/utils/analysisRunner.js +8 -6
  39. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  40. package/codeyam-cli/src/utils/analyzer.js +8 -0
  41. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  42. package/codeyam-cli/src/utils/editorAudit.js +183 -13
  43. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  44. package/codeyam-cli/src/utils/editorScenarios.js +36 -4
  45. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  46. package/codeyam-cli/src/utils/queue/job.js +6 -3
  47. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  48. package/codeyam-cli/src/utils/registerScenarioResult.js +52 -0
  49. package/codeyam-cli/src/utils/registerScenarioResult.js.map +1 -0
  50. package/codeyam-cli/src/utils/screenshotHash.js +26 -0
  51. package/codeyam-cli/src/utils/screenshotHash.js.map +1 -0
  52. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +28 -1
  53. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -1
  54. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +22 -2
  55. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
  56. package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js +57 -1
  57. package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js.map +1 -1
  58. package/codeyam-cli/src/webserver/app/lib/clientErrors.js +15 -0
  59. package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
  60. package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-scenario-data-l0sNRNKZ.js +1 -0
  61. package/codeyam-cli/src/webserver/build/client/assets/api.editor-schema-l0sNRNKZ.js +1 -0
  62. package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CCKUIm0S.svg → cy-logo-cli-CJzc4vOH.svg} +2 -2
  63. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DODLxLcw.js +1 -0
  64. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-Dx-h1rJK.js +130 -0
  65. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-NTuLi4Xg.js +41 -0
  66. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-KTQuL0aj.js → entity._sha.scenarios._scenarioId.dev-BA5L8bU-.js} +1 -1
  67. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js → entity._sha.scenarios._scenarioId.fullscreen-D4dmRgvO.js} +1 -1
  68. package/codeyam-cli/src/webserver/build/client/assets/globals-BrPXT1iR.css +1 -0
  69. package/codeyam-cli/src/webserver/build/client/assets/manifest-5025e428.js +1 -0
  70. package/codeyam-cli/src/webserver/build/client/assets/{root-BxUQigda.js → root-BCx1S8Z3.js} +26 -13
  71. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-C1kjC9UJ.js +13 -0
  72. package/codeyam-cli/src/webserver/build/server/assets/{index-Cd-ufawF.js → index-C91yWWCI.js} +1 -1
  73. package/codeyam-cli/src/webserver/build/server/assets/{init-CzeBGOto.js → init-Dkas-RUS.js} +1 -1
  74. package/codeyam-cli/src/webserver/build/server/assets/server-build-pulXLTrG.js +640 -0
  75. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  76. package/codeyam-cli/src/webserver/build-info.json +5 -5
  77. package/codeyam-cli/src/webserver/scripts/journalCapture.ts +17 -0
  78. package/codeyam-cli/src/webserver/server.js +52 -14
  79. package/codeyam-cli/src/webserver/server.js.map +1 -1
  80. package/codeyam-cli/src/webserver/terminalServer.js +61 -13
  81. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  82. package/codeyam-cli/templates/editor-step-hook.py +21 -0
  83. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +19 -1
  84. package/package.json +1 -1
  85. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +4 -1
  86. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  87. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +3 -2
  88. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
  89. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +9 -7
  90. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
  91. package/packages/analyze/src/lib/files/analyzeChange.js +1 -0
  92. package/packages/analyze/src/lib/files/analyzeChange.js.map +1 -1
  93. package/packages/analyze/src/lib/files/analyzeInitial.js +1 -0
  94. package/packages/analyze/src/lib/files/analyzeInitial.js.map +1 -1
  95. package/packages/database/src/lib/loadAnalysis.js +1 -1
  96. package/packages/database/src/lib/loadAnalysis.js.map +1 -1
  97. package/packages/database/src/lib/loadEntity.js +5 -5
  98. package/packages/database/src/lib/loadEntity.js.map +1 -1
  99. package/packages/utils/src/lib/fs/rsyncCopy.js +22 -1
  100. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  101. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DcX-ZS3p.js +0 -1
  102. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-CGzKlIHg.js +0 -58
  103. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-oepecPae.js +0 -41
  104. package/codeyam-cli/src/webserver/build/client/assets/globals-Yn9W3zp3.css +0 -1
  105. package/codeyam-cli/src/webserver/build/client/assets/manifest-2ef99f38.js +0 -1
  106. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-BPmOG9bE.js +0 -13
  107. 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
  }
@@ -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":"/codeyam-isolate/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,31 @@ 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('Mark your current task (if any) as complete.'));
452
+ console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
453
+ console.log();
454
+ let taskTitle;
455
+ if (current < totalSteps) {
456
+ taskTitle = `Complete codeyam editor step ${current}: '${currentLabel}' and move on to step ${current + 1}: '${nextLabel}'`;
457
+ }
458
+ else {
459
+ taskTitle =
460
+ 'Ask user what to build next and restart codeyam editor workflow';
461
+ }
462
+ console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
463
+ console.log();
464
+ }
399
465
  console.log(chalk.bold.red('━━━ STOP ━━━'));
400
466
  console.log();
401
467
  console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
@@ -404,20 +470,12 @@ function stopGate(current, opts) {
404
470
  console.log(chalk.yellow('Wait for user confirmation before moving on.'));
405
471
  }
406
472
  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) {
473
+ if (current < totalSteps) {
414
474
  console.log(chalk.green('When done, run: ') +
415
475
  chalk.bold(`codeyam editor ${current + 1}`));
416
476
  }
417
477
  else {
418
- console.log(chalk.green('Feature complete! Run: ') +
419
- chalk.bold('codeyam editor steps') +
420
- chalk.green(' to start the next feature'));
478
+ console.log(chalk.green('Feature complete! Ask the user what to build next, then run: ') + chalk.bold('codeyam editor 1'));
421
479
  }
422
480
  console.log();
423
481
  }
@@ -785,9 +843,7 @@ function printCycleOverview(root, state) {
785
843
  console.log();
786
844
  console.log(chalk.green('Continue with: ') +
787
845
  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'));
846
+ 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
847
  console.log();
792
848
  console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
793
849
  chalk.bold('codeyam editor change'));
@@ -815,7 +871,8 @@ function printCycleOverview(root, state) {
815
871
  console.log(` ${chalk.bold.yellow('17')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
816
872
  console.log(` ${chalk.bold.yellow('18')} ${chalk.bold('Push')} — Push to remote`);
817
873
  console.log();
818
- console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
874
+ console.log(chalk.green('Start now: ') +
875
+ 'Ask the user what they want to build, then run `codeyam editor 1` (never expose this command to the user)');
819
876
  console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
820
877
  }
821
878
  console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
@@ -894,12 +951,6 @@ function printStep1(root, feature, options, userPrompt) {
894
951
  console.log();
895
952
  console.log(chalk.yellow('Wait for user confirmation before moving on.'));
896
953
  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
954
  console.log(chalk.green('When done, run: ') +
904
955
  chalk.bold('codeyam editor 2 --feature "Feature Name"'));
905
956
  console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
@@ -927,13 +978,6 @@ function printStep2(root, feature) {
927
978
  if (isResuming) {
928
979
  printResumptionHeader(2);
929
980
  }
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
981
  console.log('Get the project ready to build.');
938
982
  console.log();
939
983
  // If no project exists yet, include scaffolding instructions first
@@ -963,6 +1007,7 @@ function printStep2(root, feature) {
963
1007
  console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
964
1008
  console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
965
1009
  console.log();
1010
+ printDataStructureInstructions();
966
1011
  }
967
1012
  else {
968
1013
  console.log(chalk.bold('Prepare the database for this feature:'));
@@ -977,6 +1022,7 @@ function printStep2(root, feature) {
977
1022
  console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
978
1023
  console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
979
1024
  console.log();
1025
+ printDataStructureInstructions();
980
1026
  }
981
1027
  stopGate(2);
982
1028
  }
@@ -1012,6 +1058,13 @@ function printStep3(root, feature) {
1012
1058
  console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
1013
1059
  console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
1014
1060
  console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
1061
+ console.log();
1062
+ console.log(chalk.bold.cyan('Make seed data visually rich:'));
1063
+ console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
1064
+ console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
1065
+ console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1066
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1067
+ console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
1015
1068
  }
1016
1069
  checkbox('Verify the dev server shows the changes');
1017
1070
  checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
@@ -1051,6 +1104,8 @@ function printStep3(root, feature) {
1051
1104
  console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
1052
1105
  console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
1053
1106
  printDimensionGuidance(dim, dimNames);
1107
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
1108
+ console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
1054
1109
  console.log();
1055
1110
  stopGate(3);
1056
1111
  }
@@ -1147,6 +1202,8 @@ function printStep5(root, feature) {
1147
1202
  printDimensionGuidance(dim, dimNames);
1148
1203
  console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
1149
1204
  console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
1205
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
1206
+ console.log(chalk.red(' The preview only updates when you explicitly call `codeyam editor preview`. Verify, then describe.'));
1150
1207
  console.log();
1151
1208
  console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
1152
1209
  checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
@@ -1300,7 +1357,10 @@ function printStep9(root, feature) {
1300
1357
  console.log();
1301
1358
  checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
1302
1359
  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.'));
1360
+ console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
1361
+ console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
1362
+ console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
1363
+ console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
1304
1364
  console.log();
1305
1365
  stopGate(9);
1306
1366
  }
@@ -1335,6 +1395,7 @@ function printStep10(root, feature) {
1335
1395
  console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
1336
1396
  console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
1337
1397
  console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
1398
+ console.log(chalk.cyan(' • Use real image URLs (Unsplash, Pravatar) — not broken placeholders or empty strings'));
1338
1399
  console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
1339
1400
  console.log();
1340
1401
  console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
@@ -1549,7 +1610,7 @@ function printStep15(root, feature) {
1549
1610
  console.log(chalk.bold('Checklist:'));
1550
1611
  checkbox(`Show the results panel: \`codeyam editor show-results\``);
1551
1612
  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.'));
1613
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1553
1614
  console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
1554
1615
  checkbox('Write a 1-2 sentence summary of what was built');
1555
1616
  checkbox('Report test count and audit status (one line)');
@@ -1789,7 +1850,7 @@ function printMigrateStep4(root) {
1789
1850
  checkbox('Check for client errors: `codeyam editor client-errors`');
1790
1851
  checkbox('If any issues: fix the scenario data, re-register affected scenarios');
1791
1852
  checkbox('Show results: `codeyam editor show-results`');
1792
- console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
1853
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1793
1854
  console.log();
1794
1855
  console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
1795
1856
  migrationStopGate(4, pageName, pageIndex, totalPages);
@@ -1956,7 +2017,7 @@ function printMigrateStep10(root) {
1956
2017
  console.log();
1957
2018
  console.log(chalk.bold('Checklist:'));
1958
2019
  checkbox('Show results: `codeyam editor show-results`');
1959
- console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
2020
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1960
2021
  checkbox('Write a 1-2 sentence summary of what was migrated');
1961
2022
  console.log();
1962
2023
  console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
@@ -2253,15 +2314,13 @@ function printStep18(root, feature) {
2253
2314
  console.log(chalk.dim(' If the user says yes, run: `git push`'));
2254
2315
  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
2316
  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.'));
2317
+ console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
2260
2318
  console.log();
2261
2319
  console.log(chalk.bold.yellow('IMPORTANT: After this step, the current feature is DONE.'));
2262
2320
  console.log(chalk.yellow(' Any new user request — even if it sounds related — is a NEW feature.'));
2263
2321
  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`.'));
2322
+ console.log(chalk.yellow(' Ask the user what they want to build. Once they tell you, run `codeyam editor 1` yourself.'));
2323
+ console.log(chalk.yellow(' NEVER tell the user to run `codeyam editor` commands — these are internal. Just ask what to build.'));
2265
2324
  console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
2266
2325
  stopGate(18, { confirm: true });
2267
2326
  }
@@ -2337,25 +2396,54 @@ async function handleAnalyzeImports(options = {}) {
2337
2396
  // Non-fatal — scenario file paths just won't be included
2338
2397
  }
2339
2398
  const progress = new ProgressReporter();
2340
- // Run data-structure-only analysis for all entities (glossary + pages)
2341
- // Don't pass entityNames entities may not exist yet (fresh clone).
2342
- // The analyzer will discover and create them from file paths.
2343
- progress.start('Running import analysis for all entities...');
2399
+ // Filter to only files that actually need analysis avoids re-analyzing
2400
+ // ~120 files when only 2 are incomplete.
2401
+ let targetFilePaths = filePaths;
2344
2402
  try {
2345
- await runAnalysisForEntities({
2346
- projectRoot: root,
2347
- filePaths,
2348
- progress,
2349
- onlyDataStructure: true,
2350
- });
2403
+ const { getDatabase } = await import('../../../packages/database/index.js');
2404
+ const db = getDatabase();
2405
+ const { requireBranchAndProject: rbp } = await import('../utils/database.js');
2406
+ const { project } = await rbp(JSON.parse(fs.readFileSync(path.join(root, '.codeyam', 'config.json'), 'utf8')).projectSlug);
2407
+ const { filterToIncompleteFilePaths } = await import('../utils/editorAudit.js');
2408
+ const incomplete = await filterToIncompleteFilePaths(db, project.id, filePaths);
2409
+ if (incomplete.length < filePaths.length && incomplete.length > 0) {
2410
+ if (!options.silent) {
2411
+ console.log(chalk.dim(`Skipping ${filePaths.length - incomplete.length} already-analyzed files, analyzing ${incomplete.length} remaining...`));
2412
+ }
2413
+ targetFilePaths = incomplete;
2414
+ }
2415
+ else if (incomplete.length === 0) {
2416
+ if (!options.silent) {
2417
+ console.log(chalk.green('All entities already have analyses — nothing to do.'));
2418
+ }
2419
+ // Still need to run the import graph + backfill below
2420
+ targetFilePaths = [];
2421
+ }
2351
2422
  }
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);
2423
+ catch {
2424
+ // Non-fatal — fall back to analyzing all files
2425
+ }
2426
+ // Run data-structure-only analysis for entities that need it.
2427
+ // Don't pass entityNames — entities may not exist yet (fresh clone).
2428
+ // The analyzer will discover and create them from file paths.
2429
+ if (targetFilePaths.length > 0) {
2430
+ progress.start(`Running import analysis for ${targetFilePaths.length} entit${targetFilePaths.length !== 1 ? 'ies' : 'y'}...`);
2431
+ try {
2432
+ await runAnalysisForEntities({
2433
+ projectRoot: root,
2434
+ filePaths: targetFilePaths,
2435
+ progress,
2436
+ onlyDataStructure: true,
2437
+ });
2438
+ }
2439
+ catch (err) {
2440
+ progress.fail('Analysis failed');
2441
+ const msg = err instanceof Error ? err.message : String(err);
2442
+ console.error(chalk.red(`Error: ${msg}`));
2443
+ process.exit(1);
2444
+ }
2445
+ progress.succeed('Analysis complete');
2357
2446
  }
2358
- progress.succeed('Analysis complete');
2359
2447
  // Load entities WITH metadata from database
2360
2448
  progress.start('Loading entity metadata...');
2361
2449
  await initializeEnvironment();
@@ -2705,11 +2793,15 @@ async function handleRegister(jsonArg) {
2705
2793
  }
2706
2794
  // Normalize to array for uniform handling
2707
2795
  const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
2796
+ const root = getProjectRoot();
2708
2797
  const port = getServerPort();
2709
2798
  const url = `http://localhost:${port}/api/editor-register-scenario`;
2710
2799
  const isBatch = items.length > 1;
2711
2800
  let succeeded = 0;
2712
2801
  let failed = 0;
2802
+ let warnings = 0;
2803
+ const issues = [];
2804
+ const screenshotEntries = [];
2713
2805
  for (let i = 0; i < items.length; i++) {
2714
2806
  const body = items[i];
2715
2807
  const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
@@ -2737,6 +2829,12 @@ async function handleRegister(jsonArg) {
2737
2829
  else {
2738
2830
  parts.push(`errors=0`);
2739
2831
  }
2832
+ if (data.visibleText) {
2833
+ const truncated = data.visibleText.length > 100
2834
+ ? data.visibleText.substring(0, 97) + '...'
2835
+ : data.visibleText;
2836
+ parts.push(`visibleText="${truncated}"`);
2837
+ }
2740
2838
  if (data.seedResult) {
2741
2839
  if (data.seedResult.success) {
2742
2840
  parts.push(`seed=ok`);
@@ -2772,12 +2870,29 @@ async function handleRegister(jsonArg) {
2772
2870
  }
2773
2871
  console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
2774
2872
  }
2873
+ const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
2775
2874
  if (!res.ok) {
2776
2875
  console.error(chalk.dim(JSON.stringify(data, null, 2)));
2777
2876
  failed++;
2778
2877
  }
2779
2878
  else {
2879
+ if (resultIssues.length > 0)
2880
+ warnings++;
2780
2881
  succeeded++;
2882
+ // Collect screenshot paths for duplicate detection
2883
+ if (data.screenshotCaptured &&
2884
+ data.scenario?.screenshotPath &&
2885
+ data.scenario?.name) {
2886
+ const absPath = path.join(root, '.codeyam', 'editor-scenarios', data.scenario.screenshotPath);
2887
+ screenshotEntries.push({
2888
+ scenarioName: data.scenario.name,
2889
+ screenshotAbsPath: absPath,
2890
+ });
2891
+ }
2892
+ }
2893
+ // Collect issues from this registration
2894
+ for (const issue of resultIssues) {
2895
+ issues.push(`"${issue.scenarioName}": ${issue.message}`);
2781
2896
  }
2782
2897
  }
2783
2898
  catch (error) {
@@ -2788,10 +2903,50 @@ async function handleRegister(jsonArg) {
2788
2903
  failed++;
2789
2904
  }
2790
2905
  }
2906
+ // Detect duplicate screenshots in the batch
2907
+ if (isBatch && screenshotEntries.length > 1) {
2908
+ const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash');
2909
+ const hashEntries = screenshotEntries
2910
+ .map((e) => {
2911
+ const hash = computeScreenshotHash(e.screenshotAbsPath);
2912
+ return hash
2913
+ ? {
2914
+ scenarioName: e.scenarioName,
2915
+ screenshotPath: e.screenshotAbsPath,
2916
+ hash,
2917
+ }
2918
+ : null;
2919
+ })
2920
+ .filter((e) => e !== null);
2921
+ const duplicates = findDuplicateScreenshots(hashEntries);
2922
+ if (duplicates.size > 0) {
2923
+ console.log('');
2924
+ console.log(chalk.yellow.bold('WARNING: Identical screenshots detected:'));
2925
+ for (const [, names] of duplicates) {
2926
+ const quoted = names.map((n) => `"${n}"`);
2927
+ console.log(chalk.yellow(` ${quoted.join(' and ')} produced the same screenshot`));
2928
+ }
2929
+ console.log(chalk.yellow(" This usually means the app's view state depends on something scenarios can't control"));
2930
+ console.log(chalk.yellow(' (e.g., a hardcoded function return value, or identical localStorage not differentiating views).'));
2931
+ }
2932
+ }
2791
2933
  if (isBatch) {
2792
- console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
2934
+ console.log('');
2935
+ if (failed > 0 || warnings > 0) {
2936
+ console.log(chalk.red.bold(`ERROR: ${failed} failed, ${warnings} with warnings out of ${items.length} scenarios`));
2937
+ console.log('');
2938
+ console.log(chalk.red.bold('Issues that MUST be fixed:'));
2939
+ for (const issue of issues) {
2940
+ console.log(chalk.red(` ✗ ${issue}`));
2941
+ }
2942
+ console.log('');
2943
+ console.log(chalk.red('Do NOT skip these errors. Fix each issue and re-register the affected scenarios.'));
2944
+ }
2945
+ else {
2946
+ console.log(chalk.green(`✓ Batch complete: ${succeeded}/${items.length} succeeded with no issues`));
2947
+ }
2793
2948
  }
2794
- if (failed > 0) {
2949
+ if (failed > 0 || warnings > 0) {
2795
2950
  process.exit(1);
2796
2951
  }
2797
2952
  }
@@ -3003,6 +3158,8 @@ function handleChange(feature) {
3003
3158
  console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
3004
3159
  printDimensionGuidance(dim, dimNames);
3005
3160
  console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
3161
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
3162
+ console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
3006
3163
  console.log();
3007
3164
  console.log(chalk.bold('0. Close the results panel:'));
3008
3165
  checkbox(`Hide results: \`codeyam editor hide-results\``);
@@ -3104,16 +3261,33 @@ async function checkAuditGate() {
3104
3261
  return true; // Server unreachable — don't block
3105
3262
  if (data.summary?.allPassing === true)
3106
3263
  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)) {
3264
+ // Lightweight auto-fix: backfill entity_sha (DB-only, fast).
3265
+ // Never run handleAnalyzeImports here it takes minutes on large projects.
3266
+ const { isOnlyIncompleteEntities, isOnlyPreExistingIncomplete } = await import('../utils/editorAudit.js');
3267
+ // If the only failures are pre-existing incomplete entities (not caused by
3268
+ // this session), don't block — the audit text already calls these "non-blocking".
3269
+ if (isOnlyPreExistingIncomplete(data.summary, data.incompleteEntities)) {
3270
+ return true;
3271
+ }
3272
+ if (isOnlyIncompleteEntities(data.summary)) {
3110
3273
  try {
3111
- await handleAnalyzeImports({ silent: true });
3274
+ const entities = await loadEntities({});
3275
+ if (entities && entities.length > 0) {
3276
+ const { getDatabase } = await import('../../../packages/database/index.js');
3277
+ const db = getDatabase();
3278
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3279
+ sha: e.sha,
3280
+ name: e.name,
3281
+ filePath: e.filePath || '',
3282
+ isDefaultExport: e.metadata?.notExported === false &&
3283
+ e.metadata?.namedExport === false,
3284
+ })));
3285
+ }
3112
3286
  }
3113
3287
  catch {
3114
- return false;
3288
+ // Fall through
3115
3289
  }
3116
- // Re-check after fix
3290
+ // Re-check after backfill
3117
3291
  const retry = await fetchAuditResult();
3118
3292
  if (retry?.summary?.allPassing === true)
3119
3293
  return true;
@@ -3137,6 +3311,8 @@ function printAuditGateFailures(data) {
3137
3311
  const missing = (data.components || []).filter((c) => c.status === 'missing');
3138
3312
  for (const c of missing) {
3139
3313
  issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
3314
+ if (c.hint)
3315
+ issues.push(` Fix: ${c.hint}`);
3140
3316
  }
3141
3317
  }
3142
3318
  if (s.componentsWithErrors > 0) {
@@ -3150,7 +3326,15 @@ function printAuditGateFailures(data) {
3150
3326
  issues.push(`${s.functionsMissing} function(s) missing test files`);
3151
3327
  const missing = (data.functions || []).filter((f) => f.status === 'missing');
3152
3328
  for (const f of missing) {
3153
- issues.push(` → ${f.name}${f.filePath ? ` (${f.filePath})` : ''}`);
3329
+ if (f.testFile) {
3330
+ issues.push(` → ${f.name} — test file missing: ${f.testFile}`);
3331
+ }
3332
+ else {
3333
+ issues.push(` → ${f.name} (${f.filePath}) — no testFile in glossary`);
3334
+ if (f.suggestedTestFile) {
3335
+ issues.push(` Fix: Create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to .codeyam/glossary.json`);
3336
+ }
3337
+ }
3154
3338
  }
3155
3339
  }
3156
3340
  if (s.functionsFailing > 0) {
@@ -3188,6 +3372,13 @@ function printAuditGateFailures(data) {
3188
3372
  issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
3189
3373
  }
3190
3374
  }
3375
+ if (s.unassociatedScenarios > 0) {
3376
+ issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
3377
+ const unassociated = data.unassociatedScenarios || [];
3378
+ for (const u of unassociated) {
3379
+ issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
3380
+ }
3381
+ }
3191
3382
  if (s.miscategorizedScenarios > 0)
3192
3383
  issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
3193
3384
  if (s.scenariosNeedingRecapture > 0)
@@ -3232,30 +3423,44 @@ async function handleAudit() {
3232
3423
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3233
3424
  process.exit(1);
3234
3425
  }
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.
3426
+ // Lightweight auto-fix: backfill entity_sha on scenarios that were
3427
+ // registered before entity records existed. This is fast (DB-only).
3428
+ // We do NOT run handleAnalyzeImports here it scans all files and takes
3429
+ // minutes on large projects. Users should run it separately if needed.
3239
3430
  const incompleteBeforeFix = data.incompleteEntities || [];
3431
+ const unassociatedBeforeFix = data.unassociatedScenarios || [];
3240
3432
  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'}...`));
3433
+ if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
3434
+ const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
3435
+ console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
3436
+ // Backfill entity_sha on scenarios that were registered before entities existed
3243
3437
  try {
3244
- await handleAnalyzeImports({ silent: true });
3438
+ const entities = await loadEntities({});
3439
+ if (entities && entities.length > 0) {
3440
+ const { getDatabase } = await import('../../../packages/database/index.js');
3441
+ const db = getDatabase();
3442
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3443
+ sha: e.sha,
3444
+ name: e.name,
3445
+ filePath: e.filePath || '',
3446
+ isDefaultExport: e.metadata?.notExported === false &&
3447
+ e.metadata?.namedExport === false,
3448
+ })));
3449
+ }
3245
3450
  }
3246
3451
  catch {
3247
- // Fall through — the audit will still report them as incomplete
3452
+ // Fall through — re-fetch will show remaining issues
3248
3453
  }
3249
- // Re-fetch audit results after the fix
3454
+ // Re-fetch audit results after the backfill
3250
3455
  data = await fetchAuditResult();
3251
3456
  if (!data) {
3252
- console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
3457
+ console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
3253
3458
  process.exit(1);
3254
3459
  }
3255
- // If entities are still incomplete after running analyze-imports,
3256
- // flag it so we can show a clear message instead of looping
3460
+ // If issues persist after backfill, flag it so we show clear guidance
3257
3461
  const incompleteAfterFix = data.incompleteEntities || [];
3258
- if (incompleteAfterFix.length > 0) {
3462
+ const unassociatedAfterFix = data.unassociatedScenarios || [];
3463
+ if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
3259
3464
  autoRemediationFailed = true;
3260
3465
  }
3261
3466
  }
@@ -3289,6 +3494,9 @@ async function handleAudit() {
3289
3494
  }
3290
3495
  else {
3291
3496
  detail = chalk.red(' — no scenarios registered');
3497
+ if (c.hint) {
3498
+ detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
3499
+ }
3292
3500
  }
3293
3501
  // Show file path for failing components always, for OK only when name is ambiguous
3294
3502
  const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
@@ -3332,9 +3540,16 @@ async function handleAudit() {
3332
3540
  break;
3333
3541
  case 'missing':
3334
3542
  default:
3335
- detail = f.testFile
3336
- ? chalk.red(` — test file missing: ${f.testFile}`)
3337
- : chalk.red(' — no test file specified');
3543
+ if (f.testFile) {
3544
+ detail = chalk.red(` — test file missing: ${f.testFile}`);
3545
+ }
3546
+ else {
3547
+ detail = chalk.red(` — no test file specified in glossary`);
3548
+ detail += chalk.dim(` (source: ${f.filePath})`);
3549
+ if (f.suggestedTestFile) {
3550
+ detail += `\n ${chalk.yellow(`Fix: Either create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to this entry in .codeyam/glossary.json`)}`;
3551
+ }
3552
+ }
3338
3553
  break;
3339
3554
  }
3340
3555
  console.log(` ${icon} ${f.name}${detail}`);
@@ -3351,23 +3566,70 @@ async function handleAudit() {
3351
3566
  console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
3352
3567
  console.log();
3353
3568
  }
3354
- // Incomplete entities (scenarios without analyses)
3355
- const incompleteEntities = data.incompleteEntities || [];
3569
+ // Incomplete entities (scenarios without analyses) — auto-fix by running
3570
+ // targeted analysis on just the affected files (fast: 2 files vs 117+).
3571
+ let incompleteEntities = data.incompleteEntities || [];
3356
3572
  if (incompleteEntities.length > 0) {
3573
+ const { getIncompleteEntityFilePaths } = await import('../utils/editorAudit.js');
3574
+ try {
3575
+ const { getDatabase } = await import('../../../packages/database/index.js');
3576
+ const db = getDatabase();
3577
+ const filePaths = await getIncompleteEntityFilePaths(db, incompleteEntities);
3578
+ if (filePaths.length > 0) {
3579
+ console.log(chalk.dim(`Running targeted import analysis for ${filePaths.length} incomplete entit${filePaths.length !== 1 ? 'ies' : 'y'}...`));
3580
+ const root = getProjectRoot();
3581
+ try {
3582
+ const { runAnalysisForEntities } = await import('../utils/analysisRunner.js');
3583
+ await runAnalysisForEntities({
3584
+ projectRoot: root,
3585
+ filePaths,
3586
+ progress: new ProgressReporter(),
3587
+ onlyDataStructure: true,
3588
+ });
3589
+ // Re-fetch audit results after targeted fix
3590
+ const refreshed = await fetchAuditResult();
3591
+ if (refreshed) {
3592
+ data = refreshed;
3593
+ incompleteEntities = data.incompleteEntities || [];
3594
+ }
3595
+ }
3596
+ catch {
3597
+ // Fall through — report remaining incomplete entities below
3598
+ }
3599
+ }
3600
+ }
3601
+ catch {
3602
+ // DB not available — fall through to reporting
3603
+ }
3604
+ }
3605
+ if (incompleteEntities.length > 0) {
3606
+ const { formatIncompleteEntityGuidance } = await import('../utils/editorAudit.js');
3357
3607
  console.log(chalk.bold('Incomplete entities (need import analysis):'));
3358
3608
  for (const e of incompleteEntities) {
3359
- console.log(` ${chalk.red('✗')} ${e.name} ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
3609
+ const guidance = formatIncompleteEntityGuidance(e);
3610
+ console.log(` ${chalk.red('✗')} ${guidance}`);
3611
+ }
3612
+ console.log();
3613
+ }
3614
+ // Unassociated scenarios (NULL entity_sha with file paths)
3615
+ const unassociatedScenarios = data.unassociatedScenarios || [];
3616
+ if (unassociatedScenarios.length > 0) {
3617
+ console.log(chalk.bold('Unassociated scenarios (missing entity link):'));
3618
+ for (const u of unassociatedScenarios) {
3619
+ console.log(` ${chalk.red('✗')} ${u.name} ${chalk.dim(`(${u.filePath})`)} — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''} with no entity_sha`);
3620
+ for (const sn of u.scenarioNames.slice(0, 3)) {
3621
+ console.log(chalk.dim(` "${sn}"`));
3622
+ }
3623
+ if (u.scenarioNames.length > 3) {
3624
+ console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
3625
+ }
3360
3626
  }
3361
3627
  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.'));
3628
+ console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
3629
+ console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to see the full error output.'));
3368
3630
  }
3369
3631
  else {
3370
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
3632
+ console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to create entity records, then re-run audit to backfill.'));
3371
3633
  }
3372
3634
  console.log();
3373
3635
  }
@@ -3444,6 +3706,9 @@ async function handleAudit() {
3444
3706
  if (summary.incompleteEntities > 0) {
3445
3707
  parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
3446
3708
  }
3709
+ if (summary.unassociatedScenarios > 0) {
3710
+ parts.push(`${summary.unassociatedScenarios} component${summary.unassociatedScenarios !== 1 ? 's' : ''} with scenarios missing entity link`);
3711
+ }
3447
3712
  if (summary.miscategorizedScenarios > 0) {
3448
3713
  parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
3449
3714
  }
@@ -4183,8 +4448,8 @@ const editorCommand = {
4183
4448
  describe: 'Editor mode guided workflow',
4184
4449
  builder: (yargs) => {
4185
4450
  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)';
4451
+ ? '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)'
4452
+ : '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)';
4188
4453
  let builder = yargs
4189
4454
  .positional('step', {
4190
4455
  type: 'string',
@@ -4245,6 +4510,18 @@ const editorCommand = {
4245
4510
  // API subcommands: preview, show-results, hide-results, commit,
4246
4511
  // journal, journal-update, dev-server, client-errors
4247
4512
  if (argv.step && EDITOR_API_SUBCOMMANDS.includes(argv.step)) {
4513
+ // Guard: commit requires step 16 to have been run first.
4514
+ // Without this, Claude shortcuts the process by running
4515
+ // `codeyam editor commit` directly from step 15, skipping
4516
+ // steps 16-18 entirely.
4517
+ if (argv.step === 'commit') {
4518
+ const state = readState(root);
4519
+ if (state && state.step !== 16) {
4520
+ console.error(chalk.red('Error: Run `codeyam editor 16` before committing.'));
4521
+ console.error(chalk.dim(` Current step: ${state.step} (${state.label || 'unknown'}). Step 16 (Commit) must be run first.`));
4522
+ process.exit(1);
4523
+ }
4524
+ }
4248
4525
  const port = getServerPort();
4249
4526
  try {
4250
4527
  const request = buildEditorApiRequest(argv.step, argv.json || undefined);
@@ -4288,6 +4565,46 @@ const editorCommand = {
4288
4565
  await handleGlossaryAdd(argv.json || '');
4289
4566
  return;
4290
4567
  }
4568
+ // Subcommand: codeyam editor task-ontrack
4569
+ // Corrective command when Claude advanced without creating a task.
4570
+ if (argv.step === 'task-ontrack') {
4571
+ const state = readState(root);
4572
+ if (!state?.step) {
4573
+ console.error(chalk.red('No editor state found. Run `codeyam editor 1` to start.'));
4574
+ process.exit(1);
4575
+ }
4576
+ const currentLabel = STEP_LABELS[state.step] || `Step ${state.step}`;
4577
+ const totalSteps = Object.keys(STEP_LABELS).length;
4578
+ const nextLabel = state.step < totalSteps ? STEP_LABELS[state.step + 1] : undefined;
4579
+ console.log();
4580
+ console.log(chalk.bold.yellow('━━━ GETTING BACK ON TRACK ━━━'));
4581
+ console.log();
4582
+ console.log(chalk.yellow('You went off-track by not creating a task. Create the task below to continue.'));
4583
+ console.log();
4584
+ // Print the TASK directive for the current step
4585
+ let taskTitle;
4586
+ if (state.step < totalSteps) {
4587
+ taskTitle = `Complete codeyam editor step ${state.step}: '${currentLabel}' and move on to step ${state.step + 1}: '${nextLabel}'`;
4588
+ }
4589
+ else {
4590
+ taskTitle =
4591
+ 'Ask user what to build next and restart codeyam editor workflow';
4592
+ }
4593
+ console.log(chalk.bold.cyan('━━━ TASK ━━━'));
4594
+ console.log(chalk.cyan('Mark your current task (if any) as complete.'));
4595
+ console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
4596
+ console.log();
4597
+ console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
4598
+ console.log();
4599
+ console.log(chalk.green(`After creating the task, re-run: codeyam editor ${state.step + 1}`));
4600
+ console.log();
4601
+ // Mark task as expected-created so the next step can proceed.
4602
+ // The hook will set taskCreated=true when it sees the actual TaskCreate call.
4603
+ // But we also allow advancement now since task-ontrack itself is the corrective action.
4604
+ const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
4605
+ fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
4606
+ return;
4607
+ }
4291
4608
  // Subcommand: codeyam editor analyze-imports
4292
4609
  if (argv.step === 'analyze-imports') {
4293
4610
  await handleAnalyzeImports();
@@ -4804,7 +5121,7 @@ const editorCommand = {
4804
5121
  // Step 1 is planning-only and may not persist state (no --feature flag).
4805
5122
  const skipValidation = step === 2 && argv.feature;
4806
5123
  if (!skipValidation) {
4807
- const stepError = validateStepTransition(step, state?.step ?? null);
5124
+ const stepError = validateStepTransition(step, state?.step ?? null, state?.startedAt, root);
4808
5125
  if (stepError) {
4809
5126
  console.error(chalk.red(`Error: ${stepError}`));
4810
5127
  process.exit(1);
@@ -4858,7 +5175,6 @@ const editorCommand = {
4858
5175
  if (!auditOk) {
4859
5176
  // checkAuditGate() already printed specific failure details above
4860
5177
  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
5178
  process.exit(1);
4863
5179
  }
4864
5180
  }