@codeyam/codeyam-cli 0.1.11 → 0.1.12

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 (200) hide show
  1. package/analyzer-template/.build-info.json +8 -8
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +2 -2
  4. package/analyzer-template/packages/ai/package.json +1 -1
  5. package/analyzer-template/packages/aws/package.json +1 -1
  6. package/analyzer-template/packages/database/package.json +1 -1
  7. package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +42 -16
  8. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +3 -1
  9. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts.map +1 -1
  10. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js +44 -16
  11. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  12. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +11 -0
  13. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -1
  14. package/codeyam-cli/src/commands/editor.js +1214 -217
  15. package/codeyam-cli/src/commands/editor.js.map +1 -1
  16. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +893 -1
  17. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  18. package/codeyam-cli/src/utils/__tests__/editorBroadcastViewport.test.js +76 -0
  19. package/codeyam-cli/src/utils/__tests__/editorBroadcastViewport.test.js.map +1 -0
  20. package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js +100 -0
  21. package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js.map +1 -0
  22. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +6 -3
  23. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -1
  24. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +261 -0
  25. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -0
  26. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +75 -1
  27. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -1
  28. package/codeyam-cli/src/utils/__tests__/editorMigration.test.js +435 -0
  29. package/codeyam-cli/src/utils/__tests__/editorMigration.test.js.map +1 -0
  30. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +441 -17
  31. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  32. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +67 -0
  33. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
  34. package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js +143 -0
  35. package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js.map +1 -0
  36. package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js +66 -0
  37. package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js.map +1 -0
  38. package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js +53 -0
  39. package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js.map +1 -0
  40. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +67 -9
  41. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  42. package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js +118 -0
  43. package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js.map +1 -0
  44. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +40 -1
  45. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
  46. package/codeyam-cli/src/utils/analysisRunner.js +3 -1
  47. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  48. package/codeyam-cli/src/utils/editorAudit.js +145 -0
  49. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  50. package/codeyam-cli/src/utils/editorBroadcastViewport.js +26 -0
  51. package/codeyam-cli/src/utils/editorBroadcastViewport.js.map +1 -0
  52. package/codeyam-cli/src/utils/editorDeleteScenario.js +67 -0
  53. package/codeyam-cli/src/utils/editorDeleteScenario.js.map +1 -0
  54. package/codeyam-cli/src/utils/editorEntityChangeStatus.js +1 -1
  55. package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -1
  56. package/codeyam-cli/src/utils/editorEntityHelpers.js +129 -0
  57. package/codeyam-cli/src/utils/editorEntityHelpers.js.map +1 -0
  58. package/codeyam-cli/src/utils/editorLoaderHelpers.js +40 -1
  59. package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -1
  60. package/codeyam-cli/src/utils/editorMigration.js +224 -0
  61. package/codeyam-cli/src/utils/editorMigration.js.map +1 -0
  62. package/codeyam-cli/src/utils/editorScenarios.js +163 -2
  63. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  64. package/codeyam-cli/src/utils/editorSeedAdapter.js +253 -4
  65. package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
  66. package/codeyam-cli/src/utils/editorShouldRevalidate.js +21 -0
  67. package/codeyam-cli/src/utils/editorShouldRevalidate.js.map +1 -0
  68. package/codeyam-cli/src/utils/entityChangeStatus.js +19 -2
  69. package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
  70. package/codeyam-cli/src/utils/entityChangeStatus.server.js +7 -3
  71. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  72. package/codeyam-cli/src/utils/fileWatcher.js +38 -0
  73. package/codeyam-cli/src/utils/fileWatcher.js.map +1 -1
  74. package/codeyam-cli/src/utils/install-skills.js +9 -0
  75. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  76. package/codeyam-cli/src/utils/routePatternMatching.js +129 -0
  77. package/codeyam-cli/src/utils/routePatternMatching.js.map +1 -0
  78. package/codeyam-cli/src/utils/scenarioCoverage.js +8 -9
  79. package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -1
  80. package/codeyam-cli/src/utils/scenariosManifest.js +18 -10
  81. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  82. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +35 -0
  83. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -0
  84. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +33 -0
  85. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
  86. package/codeyam-cli/src/webserver/app/types/editor.js +8 -0
  87. package/codeyam-cli/src/webserver/app/types/editor.js.map +1 -0
  88. package/codeyam-cli/src/webserver/backgroundServer.js +18 -4
  89. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  90. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-CzTDWkF2.js +1 -0
  91. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-BcgbViKV.js → EntityItem-BFbq6iFk.js} +3 -3
  92. package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-CQgyEGV-.js +1 -0
  93. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CQIG2qda.js → EntityTypeIcon-B6OMi58N.js} +1 -1
  94. package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-DuYodzo1.js +1 -0
  95. package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-CXo9EeCl.js +25 -0
  96. package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-DYCNb2It.js +3 -0
  97. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-BU_OAEMP.js → LoadingDots-By5zI316.js} +1 -1
  98. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-ceAyBX-H.js → LogViewer-CZgY3sxX.js} +3 -3
  99. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-BzHcG7SE.js → ReportIssueModal-CnYYwRDw.js} +2 -2
  100. package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-CDoF7ZpU.js +1 -0
  101. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-TSD3C211.js → ScenarioViewer-DrnfvaLL.js} +3 -3
  102. package/codeyam-cli/src/webserver/build/client/assets/Spinner-Df3UCi8k.js +34 -0
  103. package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-CK7-NaPZ.js +1 -0
  104. package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-DRKR9T0U.js +1 -0
  105. package/codeyam-cli/src/webserver/build/client/assets/{_index-DLxKhri3.js → _index-ClR-g3tY.js} +2 -2
  106. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BcY3q6nt.js → activity.(_tab)-DTH6ydEA.js} +3 -3
  107. package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-Duc5hnl7.js → addon-web-links-74hnHF59.js} +1 -1
  108. package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-Bni3iiUj.js → agent-transcripts-B8CYhCO9.js} +3 -3
  109. package/codeyam-cli/src/webserver/build/client/assets/api.editor-rename-scenario-l0sNRNKZ.js +1 -0
  110. package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-seed-state-l0sNRNKZ.js +1 -0
  111. package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-prompt-l0sNRNKZ.js +1 -0
  112. package/codeyam-cli/src/webserver/build/client/assets/{book-open-BYOypzCa.js → book-open-CLaoh4ac.js} +1 -1
  113. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-C_Pmso5S.js → chevron-down-BZ2DZxbW.js} +1 -1
  114. package/codeyam-cli/src/webserver/build/client/assets/{chunk-JZWAC4HX-C4pqxYJB.js → chunk-JZWAC4HX-BBXArFPl.js} +13 -21
  115. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-BVMi9VA5.js → circle-check-CT4unAk-.js} +1 -1
  116. package/codeyam-cli/src/webserver/build/client/assets/{copy-n2FB0_Sw.js → copy-zK0B6Nu-.js} +1 -1
  117. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-CC6AbExI.js → createLucideIcon-DJB0YQJL.js} +1 -1
  118. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-CkXFP_i-.js +1 -0
  119. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-DPw7NZHc.js +1 -0
  120. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DmBK1JBK.js +58 -0
  121. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DBa7T2FK.js +41 -0
  122. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-DwCV5__E.js → entity._sha._-BqAN7hyG.js} +2 -2
  123. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-BOi8kpwd.js +6 -0
  124. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-Dg1NhIms.js +6 -0
  125. package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-CJX6kkkV.js +6 -0
  126. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-BMvVHNXU.js → entity._sha_.edit._scenarioId-BhVjZhKg.js} +2 -2
  127. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-DTvKq3TY.js → entry.client-_gzKltPN.js} +6 -6
  128. package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-Daa96Fr1.js +1 -0
  129. package/codeyam-cli/src/webserver/build/client/assets/files-CV_17tZS.js +1 -0
  130. package/codeyam-cli/src/webserver/build/client/assets/git-D-YXmMbR.js +1 -0
  131. package/codeyam-cli/src/webserver/build/client/assets/globals-CGrDAxj0.css +1 -0
  132. package/codeyam-cli/src/webserver/build/client/assets/{index-yHOVb4rc.js → index-Blo6EK8G.js} +1 -1
  133. package/codeyam-cli/src/webserver/build/client/assets/{index-10oVnAAH.js → index-BsX0F-9C.js} +1 -1
  134. package/codeyam-cli/src/webserver/build/client/assets/{index-BcvgDzbZ.js → index-CCrgCshv.js} +1 -1
  135. package/codeyam-cli/src/webserver/build/client/assets/jsx-runtime-D_zvdyIk.js +9 -0
  136. package/codeyam-cli/src/webserver/build/client/assets/labs-Byazq8Pv.js +1 -0
  137. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-DaAZ_H2w.js → loader-circle-DVQ0oHR7.js} +1 -1
  138. package/codeyam-cli/src/webserver/build/client/assets/manifest-b3f77062.js +1 -0
  139. package/codeyam-cli/src/webserver/build/client/assets/{memory-9gnxSZlb.js → memory-b-VmA2Vj.js} +2 -2
  140. package/codeyam-cli/src/webserver/build/client/assets/{pause-f5-1lKBt.js → pause-DGcndCAa.js} +1 -1
  141. package/codeyam-cli/src/webserver/build/client/assets/{root-DBjt6o04.js → root-D5Zi3U2Z.js} +5 -5
  142. package/codeyam-cli/src/webserver/build/client/assets/{search-Di64LWVb.js → search-C0Uw0bcK.js} +1 -1
  143. package/codeyam-cli/src/webserver/build/client/assets/settings-OoNgHIfW.js +1 -0
  144. package/codeyam-cli/src/webserver/build/client/assets/simulations-Bcemfu8a.js +1 -0
  145. package/codeyam-cli/src/webserver/build/client/assets/{terminal-Br7MOqts.js → terminal-BgMmG7R9.js} +1 -1
  146. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-BLdiCuG-.js → triangle-alert-Cs87hJYK.js} +1 -1
  147. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-BR3Rs7JY.js +1 -0
  148. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-C14nCb1q.js → useLastLogLine-BxxP_XF9.js} +1 -1
  149. package/codeyam-cli/src/webserver/build/client/assets/useReportContext-BermyNU5.js +1 -0
  150. package/codeyam-cli/src/webserver/build/client/assets/useToast-a_QN_W9_.js +1 -0
  151. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-yTyb36j3.js +13 -0
  152. package/codeyam-cli/src/webserver/build/server/assets/{index-DsZjKspK.js → index-Cr7d_IsG.js} +1 -1
  153. package/codeyam-cli/src/webserver/build/server/assets/init-M_wqNAfu.js +10 -0
  154. package/codeyam-cli/src/webserver/build/server/assets/progress-CHTtrxFG.js +1 -0
  155. package/codeyam-cli/src/webserver/build/server/assets/server-build-_ybRgrlc.js +551 -0
  156. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  157. package/codeyam-cli/src/webserver/build-info.json +5 -5
  158. package/codeyam-cli/src/webserver/editorProxy.js +93 -11
  159. package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
  160. package/codeyam-cli/src/webserver/mockStateEvents.js +28 -0
  161. package/codeyam-cli/src/webserver/mockStateEvents.js.map +1 -0
  162. package/codeyam-cli/src/webserver/server.js +9 -0
  163. package/codeyam-cli/src/webserver/server.js.map +1 -1
  164. package/codeyam-cli/src/webserver/terminalServer.js +74 -8
  165. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  166. package/codeyam-cli/templates/editor-step-hook.py +104 -20
  167. package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +42 -7
  168. package/codeyam-cli/templates/seed-adapters/supabase.ts +282 -0
  169. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +62 -0
  170. package/package.json +1 -1
  171. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +44 -16
  172. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  173. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-BPXZwM4t.js +0 -1
  174. package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-g3saevPb.js +0 -1
  175. package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-Bu6c6aDe.js +0 -1
  176. package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-DYFW3lDD.js +0 -25
  177. package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-DLeucoVX.js +0 -3
  178. package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-BED4B6sP.js +0 -1
  179. package/codeyam-cli/src/webserver/build/client/assets/Spinner-Bb5uFQ5V.js +0 -34
  180. package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-C8OKAR5x.js +0 -1
  181. package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-oAf2Kqsf.js +0 -1
  182. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-Ii3inc0_.js +0 -1
  183. package/codeyam-cli/src/webserver/build/client/assets/editor-16o0AIFV.js +0 -15
  184. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-7Uga8I59.js +0 -41
  185. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-BwKcai0j.js +0 -6
  186. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CHMiAog3.js +0 -6
  187. package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-p9hhkjJM.js +0 -6
  188. package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-cPo8LiG3.js +0 -1
  189. package/codeyam-cli/src/webserver/build/client/assets/files-BZrlFE1F.js +0 -1
  190. package/codeyam-cli/src/webserver/build/client/assets/git-DdZcvjGh.js +0 -1
  191. package/codeyam-cli/src/webserver/build/client/assets/globals-CQPR0pFR.css +0 -1
  192. package/codeyam-cli/src/webserver/build/client/assets/labs-Zk7ryIM1.js +0 -1
  193. package/codeyam-cli/src/webserver/build/client/assets/manifest-76e7b62c.js +0 -1
  194. package/codeyam-cli/src/webserver/build/client/assets/settings-0OrEMU6J.js +0 -1
  195. package/codeyam-cli/src/webserver/build/client/assets/simulations-DWT-CvLy.js +0 -1
  196. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-C-_hOl_g.js +0 -1
  197. package/codeyam-cli/src/webserver/build/client/assets/useReportContext-O-jkvSPx.js +0 -1
  198. package/codeyam-cli/src/webserver/build/client/assets/useToast-9FIWuYfK.js +0 -1
  199. package/codeyam-cli/src/webserver/build/server/assets/init-DdqKD2p4.js +0 -10
  200. package/codeyam-cli/src/webserver/build/server/assets/server-build-CKKeWtVK.js +0 -444
