@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
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Parse file path arguments for `codeyam editor analyze-imports`.
3
+ *
4
+ * Supports multiple calling conventions:
5
+ * codeyam editor analyze-imports path/to/file.tsx → json="path/to/file.tsx", extras=[]
6
+ * codeyam editor analyze-imports file1.tsx file2.tsx → json="file1.tsx", extras=["editor","file2.tsx"]
7
+ * codeyam editor analyze-imports → json=undefined, extras=[] → empty (all entities)
8
+ */
9
+ export function parseAnalyzeImportsArgs(json, extras) {
10
+ const paths = [];
11
+ if (json) {
12
+ paths.push(json);
13
+ }
14
+ // Collect extra positional args, filtering out "editor" and non-strings
15
+ for (const arg of extras) {
16
+ if (typeof arg === 'string' && arg !== 'editor') {
17
+ paths.push(arg);
18
+ }
19
+ }
20
+ // Deduplicate while preserving order
21
+ return [...new Set(paths)];
22
+ }
23
+ //# sourceMappingURL=editorAnalyzeImportsArgs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editorAnalyzeImportsArgs.js","sourceRoot":"","sources":["../../../../src/commands/editorAnalyzeImportsArgs.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CACrC,IAAwB,EACxB,MAA2B;IAE3B,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,IAAI,IAAI,EAAE,CAAC;QACT,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,wEAAwE;IACxE,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YAChD,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,qCAAqC;IACrC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;AAC7B,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import { Kysely, SqliteDialect } from 'kysely';
3
- import { isComponent, classifyGlossaryEntries, computeAudit, filterGlossaryByChangeStatus, resolveAuditSessionScope, queryScenarioCounts, queryPageScenarioCounts, queryIncompleteEntities, queryMiscategorizedScenarios, queryUnassociatedScenarios, isOnlyIncompleteEntities, isOnlyPreExistingIncomplete, isAutoRemediable, identifyScenariosNeedingRecapture, detectDuplicateNames, } from "../editorAudit.js";
3
+ import { isComponent, classifyGlossaryEntries, computeAudit, filterGlossaryByChangeStatus, resolveAuditSessionScope, queryScenarioCounts, queryPageScenarioCounts, queryIncompleteEntities, queryMiscategorizedScenarios, queryUnassociatedScenarios, isOnlyIncompleteEntities, isOnlyPreExistingIncomplete, isAutoRemediable, identifyScenariosNeedingRecapture, detectDuplicateNames, aggregateClientErrorsByComponent, determineTargetedAnalysisPaths, shouldAutoRecapture, } from "../editorAudit.js";
4
4
  describe('editorAudit', () => {
5
5
  describe('isComponent', () => {
6
6
  it('should return true for JSX.Element return type', () => {
@@ -1987,6 +1987,24 @@ describe('editorAudit', () => {
1987
1987
  allPassing: false,
1988
1988
  })).toBe(true);
1989
1989
  });
1990
+ it('should return false when there are also runner errors', () => {
1991
+ // functionsRunnerError means the test runner crashed — a real failure
1992
+ // that cannot be fixed by entity SHA backfill or analyze-imports.
1993
+ // If this returns true, checkAuditGate would attempt a useless backfill
1994
+ // instead of reporting the runner error, and isOnlyPreExistingIncomplete
1995
+ // could let the gate pass entirely.
1996
+ expect(isOnlyIncompleteEntities({
1997
+ componentsMissing: 0,
1998
+ componentsWithErrors: 0,
1999
+ functionsFailing: 0,
2000
+ functionsRunnerError: 2,
2001
+ functionsNameMismatch: 0,
2002
+ functionsMissing: 0,
2003
+ missingFromGlossary: 0,
2004
+ incompleteEntities: 1,
2005
+ allPassing: false,
2006
+ })).toBe(false);
2007
+ });
1990
2008
  });
1991
2009
  // ── isAutoRemediable ─────────────────────────────────────────────────
1992
2010
  describe('isAutoRemediable', () => {
@@ -2779,6 +2797,25 @@ describe('editorAudit', () => {
2779
2797
  });
2780
2798
  expect(result).toHaveLength(0);
2781
2799
  });
2800
+ it('should NOT flag scenarios for new entities (they need creation, not recapture)', () => {
2801
+ // "new" entities are being seen for the first time. Their scenarios need
2802
+ // initial creation, not recapture of old screenshots. Flagging them as
2803
+ // "needs_recapture" sends the wrong remediation signal to Claude.
2804
+ const entityChangeStatus = {
2805
+ NewComponent: { status: 'new' },
2806
+ };
2807
+ const result = identifyScenariosNeedingRecapture({
2808
+ scenarios: [
2809
+ {
2810
+ name: 'NewComponent - Default',
2811
+ entityName: 'NewComponent',
2812
+ updatedInSession: false,
2813
+ },
2814
+ ],
2815
+ entityChangeStatus,
2816
+ });
2817
+ expect(result).toHaveLength(0);
2818
+ });
2782
2819
  });
