@codeyam/codeyam-cli 0.1.0-staging.b6c4c78 → 0.1.0-staging.b8b17a5

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 (101) hide show
  1. package/analyzer-template/.build-info.json +8 -8
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +26 -16
  4. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +0 -1
  5. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts.map +1 -1
  6. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js +28 -16
  7. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  8. package/codeyam-cli/src/cli.js +9 -0
  9. package/codeyam-cli/src/cli.js.map +1 -1
  10. package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js +51 -0
  11. package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js.map +1 -0
  12. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +11 -0
  13. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -1
  14. package/codeyam-cli/src/commands/editor.js +460 -65
  15. package/codeyam-cli/src/commands/editor.js.map +1 -1
  16. package/codeyam-cli/src/commands/editorIsolateArgs.js +25 -0
  17. package/codeyam-cli/src/commands/editorIsolateArgs.js.map +1 -0
  18. package/codeyam-cli/src/commands/telemetry.js +37 -0
  19. package/codeyam-cli/src/commands/telemetry.js.map +1 -0
  20. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +994 -1
  21. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  22. package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js +100 -0
  23. package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js.map +1 -0
  24. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +70 -0
  25. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -1
  26. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +97 -5
  27. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -1
  28. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +40 -1
  29. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -1
  30. package/codeyam-cli/src/utils/__tests__/editorMigration.test.js +5 -0
  31. package/codeyam-cli/src/utils/__tests__/editorMigration.test.js.map +1 -1
  32. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +354 -22
  33. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  34. package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js +143 -0
  35. package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js.map +1 -0
  36. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +28 -0
  37. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  38. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +40 -1
  39. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
  40. package/codeyam-cli/src/utils/__tests__/telemetry.test.js +159 -0
  41. package/codeyam-cli/src/utils/__tests__/telemetry.test.js.map +1 -0
  42. package/codeyam-cli/src/utils/editorAudit.js +179 -1
  43. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  44. package/codeyam-cli/src/utils/editorDeleteScenario.js +67 -0
  45. package/codeyam-cli/src/utils/editorDeleteScenario.js.map +1 -0
  46. package/codeyam-cli/src/utils/editorEntityChangeStatus.js +13 -7
  47. package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -1
  48. package/codeyam-cli/src/utils/editorEntityHelpers.js +18 -3
  49. package/codeyam-cli/src/utils/editorEntityHelpers.js.map +1 -1
  50. package/codeyam-cli/src/utils/editorLoaderHelpers.js +14 -2
  51. package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -1
  52. package/codeyam-cli/src/utils/editorMigration.js +1 -1
  53. package/codeyam-cli/src/utils/editorMigration.js.map +1 -1
  54. package/codeyam-cli/src/utils/editorScenarios.js +150 -2
  55. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  56. package/codeyam-cli/src/utils/editorSeedAdapter.js +70 -0
  57. package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
  58. package/codeyam-cli/src/utils/entityChangeStatus.js +8 -2
  59. package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
  60. package/codeyam-cli/src/utils/fileWatcher.js +38 -0
  61. package/codeyam-cli/src/utils/fileWatcher.js.map +1 -1
  62. package/codeyam-cli/src/utils/scenariosManifest.js +15 -10
  63. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  64. package/codeyam-cli/src/utils/telemetry.js +106 -0
  65. package/codeyam-cli/src/utils/telemetry.js.map +1 -0
  66. package/codeyam-cli/src/utils/telemetryMiddleware.js +22 -0
  67. package/codeyam-cli/src/utils/telemetryMiddleware.js.map +1 -0
  68. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +35 -0
  69. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -0
  70. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +61 -0
  71. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
  72. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DYqG1D_d.js +58 -0
  73. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DggyRwOr.js +41 -0
  74. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-BOi8kpwd.js → entity._sha.scenarios._scenarioId.dev-D1eikpe1.js} +1 -1
  75. package/codeyam-cli/src/webserver/build/client/assets/globals-DRvOjyO3.css +1 -0
  76. package/codeyam-cli/src/webserver/build/client/assets/{manifest-5f1c29f5.js → manifest-f4212c17.js} +1 -1
  77. package/codeyam-cli/src/webserver/build/client/assets/{root-BBCQJ_ZM.js → root-F-k2uYj5.js} +15 -15
  78. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-if8kM_1Q.js +13 -0
  79. package/codeyam-cli/src/webserver/build/server/assets/{index-BLKsJR3o.js → index-CHymws6l.js} +1 -1
  80. package/codeyam-cli/src/webserver/build/server/assets/init-D3HkMDbI.js +10 -0
  81. package/codeyam-cli/src/webserver/build/server/assets/progress-CHTtrxFG.js +1 -0
  82. package/codeyam-cli/src/webserver/build/server/assets/server-build-DTCzJQiH.js +551 -0
  83. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  84. package/codeyam-cli/src/webserver/build-info.json +5 -5
  85. package/codeyam-cli/src/webserver/editorProxy.js +78 -3
  86. package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
  87. package/codeyam-cli/src/webserver/server.js +32 -0
  88. package/codeyam-cli/src/webserver/server.js.map +1 -1
  89. package/codeyam-cli/src/webserver/terminalServer.js +7 -2
  90. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  91. package/codeyam-cli/templates/editor-step-hook.py +7 -0
  92. package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +1 -1
  93. package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +1 -1
  94. package/package.json +2 -1
  95. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +28 -16
  96. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  97. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-y_5LB2iU.js +0 -58
  98. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DBa7T2FK.js +0 -41
  99. package/codeyam-cli/src/webserver/build/client/assets/globals-BCTpZEY8.css +0 -1
  100. package/codeyam-cli/src/webserver/build/server/assets/init-C2iMAqYu.js +0 -10
  101. package/codeyam-cli/src/webserver/build/server/assets/server-build-DR42Xd5a.js +0 -489
