@codeyam/codeyam-cli 0.1.20 → 0.1.22

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 (130) hide show
  1. package/analyzer-template/.build-info.json +7 -7
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +1 -1
  4. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +36 -9
  5. package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.ts +10 -3
  6. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +16 -6
  7. package/analyzer-template/packages/analyze/index.ts +4 -1
  8. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +28 -2
  9. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +5 -36
  10. package/analyzer-template/packages/analyze/src/lib/files/analyze/findOrCreateEntity.ts +1 -0
  11. package/analyzer-template/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.ts +21 -0
  12. package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +82 -10
  13. package/analyzer-template/packages/analyze/src/lib/files/analyzeChange.ts +4 -0
  14. package/analyzer-template/packages/analyze/src/lib/files/analyzeInitial.ts +4 -0
  15. package/analyzer-template/packages/analyze/src/lib/files/analyzeNextRoute.ts +8 -3
  16. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +235 -58
  17. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +170 -26
  18. package/analyzer-template/packages/aws/package.json +1 -1
  19. package/analyzer-template/packages/database/src/lib/loadEntity.ts +11 -4
  20. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts +4 -1
  21. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts.map +1 -1
  22. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js +4 -4
  23. package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js.map +1 -1
  24. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts +3 -1
  25. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
  26. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +22 -1
  27. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  28. package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +27 -0
  29. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js +63 -0
  30. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
  31. package/codeyam-cli/src/commands/editor.js +553 -93
  32. package/codeyam-cli/src/commands/editor.js.map +1 -1
  33. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +991 -31
  34. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  35. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
  36. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
  37. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +33 -1
  38. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  39. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
  40. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
  41. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js +127 -0
  42. package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js.map +1 -0
  43. package/codeyam-cli/src/utils/__tests__/testRunner.test.js +217 -0
  44. package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
  45. package/codeyam-cli/src/utils/analysisRunner.js +28 -1
  46. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  47. package/codeyam-cli/src/utils/analyzer.js +11 -1
  48. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  49. package/codeyam-cli/src/utils/editorAudit.js +210 -14
  50. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  51. package/codeyam-cli/src/utils/editorPreview.js +5 -3
  52. package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
  53. package/codeyam-cli/src/utils/entityChangeStatus.server.js +16 -0
  54. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  55. package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
  56. package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
  57. package/codeyam-cli/src/utils/queue/job.js +20 -2
  58. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  59. package/codeyam-cli/src/utils/registerScenarioResult.js +52 -0
  60. package/codeyam-cli/src/utils/registerScenarioResult.js.map +1 -0
  61. package/codeyam-cli/src/utils/testRunner.js +199 -1
  62. package/codeyam-cli/src/utils/testRunner.js.map +1 -1
  63. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +35 -0
  64. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
  65. package/codeyam-cli/src/webserver/app/lib/clientErrors.js +3 -0
  66. package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
  67. package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-CQENLSrF.js +36 -0
  68. package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-scenario-data-l0sNRNKZ.js +1 -0
  69. package/codeyam-cli/src/webserver/build/client/assets/api.editor-schema-l0sNRNKZ.js +1 -0
  70. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-Coe5NhbS.js +1 -0
  71. package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CCKUIm0S.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
  72. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-aIHKLB-m.js +96 -0
  73. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-CluPkvXJ.js +41 -0
  74. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-Blfy9UlN.js → entity._sha._-ByHz6rAQ.js} +13 -12
  75. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-KTQuL0aj.js → entity._sha.scenarios._scenarioId.dev-CmLO432x.js} +1 -1
  76. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js → entity._sha.scenarios._scenarioId.fullscreen-Bz9sCUF_.js} +1 -1
  77. package/codeyam-cli/src/webserver/build/client/assets/globals-oyPmV37k.css +1 -0
  78. package/codeyam-cli/src/webserver/build/client/assets/manifest-bcbb3d49.js +1 -0
  79. package/codeyam-cli/src/webserver/build/client/assets/{root-BxUQigda.js → root-D2_tktnk.js} +26 -13
  80. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-DjF-soOH.js +16 -0
  81. package/codeyam-cli/src/webserver/build/server/assets/{index-CjLhfz6Z.js → index-nAvHGWbz.js} +1 -1
  82. package/codeyam-cli/src/webserver/build/server/assets/{init-BEqlbI84.js → init-XhpIt-OT.js} +1 -1
  83. package/codeyam-cli/src/webserver/build/server/assets/server-build-DVwiibFu.js +644 -0
  84. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  85. package/codeyam-cli/src/webserver/build-info.json +5 -5
  86. package/codeyam-cli/src/webserver/idleDetector.js +15 -0
  87. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
  88. package/codeyam-cli/src/webserver/terminalServer.js +18 -5
  89. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  90. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +2 -2
  91. package/package.json +1 -1
  92. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +27 -10
  93. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  94. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
  95. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
  96. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
  97. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  98. package/packages/analyze/index.js +1 -1
  99. package/packages/analyze/index.js.map +1 -1
  100. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
  101. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  102. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
  103. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  104. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +1 -0
  105. package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
  106. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
  107. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
  108. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
  109. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
  110. package/packages/analyze/src/lib/files/analyzeChange.js +1 -0
  111. package/packages/analyze/src/lib/files/analyzeChange.js.map +1 -1
  112. package/packages/analyze/src/lib/files/analyzeInitial.js +1 -0
  113. package/packages/analyze/src/lib/files/analyzeInitial.js.map +1 -1
  114. package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
  115. package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
  116. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +116 -28
  117. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  118. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +139 -24
  119. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  120. package/packages/database/src/lib/loadEntity.js +4 -4
  121. package/packages/database/src/lib/loadEntity.js.map +1 -1
  122. package/packages/utils/src/lib/fs/rsyncCopy.js +22 -1
  123. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  124. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DcX-ZS3p.js +0 -1
  125. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DII1pg_z.js +0 -58
  126. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-oepecPae.js +0 -41
  127. package/codeyam-cli/src/webserver/build/client/assets/globals-Yn9W3zp3.css +0 -1
  128. package/codeyam-cli/src/webserver/build/client/assets/manifest-cdf2c0a7.js +0 -1
  129. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-B_PsTAb1.js +0 -13
  130. package/codeyam-cli/src/webserver/build/server/assets/server-build-YI63xTu4.js +0 -553
@@ -21,6 +21,7 @@ import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } f
21
21
  import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
22
22
  import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
