@codeyam/codeyam-cli 0.1.21 → 0.1.23

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 (122) hide show
  1. package/analyzer-template/.build-info.json +7 -7
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/packages/ai/src/lib/astScopes/methodSemantics.ts +135 -0
  4. package/analyzer-template/packages/ai/src/lib/astScopes/nodeToSource.ts +19 -0
  5. package/analyzer-template/packages/ai/src/lib/astScopes/paths.ts +11 -4
  6. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +31 -8
  7. package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.ts +10 -3
  8. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +16 -6
  9. package/analyzer-template/packages/analyze/index.ts +4 -1
  10. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +28 -2
  11. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +5 -36
  12. package/analyzer-template/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.ts +21 -0
  13. package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +82 -10
  14. package/analyzer-template/packages/analyze/src/lib/files/analyzeNextRoute.ts +8 -3
  15. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +239 -58
  16. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +1684 -1462
  17. package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js +47 -0
  18. package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js.map +1 -0
  19. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js +71 -0
  20. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
  21. package/codeyam-cli/src/commands/editor.js +545 -94
  22. package/codeyam-cli/src/commands/editor.js.map +1 -1
  23. package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js +23 -0
  24. package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js.map +1 -0
  25. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +456 -1
  26. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  27. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
  28. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
  29. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +140 -1
  30. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  31. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +50 -1
  32. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
  33. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +33 -1
  34. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  35. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
  36. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
  37. package/codeyam-cli/src/utils/__tests__/testRunner.test.js +217 -0
  38. package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
  39. package/codeyam-cli/src/utils/analysisRunner.js +28 -1
  40. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  41. package/codeyam-cli/src/utils/analyzer.js +4 -2
  42. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  43. package/codeyam-cli/src/utils/editorAudit.js +136 -5
  44. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  45. package/codeyam-cli/src/utils/editorPreview.js +5 -3
  46. package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
  47. package/codeyam-cli/src/utils/editorScenarios.js +60 -0
  48. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  49. package/codeyam-cli/src/utils/editorSeedAdapter.js +42 -2
  50. package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
  51. package/codeyam-cli/src/utils/entityChangeStatus.server.js +16 -0
  52. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  53. package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
  54. package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
  55. package/codeyam-cli/src/utils/queue/job.js +20 -2
  56. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  57. package/codeyam-cli/src/utils/testRunner.js +199 -1
  58. package/codeyam-cli/src/utils/testRunner.js.map +1 -1
  59. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +30 -11
  60. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
  61. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +35 -0
  62. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
  63. package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-CQENLSrF.js +36 -0
  64. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-Coe5NhbS.js +1 -0
  65. package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CJzc4vOH.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
  66. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DMv5ESGo.js +96 -0
  67. package/codeyam-cli/src/webserver/build/client/assets/{editorPreview-NTuLi4Xg.js → editorPreview-CluPkvXJ.js} +6 -6
  68. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-Blfy9UlN.js → entity._sha._-ByHz6rAQ.js} +13 -12
  69. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-BA5L8bU-.js → entity._sha.scenarios._scenarioId.dev-CmLO432x.js} +1 -1
  70. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-D4dmRgvO.js → entity._sha.scenarios._scenarioId.fullscreen-Bz9sCUF_.js} +1 -1
  71. package/codeyam-cli/src/webserver/build/client/assets/globals-oyPmV37k.css +1 -0
  72. package/codeyam-cli/src/webserver/build/client/assets/{manifest-5025e428.js → manifest-1a45e154.js} +1 -1
  73. package/codeyam-cli/src/webserver/build/client/assets/{root-BCx1S8Z3.js → root-D2_tktnk.js} +6 -6
  74. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-By5slFjw.js +16 -0
  75. package/codeyam-cli/src/webserver/build/server/assets/{index-C91yWWCI.js → index-DXaOwBnm.js} +1 -1
  76. package/codeyam-cli/src/webserver/build/server/assets/{init-Dkas-RUS.js → init-CLG1LjQM.js} +1 -1
  77. package/codeyam-cli/src/webserver/build/server/assets/server-build-NZmUqQv6.js +688 -0
  78. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  79. package/codeyam-cli/src/webserver/build-info.json +5 -5
  80. package/codeyam-cli/src/webserver/editorProxy.js +55 -3
  81. package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
  82. package/codeyam-cli/src/webserver/idleDetector.js +15 -0
  83. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
  84. package/codeyam-cli/src/webserver/terminalServer.js +8 -2
  85. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  86. package/codeyam-cli/templates/codeyam-editor-reference.md +8 -6
  87. package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +42 -34
  88. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +2 -2
  89. package/package.json +1 -1
  90. package/packages/ai/src/lib/astScopes/methodSemantics.js +99 -0
  91. package/packages/ai/src/lib/astScopes/methodSemantics.js.map +1 -1
  92. package/packages/ai/src/lib/astScopes/nodeToSource.js +16 -0
  93. package/packages/ai/src/lib/astScopes/nodeToSource.js.map +1 -1
  94. package/packages/ai/src/lib/astScopes/paths.js +12 -3
  95. package/packages/ai/src/lib/astScopes/paths.js.map +1 -1
  96. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +23 -9
  97. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  98. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
  99. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
  100. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
  101. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  102. package/packages/analyze/index.js +1 -1
  103. package/packages/analyze/index.js.map +1 -1
  104. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
  105. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  106. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
  107. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  108. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
  109. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
  110. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
  111. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
  112. package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
  113. package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
  114. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +120 -28
  115. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  116. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +1368 -1193
  117. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  118. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DODLxLcw.js +0 -1
  119. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-Dx-h1rJK.js +0 -130
  120. package/codeyam-cli/src/webserver/build/client/assets/globals-BrPXT1iR.css +0 -1
  121. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-C1kjC9UJ.js +0 -13
  122. package/codeyam-cli/src/webserver/build/server/assets/server-build-pulXLTrG.js +0 -640