@@ -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, } from "../editorAudit.js";
3
+ import { isComponent, classifyGlossaryEntries, computeAudit, filterGlossaryByChangeStatus, resolveAuditSessionScope, queryScenarioCounts, queryIncompleteEntities, queryMiscategorizedScenarios, isOnlyIncompleteEntities, isAutoRemediable, } from "../editorAudit.js";
4
4
  describe('editorAudit', () => {
5
5
  describe('isComponent', () => {
6
6
  it('should return true for JSX.Element return type', () => {
@@ -983,5 +983,897 @@ describe('editorAudit', () => {
983
983
  expect(counts).toEqual({ ArticleCard: 1, ArticleRow: 1 });
984
984
  });
985
985
  });
986
+ // ── Audit + entity completeness integration ─────────────────────────
987
+ describe('audit should catch incomplete entities (bug reproduction)', () => {
988
+ let db;
989
+ let rawDb;
990
+ const projectId = 'test-project-id';
991
+ beforeEach(async () => {
992
+ rawDb = new Database(':memory:');
993
+ db = new Kysely({ dialect: new SqliteDialect({ database: rawDb }) });
994
+ await db.schema
995
+ .createTable('editor_scenarios')
996
+ .addColumn('id', 'varchar', (col) => col.primaryKey())
997
+ .addColumn('project_id', 'varchar', (col) => col.notNull())
998
+ .addColumn('name', 'varchar', (col) => col.notNull())
999
+ .addColumn('component_name', 'varchar')
1000
+ .addColumn('component_path', 'varchar')
1001
+ .addColumn('entity_sha', 'varchar')
1002
+ .addColumn('display_name', 'varchar')
1003
+ .addColumn('page_file_path', 'varchar')
1004
+ .addColumn('url', 'varchar')
1005
+ .addColumn('created_at', 'datetime')
1006
+ .addColumn('updated_at', 'datetime')
1007
+ .execute();
1008
+ await db.schema
1009
+ .createTable('analyses')
1010
+ .addColumn('id', 'varchar', (col) => col.primaryKey())
1011
+ .addColumn('entity_sha', 'varchar')
1012
+ .addColumn('entity_name', 'varchar')
1013
+ .addColumn('project_id', 'varchar')
1014
+ .execute();
1015
+ await db.schema
1016
+ .createTable('entities')
1017
+ .addColumn('sha', 'varchar', (col) => col.primaryKey())
1018
+ .addColumn('name', 'varchar')
1019
+ .addColumn('entity_type', 'varchar')
1020
+ .addColumn('file_path', 'varchar')
1021
+ .execute();
1022
+ });
1023
+ afterEach(async () => {
1024
+ await db.destroy();
1025
+ });
1026
+ it('demonstrates the bug: computeAudit passes but entities are incomplete', async () => {
1027
+ // Setup: Two components in glossary, both have scenarios registered.
1028
+ // CollectionChips has scenarios but NO analysis records — it's "incomplete."
1029
+ // The glossary-based audit (computeAudit) doesn't know about entity analyses
1030
+ // so it says allPassing: true. This is the bug.
1031
+ // Entities in DB
1032
+ await db
1033
+ .insertInto('entities')
1034
+ .values({
1035
+ sha: 'sha-header',
1036
+ name: 'Header',
1037
+ entity_type: 'visual',
1038
+ file_path: 'src/components/Header.tsx',
1039
+ })
1040
+ .execute();
1041
+ await db
1042
+ .insertInto('entities')
1043
+ .values({
1044
+ sha: 'sha-chips',
1045
+ name: 'CollectionChips',
1046
+ entity_type: 'visual',
1047
+ file_path: 'src/components/CollectionChips.tsx',
1048
+ })
1049
+ .execute();
1050
+ // Header has an analysis — it's complete
1051
+ await db
1052
+ .insertInto('analyses')
1053
+ .values({
1054
+ id: 'a-1',
1055
+ entity_sha: 'sha-header',
1056
+ entity_name: 'Header',
1057
+ project_id: projectId,
1058
+ })
1059
+ .execute();
1060
+ // CollectionChips has NO analysis — it's incomplete
1061
+ // Both have scenarios
1062
+ await db
1063
+ .insertInto('editor_scenarios')
1064
+ .values({
1065
+ id: 'sc-1',
1066
+ project_id: projectId,
1067
+ name: 'Header - Default',
1068
+ component_name: 'Header',
1069
+ entity_sha: 'sha-header',
1070
+ created_at: '2026-03-16 23:00:00',
1071
+ })
1072
+ .execute();
1073
+ await db
1074
+ .insertInto('editor_scenarios')
1075
+ .values({
1076
+ id: 'sc-2',
1077
+ project_id: projectId,
1078
+ name: 'CollectionChips - Default',
1079
+ component_name: 'CollectionChips',
1080
+ entity_sha: 'sha-chips',
1081
+ created_at: '2026-03-16 23:19:00',
1082
+ })
1083
+ .execute();
1084
+ await db
1085
+ .insertInto('editor_scenarios')
1086
+ .values({
1087
+ id: 'sc-3',
1088
+ project_id: projectId,
1089
+ name: 'CollectionChips - Many',
1090
+ component_name: 'CollectionChips',
1091
+ entity_sha: 'sha-chips',
1092
+ created_at: '2026-03-16 23:19:05',
1093
+ })
1094
+ .execute();
1095
+ // The glossary says both are components with scenarios
1096
+ const scenarioCounts = await queryScenarioCounts(db, projectId, null);
1097
+ expect(scenarioCounts).toEqual({ Header: 1, CollectionChips: 2 });
1098
+ // computeAudit only checks glossary coverage — it passes!
1099
+ const auditResult = computeAudit({
1100
+ components: [
1101
+ {
1102
+ name: 'Header',
1103
+ filePath: 'src/components/Header.tsx',
1104
+ returnType: 'JSX.Element',
1105
+ },
1106
+ {
1107
+ name: 'CollectionChips',
1108
+ filePath: 'src/components/CollectionChips.tsx',
1109
+ returnType: 'JSX.Element',
1110
+ },
1111
+ ],
1112
+ functions: [],
1113
+ scenarioCounts,
1114
+ testFileExistence: {},
1115
+ });
1116
+ // BUG: computeAudit alone says everything is fine
1117
+ expect(auditResult.summary.allPassing).toBe(true);
1118
+ expect(auditResult.summary.componentsOk).toBe(2);
1119
+ // But queryIncompleteEntities catches the real issue
1120
+ const incomplete = await queryIncompleteEntities(db, projectId, null);
1121
+ expect(incomplete).toHaveLength(1);
1122
+ expect(incomplete[0].name).toBe('CollectionChips');
1123
+ expect(incomplete[0].scenarioCount).toBe(2);
1124
+ });
1125
+ it('audit should fail when combining computeAudit with incomplete entity check', async () => {
1126
+ // Same setup as above — this test shows the FIX working:
1127
+ // after computeAudit, we also check queryIncompleteEntities,
1128
+ // and if any are found, allPassing becomes false.
1129
+ await db
1130
+ .insertInto('entities')
1131
+ .values({
1132
+ sha: 'sha-header',
1133
+ name: 'Header',
1134
+ entity_type: 'visual',
1135
+ file_path: 'src/components/Header.tsx',
1136
+ })
1137
+ .execute();
1138
+ await db
1139
+ .insertInto('entities')
1140
+ .values({
1141
+ sha: 'sha-chips',
1142
+ name: 'CollectionChips',
1143
+ entity_type: 'visual',
1144
+ file_path: 'src/components/CollectionChips.tsx',
1145
+ })
1146
+ .execute();
1147
+ await db
1148
+ .insertInto('analyses')
1149
+ .values({
1150
+ id: 'a-1',
1151
+ entity_sha: 'sha-header',
1152
+ entity_name: 'Header',
1153
+ project_id: projectId,
1154
+ })
1155
+ .execute();
1156
+ await db
1157
+ .insertInto('editor_scenarios')
1158
+ .values({
1159
+ id: 'sc-1',
1160
+ project_id: projectId,
1161
+ name: 'Header - Default',
1162
+ component_name: 'Header',
1163
+ entity_sha: 'sha-header',
1164
+ created_at: '2026-03-16 23:00:00',
1165
+ })
1166
+ .execute();
1167
+ await db
1168
+ .insertInto('editor_scenarios')
1169
+ .values({
1170
+ id: 'sc-2',
1171
+ project_id: projectId,
1172
+ name: 'CollectionChips - Default',
1173
+ component_name: 'CollectionChips',
1174
+ entity_sha: 'sha-chips',
1175
+ created_at: '2026-03-16 23:19:00',
1176
+ })
1177
+ .execute();
1178
+ const scenarioCounts = await queryScenarioCounts(db, projectId, null);
1179
+ const auditResult = computeAudit({
1180
+ components: [
1181
+ {
1182
+ name: 'Header',
1183
+ filePath: 'src/components/Header.tsx',
1184
+ returnType: 'JSX.Element',
1185
+ },
1186
+ {
1187
+ name: 'CollectionChips',
1188
+ filePath: 'src/components/CollectionChips.tsx',
1189
+ returnType: 'JSX.Element',
1190
+ },
1191
+ ],
1192
+ functions: [],
1193
+ scenarioCounts,
1194
+ testFileExistence: {},
1195
+ });
1196
+ // Apply the same post-processing the audit endpoint does
1197
+ const incomplete = await queryIncompleteEntities(db, projectId, null);
1198
+ if (incomplete.length > 0) {
1199
+ auditResult.summary.allPassing = false;
1200
+ auditResult.summary.incompleteEntities = incomplete.length;
1201
+ }
1202
+ // NOW the audit correctly fails
1203
+ expect(auditResult.summary.allPassing).toBe(false);
1204
+ expect(auditResult.summary.incompleteEntities).toBe(1);
1205
+ });
1206
+ it('audit should pass when all entities have analyses', async () => {
1207
+ // Both entities have analyses — everything is complete
1208
+ await db
1209
+ .insertInto('entities')
1210
+ .values({
1211
+ sha: 'sha-header',
1212
+ name: 'Header',
1213
+ entity_type: 'visual',
1214
+ file_path: 'src/components/Header.tsx',
1215
+ })
1216
+ .execute();
1217
+ await db
1218
+ .insertInto('entities')
1219
+ .values({
1220
+ sha: 'sha-chips',
1221
+ name: 'CollectionChips',
1222
+ entity_type: 'visual',
1223
+ file_path: 'src/components/CollectionChips.tsx',
1224
+ })
1225
+ .execute();
1226
+ await db
1227
+ .insertInto('analyses')
1228
+ .values({
1229
+ id: 'a-1',
1230
+ entity_sha: 'sha-header',
1231
+ entity_name: 'Header',
1232
+ project_id: projectId,
1233
+ })
1234
+ .execute();
1235
+ await db
1236
+ .insertInto('analyses')
1237
+ .values({
1238
+ id: 'a-2',
1239
+ entity_sha: 'sha-chips',
1240
+ entity_name: 'CollectionChips',
1241
+ project_id: projectId,
1242
+ })
1243
+ .execute();
1244
+ await db
1245
+ .insertInto('editor_scenarios')
1246
+ .values({
1247
+ id: 'sc-1',
1248
+ project_id: projectId,
1249
+ name: 'Header - Default',
1250
+ component_name: 'Header',
1251
+ entity_sha: 'sha-header',
1252
+ created_at: '2026-03-16 23:00:00',
1253
+ })
1254
+ .execute();
1255
+ await db
1256
+ .insertInto('editor_scenarios')
1257
+ .values({
1258
+ id: 'sc-2',
1259
+ project_id: projectId,
1260
+ name: 'CollectionChips - Default',
1261
+ component_name: 'CollectionChips',
1262
+ entity_sha: 'sha-chips',
1263
+ created_at: '2026-03-16 23:19:00',
1264
+ })
1265
+ .execute();
1266
+ const scenarioCounts = await queryScenarioCounts(db, projectId, null);
1267
+ const auditResult = computeAudit({
1268
+ components: [
1269
+ {
1270
+ name: 'Header',
1271
+ filePath: 'src/components/Header.tsx',
1272
+ returnType: 'JSX.Element',
1273
+ },
1274
+ {
1275
+ name: 'CollectionChips',
1276
+ filePath: 'src/components/CollectionChips.tsx',
1277
+ returnType: 'JSX.Element',
1278
+ },
1279
+ ],
1280
+ functions: [],
1281
+ scenarioCounts,
1282
+ testFileExistence: {},
1283
+ });
1284
+ const incomplete = await queryIncompleteEntities(db, projectId, null);
1285
+ if (incomplete.length > 0) {
1286
+ auditResult.summary.allPassing = false;
1287
+ auditResult.summary.incompleteEntities = incomplete.length;
1288
+ }
1289
+ // Everything complete — audit passes
1290
+ expect(auditResult.summary.allPassing).toBe(true);
1291
+ expect(auditResult.summary.incompleteEntities).toBeUndefined();
1292
+ });
1293
+ });
1294
+ // ── queryMiscategorizedScenarios ─────────────────────────────────────
1295
+ describe('queryMiscategorizedScenarios', () => {
1296
+ let db;
1297
+ let rawDb;
1298
+ const projectId = 'test-project-id';
1299
+ beforeEach(async () => {
1300
+ rawDb = new Database(':memory:');
1301
+ db = new Kysely({ dialect: new SqliteDialect({ database: rawDb }) });
1302
+ await db.schema
1303
+ .createTable('editor_scenarios')
1304
+ .addColumn('id', 'varchar', (col) => col.primaryKey())
1305
+ .addColumn('project_id', 'varchar', (col) => col.notNull())
1306
+ .addColumn('name', 'varchar', (col) => col.notNull())
1307
+ .addColumn('component_name', 'varchar')
1308
+ .addColumn('component_path', 'varchar')
1309
+ .addColumn('entity_sha', 'varchar')
1310
+ .addColumn('display_name', 'varchar')
1311
+ .addColumn('page_file_path', 'varchar')
1312
+ .addColumn('url', 'varchar')
1313
+ .addColumn('created_at', 'datetime')
1314
+ .addColumn('updated_at', 'datetime')
1315
+ .execute();
1316
+ });
1317
+ afterEach(async () => {
1318
+ await db.destroy();
1319
+ });
1320
+ it('should return empty when all component scenarios use isolation routes', async () => {
1321
+ await db
1322
+ .insertInto('editor_scenarios')
1323
+ .values({
1324
+ id: 'sc-1',
1325
+ project_id: projectId,
1326
+ name: 'LibraryCard - Default',
1327
+ component_name: 'LibraryCard',
1328
+ url: '/isolated-components/LibraryCard?s=Default',
1329
+ created_at: '2026-03-17 12:00:00',
1330
+ })
1331
+ .execute();
1332
+ const result = await queryMiscategorizedScenarios(db, projectId, null);
1333
+ expect(result).toEqual([]);
1334
+ });
1335
+ it('should flag component scenarios that use non-isolation URLs', async () => {
1336
+ // This is the bug: "Full Library Page" registered as component_name=LibraryPage
1337
+ // but url=/library — it's pointing at the real page, not an isolation route
1338
+ await db
1339
+ .insertInto('editor_scenarios')
1340
+ .values({
1341
+ id: 'sc-1',
1342
+ project_id: projectId,
1343
+ name: 'Full Library Page',
1344
+ component_name: 'LibraryPage',
1345
+ url: '/library',
1346
+ created_at: '2026-03-17 12:41:40',
1347
+ })
1348
+ .execute();
1349
+ await db
1350
+ .insertInto('editor_scenarios')
1351
+ .values({
1352
+ id: 'sc-2',
1353
+ project_id: projectId,
1354
+ name: 'Empty Library Page',
1355
+ component_name: 'LibraryPage',
1356
+ url: '/library',
1357
+ created_at: '2026-03-17 12:41:51',
1358
+ })
1359
+ .execute();
1360
+ const result = await queryMiscategorizedScenarios(db, projectId, null);
1361
+ expect(result).toEqual([
1362
+ {
1363
+ componentName: 'LibraryPage',
1364
+ scenarioNames: ['Full Library Page', 'Empty Library Page'],
1365
+ url: '/library',
1366
+ },
1367
+ ]);
1368
+ });
1369
+ it('should not flag page-level scenarios (no component_name)', async () => {
1370
+ // App-level scenarios have no component_name — they're fine with real URLs
1371
+ await db
1372
+ .insertInto('editor_scenarios')
1373
+ .values({
1374
+ id: 'sc-1',
1375
+ project_id: projectId,
1376
+ name: 'Library with Articles',
1377
+ url: '/',
1378
+ created_at: '2026-03-17 12:25:14',
1379
+ })
1380
+ .execute();
1381
+ const result = await queryMiscategorizedScenarios(db, projectId, null);
1382
+ expect(result).toEqual([]);
1383
+ });
1384
+ it('should group miscategorized scenarios by component and URL', async () => {
1385
+ // Two different components both misusing real URLs
1386
+ await db
1387
+ .insertInto('editor_scenarios')
1388
+ .values({
1389
+ id: 'sc-1',
1390
+ project_id: projectId,
1391
+ name: 'Full Library Page',
1392
+ component_name: 'LibraryPage',
1393
+ url: '/library',
1394
+ created_at: '2026-03-17 12:41:40',
1395
+ })
1396
+ .execute();
1397
+ await db
1398
+ .insertInto('editor_scenarios')
1399
+ .values({
1400
+ id: 'sc-2',
1401
+ project_id: projectId,
1402
+ name: 'Dashboard - Full',
1403
+ component_name: 'Dashboard',
1404
+ url: '/dashboard',
1405
+ created_at: '2026-03-17 12:50:00',
1406
+ })
1407
+ .execute();
1408
+ const result = await queryMiscategorizedScenarios(db, projectId, null);
1409
+ expect(result).toHaveLength(2);
1410
+ expect(result.map((r) => r.componentName).sort()).toEqual([
1411
+ 'Dashboard',
1412
+ 'LibraryPage',
1413
+ ]);
1414
+ });
1415
+ it('should scope to session when featureStartedAt is provided', async () => {
1416
+ // Old miscategorized scenario — before session
1417
+ await db
1418
+ .insertInto('editor_scenarios')
1419
+ .values({
1420
+ id: 'sc-old',
1421
+ project_id: projectId,
1422
+ name: 'Old Page',
1423
+ component_name: 'OldComponent',
1424
+ url: '/old',
1425
+ created_at: '2026-03-16 10:00:00',
1426
+ })
1427
+ .execute();
1428
+ // New miscategorized scenario — in session
1429
+ await db
1430
+ .insertInto('editor_scenarios')
1431
+ .values({
1432
+ id: 'sc-new',
1433
+ project_id: projectId,
1434
+ name: 'Full Library Page',
1435
+ component_name: 'LibraryPage',
1436
+ url: '/library',
1437
+ created_at: '2026-03-17 12:41:40',
1438
+ })
1439
+ .execute();
1440
+ const result = await queryMiscategorizedScenarios(db, projectId, '2026-03-17T11:58:55.562Z');
1441
+ expect(result).toHaveLength(1);
1442
+ expect(result[0].componentName).toBe('LibraryPage');
1443
+ });
1444
+ it('should not flag component scenarios with null URL', async () => {
1445
+ await db
1446
+ .insertInto('editor_scenarios')
1447
+ .values({
1448
+ id: 'sc-1',
1449
+ project_id: projectId,
1450
+ name: 'NoUrl - Default',
1451
+ component_name: 'NoUrl',
1452
+ created_at: '2026-03-17 12:00:00',
1453
+ })
1454
+ .execute();
1455
+ const result = await queryMiscategorizedScenarios(db, projectId, null);
1456
+ expect(result).toEqual([]);
1457
+ });
1458
+ });
1459
+ // ── isOnlyIncompleteEntities ─────────────────────────────────────────
1460
+ describe('isOnlyIncompleteEntities', () => {
1461
+ it('should return true when incompleteEntities is the only failure', () => {
1462
+ expect(isOnlyIncompleteEntities({
1463
+ componentsMissing: 0,
1464
+ componentsWithErrors: 0,
1465
+ functionsFailing: 0,
1466
+ functionsNameMismatch: 0,
1467
+ functionsMissing: 0,
1468
+ missingFromGlossary: 0,
1469
+ incompleteEntities: 3,
1470
+ allPassing: false,
1471
+ })).toBe(true);
1472
+ });
1473
+ it('should return false when there are also missing components', () => {
1474
+ expect(isOnlyIncompleteEntities({
1475
+ componentsMissing: 1,
1476
+ componentsWithErrors: 0,
1477
+ functionsFailing: 0,
1478
+ functionsNameMismatch: 0,
1479
+ functionsMissing: 0,
1480
+ missingFromGlossary: 0,
1481
+ incompleteEntities: 2,
1482
+ allPassing: false,
1483
+ })).toBe(false);
1484
+ });
1485
+ it('should return false when there are also failing tests', () => {
1486
+ expect(isOnlyIncompleteEntities({
1487
+ componentsMissing: 0,
1488
+ componentsWithErrors: 0,
1489
+ functionsFailing: 1,
1490
+ functionsNameMismatch: 0,
1491
+ functionsMissing: 0,
1492
+ missingFromGlossary: 0,
1493
+ incompleteEntities: 2,
1494
+ allPassing: false,
1495
+ })).toBe(false);
1496
+ });
1497
+ it('should return false when there are also missing glossary entries', () => {
1498
+ expect(isOnlyIncompleteEntities({
1499
+ componentsMissing: 0,
1500
+ componentsWithErrors: 0,
1501
+ functionsFailing: 0,
1502
+ functionsNameMismatch: 0,
1503
+ functionsMissing: 0,
1504
+ missingFromGlossary: 1,
1505
+ incompleteEntities: 2,
1506
+ allPassing: false,
1507
+ })).toBe(false);
1508
+ });
1509
+ it('should return true even when incompleteEntities is 0 (no failures at all)', () => {
1510
+ // Edge case: all zeros means nothing is failing
1511
+ expect(isOnlyIncompleteEntities({
1512
+ componentsMissing: 0,
1513
+ componentsWithErrors: 0,
1514
+ functionsFailing: 0,
1515
+ functionsNameMismatch: 0,
1516
+ functionsMissing: 0,
1517
+ missingFromGlossary: 0,
1518
+ incompleteEntities: 0,
1519
+ allPassing: true,
1520
+ })).toBe(true);
1521
+ });
1522
+ it('should return false when there are also miscategorized scenarios', () => {
1523
+ expect(isOnlyIncompleteEntities({
1524
+ componentsMissing: 0,
1525
+ componentsWithErrors: 0,
1526
+ functionsFailing: 0,
1527
+ functionsNameMismatch: 0,
1528
+ functionsMissing: 0,
1529
+ missingFromGlossary: 0,
1530
+ miscategorizedScenarios: 1,
1531
+ incompleteEntities: 2,
1532
+ allPassing: false,
1533
+ })).toBe(false);
1534
+ });
1535
+ it('should handle missing fields gracefully', () => {
1536
+ // Summary from older API version might not have all fields
1537
+ expect(isOnlyIncompleteEntities({
1538
+ incompleteEntities: 2,
1539
+ allPassing: false,
1540
+ })).toBe(true);
1541
+ });
1542
+ });
1543
+ // ── isAutoRemediable ─────────────────────────────────────────────────
1544
+ describe('isAutoRemediable', () => {
1545
+ it('should return true on first attempt when only incomplete entities', () => {
1546
+ const result = isAutoRemediable({
1547
+ componentsMissing: 0,
1548
+ componentsWithErrors: 0,
1549
+ functionsFailing: 0,
1550
+ functionsNameMismatch: 0,
1551
+ functionsMissing: 0,
1552
+ missingFromGlossary: 0,
1553
+ miscategorizedScenarios: 0,
1554
+ incompleteEntities: 3,
1555
+ allPassing: false,
1556
+ }, false);
1557
+ expect(result).toBe(true);
1558
+ });
1559
+ it('should return false on second attempt (already tried once)', () => {
1560
+ // This is the key fix: if we already tried analyze-imports and
1561
+ // entities are STILL incomplete, don't try again — report the failure
1562
+ const result = isAutoRemediable({
1563
+ componentsMissing: 0,
1564
+ componentsWithErrors: 0,
1565
+ functionsFailing: 0,
1566
+ functionsNameMismatch: 0,
1567
+ functionsMissing: 0,
1568
+ missingFromGlossary: 0,
1569
+ miscategorizedScenarios: 0,
1570
+ incompleteEntities: 3,
1571
+ allPassing: false,
1572
+ }, true);
1573
+ expect(result).toBe(false);
1574
+ });
1575
+ it('should return false when there are other failures besides incomplete entities', () => {
1576
+ const result = isAutoRemediable({
1577
+ componentsMissing: 1,
1578
+ incompleteEntities: 3,
1579
+ allPassing: false,
1580
+ }, false);
1581
+ expect(result).toBe(false);
1582
+ });
1583
+ it('should return false when there are no incomplete entities', () => {
1584
+ const result = isAutoRemediable({
1585
+ componentsMissing: 1,
1586
+ allPassing: false,
1587
+ }, false);
1588
+ expect(result).toBe(false);
1589
+ });
1590
+ });
1591
+ // ── queryIncompleteEntities ─────────────────────────────────────────
1592
+ describe('queryIncompleteEntities', () => {
1593
+ let db;
1594
+ let rawDb;
1595
+ const projectId = 'test-project-id';
1596
+ beforeEach(async () => {
1597
+ rawDb = new Database(':memory:');
1598
+ db = new Kysely({ dialect: new SqliteDialect({ database: rawDb }) });
1599
+ await db.schema
1600
+ .createTable('editor_scenarios')
1601
+ .addColumn('id', 'varchar', (col) => col.primaryKey())
1602
+ .addColumn('project_id', 'varchar', (col) => col.notNull())
1603
+ .addColumn('name', 'varchar', (col) => col.notNull())
1604
+ .addColumn('component_name', 'varchar')
1605
+ .addColumn('component_path', 'varchar')
1606
+ .addColumn('entity_sha', 'varchar')
1607
+ .addColumn('display_name', 'varchar')
1608
+ .addColumn('page_file_path', 'varchar')
1609
+ .addColumn('url', 'varchar')
1610
+ .addColumn('created_at', 'datetime')
1611
+ .addColumn('updated_at', 'datetime')
1612
+ .execute();
1613
+ await db.schema
1614
+ .createTable('analyses')
1615
+ .addColumn('id', 'varchar', (col) => col.primaryKey())
1616
+ .addColumn('entity_sha', 'varchar')
1617
+ .addColumn('entity_name', 'varchar')
1618
+ .addColumn('project_id', 'varchar')
1619
+ .execute();
1620
+ await db.schema
1621
+ .createTable('entities')
1622
+ .addColumn('sha', 'varchar', (col) => col.primaryKey())
1623
+ .addColumn('name', 'varchar')
1624
+ .addColumn('entity_type', 'varchar')
1625
+ .addColumn('file_path', 'varchar')
1626
+ .execute();
1627
+ });
1628
+ afterEach(async () => {
1629
+ await db.destroy();
1630
+ });
1631
+ it('should return empty when all scenario entity SHAs have analyses', async () => {
1632
+ // Entity with analysis
1633
+ await db
1634
+ .insertInto('entities')
1635
+ .values({
1636
+ sha: 'sha-header',
1637
+ name: 'Header',
1638
+ entity_type: 'visual',
1639
+ file_path: 'src/Header.tsx',
1640
+ })
1641
+ .execute();
1642
+ await db
1643
+ .insertInto('analyses')
1644
+ .values({
1645
+ id: 'a-1',
1646
+ entity_sha: 'sha-header',
1647
+ entity_name: 'Header',
1648
+ project_id: projectId,
1649
+ })
1650
+ .execute();
1651
+ await db
1652
+ .insertInto('editor_scenarios')
1653
+ .values({
1654
+ id: 'sc-1',
1655
+ project_id: projectId,
1656
+ name: 'Header - Default',
1657
+ component_name: 'Header',
1658
+ entity_sha: 'sha-header',
1659
+ created_at: '2026-03-16 23:00:00',
1660
+ })
1661
+ .execute();
1662
+ const result = await queryIncompleteEntities(db, projectId, null);
1663
+ expect(result).toEqual([]);
1664
+ });
1665
+ it('should return entities with scenarios but no analyses', async () => {
1666
+ // Entity WITHOUT analysis
1667
+ await db
1668
+ .insertInto('entities')
1669
+ .values({
1670
+ sha: 'sha-chips',
1671
+ name: 'CollectionChips',
1672
+ entity_type: 'visual',
1673
+ file_path: 'src/components/CollectionChips.tsx',
1674
+ })
1675
+ .execute();
1676
+ // Scenario referencing that entity
1677
+ await db
1678
+ .insertInto('editor_scenarios')
1679
+ .values({
1680
+ id: 'sc-1',
1681
+ project_id: projectId,
1682
+ name: 'CollectionChips - Default',
1683
+ component_name: 'CollectionChips',
1684
+ entity_sha: 'sha-chips',
1685
+ created_at: '2026-03-16 23:00:00',
1686
+ })
1687
+ .execute();
1688
+ await db
1689
+ .insertInto('editor_scenarios')
1690
+ .values({
1691
+ id: 'sc-2',
1692
+ project_id: projectId,
1693
+ name: 'CollectionChips - Many',
1694
+ component_name: 'CollectionChips',
1695
+ entity_sha: 'sha-chips',
1696
+ created_at: '2026-03-16 23:01:00',
1697
+ })
1698
+ .execute();
1699
+ const result = await queryIncompleteEntities(db, projectId, null);
1700
+ expect(result).toEqual([
1701
+ { entitySha: 'sha-chips', name: 'CollectionChips', scenarioCount: 2 },
1702
+ ]);
1703
+ });
1704
+ it('should only return entities without analyses, not those with analyses', async () => {
1705
+ // Entity WITH analysis (Header)
1706
+ await db
1707
+ .insertInto('entities')
1708
+ .values({
1709
+ sha: 'sha-header',
1710
+ name: 'Header',
1711
+ entity_type: 'visual',
1712
+ file_path: 'src/Header.tsx',
1713
+ })
1714
+ .execute();
1715
+ await db
1716
+ .insertInto('analyses')
1717
+ .values({
1718
+ id: 'a-1',
1719
+ entity_sha: 'sha-header',
1720
+ entity_name: 'Header',
1721
+ project_id: projectId,
1722
+ })
1723
+ .execute();
1724
+ await db
1725
+ .insertInto('editor_scenarios')
1726
+ .values({
1727
+ id: 'sc-1',
1728
+ project_id: projectId,
1729
+ name: 'Header - Default',
1730
+ component_name: 'Header',
1731
+ entity_sha: 'sha-header',
1732
+ created_at: '2026-03-16 23:00:00',
1733
+ })
1734
+ .execute();
1735
+ // Entity WITHOUT analysis (CollectionPicker)
1736
+ await db
1737
+ .insertInto('entities')
1738
+ .values({
1739
+ sha: 'sha-picker',
1740
+ name: 'CollectionPicker',
1741
+ entity_type: 'visual',
1742
+ file_path: 'src/components/CollectionPicker.tsx',
1743
+ })
1744
+ .execute();
1745
+ await db
1746
+ .insertInto('editor_scenarios')
1747
+ .values({
1748
+ id: 'sc-2',
1749
+ project_id: projectId,
1750
+ name: 'CollectionPicker - Default',
1751
+ component_name: 'CollectionPicker',
1752
+ entity_sha: 'sha-picker',
1753
+ created_at: '2026-03-16 23:00:00',
1754
+ })
1755
+ .execute();
1756
+ const result = await queryIncompleteEntities(db, projectId, null);
1757
+ expect(result).toEqual([
1758
+ { entitySha: 'sha-picker', name: 'CollectionPicker', scenarioCount: 1 },
1759
+ ]);
1760
+ });
1761
+ it('should scope to session when featureStartedAt is provided', async () => {
1762
+ // Entity without analysis, scenario created BEFORE session
1763
+ await db
1764
+ .insertInto('entities')
1765
+ .values({
1766
+ sha: 'sha-old',
1767
+ name: 'OldComponent',
1768
+ entity_type: 'visual',
1769
+ file_path: 'src/OldComponent.tsx',
1770
+ })
1771
+ .execute();
1772
+ await db
1773
+ .insertInto('editor_scenarios')
1774
+ .values({
1775
+ id: 'sc-old',
1776
+ project_id: projectId,
1777
+ name: 'OldComponent - Default',
1778
+ component_name: 'OldComponent',
1779
+ entity_sha: 'sha-old',
1780
+ created_at: '2026-03-16 20:00:00',
1781
+ })
1782
+ .execute();
1783
+ // Entity without analysis, scenario created DURING session
1784
+ await db
1785
+ .insertInto('entities')
1786
+ .values({
1787
+ sha: 'sha-new',
1788
+ name: 'NewComponent',
1789
+ entity_type: 'visual',
1790
+ file_path: 'src/NewComponent.tsx',
1791
+ })
1792
+ .execute();
1793
+ await db
1794
+ .insertInto('editor_scenarios')
1795
+ .values({
1796
+ id: 'sc-new',
1797
+ project_id: projectId,
1798
+ name: 'NewComponent - Default',
1799
+ component_name: 'NewComponent',
1800
+ entity_sha: 'sha-new',
1801
+ created_at: '2026-03-16 23:10:00',
1802
+ })
1803
+ .execute();
1804
+ const result = await queryIncompleteEntities(db, projectId, '2026-03-16T23:07:12.698Z');
1805
+ // Only NewComponent should be flagged (created in session)
1806
+ expect(result).toEqual([
1807
+ { entitySha: 'sha-new', name: 'NewComponent', scenarioCount: 1 },
1808
+ ]);
1809
+ });
1810
+ it('should include scenarios updated in session even if created before', async () => {
1811
+ await db
1812
+ .insertInto('entities')
1813
+ .values({
1814
+ sha: 'sha-updated',
1815
+ name: 'UpdatedComponent',
1816
+ entity_type: 'visual',
1817
+ file_path: 'src/Updated.tsx',
1818
+ })
1819
+ .execute();
1820
+ await db
1821
+ .insertInto('editor_scenarios')
1822
+ .values({
1823
+ id: 'sc-updated',
1824
+ project_id: projectId,
1825
+ name: 'UpdatedComponent - Default',
1826
+ component_name: 'UpdatedComponent',
1827
+ entity_sha: 'sha-updated',
1828
+ created_at: '2026-03-16 20:00:00',
1829
+ updated_at: '2026-03-16 23:20:00', // Updated in session
1830
+ })
1831
+ .execute();
1832
+ const result = await queryIncompleteEntities(db, projectId, '2026-03-16T23:07:12.698Z');
1833
+ expect(result).toEqual([
1834
+ {
1835
+ entitySha: 'sha-updated',
1836
+ name: 'UpdatedComponent',
1837
+ scenarioCount: 1,
1838
+ },
1839
+ ]);
1840
+ });
1841
+ it('should skip scenarios with null entity_sha', async () => {
1842
+ await db
1843
+ .insertInto('editor_scenarios')
1844
+ .values({
1845
+ id: 'sc-null',
1846
+ project_id: projectId,
1847
+ name: 'Orphan Scenario',
1848
+ component_name: 'Orphan',
1849
+ created_at: '2026-03-16 23:00:00',
1850
+ })
1851
+ .execute();
1852
+ const result = await queryIncompleteEntities(db, projectId, null);
1853
+ expect(result).toEqual([]);
1854
+ });
1855
+ it('should return empty when there are no scenarios', async () => {
1856
+ const result = await queryIncompleteEntities(db, projectId, null);
1857
+ expect(result).toEqual([]);
1858
+ });
1859
+ it('should use entity name from entities table, falling back to component_name', async () => {
1860
+ // Scenario has entity_sha but entity record doesn't exist
1861
+ await db
1862
+ .insertInto('editor_scenarios')
1863
+ .values({
1864
+ id: 'sc-1',
1865
+ project_id: projectId,
1866
+ name: 'Ghost - Default',
1867
+ component_name: 'GhostComponent',
1868
+ entity_sha: 'sha-ghost',
1869
+ created_at: '2026-03-16 23:00:00',
1870
+ })
1871
+ .execute();
1872
+ const result = await queryIncompleteEntities(db, projectId, null);
1873
+ expect(result).toEqual([
1874
+ { entitySha: 'sha-ghost', name: 'GhostComponent', scenarioCount: 1 },
1875
+ ]);
1876
+ });
1877
+ });
986
1878
  });
987
1879
  //# sourceMappingURL=editorAudit.test.js.map