23
23
  import { parseRegisterArg } from "../utils/parseRegisterArg.js";
24
+ import { classifyRegistrationResult } from "../utils/registerScenarioResult.js";
24
25
  import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
25
26
  import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
26
27
  const __filename = fileURLToPath(import.meta.url);
@@ -210,6 +211,34 @@ function checkbox(text) {
210
211
  const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
211
212
  console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
212
213
  }
214
+ /**
215
+ * Instructions for creating/updating .codeyam/data-structure.json.
216
+ * Only prints creation instructions if the file doesn't exist yet.
217
+ * If it exists, reminds Claude to update it if data models changed.
218
+ */
219
+ function printDataStructureInstructions() {
220
+ const root = getProjectRoot();
221
+ const dsPath = path.join(root, '.codeyam', 'data-structure.json');
222
+ const exists = fs.existsSync(dsPath);
223
+ if (!exists) {
224
+ console.log(chalk.bold('Create the data structure config:'));
225
+ checkbox('Create `.codeyam/data-structure.json` describing all data sources');
226
+ console.log(chalk.dim(" This file tells the editor what data the app uses and how it's stored."));
227
+ console.log(chalk.dim(' The file is a JSON array. Each entry describes one data source:'));
228
+ console.log(chalk.dim(' { "name": "Drink", "description": "...", "category": "datastore", "order": 1,'));
229
+ console.log(chalk.dim(' "fields": [{ "name": "id", "type": "number", "isId": true, "required": true }, ...] }'));
230
+ console.log();
231
+ console.log(chalk.dim(' category: "datastore" for persisted data (DB, localStorage)'));
232
+ console.log(chalk.dim(' "mock-api" for mocked external API responses'));
233
+ console.log(chalk.dim(' order: 1 = primary data store, 2 = secondary, etc.'));
234
+ console.log(chalk.dim(' Do NOT include types only used as component props.'));
235
+ }
236
+ else {
237
+ checkbox('If this feature changes data models, update `.codeyam/data-structure.json`');
238
+ console.log(chalk.dim(' Add new tables, update fields, or change descriptions to match.'));
239
+ }
240
+ console.log();
241
+ }
213
242
  /**
214
243
  * Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
215
244
  * Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
@@ -328,7 +357,7 @@ function printExtractionPlanInstructions() {
328
357
  */