2783
2820
  // ── detectDuplicateNames ──────────────────────────────────────────
2784
2821
  describe('detectDuplicateNames', () => {
@@ -2946,6 +2983,28 @@ describe('editorAudit', () => {
2946
2983
  expect(result.components[0].status).toBe('missing');
2947
2984
  expect(result.summary.componentsMissing).toBe(1);
2948
2985
  });
2986
+ it('should not count needs_recapture components as componentsOk', () => {
2987
+ // A needs_recapture component is not "ok" — it needs action. Counting it
2988
+ // in componentsOk is misleading: if totalComponents=2, componentsOk=2,
2989
+ // and componentsNeedingRecapture=1, the numbers don't add up (2+1 > 2).
2990
+ const result = computeAudit({
2991
+ components: [
2992
+ { name: 'Library', filePath: 'app/library/page.tsx' },
2993
+ { name: 'DrinkCard', filePath: 'app/components/DrinkCard.tsx' },
2994
+ ],
2995
+ functions: [],
2996
+ scenarioCounts: { DrinkCard: 2 },
2997
+ testFileExistence: {},
2998
+ totalScenarioCounts: { Library: 3 },
2999
+ entityChangeStatus: { Library: { status: 'impacted' } },
3000
+ });
3001
+ expect(result.components[0].status).toBe('needs_recapture');
3002
+ expect(result.components[1].status).toBe('ok');
3003
+ // needs_recapture is not "ok" — should be counted separately
3004
+ expect(result.summary.componentsOk).toBe(1);
3005
+ expect(result.summary.componentsNeedingRecapture).toBe(1);
3006
+ expect(result.summary.totalComponents).toBe(2);
3007
+ });
2949
3008
  });
2950
3009
  // ── queryUnassociatedScenarios ──────────────────────────────────────
2951
3010
  describe('queryUnassociatedScenarios', () => {
@@ -3248,6 +3307,61 @@ describe('editorAudit', () => {
3248
3307
  expect(result.functions[0].suggestedTestFile).toBeUndefined();
3249
3308
  });
3250
3309
  });