@@ -16,8 +16,9 @@ import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
16
16
  import { getProjectRoot as getStateProjectRoot } from "../state.js";
17
17
  import initCommand from "./init.js";
18
18
  import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
19
- import { clearEditorState, clearEditorUserPrompt, validateStepTransition, } from "../utils/editorScenarios.js";
20
- import { validateSeedData, detectSeedAdapter, } from "../utils/editorSeedAdapter.js";
19
+ import { clearEditorState, clearEditorUserPrompt, validateStepTransition, backfillEntityShaOnScenarios, } from "../utils/editorScenarios.js";
20
+ import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } from "../utils/editorSeedAdapter.js";
21
+ import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
21
22
  import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
22
23
  import { parseRegisterArg } from "../utils/parseRegisterArg.js";
23
24
  import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
@@ -207,20 +208,27 @@ function printAppScenarioInstructions(pageName, route) {
207
208
  checkbox('Check existing scenarios: `codeyam editor scenarios`');
208
209
  console.log(chalk.dim(' Review existing scenarios — reuse and update their data rather than'));
209
210
  console.log(chalk.dim(' creating duplicates. Re-register with the same name to update a scenario.'));
210
- checkbox('Cover key data states (at least 2-3 scenarios per page)');
211
- console.log(chalk.yellow(' Each page needs its own scenarios — a detail page needs different data than the catalog'));
211
+ console.log();
212
+ console.log(chalk.bold.yellow('App scenarios vs component scenarios — these are DIFFERENT:'));
213
+ console.log(chalk.yellow(' App scenarios: show the FULL PAGE with seeded data at a real route (/, /library, etc.)'));
214
+ console.log(chalk.yellow(' Component scenarios: show ONE COMPONENT in isolation at /isolated-components/...'));
215
+ console.log(chalk.yellow(' This step is about APP scenarios. Do NOT set "componentName" — that makes it a component scenario.'));
216
+ console.log();
217
+ checkbox('Identify every page/route in the app and ensure each has app-level scenarios');
218
+ console.log(chalk.dim(" Check the app's router/entry points for all distinct pages"));
219
+ console.log(chalk.yellow(' Every page needs at least 2-3 app scenarios — not just the main page'));
212
220
  if (pageName) {
213
221
  console.log(chalk.dim(` Example: "${pageName} - Full Data", "${pageName} - Empty State"`));
214
222
  }
215
223
  else {
216
- console.log(chalk.dim(' Catalog: "Full Catalog", "Empty Catalog", "Single Category"'));
217
- console.log(chalk.dim(' Detail: "Detail - With Reviews", "Detail - No Reviews", "Detail - High Rated"'));
224
+ console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
218
225
  }
219
226
  console.log();
220
- checkbox('Register each scenario with type "application" and the real page url:');
221
- console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data","type":"application","url":"${route || '/'}",...}'`));
222
- console.log(chalk.yellow(' IMPORTANT: "url" MUST be the real page route without it, the wrong page gets screenshotted.'));
223
- console.log(chalk.yellow(' IMPORTANT: include "pageFilePath" with the source file that renders this route (e.g., "src/App.tsx").'));
227
+ checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
228
+ console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
229
+ console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
230
+ console.log(chalk.yellow(' Required fields: "type":"application", "url" (real route), "pageFilePath" (source file)'));
231
+ console.log(chalk.yellow(' Do NOT include "componentName" — that would make it a component scenario'));
224
232
  console.log();
225
233
  checkbox('Choose the right data approach for scenarios:');
226
234
  console.log(chalk.dim(' With seed adapter: add "seed":{...} to populate the database for each state'));
@@ -323,7 +331,8 @@ function printComponentCaptureInstructions() {
323
331
  console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
324
332
  console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
325
333
  console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
326
- console.log(chalk.dim(' url is a PATH (starts with /) — the proxy routes it and intercepts API calls'));
334
+ console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/codeyam-isolate/... or /isolated-components/...).'));
335
+ console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
327
336
  console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
328
337
  console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
329
338
  console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
@@ -930,13 +939,29 @@ function printStep2(root, feature) {
930
939
  console.log();
931
940
  console.log(chalk.bold('Build the feature:'));
932
941
  }
942
+ else {
943
+ console.log(chalk.bold('Prepare the database for this feature:'));
944
+ checkbox('List existing scenarios: `codeyam editor scenarios`');
945
+ console.log(chalk.dim(' Review existing scenarios to find seed data that best demonstrates the feature.'));
946
+ console.log(chalk.dim(" Don't create throwaway seed data — use what's already been curated."));
947
+ checkbox('Pick the scenario with seed data most relevant to this feature');
948
+ console.log(chalk.dim(" Think: which existing data state best exercises the feature you're building?"));
949
+ console.log(chalk.dim(' A scenario with diverse, realistic data is usually the best starting point.'));
950
+ checkbox('Load that scenario to seed the database and preview it:');
951
+ console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
952
+ console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
953
+ console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
954
+ console.log();
955
+ }
933
956
  console.log(chalk.bold('Checklist:'));
934
957
  checkbox('Create API routes that read from the database via Prisma');
935
- checkbox('Seed the database with demo data');
936
- checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
937
- console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
938
- console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
939
- console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
958
+ if (!projectExists) {
959
+ checkbox('Seed the database with demo data');
960
+ checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
961
+ console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
962
+ console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
963
+ console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
964
+ }
940
965
  checkbox('Verify the dev server shows the changes');
941
966
  checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
942
967
  // Responsive design guidance when building a mobile-responsive web app
@@ -1419,8 +1444,6 @@ function printStep12(root, feature) {
1419
1444
  }
1420
1445
  // ─── Step 13: Present ─────────────────────────────────────────────────
1421
1446
  function printStep13(root, feature) {
1422
- const port = getServerPort();
1423
- const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1424
1447
  const prevState = readState(root);
1425
1448
  const isResuming = prevState?.step === 13;
1426
1449
  const now = new Date().toISOString();
@@ -1439,18 +1462,9 @@ function printStep13(root, feature) {
1439
1462
  console.log('Present the results to the user and get their approval.');
1440
1463
  console.log();
1441
1464
  console.log(chalk.bold('Checklist:'));
1442
- checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1443
- console.log(chalk.dim(' Each scenario has a `changeStatus` field: "new", "edited", "impacted", or null.'));
1444
- checkbox('Pick the best scenario to showcase from this working session:');
1445
- console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
1446
- console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
1447
- console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
1448
- checkbox('Switch the preview to that scenario using its `id` and `dimension`:');
1449
- console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1450
- console.log(chalk.dim(' Use the dimension that matches the scenario — check its registered dimensions.'));
1451
- printDimensionGuidance(dim, dimNames);
1452
1465
  checkbox(`Show the results panel: \`codeyam editor show-results\``);
1453
1466
  console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
1467
+ console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
1454
1468
  console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
1455
1469
  checkbox('Write a 1-2 sentence summary of what was built');
1456
1470
  checkbox('Report test count and audit status (one line)');
@@ -1689,10 +1703,8 @@ function printMigrateStep4(root) {
1689
1703
  checkbox('Review all scenario screenshots for this page — do they look correct?');
1690
1704
  checkbox('Check for client errors: `codeyam editor client-errors`');
1691
1705
  checkbox('If any issues: fix the scenario data, re-register affected scenarios');
1692
- checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1693
- checkbox('Pick the best scenario to showcase:');
1694
- console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>"}'`));
1695
1706
  checkbox('Show results: `codeyam editor show-results`');
1707
+ console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
1696
1708
  console.log();
1697
1709
  console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
1698
1710
  migrationStopGate(4, pageName, pageIndex, totalPages);
@@ -1858,10 +1870,8 @@ function printMigrateStep10(root) {
1858
1870
  console.log("Show results and commit this page's migration.");
1859
1871
  console.log();
1860
1872
  console.log(chalk.bold('Checklist:'));
1861
- checkbox('Fetch all scenarios: `codeyam editor scenarios`');
1862
- checkbox('Pick the best scenario to showcase:');
1863
- console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>"}'`));
1864
1873
  checkbox('Show results: `codeyam editor show-results`');
1874
+ console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
1865
1875
  checkbox('Write a 1-2 sentence summary of what was migrated');
1866
1876
  console.log();
1867
1877
  console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
@@ -2324,6 +2334,26 @@ async function handleAnalyzeImports(options = {}) {
2324
2334
  });
2325
2335
  console.log(summary || JSON.stringify({ imports, entities: entityData }));
2326
2336
  }
2337
+ // Backfill entity_sha on scenarios registered before entities existed.
2338
+ // This fixes the "missing data" UI state when scenarios were registered
2339
+ // while analyze-imports was still running in the background.
2340
+ try {
2341
+ const { getDatabase } = await import('../../../packages/database/index.js');
2342
+ const db = getDatabase();
2343
+ const backfillResult = await backfillEntityShaOnScenarios(db, latestEntities.map((e) => ({
2344
+ sha: e.sha,
2345
+ name: e.name,
2346
+ filePath: e.filePath || '',
2347
+ isDefaultExport: e.metadata?.notExported === false &&
2348
+ e.metadata?.namedExport === false,
2349
+ })));
2350
+ if (backfillResult.updated > 0 && !options.silent) {
2351
+ console.log(chalk.green(`Linked ${backfillResult.updated} scenario(s) to their entities.`));
2352
+ }
2353
+ }
2354
+ catch {
2355
+ /* non-fatal */
2356
+ }
2327
2357
  }
2328
2358
  // ─── Validate-seed subcommand ─────────────────────────────────────────
2329
2359
  /**
@@ -2362,6 +2392,24 @@ function handleValidateSeed(jsonArg) {
2362
2392
  : 0;
2363
2393
  console.log(chalk.dim(` ${table}: ${rows} row(s)`));
2364
2394
  }
2395
+ // Check seed keys against Prisma schema if available
2396
+ const schemaPath = path.join(root, 'prisma', 'schema.prisma');
2397
+ try {
2398
+ if (fs.existsSync(schemaPath)) {
2399
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
2400
+ const warnings = validateSeedKeysAgainstPrisma(tables, schemaContent);
2401
+ if (warnings.length > 0) {
2402
+ console.log();
2403
+ console.log(chalk.yellow('Prisma model name warnings:'));
2404
+ for (const w of warnings) {
2405
+ console.log(chalk.yellow(` ⚠ ${w}`));
2406
+ }
2407
+ }
2408
+ }
2409
+ }
2410
+ catch {
2411
+ // Schema not readable — skip Prisma validation
2412
+ }
2365
2413
  }
2366
2414
  else {
2367
2415
  console.error(chalk.red('Seed data validation failed:'));
@@ -2371,6 +2419,27 @@ function handleValidateSeed(jsonArg) {
2371
2419
  process.exit(1);
2372
2420
  }
2373
2421
  }
2422
+ // ─── Delete subcommand ────────────────────────────────────────────────
2423
+ /**
2424
+ * `codeyam editor delete <scenarioId>`
2425
+ *
2426
+ * Delete a scenario by ID via the running editor server.
2427
+ */
2428
+ async function handleDelete(scenarioId) {
2429
+ if (!scenarioId) {
2430
+ console.error(chalk.red('Error: Missing scenario ID. Usage: codeyam editor delete <scenarioId>'));
2431
+ process.exit(1);
2432
+ }
2433
+ const port = getServerPort();
2434
+ const result = await deleteScenarioViaCli(scenarioId, port);
2435
+ if (result.success) {
2436
+ console.log(chalk.green(result.message));
2437
+ }
2438
+ else {
2439
+ console.error(chalk.red(result.message));
2440
+ process.exit(1);
2441
+ }
2442
+ }
2374
2443
  // ─── Isolate subcommand ───────────────────────────────────────────────
2375
2444
  /**
2376
2445
  * `codeyam editor isolate "StarRating CategoryBadge DrinkCard"`
@@ -2845,23 +2914,108 @@ function handleChange(feature) {
2845
2914
  }
2846
2915
  // ─── Audit gate ─────────────────────────────────────────────────────
2847
2916
  /**
2848
- * Silently check whether the audit passes. Returns true if allPassing,
2849
- * false if any issues remain or the server is unreachable.
2850
- * Used as a hard gate before steps 8+.
2917
+ * Fetch the audit result from the server.
2918
+ * Returns null if the server is unreachable.
2851
2919
  */
2852
- async function checkAuditGate() {
2920
+ async function fetchAuditResult() {
2853
2921
  const port = getServerPort();
2854
2922
  try {
2855
2923
  const res = await fetch(`http://localhost:${port}/api/editor-audit`);
2856
2924
  if (!res.ok)
2857
- return false;
2858
- const data = await res.json();
2859
- return data?.summary?.allPassing === true;
2925
+ return null;
2926
+ return await res.json();
2860
2927
  }
2861
2928
  catch {
2862
- // Server not running or unreachable — can't verify, so don't block
2929
+ return null;
2930
+ }
2931
+ }
2932
+ /**
2933
+ * Silently check whether the audit passes. Returns true if allPassing,
2934
+ * false if any issues remain or the server is unreachable.
2935
+ * Used as a hard gate before steps 8+.
2936
+ *
2937
+ * Auto-runs analyze-imports if the only failure is incomplete entities,
2938
+ * then re-checks once.
2939
+ */
2940
+ async function checkAuditGate() {
2941
+ const data = await fetchAuditResult();
2942
+ if (!data)
2943
+ return true; // Server unreachable — don't block
2944
+ if (data.summary?.allPassing === true)
2863
2945
  return true;
2946
+ // If incomplete entities are the only issue, auto-fix with analyze-imports (once)
2947
+ const { isAutoRemediable } = await import('../utils/editorAudit.js');
2948
+ if (isAutoRemediable(data.summary, false)) {
2949
+ try {
2950
+ await handleAnalyzeImports({ silent: true });
2951
+ }
2952
+ catch {
2953
+ return false;
2954
+ }
2955
+ // Re-check after fix
2956
+ const retry = await fetchAuditResult();
2957
+ if (retry?.summary?.allPassing === true)
2958
+ return true;
2959
+ }
2960
+ // Print specific failures so Claude knows what to fix without running audit separately
2961
+ printAuditGateFailures(data);
2962
+ return false;
2963
+ }
2964
+ /**
2965
+ * Print a concise summary of audit failures for the gate block message.
2966
+ * This gives Claude immediate context about what to fix without needing
2967
+ * to run `codeyam editor audit` as a separate step.
2968
+ */
2969
+ function printAuditGateFailures(data) {
2970
+ const s = data.summary;
2971
+ if (!s)
2972
+ return;
2973
+ const issues = [];
2974
+ if (s.componentsMissing > 0)
2975
+ issues.push(`${s.componentsMissing} component(s) missing scenarios`);
2976
+ if (s.componentsWithErrors > 0)
2977
+ issues.push(`${s.componentsWithErrors} component(s) with client errors (browser API or runtime errors in captured scenarios)`);
2978
+ if (s.functionsMissing > 0)
2979
+ issues.push(`${s.functionsMissing} function(s) missing test files`);
2980
+ if (s.functionsFailing > 0)
2981
+ issues.push(`${s.functionsFailing} function(s) with failing tests`);
2982
+ if (s.functionsRunnerError > 0)
2983
+ issues.push(`${s.functionsRunnerError} function(s) with test runner errors (the runner crashed — not a test failure)`);
2984
+ if (s.functionsNameMismatch > 0)
2985
+ issues.push(`${s.functionsNameMismatch} function(s) with test name mismatch`);
2986
+ if (s.missingFromGlossary > 0)
2987
+ issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
2988
+ if (s.incompleteEntities > 0)
2989
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
2990
+ if (s.miscategorizedScenarios > 0)
2991
+ issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
2992
+ if (issues.length > 0) {
2993
+ console.error(chalk.yellow('\nAudit failures:'));
2994
+ for (const issue of issues) {
2995
+ console.error(chalk.yellow(` • ${issue}`));
2996
+ }
2864
2997
  }
2998
+ // Surface client error details inline — these are the most common cause of
2999
+ // Claude looping because the errors require code fixes, not audit re-runs.
3000
+ if (data.components) {
3001
+ const withErrors = data.components.filter((c) => c.status === 'has_errors' && c.clientErrors?.length > 0);
3002
+ if (withErrors.length > 0) {
3003
+ console.error(chalk.yellow('\nClient errors found:'));
3004
+ for (const c of withErrors) {
3005
+ console.error(chalk.red(` ${c.name}:`));
3006
+ for (const err of c.clientErrors.slice(0, 3)) {
3007
+ console.error(chalk.red(` → ${err}`));
3008
+ }
3009
+ if (c.clientErrors.length > 3) {
3010
+ console.error(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
3011
+ }
3012
+ }
3013
+ console.error(chalk.yellow('\nFix: Fix the code errors above, then re-capture the affected scenarios.'));
3014
+ console.error(chalk.yellow('If errors reference browser APIs (localStorage, sessionStorage, window, document),'));
3015
+ console.error(chalk.yellow('create a universal mock: codeyam detect-universal-mocks'));
3016
+ }
3017
+ }
3018
+ console.error(chalk.dim('\nRun `codeyam editor audit` for full details.\n'));
2865
3019
  }
2866
3020
  // ─── Audit subcommand ────────────────────────────────────────────────
2867
3021
  /**
@@ -2872,22 +3026,38 @@ async function checkAuditGate() {
2872
3026
  * have test files. Exits with code 1 if anything is missing.
2873
3027
  */
2874
3028
  async function handleAudit() {
2875
- const port = getServerPort();
2876
- const url = `http://localhost:${port}/api/editor-audit`;
2877
- let data;
2878
- try {
2879
- const res = await fetch(url);
2880
- if (!res.ok) {
2881
- console.error(chalk.red(`Error: Audit endpoint returned ${res.status}`));
2882
- process.exit(1);
2883
- }
2884
- data = await res.json();
2885
- }
2886
- catch (err) {
3029
+ let data = await fetchAuditResult();
3030
+ if (!data) {
2887
3031
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
2888
- console.error(chalk.dim(` ${err.message}`));
2889
3032
  process.exit(1);
2890
3033
  }
3034
+ // Auto-fix incomplete entities — but only once.
3035
+ // If analyze-imports runs and entities are STILL incomplete, report clearly
3036
+ // instead of retrying. The analysis may have errors (e.g., stale entity
3037
+ // references from refactoring) that can't be fixed by re-running.
3038
+ const incompleteBeforeFix = data.incompleteEntities || [];
3039
+ let autoRemediationFailed = false;
3040
+ if (incompleteBeforeFix.length > 0) {
3041
+ console.log(chalk.dim(`Running import analysis for ${incompleteBeforeFix.length} incomplete entit${incompleteBeforeFix.length !== 1 ? 'ies' : 'y'}...`));
3042
+ try {
3043
+ await handleAnalyzeImports({ silent: true });
3044
+ }
3045
+ catch {
3046
+ // Fall through — the audit will still report them as incomplete
3047
+ }
3048
+ // Re-fetch audit results after the fix
3049
+ data = await fetchAuditResult();
3050
+ if (!data) {
3051
+ console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
3052
+ process.exit(1);
3053
+ }
3054
+ // If entities are still incomplete after running analyze-imports,
3055
+ // flag it so we can show a clear message instead of looping
3056
+ const incompleteAfterFix = data.incompleteEntities || [];
3057
+ if (incompleteAfterFix.length > 0) {
3058
+ autoRemediationFailed = true;
3059
+ }
3060
+ }
2891
3061
  const { components, functions, summary } = data;
2892
3062
  console.log();
2893
3063
  console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
@@ -2915,6 +3085,14 @@ async function handleAudit() {
2915
3085
  if (c.clientErrors.length > 3) {
2916
3086
  console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
2917
3087
  }
3088
+ // Detect browser API errors and provide actionable guidance
3089
+ const browserApiPattern = /\b(localStorage|sessionStorage|window\.|document\.|navigator\.|indexedDB|matchMedia|ResizeObserver|IntersectionObserver|MutationObserver)\b/;
3090
+ const hasBrowserApiErrors = c.clientErrors.some((err) => browserApiPattern.test(err));
3091
+ if (hasBrowserApiErrors) {
3092
+ console.log(chalk.yellow(` ⚠ These errors are caused by browser APIs that don't exist during server-side analysis.`));
3093
+ console.log(chalk.yellow(` Fix: Create a universal mock to stub the missing API. Run: codeyam detect-universal-mocks`));
3094
+ console.log(chalk.yellow(` DO NOT re-run the audit or analyze-imports — the error will persist until a mock is created.`));
3095
+ }
2918
3096
  }
2919
3097
  }
2920
3098
  console.log();
@@ -2929,6 +3107,14 @@ async function handleAudit() {
2929
3107
  case 'ok':
2930
3108
  detail = chalk.dim(` (${f.testFile})`);
2931
3109
  break;
3110
+ case 'runner_error':
3111
+ detail = chalk.red(` — test runner crashed: ${f.testFile}`);
3112
+ if (f.errorMessage) {
3113
+ detail += `\n ${chalk.red(f.errorMessage)}`;
3114
+ detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
3115
+ detail += `\n ${chalk.yellow('Try running the test manually: npx vitest run ' + f.testFile)}`;
3116
+ }
3117
+ break;
2932
3118
  case 'failing':
2933
3119
  detail = chalk.red(` — tests failing: ${f.testFile}`);
2934
3120
  break;
@@ -2956,6 +3142,43 @@ async function handleAudit() {
2956
3142
  console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
2957
3143
  console.log();
2958
3144
  }
3145
+ // Incomplete entities (scenarios without analyses)
3146
+ const incompleteEntities = data.incompleteEntities || [];
3147
+ if (incompleteEntities.length > 0) {
3148
+ console.log(chalk.bold('Incomplete entities (need import analysis):'));
3149
+ for (const e of incompleteEntities) {
3150
+ console.log(` ${chalk.red('✗')} ${e.name} — ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
3151
+ }
3152
+ if (autoRemediationFailed) {
3153
+ console.log(chalk.red(' analyze-imports was run automatically but these entities are STILL incomplete.'));
3154
+ console.log(chalk.red(' DO NOT re-run analyze-imports or the audit in a loop — the result will be the same.'));
3155
+ console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
3156
+ console.log(chalk.yellow(' • Entity code uses browser APIs (localStorage, window, document) — create a universal mock'));
3157
+ console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
3158
+ console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
3159
+ console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
3160
+ console.log(chalk.yellow(' To fix browser API issues: run `codeyam detect-universal-mocks`'));
3161
+ }
3162
+ else {
3163
+ console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
3164
+ }
3165
+ console.log();
3166
+ }
3167
+ // Miscategorized scenarios (component scenarios using real page URLs)
3168
+ const miscategorizedScenarios = data.miscategorizedScenarios || [];
3169
+ if (miscategorizedScenarios.length > 0) {
3170
+ console.log(chalk.bold('Miscategorized scenarios (component with page URL):'));
3171
+ for (const m of miscategorizedScenarios) {
3172
+ console.log(` ${chalk.red('✗')} ${m.componentName} at ${chalk.dim(m.url)} — ${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''}`);
3173
+ for (const name of m.scenarioNames) {
3174
+ console.log(chalk.dim(` "${name}"`));
3175
+ }
3176
+ }
3177
+ console.log(chalk.yellow(' Component scenarios must use isolation routes (/isolated-components/...).'));
3178
+ console.log(chalk.yellow(' Either re-register as app scenarios (remove componentName, add pageFilePath)'));
3179
+ console.log(chalk.yellow(' or re-register with an isolation URL.'));
3180
+ console.log();
3181
+ }
2959
3182
  // Summary
2960
3183
  const allOk = summary.allPassing;
2961
3184
  if (allOk) {
@@ -2976,12 +3199,21 @@ async function handleAudit() {
2976
3199
  if (summary.functionsFailing > 0) {
2977
3200
  parts.push(`${summary.functionsFailing} function${summary.functionsFailing !== 1 ? 's' : ''} with failing tests`);
2978
3201
  }
3202
+ if (summary.functionsRunnerError > 0) {
3203
+ parts.push(`${summary.functionsRunnerError} function${summary.functionsRunnerError !== 1 ? 's' : ''} with test runner errors (not test failures — see details above)`);
3204
+ }
2979
3205
  if (summary.functionsNameMismatch > 0) {
2980
3206
  parts.push(`${summary.functionsNameMismatch} function${summary.functionsNameMismatch !== 1 ? 's' : ''} with test name mismatch (missing top-level describe)`);
2981
3207
  }
2982
3208
  if (summary.missingFromGlossary > 0) {
2983
3209
  parts.push(`${summary.missingFromGlossary} file${summary.missingFromGlossary !== 1 ? 's' : ''} with scenarios not in glossary`);
2984
3210
  }
3211
+ if (summary.incompleteEntities > 0) {
3212
+ parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
3213
+ }
3214
+ if (summary.miscategorizedScenarios > 0) {
3215
+ parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
3216
+ }
2985
3217
  console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
2986
3218
  }
2987
3219
  console.log();
@@ -3099,6 +3331,32 @@ async function handleScenarios() {
3099
3331
  }
3100
3332
  // ─── Scenario Coverage subcommand ───────────────────────────────────
3101
3333
  async function handleScenarioCoverage() {
3334
+ // Safety net: heal any scenarios with null entity_sha before checking coverage
3335
+ try {
3336
+ const { getDatabase } = await import('../../../packages/database/index.js');
3337
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
3338
+ const db = getDatabase();
3339
+ const backfillCount = await countScenariosNeedingEntityBackfill(db);
3340
+ if (backfillCount > 0) {
3341
+ console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
3342
+ await handleAnalyzeImports({ silent: true });
3343
+ // Run backfill after analysis
3344
+ const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
3345
+ const entities = await loadEntities({});
3346
+ if (entities && entities.length > 0) {
3347
+ await backfillEntityShaOnScenarios(db, entities.map((e) => ({
3348
+ sha: e.sha,
3349
+ name: e.name,
3350
+ filePath: e.filePath || '',
3351
+ isDefaultExport: e.metadata?.notExported === false &&
3352
+ e.metadata?.namedExport === false,
3353
+ })));
3354
+ }
3355
+ }
3356
+ }
3357
+ catch {
3358
+ // Non-fatal — proceed with coverage check regardless
3359
+ }
3102
3360
  const port = getServerPort();
3103
3361
  const url = `http://localhost:${port}/api/editor-scenario-coverage`;
3104
3362
  let data;
@@ -3246,7 +3504,20 @@ async function handleTemplate() {
3246
3504
  // Config parse error is non-fatal
3247
3505
  }
3248
3506
  }
3249
- // 5b. Write a fresh prototypeId so the proxy clears stale localStorage
3507
+ // 5b. Mark the project as template-scaffolded so migration detection
3508
+ // doesn't treat it as a pre-existing project that needs migration.
3509
+ const now = new Date().toISOString();
3510
+ const existingState = readState(root);
3511
+ writeState(root, {
3512
+ feature: '',
3513
+ step: 0,
3514
+ label: '',
3515
+ startedAt: now,
3516
+ featureStartedAt: now,
3517
+ ...existingState,
3518
+ scaffolded: true,
3519
+ });
3520
+ // 5c. Write a fresh prototypeId so the proxy clears stale localStorage
3250
3521
  const activeScenarioPath = path.join(root, '.codeyam', 'active-scenario.json');
3251
3522
  fs.writeFileSync(activeScenarioPath, JSON.stringify({ prototypeId: Date.now().toString() }), 'utf-8');
3252
3523
  // 6. Trigger editor-refresh so the server picks up the new project
@@ -3624,7 +3895,9 @@ const editorCommand = {
3624
3895
  describe: 'Debug: output directory for the bundle',
3625
3896
  });
3626
3897
  }
3627
- return builder;
3898
+ // Allow extra positional args for subcommands like `isolate A B C`
3899
+ // without yargs rejecting them as unknown.
3900
+ return builder.strict(false);
3628
3901
  },
3629
3902
  handler: async (argv) => {
3630
3903
  const root = getProjectRoot();
@@ -3703,14 +3976,16 @@ const editorCommand = {
3703
3976
  await handleValidateSeed(argv.json || '');
3704
3977
  return;
3705
3978
  }
3979
+ // Subcommand: codeyam editor delete <scenarioId>
3980
+ if (argv.step === 'delete') {
3981
+ await handleDelete(argv.json || '');
3982
+ return;
3983
+ }
3706
3984
  // Subcommand: codeyam editor isolate "StarRating CategoryBadge DrinkCard"
3985
+ // Also supports: codeyam editor isolate StarRating CategoryBadge DrinkCard
3707
3986
  if (argv.step === 'isolate') {
3708
- const names = [];
3709
- if (argv.json)
3710
- names.push(...argv.json.split(/[\s,]+/).filter(Boolean));
3711
- // Collect any extra positional args (yargs puts them in argv._)
3712
- const extras = argv._.filter((a) => typeof a === 'string' && a !== 'editor');
3713
- names.push(...extras);
3987
+ const { parseIsolateArgs } = await import('./editorIsolateArgs.js');
3988
+ const names = parseIsolateArgs(argv.json, argv._);
3714
3989
  handleIsolate(names);
3715
3990
  return;
3716
3991
  }
@@ -3807,6 +4082,9 @@ const editorCommand = {
3807
4082
  'screenshot_path',
3808
4083
  'viewport_width',
3809
4084
  'viewport_height',
4085
+ 'dimensions',
4086
+ 'screenshot_paths',
4087
+ 'page_file_path',
3810
4088
  'created_at',
3811
4089
  'updated_at',
3812
4090
  ])
@@ -3895,6 +4173,41 @@ const editorCommand = {
3895
4173
  catch {
3896
4174
  // Non-fatal — migration failure shouldn't block editor startup
3897
4175
  }
4176
+ // Auto-seed on fresh clone: if no scenario has ever been activated
4177
+ // (active-scenario.json doesn't exist), seed the application database
4178
+ // with the first application scenario that has seed data.
4179
+ const activeScenarioPath = path.join(projectRoot, '.codeyam', 'active-scenario.json');
4180
+ if (!fs.existsSync(activeScenarioPath)) {
4181
+ try {
4182
+ const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4183
+ const seedDb = getDb();
4184
+ const appScenario = await seedDb
4185
+ .selectFrom('editor_scenarios')
4186
+ .select(['id', 'name', 'type'])
4187
+ .where('project_id', '=', project.id)
4188
+ .where('type', '=', 'application')
4189
+ .orderBy('created_at', 'asc')
4190
+ .executeTakeFirst();
4191
+ if (appScenario) {
4192
+ const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
4193
+ if (fs.existsSync(seedFile)) {
4194
+ const { switchActiveScenario } = await import('../utils/editorScenarioSwitch.js');
4195
+ const seedResult = await switchActiveScenario({
4196
+ scenarioId: appScenario.id,
4197
+ scenarioName: appScenario.name || undefined,
4198
+ scenarioType: appScenario.type || undefined,
4199
+ projectRoot,
4200
+ });
4201
+ if (seedResult.seedResult?.success) {
4202
+ console.log(chalk.green(` Auto-seeded database with scenario: ${appScenario.name || appScenario.id}`));
4203
+ }
4204
+ }
4205
+ }
4206
+ }
4207
+ catch {
4208
+ // Non-fatal — auto-seed is best-effort
4209
+ }
4210
+ }
3898
4211
  // `codeyam editor` (no step) always implies editor mode.
3899
4212
  // The empty-folder heuristic is no longer needed here — running this
3900
4213
  // command IS the signal. We still detect empty folders so that
@@ -4013,6 +4326,22 @@ const editorCommand = {
4013
4326
  // Non-fatal
4014
4327
  }
4015
4328
  }
4329
+ // Check if any scenarios have null entity_sha with file paths — heal on startup
4330
+ if (!needsAnalysis) {
4331
+ try {
4332
+ const { getDatabase } = await import('../../../packages/database/index.js');
4333
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
4334
+ const db = getDatabase();
4335
+ const backfillCount = await countScenariosNeedingEntityBackfill(db);
4336
+ if (backfillCount > 0) {
4337
+ console.log(chalk.dim(` Found ${backfillCount} scenario(s) with unlinked entities — running import analysis...`));
4338
+ needsAnalysis = true;
4339
+ }
4340
+ }
4341
+ catch {
4342
+ // Non-fatal
4343
+ }
4344
+ }
4016
4345
  }
4017
4346
  if (needsAnalysis) {
4018
4347
  try {
@@ -4023,6 +4352,71 @@ const editorCommand = {
4023
4352
  }
4024
4353
  }
4025
4354
  }
4355
+ // Backfill page_file_path for application scenarios that have a URL but
4356
+ // no page_file_path. This resolves the file from the URL using Next.js
4357
+ // routing conventions, enabling syncScenarioEntityShas to link them to
4358
+ // entities. Covers fresh clones where JSON files lack pageFilePath.
4359
+ try {
4360
+ const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4361
+ const pfpDb = getDb();
4362
+ const unresolved = await pfpDb
4363
+ .selectFrom('editor_scenarios')
4364
+ .select(['id', 'url'])
4365
+ .where('project_id', '=', project.id)
4366
+ .where('component_name', 'is', null)
4367
+ .where('page_file_path', 'is', null)
4368
+ .where('url', 'is not', null)
4369
+ .execute();
4370
+ if (unresolved.length > 0) {
4371
+ const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
4372
+ const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
4373
+ const { allFiles: pfpFiles } = scanPfp(projectRoot);
4374
+ let pfpResolved = 0;
4375
+ for (const row of unresolved) {
4376
+ const r = row;
4377
+ if (!r.url)
4378
+ continue;
4379
+ const matched = matchUrlToPageFile(r.url, pfpFiles);
4380
+ if (matched) {
4381
+ await pfpDb
4382
+ .updateTable('editor_scenarios')
4383
+ .set({ page_file_path: matched })
4384
+ .where('id', '=', r.id)
4385
+ .execute();
4386
+ pfpResolved++;
4387
+ }
4388
+ }
4389
+ if (pfpResolved > 0) {
4390
+ console.log(chalk.green(` Resolved page_file_path for ${pfpResolved} scenario(s) from URL`));
4391
+ }
4392
+ }
4393
+ }
4394
+ catch {
4395
+ /* Non-fatal — page_file_path backfill from URL */
4396
+ }
4397
+ // Backfill entity_sha on scenarios that were synced before entities existed.
4398
+ // This runs independently of analyze-imports so fresh clones and file-synced
4399
+ // scenarios get linked to their entities even when auto-analyze doesn't trigger.
4400
+ try {
4401
+ const entities = await loadEntities({});
4402
+ if (entities && entities.length > 0) {
4403
+ const { getDatabase } = await import('../../../packages/database/index.js');
4404
+ const db = getDatabase();
4405
+ const result = await backfillEntityShaOnScenarios(db, entities.map((e) => ({
4406
+ sha: e.sha,
4407
+ name: e.name,
4408
+ filePath: e.filePath || '',
4409
+ isDefaultExport: e.metadata?.notExported === false &&
4410
+ e.metadata?.namedExport === false,
4411
+ })));
4412
+ if (result.updated > 0) {
4413
+ console.log(chalk.green(` Linked ${result.updated} scenario(s) to their entities`));
4414
+ }
4415
+ }
4416
+ }
4417
+ catch {
4418
+ /* Non-fatal */
4419
+ }
4026
4420
  console.log();
4027
4421
  console.log(` Dashboard: ${url}`);
4028
4422
  console.log(' Run "codeyam --help" for all commands');
@@ -4108,8 +4502,9 @@ const editorCommand = {
4108
4502
  if (step >= 8) {
4109
4503
  const auditOk = await checkAuditGate();
4110
4504
  if (!auditOk) {
4111
- console.error(chalk.red.bold('BLOCKED: The audit has not passed. Run `codeyam editor audit` and fix all issues before proceeding.'));
4112
- console.error(chalk.yellow('Every function and hook must have a test file. Every component must have scenarios.'));
4505
+ // checkAuditGate() already printed specific failure details above
4506
+ console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
4507
+ console.error(chalk.yellow('DO NOT re-run the audit in a loop — fix the underlying issues first.'));
4113
4508
  process.exit(1);
4114
4509
  }
4115
4510
  }