329
358
  function printComponentCaptureInstructions() {
330
359
  checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
331
- 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'));
332
361
  console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
333
362
  checkbox('For each visual component:');
334
363
  console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
@@ -340,8 +369,8 @@ function printComponentCaptureInstructions() {
340
369
  console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
341
370
  console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
342
371
  console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
343
- console.log(chalk.dim(' Remix: app/routes/codeyam-isolate.ComponentName.tsx → /codeyam-isolate/ComponentName'));
344
- 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'));
345
374
  console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
346
375
  console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
347
376
  console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
@@ -354,9 +383,9 @@ function printComponentCaptureInstructions() {
354
383
  console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
355
384
  console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
356
385
  console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
357
- console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
386
+ console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
358
387
  console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
359
- 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/...).'));
360
389
  console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
361
390
  console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
362
391
  console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
@@ -366,7 +395,7 @@ function printComponentCaptureInstructions() {
366
395
  console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
367
396
  console.log();
368
397
  console.log(chalk.bold(' Bulk registration: write all component scenarios to a temp file as an array:'));
369
- console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/codeyam-isolate/Comp?s=Default"}, ...]'));
398
+ console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
370
399
  console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
371
400
  }
372
401
  /**
@@ -419,7 +448,9 @@ function stopGate(current, opts) {
419
448
  // Skip step 1 (no feature name yet — task starts at step 2)
420
449
  if (current >= 2) {
421
450
  console.log(chalk.bold.cyan('━━━ TASK ━━━'));
422
- 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'));
423
454
  console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
424
455
  console.log();
425
456
  let taskTitle;
@@ -978,6 +1009,7 @@ function printStep2(root, feature) {
978
1009
  console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
979
1010
  console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
980
1011
  console.log();
1012
+ printDataStructureInstructions();
981
1013
  }
982
1014
  else {
983
1015
  console.log(chalk.bold('Prepare the database for this feature:'));
@@ -992,6 +1024,7 @@ function printStep2(root, feature) {
992
1024
  console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
993
1025
  console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
994
1026
  console.log();
1027
+ printDataStructureInstructions();
995
1028
  }
996
1029
  stopGate(2);
997
1030
  }
@@ -1326,7 +1359,13 @@ function printStep9(root, feature) {
1326
1359
  console.log();
1327
1360
  checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
1328
1361
  console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
1329
- console.log(chalk.dim(' When audit passes, the import graph is built automatically for change tracking.'));
1362
+ console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
1363
+ console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
1364
+ console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
1365
+ console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
1366
+ console.log(chalk.dim(' If the audit reports "MANUAL ANALYSIS REQUIRED" for any entities,'));
1367
+ console.log(chalk.dim(' follow the manual analysis steps in the audit output.'));
1368
+ console.log(chalk.dim(' Do NOT re-run analyze-imports for those entities — it will fail again.'));
1330
1369
  console.log();
1331
1370
  stopGate(9);
1332
1371
  }
@@ -1928,7 +1967,7 @@ function printMigrateStep8(root) {
1928
1967
  console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
1929
1968
  console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
1930
1969
  checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
1931
- 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)');
1932
1971
  checkbox('Re-register each one with the SAME name to update its screenshot');
1933
1972
  console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
1934
1973
  checkbox('After each registration, check the response for `clientErrors`');
@@ -2362,25 +2401,132 @@ async function handleAnalyzeImports(options = {}) {
2362
2401
  // Non-fatal — scenario file paths just won't be included
2363
2402
  }
2364
2403
  const progress = new ProgressReporter();
2365
- // Run data-structure-only analysis for all entities (glossary + pages)
2404
+ // Filter to only files that actually need analysis avoids re-analyzing
2405
+ // ~120 files when only 2 are incomplete.
2406
+ let targetFilePaths = filePaths;
2407
+ try {
2408
+ const { getDatabase } = await import('../../../packages/database/index.js');
2409
+ const db = getDatabase();
2410
+ const { requireBranchAndProject: rbp } = await import('../utils/database.js');
2411
+ const { project } = await rbp(JSON.parse(fs.readFileSync(path.join(root, '.codeyam', 'config.json'), 'utf8')).projectSlug);
2412
+ const { filterToIncompleteFilePaths } = await import('../utils/editorAudit.js');
2413
+ const incomplete = await filterToIncompleteFilePaths(db, project.id, filePaths);
2414
+ if (incomplete.length < filePaths.length && incomplete.length > 0) {
2415
+ if (!options.silent) {
2416
+ console.log(chalk.dim(`Skipping ${filePaths.length - incomplete.length} already-analyzed files, analyzing ${incomplete.length} remaining...`));
2417
+ }
2418
+ targetFilePaths = incomplete;
2419
+ }
2420
+ else if (incomplete.length === 0) {
2421
+ if (!options.silent) {
2422
+ console.log(chalk.green('All entities already have analyses — nothing to do.'));
2423
+ }
2424
+ // Still need to run the import graph + backfill below
2425
+ targetFilePaths = [];
2426
+ }
2427
+ }
2428
+ catch {
2429
+ // Non-fatal — fall back to analyzing all files
2430
+ }
2431
+ // Run data-structure-only analysis for entities that need it.
2366
2432
  // Don't pass entityNames — entities may not exist yet (fresh clone).
2367
2433
  // The analyzer will discover and create them from file paths.
2368
- progress.start('Running import analysis for all entities...');
2369
- try {
2370
- await runAnalysisForEntities({
2371
- projectRoot: root,
2372
- filePaths,
2373
- progress,
2374
- onlyDataStructure: true,
2375
- });
2434
+ if (targetFilePaths.length > 0) {
2435
+ progress.start(`Running import analysis for ${targetFilePaths.length} entit${targetFilePaths.length !== 1 ? 'ies' : 'y'}...`);
2436
+ try {
2437
+ await runAnalysisForEntities({
2438
+ projectRoot: root,
2439
+ filePaths: targetFilePaths,
2440
+ progress,
2441
+ onlyDataStructure: true,
2442
+ });
2443
+ }
2444
+ catch (err) {
2445
+ progress.fail('Analysis failed');
2446
+ const msg = err instanceof Error ? err.message : String(err);
2447
+ console.error(chalk.red(`Error: ${msg}`));
2448
+ process.exit(1);
2449
+ }
2450
+ progress.succeed('Analysis complete');
2376
2451
  }
2377
- catch (err) {
2378
- progress.fail('Analysis failed');
2379
- const msg = err instanceof Error ? err.message : String(err);
2380
- console.error(chalk.red(`Error: ${msg}`));
2381
- process.exit(1);
2452
+ // Surface analysis errors if any occurred
2453
+ const errorReportPath = path.join(root, '.codeyam', 'analysis-errors.txt');
2454
+ if (fs.existsSync(errorReportPath)) {
2455
+ if (!options.silent) {
2456
+ console.log(chalk.yellow('\nSome entity analyses failed. Full details in .codeyam/analysis-errors.txt'));
2457
+ console.log(chalk.yellow('Send this file to the CodeYam team for diagnosis.'));
2458
+ console.log(chalk.dim('Affected entities will be missing from the import graph.'));
2459
+ }
2460
+ // Write structured analysis-failures.json for the audit to detect.
2461
+ // Parse the error report to find which entities failed, and cross-reference
2462
+ // with the files we tried to analyze.
2463
+ try {
2464
+ const { readAnalysisFailures, writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
2465
+ const errorContent = fs.readFileSync(errorReportPath, 'utf8');
2466
+ const existingFailures = readAnalysisFailures(root);
2467
+ // Parse error messages to identify failed file paths.
2468
+ // Error format: "Error on step <StepName> for entity <Name> (<filePath>)"
2469
+ // or more generic "CodeYam Error: <message>"
2470
+ const entityErrorRegex = /(?:Error on step \w+ for entity (\w+))|(?:entity[: ]+["']?(\w+)["']?)/gi;
2471
+ const failedEntityNames = new Set();
2472
+ let match;
2473
+ while ((match = entityErrorRegex.exec(errorContent)) !== null) {
2474
+ const name = match[1] || match[2];
2475
+ if (name)
2476
+ failedEntityNames.add(name);
2477
+ }
2478
+ // Map failed entity names back to file paths from targetFilePaths
2479
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
2480
+ let glossary = [];
2481
+ try {
2482
+ glossary = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
2483
+ }
2484
+ catch {
2485
+ // Non-fatal
2486
+ }
2487
+ // Also: any targetFilePaths that didn't produce entities are failures
2488
+ const now = new Date().toISOString();
2489
+ const updatedFailures = { ...existingFailures };
2490
+ if (failedEntityNames.size > 0) {
2491
+ for (const name of failedEntityNames) {
2492
+ const entry = glossary.find((e) => e.name === name);
2493
+ if (entry) {
2494
+ updatedFailures[entry.filePath] = {
2495
+ entityName: name,
2496
+ error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
2497
+ failedAt: now,
2498
+ };
2499
+ }
2500
+ }
2501
+ }
2502
+ else if (targetFilePaths.length > 0) {
2503
+ // Couldn't parse specific entity names — mark all target files
2504
+ // that we attempted to analyze as potentially failed
2505
+ for (const fp of targetFilePaths) {
2506
+ const entry = glossary.find((e) => e.filePath === fp);
2507
+ updatedFailures[fp] = {
2508
+ entityName: entry?.name || path.basename(fp, path.extname(fp)),
2509
+ error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
2510
+ failedAt: now,
2511
+ };
2512
+ }
2513
+ }
2514
+ writeAnalysisFailures(root, updatedFailures);
2515
+ }
2516
+ catch {
2517
+ // Non-fatal — failure tracking is best-effort
2518
+ }
2519
+ }
2520
+ else {
2521
+ // No errors — clear any stale failure tracking
2522
+ try {
2523
+ const { writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
2524
+ writeAnalysisFailures(root, {});
2525
+ }
2526
+ catch {
2527
+ // Non-fatal
2528
+ }
2382
2529
  }
2383
- progress.succeed('Analysis complete');
2384
2530
  // Load entities WITH metadata from database
2385
2531
  progress.start('Loading entity metadata...');
2386
2532
  await initializeEnvironment();
@@ -2736,6 +2882,8 @@ async function handleRegister(jsonArg) {
2736
2882
  const isBatch = items.length > 1;
2737
2883
  let succeeded = 0;
2738
2884
  let failed = 0;
2885
+ let warnings = 0;
2886
+ const issues = [];
2739
2887
  const screenshotEntries = [];
2740
2888
  for (let i = 0; i < items.length; i++) {
2741
2889
  const body = items[i];
@@ -2805,11 +2953,14 @@ async function handleRegister(jsonArg) {
2805
2953
  }
2806
2954
  console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
2807
2955
  }
2956
+ const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
2808
2957
  if (!res.ok) {
2809
2958
  console.error(chalk.dim(JSON.stringify(data, null, 2)));
2810
2959
  failed++;
2811
2960
  }
2812
2961
  else {
2962
+ if (resultIssues.length > 0)
2963
+ warnings++;
2813
2964
  succeeded++;
2814
2965
  // Collect screenshot paths for duplicate detection
2815
2966
  if (data.screenshotCaptured &&
@@ -2822,6 +2973,10 @@ async function handleRegister(jsonArg) {
2822
2973
  });
2823
2974
  }
2824
2975
  }
2976
+ // Collect issues from this registration
2977
+ for (const issue of resultIssues) {
2978
+ issues.push(`"${issue.scenarioName}": ${issue.message}`);
2979
+ }
2825
2980
  }
2826
2981
  catch (error) {
2827
2982
  const msg = error instanceof Error ? error.message : String(error);
@@ -2833,7 +2988,7 @@ async function handleRegister(jsonArg) {
2833
2988
  }
2834
2989
  // Detect duplicate screenshots in the batch
2835
2990
  if (isBatch && screenshotEntries.length > 1) {
2836
- const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash');
2991
+ const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
2837
2992
  const hashEntries = screenshotEntries
2838
2993
  .map((e) => {
2839
2994
  const hash = computeScreenshotHash(e.screenshotAbsPath);
@@ -2859,9 +3014,22 @@ async function handleRegister(jsonArg) {
2859
3014
  }
2860
3015
  }
2861
3016
  if (isBatch) {
2862
- console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
3017
+ console.log('');
3018
+ if (failed > 0 || warnings > 0) {
3019
+ console.log(chalk.red.bold(`ERROR: ${failed} failed, ${warnings} with warnings out of ${items.length} scenarios`));
3020
+ console.log('');
3021
+ console.log(chalk.red.bold('Issues that MUST be fixed:'));
3022
+ for (const issue of issues) {
3023
+ console.log(chalk.red(` ✗ ${issue}`));
3024
+ }
3025
+ console.log('');
3026
+ console.log(chalk.red('Do NOT skip these errors. Fix each issue and re-register the affected scenarios.'));
3027
+ }
3028
+ else {
3029
+ console.log(chalk.green(`✓ Batch complete: ${succeeded}/${items.length} succeeded with no issues`));
3030
+ }
2863
3031
  }
2864
- if (failed > 0) {
3032
+ if (failed > 0 || warnings > 0) {
2865
3033
  process.exit(1);
2866
3034
  }
2867
3035
  }
@@ -3150,10 +3318,11 @@ function handleChange(feature) {
3150
3318
  * Fetch the audit result from the server.
3151
3319
  * Returns null if the server is unreachable.
3152
3320
  */
3153
- async function fetchAuditResult() {
3321
+ async function fetchAuditResult(options) {
3154
3322
  const port = getServerPort();
3323
+ const params = options?.skipTests ? '?skipTests=true' : '';
3155
3324
  try {
3156
- const res = await fetch(`http://localhost:${port}/api/editor-audit`);
3325
+ const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
3157
3326
  if (!res.ok)
3158
3327
  return null;
3159
3328
  return await res.json();
@@ -3176,17 +3345,15 @@ async function checkAuditGate() {
3176
3345
  return true; // Server unreachable — don't block
3177
3346
  if (data.summary?.allPassing === true)
3178
3347
  return true;
3179
- // If incomplete entities / unassociated scenarios are the only issue,
3180
- // auto-fix with analyze-imports + entity SHA backfill (once)
3181
- const { isAutoRemediable } = await import('../utils/editorAudit.js');
3182
- if (isAutoRemediable(data.summary, false)) {
3183
- try {
3184
- await handleAnalyzeImports({ silent: true });
3185
- }
3186
- catch {
3187
- // Fall through to backfill attempt
3188
- }
3189
- // Backfill entity_sha on scenarios registered before entities existed
3348
+ // Lightweight auto-fix: backfill entity_sha (DB-only, fast).
3349
+ // Never run handleAnalyzeImports here it takes minutes on large projects.
3350
+ const { isOnlyIncompleteEntities, isOnlyPreExistingIncomplete } = await import('../utils/editorAudit.js');
3351
+ // If the only failures are pre-existing incomplete entities (not caused by
3352
+ // this session), don't block — the audit text already calls these "non-blocking".
3353
+ if (isOnlyPreExistingIncomplete(data.summary, data.incompleteEntities)) {
3354
+ return true;
3355
+ }
3356
+ if (isOnlyIncompleteEntities(data.summary)) {
3190
3357
  try {
3191
3358
  const entities = await loadEntities({});
3192
3359
  if (entities && entities.length > 0) {
@@ -3204,8 +3371,8 @@ async function checkAuditGate() {
3204
3371
  catch {
3205
3372
  // Fall through
3206
3373
  }
3207
- // Re-check after fix
3208
- const retry = await fetchAuditResult();
3374
+ // Re-check after backfill — skip re-running tests (backfill is DB-only)
3375
+ const retry = await fetchAuditResult({ skipTests: true });
3209
3376
  if (retry?.summary?.allPassing === true)
3210
3377
  return true;
3211
3378
  }
@@ -3228,6 +3395,8 @@ function printAuditGateFailures(data) {
3228
3395
  const missing = (data.components || []).filter((c) => c.status === 'missing');
3229
3396
  for (const c of missing) {
3230
3397
  issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
3398
+ if (c.hint)
3399
+ issues.push(` Fix: ${c.hint}`);
3231
3400
  }
3232
3401
  }
3233
3402
  if (s.componentsWithErrors > 0) {
@@ -3241,7 +3410,15 @@ function printAuditGateFailures(data) {
3241
3410
  issues.push(`${s.functionsMissing} function(s) missing test files`);
3242
3411
  const missing = (data.functions || []).filter((f) => f.status === 'missing');
3243
3412
  for (const f of missing) {
3244
- issues.push(` → ${f.name}${f.filePath ? ` (${f.filePath})` : ''}`);
3413
+ if (f.testFile) {
3414
+ issues.push(` → ${f.name} — test file missing: ${f.testFile}`);
3415
+ }
3416
+ else {
3417
+ issues.push(` → ${f.name} (${f.filePath}) — no testFile in glossary`);
3418
+ if (f.suggestedTestFile) {
3419
+ issues.push(` Fix: Create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to .codeyam/glossary.json`);
3420
+ }
3421
+ }
3245
3422
  }
3246
3423
  }
3247
3424
  if (s.functionsFailing > 0) {
@@ -3256,6 +3433,10 @@ function printAuditGateFailures(data) {
3256
3433
  const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
3257
3434
  for (const f of runnerErrors) {
3258
3435
  issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3436
+ if (f.hint)
3437
+ issues.push(` ${f.hint}`);
3438
+ else if (f.errorMessage)
3439
+ issues.push(` Error: ${f.errorMessage}`);
3259
3440
  }
3260
3441
  }
3261
3442
  if (s.functionsNameMismatch > 0) {
@@ -3263,13 +3444,36 @@ function printAuditGateFailures(data) {
3263
3444
  const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
3264
3445
  for (const f of mismatch) {
3265
3446
  issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
3447
+ if (f.hint)
3448
+ issues.push(` Fix: ${f.hint}`);
3266
3449
  }
3267
3450
  }
3268
- if (s.missingFromGlossary > 0)
3451
+ if (s.missingFromGlossary > 0) {
3269
3452
  issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
3453
+ const missingGlossary = data.missingFromGlossary || [];
3454
+ for (const m of missingGlossary) {
3455
+ issues.push(` → ${m.name} (${m.filePath})`);
3456
+ }
3457
+ issues.push(` Fix: Run \`codeyam editor analyze-imports\` to add these files to the glossary`);
3458
+ }
3270
3459
  if (s.incompleteEntities > 0) {
3460
+ // Check for persistent analysis failures
3461
+ let analysisFailures = {};
3462
+ try {
3463
+ const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
3464
+ if (fs.existsSync(failuresPath)) {
3465
+ analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
3466
+ }
3467
+ }
3468
+ catch {
3469
+ // Non-fatal
3470
+ }
3271
3471
  const preCount = s.preExistingIncompleteEntities || 0;
3272
- if (preCount > 0 && preCount === s.incompleteEntities) {
3472
+ const hasFailures = Object.keys(analysisFailures).length > 0;
3473
+ if (hasFailures) {
3474
+ issues.push(`${s.incompleteEntities} entity/entities — automated analysis failed, manual analysis required`);
3475
+ }
3476
+ else if (preCount > 0 && preCount === s.incompleteEntities) {
3273
3477
  issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
3274
3478
  }
3275
3479
  else if (preCount > 0) {
@@ -3278,6 +3482,20 @@ function printAuditGateFailures(data) {
3278
3482
  else {
3279
3483
  issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
3280
3484
  }
3485
+ const incomplete = data.incompleteEntities || [];
3486
+ for (const e of incomplete) {
3487
+ const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
3488
+ if (failureEntry) {
3489
+ issues.push(` → ${e.name} — MANUAL ANALYSIS REQUIRED (automated analysis failed)`);
3490
+ issues.push(` Run \`codeyam editor audit\` for step-by-step manual analysis instructions`);
3491
+ }
3492
+ else {
3493
+ issues.push(` → ${e.name} (${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''})${e.preExisting ? ' [pre-existing]' : ''}`);
3494
+ }
3495
+ }
3496
+ if (!hasFailures) {
3497
+ issues.push(` Fix: Run \`codeyam editor analyze-imports\` to build import graphs`);
3498
+ }
3281
3499
  }
3282
3500
  if (s.unassociatedScenarios > 0) {
3283
3501
  issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
@@ -3286,10 +3504,22 @@ function printAuditGateFailures(data) {
3286
3504
  issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
3287
3505
  }
3288
3506
  }
3289
- if (s.miscategorizedScenarios > 0)
3507
+ if (s.miscategorizedScenarios > 0) {
3290
3508
  issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
3291
- if (s.scenariosNeedingRecapture > 0)
3509
+ const miscategorized = data.miscategorizedScenarios || [];
3510
+ for (const m of miscategorized) {
3511
+ issues.push(` → ${m.componentName} at ${m.url} (${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''})`);
3512
+ }
3513
+ issues.push(` Fix: Re-register these as app-level scenarios (with pageFilePath, no componentName) or use isolation routes`);
3514
+ }
3515
+ if (s.scenariosNeedingRecapture > 0) {
3292
3516
  issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
3517
+ const recapture = data.scenariosNeedingRecapture || [];
3518
+ for (const r of recapture) {
3519
+ issues.push(` → "${r.scenarioName}" (entity: ${r.entityName}, ${r.status?.status || 'changed'})`);
3520
+ }
3521
+ issues.push(` Fix: Re-capture affected scenarios to update screenshots after code changes`);
3522
+ }
3293
3523
  if (issues.length > 0) {
3294
3524
  console.error(chalk.yellow('\nAudit failures:'));
3295
3525
  for (const issue of issues) {
@@ -3330,22 +3560,16 @@ async function handleAudit() {
3330
3560
  console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3331
3561
  process.exit(1);
3332
3562
  }
3333
- // Auto-fix incomplete entities and unassociated scenarios but only once.
3334
- // If analyze-imports + entity SHA sync runs and issues persist, report clearly
3335
- // instead of retrying. The analysis may have errors (e.g., stale entity
3336
- // references from refactoring) that can't be fixed by re-running.
3563
+ // Lightweight auto-fix: backfill entity_sha on scenarios that were
3564
+ // registered before entity records existed. This is fast (DB-only).
3565
+ // Import analysis is NOT triggered here it scans all files and takes
3566
+ // minutes on large projects. Users should run it separately if needed.
3337
3567
  const incompleteBeforeFix = data.incompleteEntities || [];
3338
3568
  const unassociatedBeforeFix = data.unassociatedScenarios || [];
3339
3569
  let autoRemediationFailed = false;
3340
3570
  if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
3341
3571
  const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
3342
- console.log(chalk.dim(`Auto-fixing ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
3343
- try {
3344
- await handleAnalyzeImports({ silent: true });
3345
- }
3346
- catch {
3347
- // Fall through — the audit will still report them
3348
- }
3572
+ console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
3349
3573
  // Backfill entity_sha on scenarios that were registered before entities existed
3350
3574
  try {
3351
3575
  const entities = await loadEntities({});
@@ -3364,13 +3588,14 @@ async function handleAudit() {
3364
3588
  catch {
3365
3589
  // Fall through — re-fetch will show remaining issues
3366
3590
  }
3367
- // Re-fetch audit results after the fix
3368
- data = await fetchAuditResult();
3591
+ // Re-fetch audit results after the backfill — skip re-running tests
3592
+ // since they haven't changed (backfill is DB-only).
3593
+ data = await fetchAuditResult({ skipTests: true });
3369
3594
  if (!data) {
3370
- console.error(chalk.red('Error: Could not reach the CodeYam server after auto-fix.'));
3595
+ console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
3371
3596
  process.exit(1);
3372
3597
  }
3373
- // If issues persist after auto-fix, flag it so we show clear guidance
3598
+ // If issues persist after backfill, flag it so we show clear guidance
3374
3599
  const incompleteAfterFix = data.incompleteEntities || [];
3375
3600
  const unassociatedAfterFix = data.unassociatedScenarios || [];
3376
3601
  if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
@@ -3407,6 +3632,9 @@ async function handleAudit() {
3407
3632
  }
3408
3633
  else {
3409
3634
  detail = chalk.red(' — no scenarios registered');
3635
+ if (c.hint) {
3636
+ detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
3637
+ }
3410
3638
  }
3411
3639
  // Show file path for failing components always, for OK only when name is ambiguous
3412
3640
  const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
@@ -3450,9 +3678,16 @@ async function handleAudit() {
3450
3678
  break;
3451
3679
  case 'missing':
3452
3680
  default:
3453
- detail = f.testFile
3454
- ? chalk.red(` — test file missing: ${f.testFile}`)
3455
- : chalk.red(' — no test file specified');
3681
+ if (f.testFile) {
3682
+ detail = chalk.red(` — test file missing: ${f.testFile}`);
3683
+ }
3684
+ else {
3685
+ detail = chalk.red(` — no test file specified in glossary`);
3686
+ detail += chalk.dim(` (source: ${f.filePath})`);
3687
+ if (f.suggestedTestFile) {
3688
+ detail += `\n ${chalk.yellow(`Fix: Either create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to this entry in .codeyam/glossary.json`)}`;
3689
+ }
3690
+ }
3456
3691
  break;
3457
3692
  }
3458
3693
  console.log(` ${icon} ${f.name}${detail}`);
@@ -3469,19 +3704,42 @@ async function handleAudit() {
3469
3704
  console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
3470
3705
  console.log();
3471
3706
  }
3472
- // Incomplete entities (scenarios without analyses)
3707
+ // Incomplete entities (scenarios without analyses) — report with guidance.
3708
+ // We intentionally do NOT run analysis here: it starts the analyzer
3709
+ // template which is slow even for a few files. Users should run
3710
+ // `codeyam editor analyze-imports` separately if needed.
3473
3711
  const incompleteEntities = data.incompleteEntities || [];
3474
3712
  if (incompleteEntities.length > 0) {
3713
+ const { formatIncompleteEntityGuidance, formatManualAnalysisGuidance } = await import('../utils/editorAudit.js');
3714
+ const { readAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
3715
+ // Check for persistent analysis failures
3716
+ const analysisFailures = readAnalysisFailures(getProjectRoot());
3475
3717
  console.log(chalk.bold('Incomplete entities (need import analysis):'));
3476
3718
  for (const e of incompleteEntities) {
3477
- console.log(` ${chalk.red('✗')} ${e.name} ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
3478
- }
3479
- if (autoRemediationFailed) {
3480
- console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
3481
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to see the full error output.'));
3719
+ // Check if this entity has a persistent failure
3720
+ const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
3721
+ if (failureEntry) {
3722
+ // Show manual analysis instructions instead of "run analyze-imports"
3723
+ const filePath = Object.entries(analysisFailures).find(([, f]) => f.entityName === e.name)?.[0];
3724
+ const guidance = formatManualAnalysisGuidance({
3725
+ name: e.name,
3726
+ filePath: filePath || '',
3727
+ scenarioCount: e.scenarioCount,
3728
+ error: failureEntry.error,
3729
+ });
3730
+ // Print each line with proper indentation
3731
+ for (const line of guidance.split('\n')) {
3732
+ console.log(` ${chalk.red('✗')} ${line}`);
3733
+ }
3734
+ }
3735
+ else {
3736
+ const guidance = formatIncompleteEntityGuidance(e);
3737
+ console.log(` ${chalk.red('✗')} ${guidance}`);
3738
+ }
3482
3739
  }
3483
- else {
3484
- console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities.'));
3740
+ const incompleteErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
3741
+ if (fs.existsSync(incompleteErrorReportPath)) {
3742
+ console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
3485
3743
  }
3486
3744
  console.log();
3487
3745
  }
@@ -3505,6 +3763,10 @@ async function handleAudit() {
3505
3763
  else {
3506
3764
  console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to create entity records, then re-run audit to backfill.'));
3507
3765
  }
3766
+ const unassocErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
3767
+ if (fs.existsSync(unassocErrorReportPath)) {
3768
+ console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
3769
+ }
3508
3770
  console.log();
3509
3771
  }
3510
3772
  // Miscategorized scenarios (component scenarios using real page URLs)
@@ -3595,17 +3857,6 @@ async function handleAudit() {
3595
3857
  if (!allOk) {
3596
3858
  process.exit(1);
3597
3859
  }
3598
- // Auto-run analyze-imports when audit passes — this builds the import graph
3599
- // so the App tab can show component/function dependencies for each page.
3600
- // Previously this was a manual step that Claude had to remember to run.
3601
- console.log(chalk.dim('Building import graph...'));
3602
- try {
3603
- await handleAnalyzeImports({ silent: true });
3604
- }
3605
- catch {
3606
- // Non-fatal — audit passed, import graph is a bonus
3607
- console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
3608
- }
3609
3860
  }
3610
3861
  // ─── Recapture-stale subcommand ────────────────────────────────────────
3611
3862
  /**
@@ -3812,14 +4063,14 @@ async function handleScenarioCoverage() {
3812
4063
  // Safety net: heal any scenarios with null entity_sha before checking coverage
3813
4064
  try {
3814
4065
  const { getDatabase } = await import('../../../packages/database/index.js');
3815
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
4066
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
3816
4067
  const db = getDatabase();
3817
4068
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
3818
4069
  if (backfillCount > 0) {
3819
4070
  console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
3820
4071
  await handleAnalyzeImports({ silent: true });
3821
4072
  // Run backfill after analysis
3822
- const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
4073
+ const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
3823
4074
  const entities = await loadEntities({});
3824
4075
  if (entities && entities.length > 0) {
3825
4076
  await backfillEntityShaOnScenarios(db, entities.map((e) => ({
@@ -4316,14 +4567,205 @@ function handleEditorDebug(args) {
4316
4567
  console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
4317
4568
  console.log();
4318
4569
  }
4570
+ // ─── Manual entity analysis ───────────────────────────────────────────
4571
+ /**
4572
+ * `codeyam editor manual-entity <JSON|@file>`
4573
+ *
4574
+ * Creates entity and analysis records from Claude-provided metadata when
4575
+ * automated analyze-imports fails. This unblocks the audit gate without
4576
+ * needing the analyzer template to parse the source file.
4577
+ */
4578
+ async function handleManualEntity(jsonArg) {
4579
+ const root = getProjectRoot();
4580
+ // Parse JSON input (supports @file.json convention)
4581
+ const parsed = parseRegisterArg(jsonArg);
4582
+ if (parsed.error || !parsed.body) {
4583
+ console.error(chalk.red(`Error: ${parsed.error || 'Invalid input'}`));
4584
+ console.error(chalk.dim(' Usage: codeyam editor manual-entity \'{"name":"Header","filePath":"src/components/Header.tsx","entityType":"visual",...}\''));
4585
+ console.error(chalk.dim(' Or: codeyam editor manual-entity @.codeyam/tmp/manual-entity.json'));
4586
+ process.exit(1);
4587
+ }
4588
+ const input = parsed.body;
4589
+ // Validate input
4590
+ const { validateManualEntityInput, convertTypeInfoToDataForMocks, readAnalysisFailures, clearFailureForPath, writeAnalysisFailures, } = await import('../utils/manualEntityAnalysis.js');
4591
+ // Read glossary for validation
4592
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
4593
+ let glossaryEntries = [];
4594
+ try {
4595
+ glossaryEntries = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
4596
+ }
4597
+ catch {
4598
+ // Empty glossary — validation will still check file existence
4599
+ }
4600
+ const errors = validateManualEntityInput(input, glossaryEntries, {
4601
+ fileExists: (p) => fs.existsSync(path.join(root, p)),
4602
+ });
4603
+ if (errors.length > 0) {
4604
+ console.error(chalk.red('Validation errors:'));
4605
+ for (const err of errors) {
4606
+ console.error(chalk.red(` • ${err}`));
4607
+ }
4608
+ process.exit(1);
4609
+ }
4610
+ // Read source file and compute SHA
4611
+ const sourceContent = fs.readFileSync(path.join(root, input.filePath), 'utf8');
4612
+ const { generateSha } = await import('../../../packages/database/index.js');
4613
+ const entitySha = generateSha(input.filePath, input.name, sourceContent);
4614
+ // Get project and branch
4615
+ await initializeEnvironment();
4616
+ const configPath = path.join(root, '.codeyam', 'config.json');
4617
+ let projectSlug;
4618
+ try {
4619
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
4620
+ projectSlug = config.projectSlug;
4621
+ }
4622
+ catch {
4623
+ console.error(chalk.red('Error: .codeyam/config.json not found or invalid.'));
4624
+ process.exit(1);
4625
+ }
4626
+ const { project, branch } = await requireBranchAndProject(projectSlug);
4627
+ const { getDatabase } = await import('../../../packages/database/index.js');
4628
+ const db = getDatabase();
4629
+ // Convert type info to dataForMocks format
4630
+ const dataForMocks = convertTypeInfoToDataForMocks(input.typeInfo);
4631
+ // Create entity record
4632
+ const entityMetadata = {
4633
+ importedExports: (input.importedExports || []).map((ie) => ({
4634
+ name: ie.name,
4635
+ filePath: ie.filePath,
4636
+ })),
4637
+ manuallyAnalyzed: true,
4638
+ };
4639
+ // Check if entity already exists
4640
+ const existingEntity = await db
4641
+ .selectFrom('entities')
4642
+ .select('sha')
4643
+ .where('sha', '=', entitySha)
4644
+ .executeTakeFirst();
4645
+ if (!existingEntity) {
4646
+ await db
4647
+ .insertInto('entities')
4648
+ .values({
4649
+ sha: entitySha,
4650
+ project_id: project.id,
4651
+ name: input.name,
4652
+ entity_type: input.entityType,
4653
+ file_path: input.filePath,
4654
+ metadata: JSON.stringify(entityMetadata),
4655
+ })
4656
+ .onConflict((oc) => oc.column('sha').doNothing())
4657
+ .execute();
4658
+ console.log(chalk.dim(`Created entity record: ${input.name} (${entitySha.slice(0, 8)}...)`));
4659
+ }
4660
+ else {
4661
+ // Update metadata on existing entity
4662
+ await db
4663
+ .updateTable('entities')
4664
+ .set({
4665
+ metadata: JSON.stringify(entityMetadata),
4666
+ })
4667
+ .where('sha', '=', entitySha)
4668
+ .execute();
4669
+ console.log(chalk.dim(`Updated existing entity: ${input.name} (${entitySha.slice(0, 8)}...)`));
4670
+ }
4671
+ // Create entity_branch record
4672
+ await db
4673
+ .insertInto('entity_branches')
4674
+ .values({
4675
+ entity_sha: entitySha,
4676
+ branch_id: branch.id,
4677
+ active: true,
4678
+ })
4679
+ .onConflict((oc) => oc.columns(['entity_sha', 'branch_id']).doUpdateSet({ active: true }))
4680
+ .execute();
4681
+ // Create analysis record
4682
+ const { randomUUID } = await import('crypto');
4683
+ const analysisId = randomUUID();
4684
+ const now = new Date().toISOString();
4685
+ const analysisMetadata = {
4686
+ scenariosDataStructure: {
4687
+ dataForMocks: Object.keys(dataForMocks).length > 0 ? dataForMocks : null,
4688
+ },
4689
+ mergedDataStructure: {
4690
+ dependencySchemas: null,
4691
+ },
4692
+ manuallyAnalyzed: true,
4693
+ };
4694
+ const analysisStatus = {
4695
+ startedAt: now,
4696
+ finishedAt: now,
4697
+ steps: [],
4698
+ errors: [],
4699
+ };
4700
+ // Check if analysis already exists for this entity
4701
+ const existingAnalysis = await db
4702
+ .selectFrom('analyses')
4703
+ .select('id')
4704
+ .where('entity_sha', '=', entitySha)
4705
+ .executeTakeFirst();
4706
+ if (!existingAnalysis) {
4707
+ await db
4708
+ .insertInto('analyses')
4709
+ .values({
4710
+ id: analysisId,
4711
+ project_id: project.id,
4712
+ entity_sha: entitySha,
4713
+ entity_name: input.name,
4714
+ entity_type: input.entityType,
4715
+ file_path: input.filePath,
4716
+ status: JSON.stringify(analysisStatus),
4717
+ metadata: JSON.stringify(analysisMetadata),
4718
+ })
4719
+ .execute();
4720
+ console.log(chalk.dim(`Created analysis record: ${analysisId.slice(0, 8)}...`));
4721
+ }
4722
+ else {
4723
+ // Update existing analysis with manual metadata
4724
+ await db
4725
+ .updateTable('analyses')
4726
+ .set({
4727
+ metadata: JSON.stringify(analysisMetadata),
4728
+ status: JSON.stringify(analysisStatus),
4729
+ })
4730
+ .where('entity_sha', '=', entitySha)
4731
+ .execute();
4732
+ console.log(chalk.dim(`Updated existing analysis for ${input.name}`));
4733
+ }
4734
+ // Backfill entity_sha on scenarios
4735
+ const backfillResult = await backfillEntityShaOnScenarios(db, [
4736
+ {
4737
+ sha: entitySha,
4738
+ name: input.name,
4739
+ filePath: input.filePath,
4740
+ isDefaultExport: false,
4741
+ },
4742
+ ]);
4743
+ if (backfillResult.updated > 0) {
4744
+ console.log(chalk.dim(`Linked ${backfillResult.updated} scenario(s) to entity`));
4745
+ }
4746
+ // Clear from analysis failures tracker
4747
+ const failures = readAnalysisFailures(root);
4748
+ if (failures[input.filePath]) {
4749
+ const updated = clearFailureForPath(failures, input.filePath);
4750
+ writeAnalysisFailures(root, updated);
4751
+ console.log(chalk.dim(`Cleared analysis failure for ${input.filePath}`));
4752
+ }
4753
+ console.log();
4754
+ console.log(chalk.green.bold(`✓ Manual entity analysis complete: ${input.name}`));
4755
+ console.log(chalk.dim(` Entity SHA: ${entitySha.slice(0, 12)}...`));
4756
+ console.log(chalk.dim(` Imports: ${(input.importedExports || []).length} glossary entities`));
4757
+ console.log(chalk.dim(` Type info: ${Object.keys(dataForMocks).length} fields`));
4758
+ console.log();
4759
+ console.log(chalk.dim('Re-run `codeyam editor audit` to verify.'));
4760
+ }
4319
4761
  // ─── Command definition ───────────────────────────────────────────────
4320
4762
  const editorCommand = {
4321
4763
  command: 'editor [step] [json]',
4322
4764
  describe: 'Editor mode guided workflow',
4323
4765
  builder: (yargs) => {
4324
4766
  const stepDescription = IS_INTERNAL_BUILD
4325
- ? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)'
4326
- : 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)';
4767
+ ? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)'
4768
+ : 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)';
4327
4769
  let builder = yargs
4328
4770
  .positional('step', {
4329
4771
  type: 'string',
@@ -4465,7 +4907,9 @@ const editorCommand = {
4465
4907
  'Ask user what to build next and restart codeyam editor workflow';
4466
4908
  }
4467
4909
  console.log(chalk.bold.cyan('━━━ TASK ━━━'));
4468
- console.log(chalk.cyan('Mark your current task (if any) as complete.'));
4910
+ console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
4911
+ console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
4912
+ console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
4469
4913
  console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
4470
4914
  console.log();
4471
4915
  console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
@@ -4484,6 +4928,11 @@ const editorCommand = {
4484
4928
  await handleAnalyzeImports();
4485
4929
  return;
4486
4930
  }
4931
+ // Subcommand: codeyam editor manual-entity <JSON|@file>
4932
+ if (argv.step === 'manual-entity') {
4933
+ await handleManualEntity(argv.json || '');
4934
+ return;
4935
+ }
4487
4936
  // Subcommand: codeyam editor dependents <EntityName>
4488
4937
  if (argv.step === 'dependents') {
4489
4938
  await handleDependents(argv.json || '');
@@ -4724,13 +5173,24 @@ const editorCommand = {
4724
5173
  try {
4725
5174
  const { getDatabase: getDb } = await import('../../../packages/database/index.js');
4726
5175
  const seedDb = getDb();
4727
- const appScenario = await seedDb
5176
+ // Prefer the home page scenario (url "/") since the App tab
5177
+ // sorts "Home" first — fall back to any application scenario.
5178
+ const homeScenario = await seedDb
4728
5179
  .selectFrom('editor_scenarios')
4729
5180
  .select(['id', 'name', 'type'])
4730
5181
  .where('project_id', '=', project.id)
4731
- .where('type', '=', 'application')
5182
+ .where('url', '=', '/')
5183
+ .where('component_name', 'is', null)
4732
5184
  .orderBy('created_at', 'asc')
4733
5185
  .executeTakeFirst();
5186
+ const appScenario = homeScenario ||
5187
+ (await seedDb
5188
+ .selectFrom('editor_scenarios')
5189
+ .select(['id', 'name', 'type'])
5190
+ .where('project_id', '=', project.id)
5191
+ .where('type', '=', 'application')
5192
+ .orderBy('created_at', 'asc')
5193
+ .executeTakeFirst());
4734
5194
  if (appScenario) {
4735
5195
  const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
4736
5196
  if (fs.existsSync(seedFile)) {
@@ -4873,7 +5333,7 @@ const editorCommand = {
4873
5333
  if (!needsAnalysis) {
4874
5334
  try {
4875
5335
  const { getDatabase } = await import('../../../packages/database/index.js');
4876
- const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
5336
+ const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
4877
5337
  const db = getDatabase();
4878
5338
  const backfillCount = await countScenariosNeedingEntityBackfill(db);
4879
5339
  if (backfillCount > 0) {
@@ -4912,7 +5372,7 @@ const editorCommand = {
4912
5372
  .execute();
4913
5373
  if (unresolved.length > 0) {
4914
5374
  const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
4915
- const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
5375
+ const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
4916
5376
  const { allFiles: pfpFiles } = scanPfp(projectRoot);
4917
5377
  let pfpResolved = 0;
4918
5378
  for (const row of unresolved) {