@codeyam/codeyam-cli 0.1.19 → 0.1.20

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 (68) hide show
  1. package/analyzer-template/.build-info.json +7 -7
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +1 -1
  4. package/analyzer-template/packages/analyze/src/lib/files/analyze/findOrCreateEntity.ts +9 -6
  5. package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +9 -12
  6. package/analyzer-template/packages/database/package.json +1 -1
  7. package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +19 -15
  8. package/analyzer-template/packages/database/src/lib/loadEntity.ts +8 -4
  9. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.d.ts.map +1 -1
  10. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js +1 -1
  11. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js.map +1 -1
  12. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts.map +1 -1
  13. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js +1 -1
  14. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js.map +1 -1
  15. package/analyzer-template/project/analyzeFileEntities.ts +26 -0
  16. package/background/src/lib/virtualized/project/analyzeFileEntities.js +22 -0
  17. package/background/src/lib/virtualized/project/analyzeFileEntities.js.map +1 -1
  18. package/codeyam-cli/src/commands/editor.js +252 -62
  19. package/codeyam-cli/src/commands/editor.js.map +1 -1
  20. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +273 -1
  21. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  22. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +20 -0
  23. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  24. package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js +84 -0
  25. package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js.map +1 -0
  26. package/codeyam-cli/src/utils/analysisRunner.js +8 -6
  27. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  28. package/codeyam-cli/src/utils/editorAudit.js +73 -4
  29. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  30. package/codeyam-cli/src/utils/editorScenarios.js +36 -4
  31. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  32. package/codeyam-cli/src/utils/queue/job.js +6 -3
  33. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  34. package/codeyam-cli/src/utils/screenshotHash.js +26 -0
  35. package/codeyam-cli/src/utils/screenshotHash.js.map +1 -0
  36. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +28 -1
  37. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -1
  38. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +22 -2
  39. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
  40. package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js +57 -1
  41. package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js.map +1 -1
  42. package/codeyam-cli/src/webserver/app/lib/clientErrors.js +12 -0
  43. package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
  44. package/codeyam-cli/src/webserver/build/client/assets/{editor.entity.(_sha)-CGzKlIHg.js → editor.entity.(_sha)-DII1pg_z.js} +3 -3
  45. package/codeyam-cli/src/webserver/build/client/assets/{manifest-2ef99f38.js → manifest-cdf2c0a7.js} +1 -1
  46. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-B_PsTAb1.js +13 -0
  47. package/codeyam-cli/src/webserver/build/server/assets/{index-Cd-ufawF.js → index-CjLhfz6Z.js} +1 -1
  48. package/codeyam-cli/src/webserver/build/server/assets/{init-CzeBGOto.js → init-BEqlbI84.js} +1 -1
  49. package/codeyam-cli/src/webserver/build/server/assets/{server-build-Dht7CKXY.js → server-build-YI63xTu4.js} +94 -93
  50. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  51. package/codeyam-cli/src/webserver/build-info.json +5 -5
  52. package/codeyam-cli/src/webserver/scripts/journalCapture.ts +17 -0
  53. package/codeyam-cli/src/webserver/server.js +52 -14
  54. package/codeyam-cli/src/webserver/server.js.map +1 -1
  55. package/codeyam-cli/src/webserver/terminalServer.js +51 -10
  56. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  57. package/codeyam-cli/templates/editor-step-hook.py +21 -0
  58. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +19 -1
  59. package/package.json +1 -1
  60. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +2 -2
  61. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
  62. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +9 -7
  63. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
  64. package/packages/database/src/lib/loadAnalysis.js +1 -1
  65. package/packages/database/src/lib/loadAnalysis.js.map +1 -1
  66. package/packages/database/src/lib/loadEntity.js +1 -1
  67. package/packages/database/src/lib/loadEntity.js.map +1 -1
  68. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-BPmOG9bE.js +0 -13
@@ -121,6 +121,13 @@ function writeState(root, state) {
121
121
  const dir = path.dirname(statePath);
122
122
  fs.mkdirSync(dir, { recursive: true });
123
123
  fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
124
+ // Write task tracking file — marks that a task is expected for this step.
125
+ // The step hook sets taskCreated=true when it sees a TaskCreate tool call.
126
+ // Steps 1 and below don't require tasks (feature not named yet).
127
+ if (state.step && state.step >= 2) {
128
+ const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
129
+ fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: false }, null, 2), 'utf8');
130
+ }
124
131
  }