3310
+ describe('hint for function audit entries', () => {
3311
+ it('should include a hint for name_mismatch functions explaining the fix', () => {
3312
+ // Claude sees "name mismatch" with no guidance on what it means or how
3313
+ // to fix it. The hint should explain that a top-level describe block
3314
+ // matching the function name is required for the CodeYam UI.
3315
+ const result = computeAudit({
3316
+ components: [],
3317
+ functions: [
3318
+ {
3319
+ name: 'useDrinks',
3320
+ filePath: 'app/hooks/useDrinks.ts',
3321
+ testFile: 'app/hooks/useDrinks.test.ts',
3322
+ },
3323
+ ],
3324
+ scenarioCounts: {},
3325
+ testFileExistence: { 'app/hooks/useDrinks.test.ts': true },
3326
+ testResults: {
3327
+ 'app/hooks/useDrinks.test.ts': {
3328
+ passing: true,
3329
+ hasEntityNameDescribe: false,
3330
+ },
3331
+ },
3332
+ });
3333
+ expect(result.functions[0].status).toBe('name_mismatch');
3334
+ expect(result.functions[0].hint).toBeDefined();
3335
+ expect(result.functions[0].hint).toContain('describe');
3336
+ expect(result.functions[0].hint).toContain('useDrinks');
3337
+ });
3338
+ it('should include a hint for runner_error functions showing the error', () => {
3339
+ // When the test runner crashes, Claude needs to see WHY it crashed
3340
+ // to fix the underlying issue. Without this, Claude loops re-running audit.
3341
+ const result = computeAudit({
3342
+ components: [],
3343
+ functions: [
3344
+ {
3345
+ name: 'getTimeAgo',
3346
+ filePath: 'src/lib/format.ts',
3347
+ testFile: 'src/lib/format.test.ts',
3348
+ },
3349
+ ],
3350
+ scenarioCounts: {},
3351
+ testFileExistence: { 'src/lib/format.test.ts': true },
3352
+ testResults: {
3353
+ 'src/lib/format.test.ts': {
3354
+ passing: false,
3355
+ hasEntityNameDescribe: false,
3356
+ errorMessage: 'Cannot find module "@/lib/format"',
3357
+ },
3358
+ },
3359
+ });
3360
+ expect(result.functions[0].status).toBe('runner_error');
3361
+ expect(result.functions[0].hint).toBeDefined();
3362
+ expect(result.functions[0].hint).toContain('Cannot find module');
3363
+ });
3364
+ });
3251
3365
  describe('hint for missing components', () => {
3252
3366
  it('should hint that layout files need app-level scenarios', () => {
3253
3367
  const result = computeAudit({
@@ -3295,6 +3409,21 @@ describe('editorAudit', () => {
3295
3409
  });
3296
3410
  expect(result.components[0].hint).toBeUndefined();
3297
3411
  });
3412
+ it('should provide a hint for needs_recapture components', () => {
3413
+ // Components with needs_recapture status need guidance on what to do.
3414
+ // Without a hint, Claude has no instructions for fixing the issue.
3415
+ const result = computeAudit({
3416
+ components: [{ name: 'Library', filePath: 'app/library/page.tsx' }],
3417
+ functions: [],
3418
+ scenarioCounts: {},
3419
+ testFileExistence: {},
3420
+ totalScenarioCounts: { Library: 3 },
3421
+ entityChangeStatus: { Library: { status: 'impacted' } },
3422
+ });
3423
+ expect(result.components[0].status).toBe('needs_recapture');
3424
+ expect(result.components[0].hint).toBeDefined();
3425
+ expect(result.components[0].hint).toContain('recapture');
3426
+ });
3298
3427
  });
3299
3428
  describe('formatIncompleteEntityGuidance', () => {
3300
3429
  it('should include the entity name and scenario count', () => {
@@ -3340,6 +3469,60 @@ describe('editorAudit', () => {
3340
3469
  expect(result).toMatch(/scenario.*without.*import graph|import graph.*not.*built/i);
3341
3470
  });
3342
3471
  });
3472
+ describe('formatManualAnalysisGuidance', () => {
3473
+ it('should include entity name, scenario count, and error message', () => {
3474
+ const { formatManualAnalysisGuidance } = require('../editorAudit');
3475
+ const result = formatManualAnalysisGuidance({
3476
+ name: 'Header',
3477
+ filePath: 'src/components/Header.tsx',
3478
+ scenarioCount: 3,
3479
+ error: 'TypeScript parsing error: unexpected token',
3480
+ });
3481
+ expect(result).toContain('Header');
3482
+ expect(result).toContain('3 scenario(s)');
3483
+ expect(result).toContain('TypeScript parsing error');
3484
+ });
3485
+ it('should include MANUAL ANALYSIS REQUIRED header', () => {
3486
+ const { formatManualAnalysisGuidance } = require('../editorAudit');
3487
+ const result = formatManualAnalysisGuidance({
3488
+ name: 'Header',
3489
+ filePath: 'src/components/Header.tsx',
3490
+ scenarioCount: 2,
3491
+ error: 'Parse error',
3492
+ });
3493
+ expect(result).toContain('MANUAL ANALYSIS REQUIRED');
3494
+ });
3495
+ it('should tell Claude to read the source file', () => {
3496
+ const { formatManualAnalysisGuidance } = require('../editorAudit');
3497
+ const result = formatManualAnalysisGuidance({
3498
+ name: 'Header',
3499
+ filePath: 'src/components/Header.tsx',
3500
+ scenarioCount: 2,
3501
+ error: 'Parse error',
3502
+ });
3503
+ expect(result).toContain('Read src/components/Header.tsx');
3504
+ });
3505
+ it('should include the manual-entity command', () => {
3506
+ const { formatManualAnalysisGuidance } = require('../editorAudit');
3507
+ const result = formatManualAnalysisGuidance({
3508
+ name: 'Header',
3509
+ filePath: 'src/components/Header.tsx',
3510
+ scenarioCount: 2,
3511
+ error: 'Parse error',
3512
+ });
3513
+ expect(result).toContain('codeyam editor manual-entity');
3514
+ });
3515
+ it('should tell Claude to check glossary for imports', () => {
3516
+ const { formatManualAnalysisGuidance } = require('../editorAudit');
3517
+ const result = formatManualAnalysisGuidance({
3518
+ name: 'Header',
3519
+ filePath: 'src/components/Header.tsx',
3520
+ scenarioCount: 2,
3521
+ error: 'Parse error',
3522
+ });
3523
+ expect(result).toContain('glossary');
3524
+ });
3525
+ });
3343
3526
  describe('getIncompleteEntityFilePaths', () => {
3344
3527
  // The audit should auto-fix incomplete entities by running analysis on
3345
3528
  // just their specific file paths, not all 117+ files. This function
@@ -3490,6 +3673,29 @@ describe('editorAudit', () => {
3490
3673
  },
3491
3674
  ])).toBe(true);
3492
3675
  });
3676
+ it('should return false when there are runner errors even if all incompletes are pre-existing', () => {
3677
+ // Safety net: runner errors (crashed test runner) must NEVER bypass the gate.
3678
+ // Without this test, a regression removing functionsRunnerError from
3679
+ // isOnlyIncompleteEntities would silently let broken test infrastructure through.
3680
+ expect(isOnlyPreExistingIncomplete({
3681
+ incompleteEntities: 1,
3682
+ preExistingIncompleteEntities: 1,
3683
+ componentsMissing: 0,
3684
+ componentsWithErrors: 0,
3685
+ functionsFailing: 0,
3686
+ functionsRunnerError: 1,
3687
+ functionsNameMismatch: 0,
3688
+ functionsMissing: 0,
3689
+ missingFromGlossary: 0,
3690
+ }, [
3691
+ {
3692
+ entitySha: 'a',
3693
+ name: 'OldComponent',
3694
+ scenarioCount: 1,
3695
+ preExisting: true,
3696
+ },
3697
+ ])).toBe(false);
3698
+ });
3493
3699
  it('should return false when some incomplete entities are NOT pre-existing', () => {
3494
3700
  expect(isOnlyPreExistingIncomplete({
3495
3701
  incompleteEntities: 2,
@@ -3555,5 +3761,254 @@ describe('editorAudit', () => {
3555
3761
  })).toBe(false);
3556
3762
  });
3557
3763
  });
3764
+ // ── aggregateClientErrorsByComponent ─────────────────────────────────
3765
+ describe('aggregateClientErrorsByComponent', () => {
3766
+ it('should attribute errors to component using componentName from metadata', () => {
3767
+ const result = aggregateClientErrorsByComponent({
3768
+ 'scenario-1': {
3769
+ scenarioName: 'DrinkCard - Default',
3770
+ errors: ['TypeError: Cannot read property "price"'],
3771
+ },
3772
+ }, [
3773
+ {
3774
+ name: 'DrinkCard - Default',
3775
+ componentName: 'DrinkCard',
3776
+ },
3777
+ ]);
3778
+ expect(result['DrinkCard']).toEqual([
3779
+ 'TypeError: Cannot read property "price"',
3780
+ ]);
3781
+ });
3782
+ it('should attribute app-level scenario errors using pageFilePath', () => {
3783
+ // App-level scenarios have componentName=null. The old approach
3784
+ // parsed the scenario name "Full Page with Library" and got
3785
+ // "Full Page with Library" as the component — which matches nothing.
3786
+ // With metadata, we derive the entity name from pageFilePath.
3787
+ const result = aggregateClientErrorsByComponent({
3788
+ 'scenario-1': {
3789
+ scenarioName: 'Full Page with Library',
3790
+ errors: ['TypeError: Cannot read property "title"'],
3791
+ },
3792
+ }, [
3793
+ {
3794
+ name: 'Full Page with Library',
3795
+ componentName: null,
3796
+ pageFilePath: 'app/library/page.tsx',
3797
+ },
3798
+ ]);
3799
+ // Should be keyed by the canonical entity name from scenarioEntityName()
3800
+ expect(result['Library']).toEqual([
3801
+ 'TypeError: Cannot read property "title"',
3802
+ ]);
3803
+ });
3804
+ it('should fall back to scenario name parsing when no metadata match exists', () => {
3805
+ // If the scenario is not in the metadata list (e.g., metadata not yet loaded),
3806
+ // fall back to the "ComponentName - Variant" convention
3807
+ const result = aggregateClientErrorsByComponent({
3808
+ 'scenario-1': {
3809
+ scenarioName: 'Header - Dark Mode',
3810
+ errors: ['ReferenceError: theme is not defined'],
3811
+ },
3812
+ }, []);
3813
+ expect(result['Header']).toEqual([
3814
+ 'ReferenceError: theme is not defined',
3815
+ ]);
3816
+ });
3817
+ it('should handle non-route file paths via scenarioEntityName', () => {
3818
+ // Non-route files (src/...) are treated as app entry points by
3819
+ // scenarioEntityName → buildRoutePattern returns '/' → 'Home'.
3820
+ // When a url is available, it provides a better entity name.
3821
+ const result = aggregateClientErrorsByComponent({
3822
+ 'scenario-1': {
3823
+ scenarioName: 'LibraryApp - Rich',
3824
+ errors: ['Error: fetch failed'],
3825
+ },
3826
+ }, [
3827
+ {
3828
+ name: 'LibraryApp - Rich',
3829
+ componentName: null,
3830
+ pageFilePath: 'src/library/LibraryApp.tsx',
3831
+ url: '/library',
3832
+ },
3833
+ ]);
3834
+ // scenarioEntityName uses url fallback when pageFilePath is a non-route file
3835
+ // pageFilePath 'src/...' → buildRoutePattern → '/' → routeDisplayName → 'Home'
3836
+ // But componentName takes priority, and with url '/library' as final fallback
3837
+ // scenarioEntityName({ pageFilePath: 'src/library/LibraryApp.tsx' }) → 'Home'
3838
+ // because pageFilePath is checked before url
3839
+ expect(result['Home']).toEqual(['Error: fetch failed']);
3840
+ });
3841
+ it('should skip scenarios with no errors', () => {
3842
+ const result = aggregateClientErrorsByComponent({
3843
+ 'scenario-1': {
3844
+ scenarioName: 'DrinkCard - Default',
3845
+ errors: [],
3846
+ },
3847
+ }, [
3848
+ {
3849
+ name: 'DrinkCard - Default',
3850
+ componentName: 'DrinkCard',
3851
+ },
3852
+ ]);
3853
+ expect(result).toEqual({});
3854
+ });
3855
+ it('should aggregate errors from multiple scenarios for same component', () => {
3856
+ const result = aggregateClientErrorsByComponent({
3857
+ 'scenario-1': {
3858
+ scenarioName: 'DrinkCard - Default',
3859
+ errors: ['Error A'],
3860
+ },
3861
+ 'scenario-2': {
3862
+ scenarioName: 'DrinkCard - Hover',
3863
+ errors: ['Error B', 'Error C'],
3864
+ },
3865
+ }, [
3866
+ { name: 'DrinkCard - Default', componentName: 'DrinkCard' },
3867
+ { name: 'DrinkCard - Hover', componentName: 'DrinkCard' },
3868
+ ]);
3869
+ expect(result['DrinkCard']).toEqual(['Error A', 'Error B', 'Error C']);
3870
+ });
3871
+ it('should use capitalized display name for page file paths, not lowercase directory', () => {
3872
+ // aggregateClientErrorsByComponent must produce keys matching scenarioEntityName().
3873
+ // scenarioEntityName({ pageFilePath: 'app/library/page.tsx' }) returns 'Library',
3874
+ // so the key must be 'Library' — not 'library' (the raw directory name).
3875
+ // computeAudit checks clientErrors[glossaryEntryName], so a lowercase key
3876
+ // will never match, silently dropping all client errors for page-level scenarios.
3877
+ const result = aggregateClientErrorsByComponent({
3878
+ 'sc-1': {
3879
+ scenarioName: 'Library - Default',
3880
+ errors: ['TypeError: fetch failed'],
3881
+ },
3882
+ }, [
3883
+ {
3884
+ name: 'Library - Default',
3885
+ componentName: null,
3886
+ pageFilePath: 'app/library/page.tsx',
3887
+ },
3888
+ ]);
3889
+ expect(result).toHaveProperty('Library');
3890
+ expect(result['Library']).toEqual(['TypeError: fetch failed']);
3891
+ });
3892
+ it('should use "Home" for root page app/page.tsx, not "app"', () => {
3893
+ // Root page: scenarioEntityName returns 'Home', not 'app'.
3894
+ // Custom path parsing incorrectly pops the parent dir ('app').
3895
+ const result = aggregateClientErrorsByComponent({
3896
+ 'sc-1': {
3897
+ scenarioName: 'Home - Default',
3898
+ errors: ['ReferenceError: window is not defined'],
3899
+ },
3900
+ }, [
3901
+ {
3902
+ name: 'Home - Default',
3903
+ componentName: null,
3904
+ pageFilePath: 'app/page.tsx',
3905
+ },
3906
+ ]);
3907
+ expect(result).toHaveProperty('Home');
3908
+ expect(result['Home']).toEqual(['ReferenceError: window is not defined']);
3909
+ });
3910
+ });
3911
+ // ── determineTargetedAnalysisPaths ──────────────────────────────────
3912
+ describe('determineTargetedAnalysisPaths', () => {
3913
+ it('should return unique file paths from unassociated scenarios', () => {
3914
+ const result = determineTargetedAnalysisPaths({
3915
+ unassociatedScenarios: [
3916
+ { filePath: 'app/components/CreateFromFiltersButton.tsx' },
3917
+ { filePath: 'app/components/StaleDismissedBanner.tsx' },
3918
+ ],
3919
+ incompleteEntities: [],
3920
+ });
3921
+ expect(result).toEqual([
3922
+ 'app/components/CreateFromFiltersButton.tsx',
3923
+ 'app/components/StaleDismissedBanner.tsx',
3924
+ ]);
3925
+ });
3926
+ it('should include file paths from incomplete entities', () => {
3927
+ const result = determineTargetedAnalysisPaths({
3928
+ unassociatedScenarios: [{ filePath: 'app/components/TaskCard.tsx' }],
3929
+ incompleteEntities: [{ filePath: 'app/components/Dashboard.tsx' }],
3930
+ });
3931
+ expect(result).toEqual([
3932
+ 'app/components/TaskCard.tsx',
3933
+ 'app/components/Dashboard.tsx',
3934
+ ]);
3935
+ });
3936
+ it('should deduplicate file paths across both sources', () => {
3937
+ const result = determineTargetedAnalysisPaths({
3938
+ unassociatedScenarios: [{ filePath: 'app/components/TaskCard.tsx' }],
3939
+ incompleteEntities: [{ filePath: 'app/components/TaskCard.tsx' }],
3940
+ });
3941
+ expect(result).toEqual(['app/components/TaskCard.tsx']);
3942
+ });
3943
+ it('should return empty array when no issues', () => {
3944
+ const result = determineTargetedAnalysisPaths({
3945
+ unassociatedScenarios: [],
3946
+ incompleteEntities: [],
3947
+ });
3948
+ expect(result).toEqual([]);
3949
+ });
3950
+ it('should filter out entries with empty file paths', () => {
3951
+ const result = determineTargetedAnalysisPaths({
3952
+ unassociatedScenarios: [{ filePath: '' }],
3953
+ incompleteEntities: [],
3954
+ });
3955
+ expect(result).toEqual([]);
3956
+ });
3957
+ it('should handle incomplete entities with undefined filePath', () => {
3958
+ const result = determineTargetedAnalysisPaths({
3959
+ unassociatedScenarios: [{ filePath: 'app/components/Good.tsx' }],
3960
+ incompleteEntities: [
3961
+ {
3962
+ /* no filePath at all */
3963
+ },
3964
+ { filePath: undefined },
3965
+ { filePath: 'app/components/AlsoGood.tsx' },
3966
+ ],
3967
+ });
3968
+ expect(result).toEqual([
3969
+ 'app/components/Good.tsx',
3970
+ 'app/components/AlsoGood.tsx',
3971
+ ]);
3972
+ });
3973
+ });
3974
+ // ── shouldAutoRecapture ─────────────────────────────────────────────
3975
+ describe('shouldAutoRecapture', () => {
3976
+ it('should return true when fix flag is set and stale scenarios exist', () => {
3977
+ expect(shouldAutoRecapture({
3978
+ fix: true,
3979
+ scenariosNeedingRecapture: [
3980
+ {
3981
+ scenarioName: 'Default',
3982
+ entityName: 'TaskCard',
3983
+ status: { status: 'edited' },
3984
+ },
3985
+ ],
3986
+ })).toBe(true);
3987
+ });
3988
+ it('should return false when fix flag is not set', () => {
3989
+ expect(shouldAutoRecapture({
3990
+ fix: false,
3991
+ scenariosNeedingRecapture: [
3992
+ {
3993
+ scenarioName: 'Default',
3994
+ entityName: 'TaskCard',
3995
+ status: { status: 'edited' },
3996
+ },
3997
+ ],
3998
+ })).toBe(false);
3999
+ });
4000
+ it('should return false when fix is set but no stale scenarios', () => {
4001
+ expect(shouldAutoRecapture({
4002
+ fix: true,
4003
+ scenariosNeedingRecapture: [],
4004
+ })).toBe(false);
4005
+ });
4006
+ it('should return false when both are falsy', () => {
4007
+ expect(shouldAutoRecapture({
4008
+ fix: false,
4009
+ scenariosNeedingRecapture: [],
4010
+ })).toBe(false);
4011
+ });
4012
+ });
3558
4013
  });
3559
4014
  //# sourceMappingURL=editorAudit.test.js.map