@@ -357,7 +357,7 @@ function printExtractionPlanInstructions() {
357
357
  */
358
358
  function printComponentCaptureInstructions() {
359
359
  checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
360
- console.log(chalk.dim(' This creates app/codeyam-isolate/layout.tsx (with production notFound() guard) and'));
360
+ console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
361
361
  console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
362
362
  checkbox('For each visual component:');
363
363
  console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
@@ -369,8 +369,8 @@ function printComponentCaptureInstructions() {
369
369
  console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
370
370
  console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
371
371
  console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
372
- console.log(chalk.dim(' Remix: app/routes/codeyam-isolate.ComponentName.tsx → /codeyam-isolate/ComponentName'));
373
- console.log(chalk.dim(' Next.js: app/codeyam-isolate/ComponentName/page.tsx → /codeyam-isolate/ComponentName'));
372
+ console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
373
+ console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
374
374
  console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
375
375
  console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
376
376
  console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
@@ -383,9 +383,9 @@ function printComponentCaptureInstructions() {
383
383
  console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
384
384
  console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
385
385
  console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
386
- console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
386
+ console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
387
387
  console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
388
- console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/codeyam-isolate/... or /isolated-components/...).'));
388
+ console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/isolated-components/...).'));
389
389
  console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
390
390
  console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
391
391
  console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
@@ -395,7 +395,7 @@ function printComponentCaptureInstructions() {
395
395
  console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
396
396
  console.log();
397
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"}, ...]'));
398
+ console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
399
399
  console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
400
400
  }
401
401
  /**
@@ -448,7 +448,9 @@ function stopGate(current, opts) {
448
448
  // Skip step 1 (no feature name yet — task starts at step 2)
449
449
  if (current >= 2) {
450
450
  console.log(chalk.bold.cyan('━━━ TASK ━━━'));
451
- console.log(chalk.cyan('Mark your current task (if any) as complete.'));
451
+ console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
452
+ console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
453
+ console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
452
454
  console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
453
455
  console.log();
454
456
  let taskTitle;
@@ -1361,6 +1363,9 @@ function printStep9(root, feature) {
1361
1363
  console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
1362
1364
  console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
1363
1365
  console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
1366
+ console.log(chalk.dim(' If the audit reports "MANUAL ANALYSIS REQUIRED" for any entities,'));
1367
+ console.log(chalk.dim(' follow the manual analysis steps in the audit output.'));
1368
+ console.log(chalk.dim(' Do NOT re-run analyze-imports for those entities — it will fail again.'));
1364
1369
  console.log();
1365
1370
  stopGate(9);
1366
1371
  }
@@ -1962,7 +1967,7 @@ function printMigrateStep8(root) {
1962
1967
  console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
1963
1968
  console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
1964
1969
  checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
1965
- checkbox('Find all scenarios that use the real page route (not /codeyam-isolate/ paths)');
1970
+ checkbox('Find all scenarios that use the real page route (not /isolated-components/ paths)');
1966
1971
  checkbox('Re-register each one with the SAME name to update its screenshot');
1967
1972
  console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
1968
1973
  checkbox('After each registration, check the response for `clientErrors`');
@@ -1973,7 +1978,7 @@ function printMigrateStep8(root) {
1973
1978
  printComponentCaptureInstructions();
1974
1979
  console.log();
1975
1980
  console.log(chalk.bold('Verify:'));
1976
- checkbox('Run `codeyam editor analyze-imports` to populate import graph');
1981
+ checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
1977
1982
  checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
1978
1983
  checkbox('Run `codeyam editor audit` to check completeness');
1979
1984
  checkbox('Check for client errors: `codeyam editor client-errors`');
@@ -2351,10 +2356,14 @@ async function handleAnalyzeImports(options = {}) {
2351
2356
  glossaryEntries = sanitizeGlossaryEntries(parsed);
2352
2357
  }
2353
2358
  catch {
2359
+ if (options.silent)
2360
+ return;
2354
2361
  console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
2355
2362
  process.exit(1);
2356
2363
  }
2357
2364
  if (glossaryEntries.length === 0) {
2365
+ if (options.silent)
2366
+ return;
2358
2367
  console.error(chalk.red('Error: glossary.json is empty.'));
2359
2368
  process.exit(1);
2360
2369
  }
@@ -2423,6 +2432,15 @@ async function handleAnalyzeImports(options = {}) {
2423
2432
  catch {
2424
2433
  // Non-fatal — fall back to analyzing all files
2425
2434
  }
2435
+ // If specific file paths were requested, scope analysis to only those files.
2436
+ // The full filePaths set is still used for the import graph below.
2437
+ if (options.filePaths && options.filePaths.length > 0) {
2438
+ const requested = new Set(options.filePaths);
2439
+ targetFilePaths = targetFilePaths.filter((fp) => requested.has(fp));
2440
+ if (targetFilePaths.length === 0 && !options.silent) {
2441
+ console.log(chalk.dim('Requested file(s) already analyzed or not in glossary — skipping analysis.'));
2442
+ }
2443
+ }
2426
2444
  // Run data-structure-only analysis for entities that need it.
2427
2445
  // Don't pass entityNames — entities may not exist yet (fresh clone).
2428
2446
  // The analyzer will discover and create them from file paths.
@@ -2439,11 +2457,93 @@ async function handleAnalyzeImports(options = {}) {
2439
2457
  catch (err) {
2440
2458
  progress.fail('Analysis failed');
2441
2459
  const msg = err instanceof Error ? err.message : String(err);
2460
+ if (options.silent) {
2461
+ // Internal caller — don't kill the process, let the caller handle it
2462
+ return;
2463
+ }
2442
2464
  console.error(chalk.red(`Error: ${msg}`));
2443
2465
  process.exit(1);
2444
2466
  }
2445
2467
  progress.succeed('Analysis complete');
2446
2468
  }
2469
+ // Surface analysis errors if any occurred
2470
+ const errorReportPath = path.join(root, '.codeyam', 'analysis-errors.txt');
2471
+ if (fs.existsSync(errorReportPath)) {
2472
+ if (!options.silent) {
2473
+ console.log(chalk.yellow('\nSome entity analyses failed. Full details in .codeyam/analysis-errors.txt'));
2474
+ console.log(chalk.yellow('Send this file to the CodeYam team for diagnosis.'));
2475
+ console.log(chalk.dim('Affected entities will be missing from the import graph.'));
2476
+ }
2477
+ // Write structured analysis-failures.json for the audit to detect.
2478
+ // Parse the error report to find which entities failed, and cross-reference
2479
+ // with the files we tried to analyze.
2480
+ try {
2481
+ const { readAnalysisFailures, writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
2482
+ const errorContent = fs.readFileSync(errorReportPath, 'utf8');
2483
+ const existingFailures = readAnalysisFailures(root);
2484
+ // Parse error messages to identify failed file paths.
2485
+ // Error format: "Error on step <StepName> for entity <Name> (<filePath>)"
2486
+ // or more generic "CodeYam Error: <message>"
2487
+ const entityErrorRegex = /(?:Error on step \w+ for entity (\w+))|(?:entity[: ]+["']?(\w+)["']?)/gi;
2488
+ const failedEntityNames = new Set();
2489
+ let match;
2490
+ while ((match = entityErrorRegex.exec(errorContent)) !== null) {
2491
+ const name = match[1] || match[2];
2492
+ if (name)
2493
+ failedEntityNames.add(name);
2494
+ }
2495
+ // Map failed entity names back to file paths from targetFilePaths
2496
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
2497
+ let glossary = [];
2498
+ try {
2499
+ glossary = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
2500
+ }
2501
+ catch {
2502
+ // Non-fatal
2503
+ }
2504
+ // Also: any targetFilePaths that didn't produce entities are failures
2505
+ const now = new Date().toISOString();
2506
+ const updatedFailures = { ...existingFailures };
2507
+ if (failedEntityNames.size > 0) {
2508
+ for (const name of failedEntityNames) {
2509
+ const entry = glossary.find((e) => e.name === name);
2510
+ if (entry) {
2511
+ updatedFailures[entry.filePath] = {
2512
+ entityName: name,
2513
+ error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
2514
+ failedAt: now,
2515
+ };
2516
+ }
2517
+ }
2518
+ }
2519
+ else if (targetFilePaths.length > 0) {
2520
+ // Couldn't parse specific entity names — mark all target files
2521
+ // that we attempted to analyze as potentially failed
2522
+ for (const fp of targetFilePaths) {
2523
+ const entry = glossary.find((e) => e.filePath === fp);
2524
+ updatedFailures[fp] = {
2525
+ entityName: entry?.name || path.basename(fp, path.extname(fp)),
2526
+ error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
2527
+ failedAt: now,
2528
+ };
2529
+ }
2530
+ }
2531
+ writeAnalysisFailures(root, updatedFailures);
2532
+ }
2533
+ catch {
2534
+ // Non-fatal — failure tracking is best-effort
2535
+ }
2536
+ }
2537
+ else {
2538
+ // No errors — clear any stale failure tracking
2539
+ try {
2540
+ const { writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
2541
+ writeAnalysisFailures(root, {});
2542
+ }
2543
+ catch {
2544
+ // Non-fatal
2545
+ }
2546
+ }
2447
2547
  // Load entities WITH metadata from database
2448
2548
  progress.start('Loading entity metadata...');
2449
2549
  await initializeEnvironment();
@@ -2905,7 +3005,7 @@ async function handleRegister(jsonArg) {
2905
3005
  }
2906
3006
  // Detect duplicate screenshots in the batch
2907
3007
  if (isBatch && screenshotEntries.length > 1) {
2908
- const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash');
3008
+ const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
2909
3009
  const hashEntries = screenshotEntries
2910
3010
  .map((e) => {
2911
3011
  const hash = computeScreenshotHash(e.screenshotAbsPath);
@@ -3235,10 +3335,11 @@ function handleChange(feature) {
3235
3335
  * Fetch the audit result from the server.
3236
3336
  * Returns null if the server is unreachable.
3237
3337
  */
3238
- async function fetchAuditResult() {
3338
+ async function fetchAuditResult(options) {
3239
3339
  const port = getServerPort();
3340
+ const params = options?.skipTests ? '?skipTests=true' : '';
3240
3341
  try {
3241
- const res = await fetch(`http://localhost:${port}/api/editor-audit`);
3342
+ const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
3242
3343
  if (!res.ok)
3243
3344
  return null;
3244
3345
  return await res.json();
@@ -3287,8 +3388,8 @@ async function checkAuditGate() {
3287
3388
  catch {
3288
3389
  // Fall through
3289
3390
  }
3290
- // Re-check after backfill
3291
- const retry = await fetchAuditResult();
3391
+ // Re-check after backfill — skip re-running tests (backfill is DB-only)
3392
+ const retry = await fetchAuditResult({ skipTests: true });
3292
3393
  if (retry?.summary?.allPassing === true)
3293
3394
  return true;
3294
3395
  }
@@ -3349,6 +3450,10 @@ function printAuditGateFailures(data) {
3349
3450
  const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
3350
3451
  for (const f of runnerErrors) {
3351
3452
  issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3453
+ if (f.hint)
3454
+ issues.push(` ${f.hint}`);
3455
+ else if (f.errorMessage)
3456
+ issues.push(` Error: ${f.errorMessage}`);
3352
3457
  }
3353
3458
  }
3354
3459
  if (s.functionsNameMismatch > 0) {
@@ -3356,13 +3461,44 @@ function printAuditGateFailures(data) {
3356
3461
  const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
3357
3462
  for (const f of mismatch) {
3358
3463
  issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3464
+ if (f.hint)
3465
+ issues.push(` Fix: ${f.hint}`);
3359
3466
  }
3360
3467
  }
3361
- if (s.missingFromGlossary > 0)
3468
+ if (s.missingFromGlossary > 0) {
3362
3469
  issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
3470
+ const missingGlossary = data.missingFromGlossary || [];
3471
+ for (const m of missingGlossary) {
3472
+ issues.push(` → ${m.name} (${m.filePath})`);
3473
+ }
3474
+ const missingPaths = missingGlossary
3475
+ .map((m) => m.filePath)
3476
+ .filter(Boolean);
3477
+ if (missingPaths.length > 0) {
3478
+ issues.push(` Fix: Run \`codeyam editor analyze-imports ${missingPaths.join(' ')}\``);
3479
+ }
3480
+ else {
3481
+ issues.push(` Fix: Run \`codeyam editor analyze-imports\` to add these files to the glossary`);
3482
+ }
3483
+ }
3363
3484
  if (s.incompleteEntities > 0) {
3485
+ // Check for persistent analysis failures
3486
+ let analysisFailures = {};
3487
+ try {
3488
+ const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
3489
+ if (fs.existsSync(failuresPath)) {
3490
+ analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
3491
+ }
3492
+ }
3493
+ catch {
3494
+ // Non-fatal
3495
+ }
3364
3496
  const preCount = s.preExistingIncompleteEntities || 0;
3365
- if (preCount > 0 && preCount === s.incompleteEntities) {
3497
+ const hasFailures = Object.keys(analysisFailures).length > 0;
3498
+ if (hasFailures) {
3499
+ issues.push(`${s.incompleteEntities} entity/entities — automated analysis failed, manual analysis required`);
3500
+ }
3501
+ else if (preCount > 0 && preCount === s.incompleteEntities) {
3366
3502
  issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
3367
3503
  }
3368
3504
  else if (preCount > 0) {
@@ -3371,18 +3507,52 @@ function printAuditGateFailures(data) {
3371
3507
  else {
3372
3508
  issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
3373
3509
  }
3510
+ const incomplete = data.incompleteEntities || [];
3511
+ for (const e of incomplete) {
3512
+ const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
3513
+ if (failureEntry) {
3514
+ issues.push(` → ${e.name} — MANUAL ANALYSIS REQUIRED (automated analysis failed)`);
3515
+ issues.push(` Run \`codeyam editor audit\` for step-by-step manual analysis instructions`);
3516
+ }
3517
+ else {
3518
+ issues.push(` → ${e.name} (${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''})${e.preExisting ? ' [pre-existing]' : ''}`);
3519
+ }
3520
+ }
3521
+ if (!hasFailures) {
3522
+ issues.push(` Fix: Run \`codeyam editor analyze-imports\` to build import graphs`);
3523
+ }
3374
3524
  }
3375
3525
  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
3526
  const unassociated = data.unassociatedScenarios || [];
3527
+ const unassocPaths = unassociated
3528
+ .map((u) => u.filePath)
3529
+ .filter(Boolean);
3530
+ if (unassocPaths.length > 0) {
3531
+ issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports ${unassocPaths.join(' ')}\` then re-run audit`);
3532
+ }
3533
+ else {
3534
+ issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
3535
+ }
3378
3536
  for (const u of unassociated) {
3379
3537
  issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
3380
3538
  }
3381
3539
  }
3382
- if (s.miscategorizedScenarios > 0)
3540
+ if (s.miscategorizedScenarios > 0) {
3383
3541
  issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
3384
- if (s.scenariosNeedingRecapture > 0)
3542
+ const miscategorized = data.miscategorizedScenarios || [];
3543
+ for (const m of miscategorized) {
3544
+ issues.push(` → ${m.componentName} at ${m.url} (${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''})`);
3545
+ }
3546
+ issues.push(` Fix: Re-register these as app-level scenarios (with pageFilePath, no componentName) or use isolation routes`);
3547
+ }
3548
+ if (s.scenariosNeedingRecapture > 0) {
3385
3549
  issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
3550
+ const recapture = data.scenariosNeedingRecapture || [];
3551
+ for (const r of recapture) {
3552
+ issues.push(` → "${r.scenarioName}" (entity: ${r.entityName}, ${r.status?.status || 'changed'})`);
3553
+ }
3554
+ issues.push(` Fix: Re-capture affected scenarios to update screenshots after code changes`);
3555
+ }
3386
3556
  if (issues.length > 0) {
3387
3557
  console.error(chalk.yellow('\nAudit failures:'));
3388
3558
  for (const issue of issues) {
@@ -3417,16 +3587,16 @@ function printAuditGateFailures(data) {
3417
3587
  * which glossary components have registered scenarios and which functions
3418
3588
  * have test files. Exits with code 1 if anything is missing.
3419
3589
  */
3420
- async function handleAudit() {
3590
+ async function handleAudit(options) {
3421
3591
  let data = await fetchAuditResult();
3422
3592
  if (!data) {
3423
3593
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3424
3594
  process.exit(1);
3425
3595
  }
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.
3596
+ // Two-phase auto-fix for entity associations:
3597
+ // Phase 1: DB-only backfill fast, matches existing entities to scenarios.
3598
+ // Phase 2: If backfill fails, targeted analyze-imports for only the
3599
+ // specific unresolved files (1-3 files, not the full glossary scan).
3430
3600
  const incompleteBeforeFix = data.incompleteEntities || [];
3431
3601
  const unassociatedBeforeFix = data.unassociatedScenarios || [];
3432
3602
  let autoRemediationFailed = false;
@@ -3451,20 +3621,65 @@ async function handleAudit() {
3451
3621
  catch {
3452
3622
  // Fall through — re-fetch will show remaining issues
3453
3623
  }
3454
- // Re-fetch audit results after the backfill
3455
- data = await fetchAuditResult();
3624
+ // Re-fetch audit results after the backfill — skip re-running tests
3625
+ // since they haven't changed (backfill is DB-only).
3626
+ data = await fetchAuditResult({ skipTests: true });
3456
3627
  if (!data) {
3457
3628
  console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
3458
3629
  process.exit(1);
3459
3630
  }
3460
- // If issues persist after backfill, flag it so we show clear guidance
3631
+ // If issues persist after DB-only backfill, run targeted analyze-imports
3632
+ // for ONLY the specific files that need entities. This is fast (1-3 files)
3633
+ // unlike full analyze-imports which scans all glossary entries.
3634
+ const incompleteAfterBackfill = data.incompleteEntities || [];
3635
+ const unassociatedAfterBackfill = data.unassociatedScenarios || [];
3636
+ if (incompleteAfterBackfill.length > 0 ||
3637
+ unassociatedAfterBackfill.length > 0) {
3638
+ const { determineTargetedAnalysisPaths } = await import('../utils/editorAudit.js');
3639
+ const targetPaths = determineTargetedAnalysisPaths({
3640
+ unassociatedScenarios: unassociatedAfterBackfill,
3641
+ incompleteEntities: incompleteAfterBackfill,
3642
+ });
3643
+ if (targetPaths.length > 0) {
3644
+ console.log(chalk.dim(`Running targeted analysis for ${targetPaths.length} file${targetPaths.length !== 1 ? 's' : ''}...`));
3645
+ try {
3646
+ await handleAnalyzeImports({
3647
+ silent: true,
3648
+ filePaths: targetPaths,
3649
+ });
3650
+ // Retry backfill after analysis created entities
3651
+ const entities = await loadEntities({});
3652
+ if (entities && entities.length > 0) {
3653
+ const { getDatabase: getDb } = await import('../../../packages/database/index.js');
3654
+ const freshDb = getDb();
3655
+ await backfillEntityShaOnScenarios(freshDb, entities.map((e) => ({
3656
+ sha: e.sha,
3657
+ name: e.name,
3658
+ filePath: e.filePath || '',
3659
+ isDefaultExport: e.metadata?.notExported === false &&
3660
+ e.metadata?.namedExport === false,
3661
+ })));
3662
+ }
3663
+ // Re-fetch audit results after targeted analysis
3664
+ data = await fetchAuditResult({ skipTests: true });
3665
+ if (!data) {
3666
+ console.error(chalk.red('Error: Could not reach the CodeYam server after analysis.'));
3667
+ process.exit(1);
3668
+ }
3669
+ }
3670
+ catch {
3671
+ // Targeted analysis failed — fall through to show remaining issues
3672
+ }
3673
+ }
3674
+ }
3675
+ // Check if issues persist after all remediation attempts
3461
3676
  const incompleteAfterFix = data.incompleteEntities || [];
3462
3677
  const unassociatedAfterFix = data.unassociatedScenarios || [];
3463
3678
  if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
3464
3679
  autoRemediationFailed = true;
3465
3680
  }
3466
3681
  }
3467
- const { components, functions, summary } = data;
3682
+ let { components, functions, summary } = data;
3468
3683
  console.log();
3469
3684
  console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
3470
3685
  console.log();
@@ -3563,51 +3778,53 @@ async function handleAudit() {
3563
3778
  for (const m of missingFromGlossary) {
3564
3779
  console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
3565
3780
  }
3566
- console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
3567
- console.log();
3568
- }
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 || [];
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
- }
3781
+ const mgPaths = missingFromGlossary
3782
+ .map((m) => m.filePath)
3783
+ .filter(Boolean);
3784
+ if (mgPaths.length > 0) {
3785
+ console.log(chalk.yellow(` Add these to .codeyam/glossary.json and run \`codeyam editor analyze-imports ${mgPaths.join(' ')}\``));
3600
3786
  }
3601
- catch {
3602
- // DB not available fall through to reporting
3787
+ else {
3788
+ console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
3603
3789
  }
3790
+ console.log();
3604
3791
  }
3792
+ // Incomplete entities (scenarios without analyses) — report with guidance.
3793
+ // We intentionally do NOT run analysis here: it starts the analyzer
3794
+ // template which is slow even for a few files. Users should run
3795
+ // `codeyam editor analyze-imports` separately if needed.
3796
+ const incompleteEntities = data.incompleteEntities || [];
3605
3797
  if (incompleteEntities.length > 0) {
3606
- const { formatIncompleteEntityGuidance } = await import('../utils/editorAudit.js');
3798
+ const { formatIncompleteEntityGuidance, formatManualAnalysisGuidance } = await import('../utils/editorAudit.js');
3799
+ const { readAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
3800
+ // Check for persistent analysis failures
3801
+ const analysisFailures = readAnalysisFailures(getProjectRoot());
3607
3802
  console.log(chalk.bold('Incomplete entities (need import analysis):'));
3608
3803
  for (const e of incompleteEntities) {
3609
- const guidance = formatIncompleteEntityGuidance(e);
3610
- console.log(` ${chalk.red('✗')} ${guidance}`);
3804
+ // Check if this entity has a persistent failure
3805
+ const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
3806
+ if (failureEntry) {
3807
+ // Show manual analysis instructions instead of "run analyze-imports"
3808
+ const filePath = Object.entries(analysisFailures).find(([, f]) => f.entityName === e.name)?.[0];
3809
+ const guidance = formatManualAnalysisGuidance({
3810
+ name: e.name,
3811
+ filePath: filePath || '',
3812
+ scenarioCount: e.scenarioCount,
3813
+ error: failureEntry.error,
3814
+ });
3815
+ // Print each line with proper indentation
3816
+ for (const line of guidance.split('\n')) {
3817
+ console.log(` ${chalk.red('✗')} ${line}`);
3818
+ }
3819
+ }
3820
+ else {
3821
+ const guidance = formatIncompleteEntityGuidance(e);
3822
+ console.log(` ${chalk.red('✗')} ${guidance}`);
3823
+ }
3824
+ }
3825
+ const incompleteErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
3826
+ if (fs.existsSync(incompleteErrorReportPath)) {
3827
+ console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
3611
3828
  }
3612
3829
  console.log();
3613
3830
  }
@@ -3624,12 +3841,22 @@ async function handleAudit() {
3624
3841
  console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
3625
3842
  }
3626
3843
  }
3844
+ const uaPaths = unassociatedScenarios
3845
+ .map((u) => u.filePath)
3846
+ .filter(Boolean);
3847
+ const uaCmd = uaPaths.length > 0
3848
+ ? `codeyam editor analyze-imports ${uaPaths.join(' ')}`
3849
+ : 'codeyam editor analyze-imports';
3627
3850
  if (autoRemediationFailed) {
3628
3851
  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.'));
3852
+ console.log(chalk.yellow(` Run \`${uaCmd}\` to see the full error output.`));
3630
3853
  }
3631
3854
  else {
3632
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to create entity records, then re-run audit to backfill.'));
3855
+ console.log(chalk.yellow(` Run \`${uaCmd}\` to create entity records, then re-run audit to backfill.`));
3856
+ }
3857
+ const unassocErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
3858
+ if (fs.existsSync(unassocErrorReportPath)) {
3859
+ console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
3633
3860
  }
3634
3861
  console.log();
3635
3862
  }
@@ -3658,7 +3885,26 @@ async function handleAudit() {
3658
3885
  : `${s.status.status}`;
3659
3886
  console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
3660
3887
  }
3661
- console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
3888
+ if (options?.fix) {
3889
+ // --fix: auto-recapture stale scenarios instead of just reporting them
3890
+ const { shouldAutoRecapture } = await import('../utils/editorAudit.js');
3891
+ if (shouldAutoRecapture({ fix: true, scenariosNeedingRecapture })) {
3892
+ console.log(chalk.cyan(' --fix: auto-recapturing stale scenarios...'));
3893
+ console.log();
3894
+ await handleRecaptureStale();
3895
+ // Re-fetch audit results so the summary and exit code reflect
3896
+ // the post-fix state, not the pre-fix state.
3897
+ const refreshed = await fetchAuditResult({ skipTests: true });
3898
+ if (refreshed) {
3899
+ data = refreshed;
3900
+ ({ components, functions, summary } = data);
3901
+ }
3902
+ }
3903
+ }
3904
+ else {
3905
+ console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
3906
+ console.log(chalk.dim(' Or: codeyam editor audit --fix (to auto-recapture)'));
3907
+ }
3662
3908
  console.log();
3663
3909
  }
3664
3910
  // Duplicate glossary names (warning, not a failure)
@@ -3721,17 +3967,6 @@ async function handleAudit() {
3721
3967
  if (!allOk) {
3722
3968
  process.exit(1);
3723
3969
  }
3724
- // Auto-run analyze-imports when audit passes — this builds the import graph
3725
- // so the App tab can show component/function dependencies for each page.
3726
- // Previously this was a manual step that Claude had to remember to run.
3727
- console.log(chalk.dim('Building import graph...'));
3728
- try {
3729
- await handleAnalyzeImports({ silent: true });
3730
- }
3731
- catch {
3732
- // Non-fatal — audit passed, import graph is a bonus
3733
- console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
3734
- }
3735
3970
  }
3736
3971
  // ─── Recapture-stale subcommand ────────────────────────────────────────
3737
3972
  /**
@@ -3938,14 +4173,14 @@ async function handleScenarioCoverage() {
3938
4173
  // Safety net: heal any scenarios with null entity_sha before checking coverage
3939
4174
  try {
3940
4175
  const { getDatabase } = await import('../../../packages/database/index.js');
3941
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
4176
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
3942
4177
  const db = getDatabase();
3943
4178
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
3944
4179
  if (backfillCount > 0) {
3945
4180
  console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
3946
4181
  await handleAnalyzeImports({ silent: true });
3947
4182
  // Run backfill after analysis
3948
- const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
4183
+ const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
3949
4184
  const entities = await loadEntities({});
3950
4185
  if (entities && entities.length > 0) {
3951
4186
  await backfillEntityShaOnScenarios(db, entities.map((e) => ({
@@ -4442,14 +4677,205 @@ function handleEditorDebug(args) {
4442
4677
  console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
4443
4678
  console.log();
4444
4679
  }
4680
+ // ─── Manual entity analysis ───────────────────────────────────────────
4681
+ /**
4682
+ * `codeyam editor manual-entity <JSON|@file>`
4683
+ *
4684
+ * Creates entity and analysis records from Claude-provided metadata when
4685
+ * automated analyze-imports fails. This unblocks the audit gate without
4686
+ * needing the analyzer template to parse the source file.
4687
+ */
4688
+ async function handleManualEntity(jsonArg) {
4689
+ const root = getProjectRoot();
4690
+ // Parse JSON input (supports @file.json convention)
4691
+ const parsed = parseRegisterArg(jsonArg);
4692
+ if (parsed.error || !parsed.body) {
4693
+ console.error(chalk.red(`Error: ${parsed.error || 'Invalid input'}`));
4694
+ console.error(chalk.dim(' Usage: codeyam editor manual-entity \'{"name":"Header","filePath":"src/components/Header.tsx","entityType":"visual",...}\''));
4695
+ console.error(chalk.dim(' Or: codeyam editor manual-entity @.codeyam/tmp/manual-entity.json'));
4696
+ process.exit(1);
4697
+ }
4698
+ const input = parsed.body;
4699
+ // Validate input
4700
+ const { validateManualEntityInput, convertTypeInfoToDataForMocks, readAnalysisFailures, clearFailureForPath, writeAnalysisFailures, } = await import('../utils/manualEntityAnalysis.js');
4701
+ // Read glossary for validation
4702
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
4703
+ let glossaryEntries = [];
4704
+ try {
4705
+ glossaryEntries = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
4706
+ }
4707
+ catch {
4708
+ // Empty glossary — validation will still check file existence
4709
+ }
4710
+ const errors = validateManualEntityInput(input, glossaryEntries, {
4711
+ fileExists: (p) => fs.existsSync(path.join(root, p)),
4712
+ });
4713
+ if (errors.length > 0) {
4714
+ console.error(chalk.red('Validation errors:'));
4715
+ for (const err of errors) {
4716
+ console.error(chalk.red(` • ${err}`));
4717
+ }
4718
+ process.exit(1);
4719
+ }
4720
+ // Read source file and compute SHA
4721
+ const sourceContent = fs.readFileSync(path.join(root, input.filePath), 'utf8');
4722
+ const { generateSha } = await import('../../../packages/database/index.js');
4723
+ const entitySha = generateSha(input.filePath, input.name, sourceContent);
4724
+ // Get project and branch
4725
+ await initializeEnvironment();
4726
+ const configPath = path.join(root, '.codeyam', 'config.json');
4727
+ let projectSlug;
4728
+ try {
4729
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
4730
+ projectSlug = config.projectSlug;
4731
+ }
4732
+ catch {
4733
+ console.error(chalk.red('Error: .codeyam/config.json not found or invalid.'));
4734
+ process.exit(1);
4735
+ }
4736
+ const { project, branch } = await requireBranchAndProject(projectSlug);
4737
+ const { getDatabase } = await import('../../../packages/database/index.js');
4738
+ const db = getDatabase();
4739
+ // Convert type info to dataForMocks format
4740
+ const dataForMocks = convertTypeInfoToDataForMocks(input.typeInfo);
4741
+ // Create entity record
4742
+ const entityMetadata = {
4743
+ importedExports: (input.importedExports || []).map((ie) => ({
4744
+ name: ie.name,
4745
+ filePath: ie.filePath,
4746
+ })),
4747
+ manuallyAnalyzed: true,
4748
+ };
4749
+ // Check if entity already exists
4750
+ const existingEntity = await db
4751
+ .selectFrom('entities')
4752
+ .select('sha')
4753
+ .where('sha', '=', entitySha)
4754
+ .executeTakeFirst();
4755
+ if (!existingEntity) {
4756
+ await db
4757
+ .insertInto('entities')
4758
+ .values({
4759
+ sha: entitySha,
4760
+ project_id: project.id,
4761
+ name: input.name,
4762
+ entity_type: input.entityType,
4763
+ file_path: input.filePath,
4764
+ metadata: JSON.stringify(entityMetadata),
4765
+ })
4766
+ .onConflict((oc) => oc.column('sha').doNothing())
4767
+ .execute();
4768
+ console.log(chalk.dim(`Created entity record: ${input.name} (${entitySha.slice(0, 8)}...)`));
4769
+ }
4770
+ else {
4771
+ // Update metadata on existing entity
4772
+ await db
4773
+ .updateTable('entities')
4774
+ .set({
4775
+ metadata: JSON.stringify(entityMetadata),
4776
+ })
4777
+ .where('sha', '=', entitySha)
4778
+ .execute();
4779
+ console.log(chalk.dim(`Updated existing entity: ${input.name} (${entitySha.slice(0, 8)}...)`));
4780
+ }
4781
+ // Create entity_branch record
4782
+ await db
4783
+ .insertInto('entity_branches')
4784
+ .values({
4785
+ entity_sha: entitySha,
4786
+ branch_id: branch.id,
4787
+ active: true,
4788
+ })
4789
+ .onConflict((oc) => oc.columns(['entity_sha', 'branch_id']).doUpdateSet({ active: true }))
4790
+ .execute();
4791
+ // Create analysis record
4792
+ const { randomUUID } = await import('crypto');
4793
+ const analysisId = randomUUID();
4794
+ const now = new Date().toISOString();
4795
+ const analysisMetadata = {
4796
+ scenariosDataStructure: {
4797
+ dataForMocks: Object.keys(dataForMocks).length > 0 ? dataForMocks : null,
4798
+ },
4799
+ mergedDataStructure: {
4800
+ dependencySchemas: null,
4801
+ },
4802
+ manuallyAnalyzed: true,
4803
+ };
4804
+ const analysisStatus = {
4805
+ startedAt: now,
4806
+ finishedAt: now,
4807
+ steps: [],
4808
+ errors: [],
4809
+ };
4810
+ // Check if analysis already exists for this entity
4811
+ const existingAnalysis = await db
4812
+ .selectFrom('analyses')
4813
+ .select('id')
4814
+ .where('entity_sha', '=', entitySha)
4815
+ .executeTakeFirst();
4816
+ if (!existingAnalysis) {
4817
+ await db
4818
+ .insertInto('analyses')
4819
+ .values({
4820
+ id: analysisId,
4821
+ project_id: project.id,
4822
+ entity_sha: entitySha,
4823
+ entity_name: input.name,
4824
+ entity_type: input.entityType,
4825
+ file_path: input.filePath,
4826
+ status: JSON.stringify(analysisStatus),
4827
+ metadata: JSON.stringify(analysisMetadata),
4828
+ })
4829
+ .execute();
4830
+ console.log(chalk.dim(`Created analysis record: ${analysisId.slice(0, 8)}...`));
4831
+ }
4832
+ else {
4833
+ // Update existing analysis with manual metadata
4834
+ await db
4835
+ .updateTable('analyses')
4836
+ .set({
4837
+ metadata: JSON.stringify(analysisMetadata),
4838
+ status: JSON.stringify(analysisStatus),
4839
+ })
4840
+ .where('entity_sha', '=', entitySha)
4841
+ .execute();
4842
+ console.log(chalk.dim(`Updated existing analysis for ${input.name}`));
4843
+ }
4844
+ // Backfill entity_sha on scenarios
4845
+ const backfillResult = await backfillEntityShaOnScenarios(db, [
4846
+ {
4847
+ sha: entitySha,
4848
+ name: input.name,
4849
+ filePath: input.filePath,
4850
+ isDefaultExport: false,
4851
+ },
4852
+ ]);
4853
+ if (backfillResult.updated > 0) {
4854
+ console.log(chalk.dim(`Linked ${backfillResult.updated} scenario(s) to entity`));
4855
+ }
4856
+ // Clear from analysis failures tracker
4857
+ const failures = readAnalysisFailures(root);
4858
+ if (failures[input.filePath]) {
4859
+ const updated = clearFailureForPath(failures, input.filePath);
4860
+ writeAnalysisFailures(root, updated);
4861
+ console.log(chalk.dim(`Cleared analysis failure for ${input.filePath}`));
4862
+ }
4863
+ console.log();
4864
+ console.log(chalk.green.bold(`✓ Manual entity analysis complete: ${input.name}`));
4865
+ console.log(chalk.dim(` Entity SHA: ${entitySha.slice(0, 12)}...`));
4866
+ console.log(chalk.dim(` Imports: ${(input.importedExports || []).length} glossary entities`));
4867
+ console.log(chalk.dim(` Type info: ${Object.keys(dataForMocks).length} fields`));
4868
+ console.log();
4869
+ console.log(chalk.dim('Re-run `codeyam editor audit` to verify.'));
4870
+ }
4445
4871
  // ─── Command definition ───────────────────────────────────────────────
4446
4872
  const editorCommand = {
4447
4873
  command: 'editor [step] [json]',
4448
4874
  describe: 'Editor mode guided workflow',
4449
4875
  builder: (yargs) => {
4450
4876
  const stepDescription = IS_INTERNAL_BUILD
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)';
4877
+ ? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)'
4878
+ : 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)';
4453
4879
  let builder = yargs
4454
4880
  .positional('step', {
4455
4881
  type: 'string',
@@ -4480,6 +4906,11 @@ const editorCommand = {
4480
4906
  alias: 'p',
4481
4907
  describe: 'Port to run the web server on',
4482
4908
  default: 3111,
4909
+ })
4910
+ .option('fix', {
4911
+ type: 'boolean',
4912
+ describe: 'For audit: also recapture stale scenarios after displaying results',
4913
+ default: false,
4483
4914
  });
4484
4915
  if (IS_INTERNAL_BUILD) {
4485
4916
  builder = builder
@@ -4591,7 +5022,9 @@ const editorCommand = {
4591
5022
  'Ask user what to build next and restart codeyam editor workflow';
4592
5023
  }
4593
5024
  console.log(chalk.bold.cyan('━━━ TASK ━━━'));
4594
- console.log(chalk.cyan('Mark your current task (if any) as complete.'));
5025
+ console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
5026
+ console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
5027
+ console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
4595
5028
  console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
4596
5029
  console.log();
4597
5030
  console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
@@ -4605,9 +5038,16 @@ const editorCommand = {
4605
5038
  fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
4606
5039
  return;
4607
5040
  }
4608
- // Subcommand: codeyam editor analyze-imports
5041
+ // Subcommand: codeyam editor analyze-imports [file1.tsx file2.tsx ...]
4609
5042
  if (argv.step === 'analyze-imports') {
4610
- await handleAnalyzeImports();
5043
+ const { parseAnalyzeImportsArgs } = await import('./editorAnalyzeImportsArgs.js');
5044
+ const requestedPaths = parseAnalyzeImportsArgs(argv.json, argv._);
5045
+ await handleAnalyzeImports(requestedPaths.length > 0 ? { filePaths: requestedPaths } : {});
5046
+ return;
5047
+ }
5048
+ // Subcommand: codeyam editor manual-entity <JSON|@file>
5049
+ if (argv.step === 'manual-entity') {
5050
+ await handleManualEntity(argv.json || '');
4611
5051
  return;
4612
5052
  }
4613
5053
  // Subcommand: codeyam editor dependents <EntityName>
@@ -4615,9 +5055,9 @@ const editorCommand = {
4615
5055
  await handleDependents(argv.json || '');
4616
5056
  return;
4617
5057
  }
4618
- // Subcommand: codeyam editor audit
5058
+ // Subcommand: codeyam editor audit [--fix]
4619
5059
  if (argv.step === 'audit') {
4620
- await handleAudit();
5060
+ await handleAudit({ fix: argv.fix || false });
4621
5061
  return;
4622
5062
  }
4623
5063
  // Subcommand: codeyam editor scenarios
@@ -4850,13 +5290,24 @@ const editorCommand = {
4850
5290
  try {
4851
5291
  const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4852
5292
  const seedDb = getDb();
4853
- const appScenario = await seedDb
5293
+ // Prefer the home page scenario (url "/") since the App tab
5294
+ // sorts "Home" first — fall back to any application scenario.
5295
+ const homeScenario = await seedDb
4854
5296
  .selectFrom('editor_scenarios')
4855
5297
  .select(['id', 'name', 'type'])
4856
5298
  .where('project_id', '=', project.id)
4857
- .where('type', '=', 'application')
5299
+ .where('url', '=', '/')
5300
+ .where('component_name', 'is', null)
4858
5301
  .orderBy('created_at', 'asc')
4859
5302
  .executeTakeFirst();
5303
+ const appScenario = homeScenario ||
5304
+ (await seedDb
5305
+ .selectFrom('editor_scenarios')
5306
+ .select(['id', 'name', 'type'])
5307
+ .where('project_id', '=', project.id)
5308
+ .where('type', '=', 'application')
5309
+ .orderBy('created_at', 'asc')
5310
+ .executeTakeFirst());
4860
5311
  if (appScenario) {
4861
5312
  const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
4862
5313
  if (fs.existsSync(seedFile)) {
@@ -4999,7 +5450,7 @@ const editorCommand = {
4999
5450
  if (!needsAnalysis) {
5000
5451
  try {
5001
5452
  const { getDatabase } = await import('../../../packages/database/index.js');
5002
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
5453
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
5003
5454
  const db = getDatabase();
5004
5455
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
5005
5456
  if (backfillCount > 0) {
@@ -5038,7 +5489,7 @@ const editorCommand = {
5038
5489
  .execute();
5039
5490
  if (unresolved.length > 0) {
5040
5491
  const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
5041
- const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
5492
+ const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
5042
5493
  const { allFiles: pfpFiles } = scanPfp(projectRoot);
5043
5494
  let pfpResolved = 0;
5044
5495
  for (const row of unresolved) {