125
132
  /**
126
133
  * Clear the editor state (for starting a new feature).
@@ -248,11 +255,15 @@ function printAppScenarioInstructions(pageName, route) {
248
255
  console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
249
256
  console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
250
257
  }
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'));
258
+ console.log();
259
+ console.log(chalk.bold('Register ALL scenarios at once (bulk registration):'));
260
+ console.log(chalk.dim(' Write an array of scenario objects to a temp file:'));
261
+ console.log(chalk.dim(' [{"name":"...","type":"application","url":"/","seed":{...}}, {"name":"...","url":"/other",...}]'));
262
+ console.log(chalk.dim(' Then register all at once: codeyam editor register @.codeyam/tmp/scenarios.json'));
263
+ console.log(chalk.yellow(' Bulk registration is preferred — faster and avoids repeated capture overhead.'));
264
+ console.log();
254
265
  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`');
266
+ checkbox('After registration, check the response for `clientErrors`');
256
267
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
257
268
  console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
258
269
  }
@@ -353,6 +364,10 @@ function printComponentCaptureInstructions() {
353
364
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
354
365
  console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
355
366
  console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
367
+ console.log();
368
+ console.log(chalk.bold(' Bulk registration: write all component scenarios to a temp file as an array:'));
369
+ console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/codeyam-isolate/Comp?s=Default"}, ...]'));
370
+ console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
356
371
  }
357
372
  /**
358
373
  * Print a section header.
@@ -393,9 +408,31 @@ function printProgressTracker(current) {
393
408
  *
394
409
  * Options:
395
410
  * - confirm: true → step requires user confirmation before proceeding (steps 1, 3, 11)
411
+ * - feature: string → feature name for task directive
396
412
  */
397
413
  function stopGate(current, opts) {
398
- console.log();
414
+ const totalSteps = Object.keys(STEP_LABELS).length;
415
+ const currentLabel = STEP_LABELS[current] || `Step ${current}`;
416
+ const nextLabel = current < totalSteps ? STEP_LABELS[current + 1] : undefined;
417
+ console.log();
418
+ // ━━━ TASK ━━━ — deterministic task lifecycle directive
419
+ // Skip step 1 (no feature name yet — task starts at step 2)
420
+ if (current >= 2) {
421
+ console.log(chalk.bold.cyan('━━━ TASK ━━━'));
422
+ console.log(chalk.cyan('Mark your current task (if any) as complete.'));
423
+ console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
424
+ console.log();
425
+ let taskTitle;
426
+ if (current < totalSteps) {
427
+ taskTitle = `Complete codeyam editor step ${current}: '${currentLabel}' and move on to step ${current + 1}: '${nextLabel}'`;
428
+ }
429
+ else {
430
+ taskTitle =
431
+ 'Ask user what to build next and restart codeyam editor workflow';
432
+ }
433
+ console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
434
+ console.log();
435
+ }
399
436
  console.log(chalk.bold.red('━━━ STOP ━━━'));
400
437
  console.log();
401
438
  console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
@@ -404,20 +441,12 @@ function stopGate(current, opts) {
404
441
  console.log(chalk.yellow('Wait for user confirmation before moving on.'));
405
442
  }
406
443
  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) {
444
+ if (current < totalSteps) {
414
445
  console.log(chalk.green('When done, run: ') +
415
446
  chalk.bold(`codeyam editor ${current + 1}`));
416
447
  }
417
448
  else {
418
- console.log(chalk.green('Feature complete! Run: ') +
419
- chalk.bold('codeyam editor steps') +
420
- chalk.green(' to start the next feature'));
449
+ console.log(chalk.green('Feature complete! Ask the user what to build next, then run: ') + chalk.bold('codeyam editor 1'));
421
450
  }
422
451
  console.log();
423
452
  }
@@ -785,9 +814,7 @@ function printCycleOverview(root, state) {
785
814
  console.log();
786
815
  console.log(chalk.green('Continue with: ') +
787
816
  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'));
817
+ 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
818
  console.log();
792
819
  console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
793
820
  chalk.bold('codeyam editor change'));
@@ -815,7 +842,8 @@ function printCycleOverview(root, state) {
815
842
  console.log(` ${chalk.bold.yellow('17')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
816
843
  console.log(` ${chalk.bold.yellow('18')} ${chalk.bold('Push')} — Push to remote`);
817
844
  console.log();
818
- console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
845
+ console.log(chalk.green('Start now: ') +
846
+ 'Ask the user what they want to build, then run `codeyam editor 1` (never expose this command to the user)');
819
847
  console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
820
848
  }
821
849
  console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
@@ -894,12 +922,6 @@ function printStep1(root, feature, options, userPrompt) {
894
922
  console.log();
895
923
  console.log(chalk.yellow('Wait for user confirmation before moving on.'));
896
924
  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
925
  console.log(chalk.green('When done, run: ') +
904
926
  chalk.bold('codeyam editor 2 --feature "Feature Name"'));
905
927
  console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
@@ -927,13 +949,6 @@ function printStep2(root, feature) {
927
949
  if (isResuming) {
928
950
  printResumptionHeader(2);
929
951
  }
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
952
  console.log('Get the project ready to build.');
938
953
  console.log();
939
954
  // If no project exists yet, include scaffolding instructions first
@@ -1012,6 +1027,13 @@ function printStep3(root, feature) {
1012
1027
  console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
1013
1028
  console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
1014
1029
  console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
1030
+ console.log();
1031
+ console.log(chalk.bold.cyan('Make seed data visually rich:'));
1032
+ console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
1033
+ console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
1034
+ console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1035
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1036
+ console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
1015
1037
  }
1016
1038
  checkbox('Verify the dev server shows the changes');
1017
1039
  checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
@@ -1051,6 +1073,8 @@ function printStep3(root, feature) {
1051
1073
  console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
1052
1074
  console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
1053
1075
  printDimensionGuidance(dim, dimNames);
1076
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
1077
+ console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
1054
1078
  console.log();
1055
1079
  stopGate(3);
1056
1080
  }
@@ -1147,6 +1171,8 @@ function printStep5(root, feature) {
1147
1171
  printDimensionGuidance(dim, dimNames);
1148
1172
  console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
1149
1173
  console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
1174
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
1175
+ console.log(chalk.red(' The preview only updates when you explicitly call `codeyam editor preview`. Verify, then describe.'));
1150
1176
  console.log();
1151
1177
  console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
1152
1178
  checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
@@ -1335,6 +1361,7 @@ function printStep10(root, feature) {
1335
1361
  console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
1336
1362
  console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
1337
1363
  console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
1364
+ console.log(chalk.cyan(' • Use real image URLs (Unsplash, Pravatar) — not broken placeholders or empty strings'));
1338
1365
  console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
1339
1366
  console.log();
1340
1367
  console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
@@ -1549,7 +1576,7 @@ function printStep15(root, feature) {
1549
1576
  console.log(chalk.bold('Checklist:'));
1550
1577
  checkbox(`Show the results panel: \`codeyam editor show-results\``);
1551
1578
  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.'));
1579
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1553
1580
  console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
1554
1581
  checkbox('Write a 1-2 sentence summary of what was built');
1555
1582
  checkbox('Report test count and audit status (one line)');
@@ -1789,7 +1816,7 @@ function printMigrateStep4(root) {
1789
1816
  checkbox('Check for client errors: `codeyam editor client-errors`');
1790
1817
  checkbox('If any issues: fix the scenario data, re-register affected scenarios');
1791
1818
  checkbox('Show results: `codeyam editor show-results`');
1792
- console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
1819
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1793
1820
  console.log();
1794
1821
  console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
1795
1822
  migrationStopGate(4, pageName, pageIndex, totalPages);
@@ -1956,7 +1983,7 @@ function printMigrateStep10(root) {
1956
1983
  console.log();
1957
1984
  console.log(chalk.bold('Checklist:'));
1958
1985
  checkbox('Show results: `codeyam editor show-results`');
1959
- console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
1986
+ console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
1960
1987
  checkbox('Write a 1-2 sentence summary of what was migrated');
1961
1988
  console.log();
1962
1989
  console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
@@ -2253,15 +2280,13 @@ function printStep18(root, feature) {
2253
2280
  console.log(chalk.dim(' If the user says yes, run: `git push`'));
2254
2281
  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
2282
  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.'));
2283
+ console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
2260
2284
  console.log();
2261
2285
  console.log(chalk.bold.yellow('IMPORTANT: After this step, the current feature is DONE.'));
2262
2286
  console.log(chalk.yellow(' Any new user request — even if it sounds related — is a NEW feature.'));
2263
2287
  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`.'));
2288
+ console.log(chalk.yellow(' Ask the user what they want to build. Once they tell you, run `codeyam editor 1` yourself.'));
2289
+ console.log(chalk.yellow(' NEVER tell the user to run `codeyam editor` commands — these are internal. Just ask what to build.'));
2265
2290
  console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
2266
2291
  stopGate(18, { confirm: true });
2267
2292
  }
@@ -2705,11 +2730,13 @@ async function handleRegister(jsonArg) {
2705
2730
  }
2706
2731
  // Normalize to array for uniform handling
2707
2732
  const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
2733
+ const root = getProjectRoot();
2708
2734
  const port = getServerPort();
2709
2735
  const url = `http://localhost:${port}/api/editor-register-scenario`;
2710
2736
  const isBatch = items.length > 1;
2711
2737
  let succeeded = 0;
2712
2738
  let failed = 0;
2739
+ const screenshotEntries = [];
2713
2740
  for (let i = 0; i < items.length; i++) {
2714
2741
  const body = items[i];
2715
2742
  const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
@@ -2737,6 +2764,12 @@ async function handleRegister(jsonArg) {
2737
2764
  else {
2738
2765
  parts.push(`errors=0`);
2739
2766
  }
2767
+ if (data.visibleText) {
2768
+ const truncated = data.visibleText.length > 100
2769
+ ? data.visibleText.substring(0, 97) + '...'
2770
+ : data.visibleText;
2771
+ parts.push(`visibleText="${truncated}"`);
2772
+ }
2740
2773
  if (data.seedResult) {
2741
2774
  if (data.seedResult.success) {
2742
2775
  parts.push(`seed=ok`);
@@ -2778,6 +2811,16 @@ async function handleRegister(jsonArg) {
2778
2811
  }
2779
2812
  else {
2780
2813
  succeeded++;
2814
+ // Collect screenshot paths for duplicate detection
2815
+ if (data.screenshotCaptured &&
2816
+ data.scenario?.screenshotPath &&
2817
+ data.scenario?.name) {
2818
+ const absPath = path.join(root, '.codeyam', 'editor-scenarios', data.scenario.screenshotPath);
2819
+ screenshotEntries.push({
2820
+ scenarioName: data.scenario.name,
2821
+ screenshotAbsPath: absPath,
2822
+ });
2823
+ }
2781
2824
  }
2782
2825
  }
2783
2826
  catch (error) {
@@ -2788,6 +2831,33 @@ async function handleRegister(jsonArg) {
2788
2831
  failed++;
2789
2832
  }
2790
2833
  }
2834
+ // Detect duplicate screenshots in the batch
2835
+ if (isBatch && screenshotEntries.length > 1) {
2836
+ const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash');
2837
+ const hashEntries = screenshotEntries
2838
+ .map((e) => {
2839
+ const hash = computeScreenshotHash(e.screenshotAbsPath);
2840
+ return hash
2841
+ ? {
2842
+ scenarioName: e.scenarioName,
2843
+ screenshotPath: e.screenshotAbsPath,
2844
+ hash,
2845
+ }
2846
+ : null;
2847
+ })
2848
+ .filter((e) => e !== null);
2849
+ const duplicates = findDuplicateScreenshots(hashEntries);
2850
+ if (duplicates.size > 0) {
2851
+ console.log('');
2852
+ console.log(chalk.yellow.bold('WARNING: Identical screenshots detected:'));
2853
+ for (const [, names] of duplicates) {
2854
+ const quoted = names.map((n) => `"${n}"`);
2855
+ console.log(chalk.yellow(` ${quoted.join(' and ')} produced the same screenshot`));
2856
+ }
2857
+ console.log(chalk.yellow(" This usually means the app's view state depends on something scenarios can't control"));
2858
+ console.log(chalk.yellow(' (e.g., a hardcoded function return value, or identical localStorage not differentiating views).'));
2859
+ }
2860
+ }
2791
2861
  if (isBatch) {
2792
2862
  console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
2793
2863
  }
@@ -3003,6 +3073,8 @@ function handleChange(feature) {
3003
3073
  console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
3004
3074
  printDimensionGuidance(dim, dimNames);
3005
3075
  console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
3076
+ console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
3077
+ console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
3006
3078
  console.log();
3007
3079
  console.log(chalk.bold('0. Close the results panel:'));
3008
3080
  checkbox(`Hide results: \`codeyam editor hide-results\``);
@@ -3104,14 +3176,33 @@ async function checkAuditGate() {
3104
3176
  return true; // Server unreachable — don't block
3105
3177
  if (data.summary?.allPassing === true)
3106
3178
  return true;
3107
- // If incomplete entities are the only issue, auto-fix with analyze-imports (once)
3179
+ // If incomplete entities / unassociated scenarios are the only issue,
3180
+ // auto-fix with analyze-imports + entity SHA backfill (once)
3108
3181
  const { isAutoRemediable } = await import('../utils/editorAudit.js');
3109
3182
  if (isAutoRemediable(data.summary, false)) {
3110
3183
  try {
3111
3184
  await handleAnalyzeImports({ silent: true });
3112
3185
  }
3113
3186
  catch {
3114
- return false;
3187
+ // Fall through to backfill attempt
3188
+ }
3189
+ // Backfill entity_sha on scenarios registered before entities existed
3190
+ try {
3191
+ const entities = await loadEntities({});
3192
+ if (entities && entities.length > 0) {
3193
+ const { getDatabase } = await import('../../../packages/database/index.js');
3194
+ const db = getDatabase();
3195
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3196
+ sha: e.sha,
3197
+ name: e.name,
3198
+ filePath: e.filePath || '',
3199
+ isDefaultExport: e.metadata?.notExported === false &&
3200
+ e.metadata?.namedExport === false,
3201
+ })));
3202
+ }
3203
+ }
3204
+ catch {
3205
+ // Fall through
3115
3206
  }
3116
3207
  // Re-check after fix
3117
3208
  const retry = await fetchAuditResult();
@@ -3188,6 +3279,13 @@ function printAuditGateFailures(data) {
3188
3279
  issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
3189
3280
  }
3190
3281
  }
3282
+ if (s.unassociatedScenarios > 0) {
3283
+ issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
3284
+ const unassociated = data.unassociatedScenarios || [];
3285
+ for (const u of unassociated) {
3286
+ issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
3287
+ }
3288
+ }
3191
3289
  if (s.miscategorizedScenarios > 0)
3192
3290
  issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
3193
3291
  if (s.scenariosNeedingRecapture > 0)
@@ -3232,30 +3330,50 @@ async function handleAudit() {
3232
3330
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3233
3331
  process.exit(1);
3234
3332
  }
3235
- // Auto-fix incomplete entities — but only once.
3236
- // If analyze-imports runs and entities are STILL incomplete, report clearly
3333
+ // Auto-fix incomplete entities and unassociated scenarios — but only once.
3334
+ // If analyze-imports + entity SHA sync runs and issues persist, report clearly
3237
3335
  // instead of retrying. The analysis may have errors (e.g., stale entity
3238
3336
  // references from refactoring) that can't be fixed by re-running.
3239
3337
  const incompleteBeforeFix = data.incompleteEntities || [];
3338
+ const unassociatedBeforeFix = data.unassociatedScenarios || [];
3240
3339
  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'}...`));
3340
+ if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
3341
+ const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
3342
+ console.log(chalk.dim(`Auto-fixing ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
3243
3343
  try {
3244
3344
  await handleAnalyzeImports({ silent: true });
3245
3345
  }
3246
3346
  catch {
3247
- // Fall through — the audit will still report them as incomplete
3347
+ // Fall through — the audit will still report them
3348
+ }
3349
+ // Backfill entity_sha on scenarios that were registered before entities existed
3350
+ try {
3351
+ const entities = await loadEntities({});
3352
+ if (entities && entities.length > 0) {
3353
+ const { getDatabase } = await import('../../../packages/database/index.js');
3354
+ const db = getDatabase();
3355
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3356
+ sha: e.sha,
3357
+ name: e.name,
3358
+ filePath: e.filePath || '',
3359
+ isDefaultExport: e.metadata?.notExported === false &&
3360
+ e.metadata?.namedExport === false,
3361
+ })));
3362
+ }
3363
+ }
3364
+ catch {
3365
+ // Fall through — re-fetch will show remaining issues
3248
3366
  }
3249
3367
  // Re-fetch audit results after the fix
3250
3368
  data = await fetchAuditResult();
3251
3369
  if (!data) {
3252
- console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
3370
+ console.error(chalk.red('Error: Could not reach the CodeYam server after auto-fix.'));
3253
3371
  process.exit(1);
3254
3372
  }
3255
- // If entities are still incomplete after running analyze-imports,
3256
- // flag it so we can show a clear message instead of looping
3373
+ // If issues persist after auto-fix, flag it so we show clear guidance
3257
3374
  const incompleteAfterFix = data.incompleteEntities || [];
3258
- if (incompleteAfterFix.length > 0) {
3375
+ const unassociatedAfterFix = data.unassociatedScenarios || [];
3376
+ if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
3259
3377
  autoRemediationFailed = true;
3260
3378
  }
3261
3379
  }
@@ -3359,15 +3477,33 @@ async function handleAudit() {
3359
3477
  console.log(` ${chalk.red('✗')} ${e.name} — ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
3360
3478
  }
3361
3479
  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.'));
3480
+ console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
3481
+ console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to see the full error output.'));
3368
3482
  }
3369
3483
  else {
3370
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
3484
+ console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities.'));
3485
+ }
3486
+ console.log();
3487
+ }
3488
+ // Unassociated scenarios (NULL entity_sha with file paths)
3489
+ const unassociatedScenarios = data.unassociatedScenarios || [];
3490
+ if (unassociatedScenarios.length > 0) {
3491
+ console.log(chalk.bold('Unassociated scenarios (missing entity link):'));
3492
+ for (const u of unassociatedScenarios) {
3493
+ console.log(` ${chalk.red('✗')} ${u.name} ${chalk.dim(`(${u.filePath})`)} — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''} with no entity_sha`);
3494
+ for (const sn of u.scenarioNames.slice(0, 3)) {
3495
+ console.log(chalk.dim(` "${sn}"`));
3496
+ }
3497
+ if (u.scenarioNames.length > 3) {
3498
+ console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
3499
+ }
3500
+ }
3501
+ if (autoRemediationFailed) {
3502
+ console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
3503
+ console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to see the full error output.'));
3504
+ }
3505
+ else {
3506
+ console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to create entity records, then re-run audit to backfill.'));
3371
3507
  }
3372
3508
  console.log();
3373
3509
  }
@@ -3444,6 +3580,9 @@ async function handleAudit() {
3444
3580
  if (summary.incompleteEntities > 0) {
3445
3581
  parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
3446
3582
  }
3583
+ if (summary.unassociatedScenarios > 0) {
3584
+ parts.push(`${summary.unassociatedScenarios} component${summary.unassociatedScenarios !== 1 ? 's' : ''} with scenarios missing entity link`);
3585
+ }
3447
3586
  if (summary.miscategorizedScenarios > 0) {
3448
3587
  parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
3449
3588
  }
@@ -4183,8 +4322,8 @@ const editorCommand = {
4183
4322
  describe: 'Editor mode guided workflow',
4184
4323
  builder: (yargs) => {
4185
4324
  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)';
4325
+ ? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)'
4326
+ : 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)';
4188
4327
  let builder = yargs
4189
4328
  .positional('step', {
4190
4329
  type: 'string',
@@ -4245,6 +4384,18 @@ const editorCommand = {
4245
4384
  // API subcommands: preview, show-results, hide-results, commit,
4246
4385
  // journal, journal-update, dev-server, client-errors
4247
4386
  if (argv.step && EDITOR_API_SUBCOMMANDS.includes(argv.step)) {
4387
+ // Guard: commit requires step 16 to have been run first.
4388
+ // Without this, Claude shortcuts the process by running
4389
+ // `codeyam editor commit` directly from step 15, skipping
4390
+ // steps 16-18 entirely.
4391
+ if (argv.step === 'commit') {
4392
+ const state = readState(root);
4393
+ if (state && state.step !== 16) {
4394
+ console.error(chalk.red('Error: Run `codeyam editor 16` before committing.'));
4395
+ console.error(chalk.dim(` Current step: ${state.step} (${state.label || 'unknown'}). Step 16 (Commit) must be run first.`));
4396
+ process.exit(1);
4397
+ }
4398
+ }
4248
4399
  const port = getServerPort();
4249
4400
  try {
4250
4401
  const request = buildEditorApiRequest(argv.step, argv.json || undefined);
@@ -4288,6 +4439,46 @@ const editorCommand = {
4288
4439
  await handleGlossaryAdd(argv.json || '');
4289
4440
  return;
4290
4441
  }
4442
+ // Subcommand: codeyam editor task-ontrack
4443
+ // Corrective command when Claude advanced without creating a task.
4444
+ if (argv.step === 'task-ontrack') {
4445
+ const state = readState(root);
4446
+ if (!state?.step) {
4447
+ console.error(chalk.red('No editor state found. Run `codeyam editor 1` to start.'));
4448
+ process.exit(1);
4449
+ }
4450
+ const currentLabel = STEP_LABELS[state.step] || `Step ${state.step}`;
4451
+ const totalSteps = Object.keys(STEP_LABELS).length;
4452
+ const nextLabel = state.step < totalSteps ? STEP_LABELS[state.step + 1] : undefined;
4453
+ console.log();
4454
+ console.log(chalk.bold.yellow('━━━ GETTING BACK ON TRACK ━━━'));
4455
+ console.log();
4456
+ console.log(chalk.yellow('You went off-track by not creating a task. Create the task below to continue.'));
4457
+ console.log();
4458
+ // Print the TASK directive for the current step
4459
+ let taskTitle;
4460
+ if (state.step < totalSteps) {
4461
+ taskTitle = `Complete codeyam editor step ${state.step}: '${currentLabel}' and move on to step ${state.step + 1}: '${nextLabel}'`;
4462
+ }
4463
+ else {
4464
+ taskTitle =
4465
+ 'Ask user what to build next and restart codeyam editor workflow';
4466
+ }
4467
+ console.log(chalk.bold.cyan('━━━ TASK ━━━'));
4468
+ console.log(chalk.cyan('Mark your current task (if any) as complete.'));
4469
+ console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
4470
+ console.log();
4471
+ console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
4472
+ console.log();
4473
+ console.log(chalk.green(`After creating the task, re-run: codeyam editor ${state.step + 1}`));
4474
+ console.log();
4475
+ // Mark task as expected-created so the next step can proceed.
4476
+ // The hook will set taskCreated=true when it sees the actual TaskCreate call.
4477
+ // But we also allow advancement now since task-ontrack itself is the corrective action.
4478
+ const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
4479
+ fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
4480
+ return;
4481
+ }
4291
4482
  // Subcommand: codeyam editor analyze-imports
4292
4483
  if (argv.step === 'analyze-imports') {
4293
4484
  await handleAnalyzeImports();
@@ -4804,7 +4995,7 @@ const editorCommand = {
4804
4995
  // Step 1 is planning-only and may not persist state (no --feature flag).
4805
4996
  const skipValidation = step === 2 && argv.feature;
4806
4997
  if (!skipValidation) {
4807
- const stepError = validateStepTransition(step, state?.step ?? null);
4998
+ const stepError = validateStepTransition(step, state?.step ?? null, state?.startedAt, root);
4808
4999
  if (stepError) {
4809
5000
  console.error(chalk.red(`Error: ${stepError}`));
4810
5001
  process.exit(1);
@@ -4858,7 +5049,6 @@ const editorCommand = {
4858
5049
  if (!auditOk) {
4859
5050
  // checkAuditGate() already printed specific failure details above
4860
5051
  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
5052
  process.exit(1);
4863
5053
  }
4864
5054
  }