@jmruthers/pace-core 0.6.4 → 0.6.6

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 (387) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/core-usage-manifest.json +93 -0
  4. package/cursor-rules/00-pace-core-compliance.mdc +128 -26
  5. package/cursor-rules/01-standards-compliance.mdc +49 -8
  6. package/cursor-rules/02-project-structure.mdc +6 -0
  7. package/cursor-rules/03-solid-principles.mdc +2 -0
  8. package/cursor-rules/04-testing-standards.mdc +2 -0
  9. package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
  10. package/cursor-rules/06-code-quality.mdc +2 -0
  11. package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
  12. package/cursor-rules/08-markup-quality.mdc +52 -27
  13. package/cursor-rules/09-rbac-compliance.mdc +462 -0
  14. package/cursor-rules/10-error-handling-patterns.mdc +179 -0
  15. package/cursor-rules/11-performance-optimization.mdc +169 -0
  16. package/cursor-rules/12-ci-cd-integration.mdc +150 -0
  17. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  18. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
  19. package/dist/DataTable-LRJL4IRV.js +15 -0
  20. package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
  21. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  22. package/dist/api-Y4MQWOFW.js +4 -0
  23. package/dist/audit-MYQXYZFU.js +3 -0
  24. package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
  25. package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
  26. package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
  27. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  28. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  29. package/dist/chunk-4T7OBVTU.js +62 -0
  30. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  31. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  32. package/dist/{chunk-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
  33. package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
  34. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  35. package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
  36. package/dist/chunk-C7NSAPTL.js +1 -0
  37. package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
  38. package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
  39. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  40. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  41. package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
  42. package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
  43. package/dist/chunk-MBADTM7L.js +64 -0
  44. package/dist/chunk-OHIK3MIO.js +994 -0
  45. package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
  46. package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
  47. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  48. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  49. package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
  50. package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
  51. package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
  52. package/dist/components.d.ts +6 -6
  53. package/dist/components.js +57 -267
  54. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  55. package/dist/eslint-rules/index.cjs +22 -0
  56. package/dist/eslint-rules/rules/compliance.cjs +348 -0
  57. package/dist/eslint-rules/rules/components.cjs +113 -0
  58. package/dist/eslint-rules/rules/imports.cjs +102 -0
  59. package/dist/eslint-rules/rules/rbac.cjs +790 -0
  60. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  61. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  62. package/dist/hooks.d.ts +5 -5
  63. package/dist/hooks.js +62 -270
  64. package/dist/icons/index.d.ts +1 -0
  65. package/dist/icons/index.js +1 -0
  66. package/dist/index.d.ts +36 -26
  67. package/dist/index.js +87 -690
  68. package/dist/providers.d.ts +2 -2
  69. package/dist/providers.js +8 -35
  70. package/dist/rbac/eslint-rules.d.ts +46 -44
  71. package/dist/rbac/eslint-rules.js +7 -4
  72. package/dist/rbac/index.d.ts +124 -594
  73. package/dist/rbac/index.js +14 -207
  74. package/dist/styles/index.js +2 -12
  75. package/dist/theming/runtime.js +3 -19
  76. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  77. package/dist/{types-CkbwOr4Y.d.ts → types-B-K_5VnO.d.ts} +4 -0
  78. package/dist/types-t9H8qKRw.d.ts +55 -0
  79. package/dist/types.d.ts +1 -1
  80. package/dist/types.js +7 -94
  81. package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
  82. package/dist/utils.d.ts +24 -117
  83. package/dist/utils.js +54 -392
  84. package/docs/README.md +16 -6
  85. package/docs/api/README.md +4 -402
  86. package/docs/api/modules.md +454 -930
  87. package/docs/api-reference/components.md +3 -1
  88. package/docs/api-reference/deprecated.md +31 -6
  89. package/docs/api-reference/rpc-functions.md +78 -3
  90. package/docs/best-practices/accessibility.md +6 -3
  91. package/docs/getting-started/cursor-rules.md +3 -23
  92. package/docs/getting-started/dependencies.md +650 -0
  93. package/docs/getting-started/installation-guide.md +20 -7
  94. package/docs/getting-started/quick-start.md +23 -12
  95. package/docs/implementation-guides/permission-enforcement.md +4 -0
  96. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  97. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  98. package/docs/rbac/README.md +12 -3
  99. package/docs/rbac/edge-functions-guide.md +376 -0
  100. package/docs/rbac/secure-client-protection.md +0 -34
  101. package/docs/standards/00-pace-core-compliance.md +967 -0
  102. package/docs/standards/01-standards-compliance.md +188 -0
  103. package/docs/standards/02-project-structure.md +985 -0
  104. package/docs/standards/03-solid-principles.md +39 -0
  105. package/docs/standards/04-testing-standards.md +36 -0
  106. package/docs/standards/05-bug-reports-and-features.md +27 -0
  107. package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
  108. package/docs/standards/07-tech-stack-compliance.md +30 -0
  109. package/docs/standards/08-markup-quality.md +345 -0
  110. package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
  111. package/docs/standards/10-error-handling-patterns.md +401 -0
  112. package/docs/standards/11-performance-optimization.md +348 -0
  113. package/docs/standards/12-ci-cd-integration.md +370 -0
  114. package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
  115. package/docs/standards/README.md +62 -33
  116. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  117. package/eslint-config-pace-core.cjs +20 -4
  118. package/package.json +31 -21
  119. package/scripts/audit/audit-compliance.cjs +1295 -0
  120. package/scripts/audit/audit-components.cjs +260 -0
  121. package/scripts/audit/audit-dependencies.cjs +395 -0
  122. package/scripts/audit/audit-rbac.cjs +954 -0
  123. package/scripts/audit/audit-standards.cjs +1268 -0
  124. package/scripts/audit/index.cjs +1898 -194
  125. package/scripts/install-cursor-rules.cjs +259 -8
  126. package/scripts/validate-master.js +1 -1
  127. package/src/__tests__/fixtures/supabase.ts +1 -1
  128. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
  129. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  130. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  131. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +3 -3
  132. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  133. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  134. package/src/__tests__/public-recipe-view.test.ts +38 -9
  135. package/src/components/Button/Button.tsx +5 -1
  136. package/src/components/ContextSelector/ContextSelector.tsx +42 -39
  137. package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
  138. package/src/components/DataTable/components/DataTableBody.tsx +55 -31
  139. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  140. package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
  141. package/src/components/DataTable/components/EditFields.tsx +23 -3
  142. package/src/components/DataTable/components/EditableRow.tsx +7 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +4 -6
  144. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  145. package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
  146. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
  147. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
  148. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  149. package/src/components/DataTable/core/DataTableContext.tsx +1 -1
  150. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  151. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  152. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  153. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  154. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  155. package/src/components/DataTable/types.ts +5 -0
  156. package/src/components/DateTimeField/DateTimeField.tsx +20 -20
  157. package/src/components/DateTimeField/README.md +5 -2
  158. package/src/components/Dialog/Dialog.test.tsx +361 -318
  159. package/src/components/Dialog/Dialog.tsx +1154 -323
  160. package/src/components/Dialog/index.ts +3 -3
  161. package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
  162. package/src/components/FileDisplay/FileDisplay.tsx +28 -22
  163. package/src/components/Form/Form.test.tsx +9 -10
  164. package/src/components/Form/Form.tsx +369 -9
  165. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
  166. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
  167. package/src/components/LoginForm/LoginForm.tsx +2 -2
  168. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  169. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
  170. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  171. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  172. package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
  173. package/src/components/PaceAppLayout/README.md +10 -9
  174. package/src/components/PaceAppLayout/test-setup.tsx +40 -31
  175. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  176. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  177. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
  178. package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
  179. package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
  180. package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
  181. package/src/components/Select/Select.tsx +23 -21
  182. package/src/components/Select/types.ts +1 -1
  183. package/src/components/UserMenu/UserMenu.test.tsx +38 -6
  184. package/src/components/UserMenu/UserMenu.tsx +39 -34
  185. package/src/components/index.ts +3 -4
  186. package/src/eslint-rules/index.cjs +22 -0
  187. package/src/eslint-rules/rules/compliance.cjs +348 -0
  188. package/src/eslint-rules/rules/components.cjs +113 -0
  189. package/src/eslint-rules/rules/imports.cjs +102 -0
  190. package/src/eslint-rules/rules/rbac.cjs +790 -0
  191. package/src/eslint-rules/utils/helpers.cjs +42 -0
  192. package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
  193. package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
  194. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  195. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  196. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  197. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  198. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  199. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  200. package/src/hooks/public/usePublicEvent.ts +62 -190
  201. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  202. package/src/hooks/public/usePublicEventLogo.ts +19 -9
  203. package/src/hooks/useAppConfig.ts +26 -24
  204. package/src/hooks/useEventTheme.test.ts +211 -233
  205. package/src/hooks/useEventTheme.ts +19 -28
  206. package/src/hooks/useEvents.ts +11 -7
  207. package/src/hooks/useKeyboardShortcuts.ts +1 -1
  208. package/src/hooks/useOrganisationPermissions.ts +9 -11
  209. package/src/hooks/useOrganisations.ts +13 -7
  210. package/src/hooks/useQueryCache.ts +0 -1
  211. package/src/hooks/useSessionDraft.ts +380 -0
  212. package/src/hooks/useSessionRestoration.ts +3 -1
  213. package/src/icons/index.ts +27 -0
  214. package/src/index.ts +16 -1
  215. package/src/providers/OrganisationProvider.tsx +23 -14
  216. package/src/providers/services/EventServiceProvider.tsx +1 -24
  217. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  218. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
  219. package/src/rbac/README.md +20 -20
  220. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  221. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  222. package/src/rbac/adapters.tsx +7 -295
  223. package/src/rbac/api.test.ts +44 -56
  224. package/src/rbac/api.ts +10 -17
  225. package/src/rbac/cache-invalidation.ts +0 -1
  226. package/src/rbac/compliance/index.ts +10 -0
  227. package/src/rbac/compliance/pattern-detector.ts +553 -0
  228. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  229. package/src/rbac/components/AccessDenied.tsx +150 -0
  230. package/src/rbac/components/NavigationGuard.tsx +12 -20
  231. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  232. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  233. package/src/rbac/components/index.ts +3 -41
  234. package/src/rbac/eslint-rules.js +1 -1
  235. package/src/rbac/hooks/index.ts +0 -3
  236. package/src/rbac/hooks/permissions/index.ts +0 -3
  237. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  238. package/src/rbac/hooks/usePermissions.ts +0 -3
  239. package/src/rbac/hooks/useRBAC.test.ts +21 -3
  240. package/src/rbac/hooks/useRBAC.ts +4 -3
  241. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  242. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  243. package/src/rbac/hooks/useResourcePermissions.test.ts +241 -60
  244. package/src/rbac/hooks/useResourcePermissions.ts +182 -63
  245. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  246. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  247. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  248. package/src/rbac/index.ts +7 -9
  249. package/src/rbac/permissions.ts +17 -17
  250. package/src/rbac/utils/contextValidator.ts +45 -7
  251. package/src/services/AuthService.ts +132 -23
  252. package/src/services/EventService.ts +4 -97
  253. package/src/services/InactivityService.ts +155 -58
  254. package/src/services/OrganisationService.ts +7 -44
  255. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  256. package/src/services/base/BaseService.ts +0 -3
  257. package/src/styles/core.css +4 -0
  258. package/src/types/database.generated.ts +4733 -3809
  259. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  260. package/src/utils/context/organisationContext.test.ts +13 -28
  261. package/src/utils/context/organisationContext.ts +21 -52
  262. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  263. package/src/utils/file-reference/index.ts +39 -15
  264. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  265. package/src/utils/formatting/formatTime.test.ts +3 -2
  266. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  267. package/src/utils/index.ts +4 -1
  268. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  269. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  270. package/src/utils/persistence/keyDerivation.ts +304 -0
  271. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  272. package/src/utils/security/secureStorage.ts +5 -5
  273. package/src/utils/storage/helpers.ts +3 -3
  274. package/src/utils/supabase/createBaseClient.ts +147 -0
  275. package/src/utils/timezone/timezone.test.ts +1 -2
  276. package/src/utils/timezone/timezone.ts +1 -1
  277. package/src/utils/validation/csrf.ts +4 -4
  278. package/cursor-rules/CHANGELOG.md +0 -119
  279. package/cursor-rules/README.md +0 -192
  280. package/dist/DataTable-E7YQZD7D.js +0 -175
  281. package/dist/DataTable-E7YQZD7D.js.map +0 -1
  282. package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
  283. package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
  284. package/dist/api-6LVZTHDS.js +0 -52
  285. package/dist/api-6LVZTHDS.js.map +0 -1
  286. package/dist/audit-V53FV5AG.js +0 -17
  287. package/dist/audit-V53FV5AG.js.map +0 -1
  288. package/dist/chunk-36LVWXB2.js +0 -227
  289. package/dist/chunk-36LVWXB2.js.map +0 -1
  290. package/dist/chunk-3LPHPB62.js.map +0 -1
  291. package/dist/chunk-5DRSZLL2.js.map +0 -1
  292. package/dist/chunk-5EC5MEWX.js.map +0 -1
  293. package/dist/chunk-63FOKYGO.js.map +0 -1
  294. package/dist/chunk-6SOIHG6Z.js.map +0 -1
  295. package/dist/chunk-7JPAB3T5.js.map +0 -1
  296. package/dist/chunk-ATKZM7RX.js +0 -2053
  297. package/dist/chunk-ATKZM7RX.js.map +0 -1
  298. package/dist/chunk-AVMLPIM7.js.map +0 -1
  299. package/dist/chunk-DGUM43GV.js.map +0 -1
  300. package/dist/chunk-E66EQZE6.js.map +0 -1
  301. package/dist/chunk-FFQEQTNW.js.map +0 -1
  302. package/dist/chunk-FMUCXFII.js.map +0 -1
  303. package/dist/chunk-G37KK66H.js.map +0 -1
  304. package/dist/chunk-I6DAQMWX.js.map +0 -1
  305. package/dist/chunk-J36DSWQK.js.map +0 -1
  306. package/dist/chunk-KQCRWDSA.js +0 -1
  307. package/dist/chunk-KQCRWDSA.js.map +0 -1
  308. package/dist/chunk-L4OXEN46.js.map +0 -1
  309. package/dist/chunk-LMC26NLJ.js +0 -84
  310. package/dist/chunk-LMC26NLJ.js.map +0 -1
  311. package/dist/chunk-M43Y4SSO.js.map +0 -1
  312. package/dist/chunk-M7MPQISP.js.map +0 -1
  313. package/dist/chunk-NN6WWZ5U.js.map +0 -1
  314. package/dist/chunk-OEWDTMG7.js.map +0 -1
  315. package/dist/chunk-PWLANIRT.js.map +0 -1
  316. package/dist/chunk-QXHPKYJV.js.map +0 -1
  317. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  318. package/dist/chunk-YKRAFF5K.js.map +0 -1
  319. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  320. package/dist/components.js.map +0 -1
  321. package/dist/contextValidator-OOPCLPZW.js +0 -9
  322. package/dist/contextValidator-OOPCLPZW.js.map +0 -1
  323. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  324. package/dist/hooks.js.map +0 -1
  325. package/dist/index.js.map +0 -1
  326. package/dist/providers.js.map +0 -1
  327. package/dist/rbac/eslint-rules.js.map +0 -1
  328. package/dist/rbac/index.js.map +0 -1
  329. package/dist/styles/index.js.map +0 -1
  330. package/dist/theming/runtime.js.map +0 -1
  331. package/dist/types.js.map +0 -1
  332. package/dist/utils.js.map +0 -1
  333. package/docs/standards/01-architecture-standard.md +0 -44
  334. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  335. package/docs/standards/03-component-standard.md +0 -32
  336. package/docs/standards/05-security-standard.md +0 -44
  337. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  338. package/docs/standards/pace-core-compliance.md +0 -432
  339. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  340. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  341. package/scripts/audit/core/checks/bundle.cjs +0 -142
  342. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  343. package/scripts/audit/core/checks/config.cjs +0 -54
  344. package/scripts/audit/core/checks/coverage.cjs +0 -84
  345. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  346. package/scripts/audit/core/checks/documentation.cjs +0 -268
  347. package/scripts/audit/core/checks/environment.cjs +0 -116
  348. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  349. package/scripts/audit/core/checks/forms.cjs +0 -172
  350. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  351. package/scripts/audit/core/checks/hooks.cjs +0 -334
  352. package/scripts/audit/core/checks/imports.cjs +0 -244
  353. package/scripts/audit/core/checks/performance.cjs +0 -325
  354. package/scripts/audit/core/checks/routes.cjs +0 -117
  355. package/scripts/audit/core/checks/state.cjs +0 -130
  356. package/scripts/audit/core/checks/structure.cjs +0 -65
  357. package/scripts/audit/core/checks/style.cjs +0 -584
  358. package/scripts/audit/core/checks/testing.cjs +0 -122
  359. package/scripts/audit/core/checks/typescript.cjs +0 -61
  360. package/scripts/audit/core/scanner.cjs +0 -199
  361. package/scripts/audit/core/utils.cjs +0 -137
  362. package/scripts/audit/reporters/console.cjs +0 -151
  363. package/scripts/audit/reporters/json.cjs +0 -54
  364. package/scripts/audit/reporters/markdown.cjs +0 -124
  365. package/scripts/audit-consuming-app.cjs +0 -86
  366. package/src/eslint-rules/pace-core-compliance.cjs +0 -510
  367. package/src/eslint-rules/pace-core-compliance.js +0 -638
  368. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  369. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  370. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  371. package/src/rbac/components/NavigationProvider.tsx +0 -345
  372. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  373. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  374. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  375. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  376. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  377. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  378. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  379. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  380. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  381. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  382. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  383. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  384. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  385. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  386. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  387. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -1,223 +1,1927 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Main Audit Entry Point
5
- * @package @jmruthers/pace-core
6
- * @module Audit
4
+ * Comprehensive Audit Script for Consuming Apps
7
5
  *
8
- * CLI interface for running comprehensive audits
6
+ * Audits consuming apps against pace-core standards and generates a markdown report.
7
+ * Includes:
8
+ * - Dependency compliance checks
9
+ * - Code compliance checks (future)
10
+ * - Markdown report generation
11
+ *
12
+ * Usage:
13
+ * node scripts/audit/index.cjs [path-to-consuming-app] [--output report.md]
14
+ *
15
+ * If no path provided, assumes current directory is consuming app.
9
16
  */
10
17
 
18
+ const fs = require('fs');
11
19
  const path = require('path');
12
- const Scanner = require('./core/scanner.cjs');
13
- const { findProjectRoot, getPackageInfo, getPaceCoreVersion, colors } = require('./core/utils.cjs');
14
- const consoleReporter = require('./reporters/console.cjs');
15
- const markdownReporter = require('./reporters/markdown.cjs');
16
- const jsonReporter = require('./reporters/json.cjs');
17
20
 
18
- // Import all checks
19
- const complianceCheck = require('./core/checks/compliance.cjs');
20
- const structureCheck = require('./core/checks/structure.cjs');
21
- const configCheck = require('./core/checks/config.cjs');
22
- const typescriptCheck = require('./core/checks/typescript.cjs');
23
- const coverageCheck = require('./core/checks/coverage.cjs');
24
- const heuristicsCheck = require('./core/checks/heuristics.cjs');
25
- const importsCheck = require('./core/checks/imports.cjs');
26
- const hooksCheck = require('./core/checks/hooks.cjs');
27
- const performanceCheck = require('./core/checks/performance.cjs');
28
- const dependenciesCheck = require('./core/checks/dependencies.cjs');
29
- const errorHandlingCheck = require('./core/checks/error-handling.cjs');
30
- const bundleCheck = require('./core/checks/bundle.cjs');
31
- const accessibilityCheck = require('./core/checks/accessibility.cjs');
32
- const styleCheck = require('./core/checks/style.cjs');
33
- const stateCheck = require('./core/checks/state.cjs');
34
- const apiUsageCheck = require('./core/checks/api-usage.cjs');
35
- const documentationCheck = require('./core/checks/documentation.cjs');
36
- const testingCheck = require('./core/checks/testing.cjs');
37
- const environmentCheck = require('./core/checks/environment.cjs');
38
- const routesCheck = require('./core/checks/routes.cjs');
39
- const formsCheck = require('./core/checks/forms.cjs');
21
+ // Import audit functions
22
+ const { runDependencyAudit } = require('./audit-dependencies.cjs');
23
+ const { runComponentAudit } = require('./audit-components.cjs');
24
+ const { runComplianceAudit } = require('./audit-compliance.cjs');
25
+ const { runStandardsAudit } = require('./audit-standards.cjs');
26
+ const { runRBACAudit } = require('./audit-rbac.cjs');
40
27
 
41
- /**
42
- * Parse CLI arguments
43
- */
44
- function parseArgs() {
45
- const args = process.argv.slice(2);
46
- const options = {
47
- checks: [],
48
- exclude: [],
49
- reporters: ['console', 'markdown'],
50
- verbose: false,
51
- json: false
52
- };
28
+ // Colors for terminal output
29
+ const colors = {
30
+ reset: '\x1b[0m',
31
+ red: '\x1b[31m',
32
+ green: '\x1b[32m',
33
+ yellow: '\x1b[33m',
34
+ blue: '\x1b[34m',
35
+ cyan: '\x1b[36m',
36
+ bold: '\x1b[1m',
37
+ };
38
+
39
+ // Get pace-core package.json (for Vite alias checks)
40
+ function findPaceCorePackageJson(consumingAppPath) {
41
+ // Try relative to script location first (when in pace-core repo or installed package)
42
+ let paceCorePath = path.resolve(__dirname, '../../package.json');
43
+ if (fs.existsSync(paceCorePath)) {
44
+ return paceCorePath;
45
+ }
53
46
 
54
- for (let i = 0; i < args.length; i++) {
55
- const arg = args[i];
56
-
57
- if (arg === '--check' || arg === '-c') {
58
- options.checks.push(args[++i]);
59
- } else if (arg === '--exclude' || arg === '-e') {
60
- options.exclude.push(args[++i]);
61
- } else if (arg === '--reporter' || arg === '-r') {
62
- options.reporters = [args[++i]];
63
- } else if (arg === '--verbose' || arg === '-v') {
64
- options.verbose = true;
65
- } else if (arg === '--json') {
66
- options.json = true;
67
- options.reporters = ['json'];
68
- } else if (arg === '--help' || arg === '-h') {
69
- printHelp();
70
- process.exit(0);
71
- }
47
+ // Try alternative location
48
+ paceCorePath = path.resolve(__dirname, '../../../package.json');
49
+ if (fs.existsSync(paceCorePath)) {
50
+ return paceCorePath;
51
+ }
52
+
53
+ // Try finding from consuming app's node_modules
54
+ const nodeModulesPath = path.join(consumingAppPath, 'node_modules', '@jmruthers', 'pace-core', 'package.json');
55
+ if (fs.existsSync(nodeModulesPath)) {
56
+ return nodeModulesPath;
72
57
  }
73
58
 
74
- return options;
59
+ return null;
75
60
  }
76
61
 
77
- /**
78
- * Print help message
79
- */
80
- function printHelp() {
81
- console.log(`
82
- ${colors.bold}pace-core Audit Tool${colors.reset}
83
-
84
- Usage: node audit/index.cjs [options]
85
-
86
- Options:
87
- --check, -c <name> Run specific check (can be used multiple times)
88
- --exclude, -e <name> Exclude specific check (can be used multiple times)
89
- --reporter, -r <name> Use specific reporter (console, markdown, json)
90
- --verbose, -v Show detailed output
91
- --json Output JSON only (for CI/CD)
92
- --help, -h Show this help message
62
+ // Run dependency audit and collect results (with additional checks)
63
+ function runDependencyAuditWithExtras(consumingAppPath, showProgress = false) {
64
+ // Get the base dependency audit results
65
+ const baseResult = runDependencyAudit(consumingAppPath);
66
+
67
+ if (baseResult.error) {
68
+ return baseResult;
69
+ }
70
+
71
+ // Add Vite alias checks
72
+ const paceCorePath = findPaceCorePackageJson(consumingAppPath);
73
+ if (paceCorePath) {
74
+ const paceCorePkg = JSON.parse(fs.readFileSync(paceCorePath, 'utf8'));
75
+ const INCLUDED_DEPS = Object.keys(paceCorePkg.dependencies || {});
76
+ const viteAliasIssues = checkViteAliases(consumingAppPath, INCLUDED_DEPS);
77
+ baseResult.issues.viteAliases = viteAliasIssues;
78
+ } else {
79
+ baseResult.issues.viteAliases = [];
80
+ }
81
+
82
+ // Add component usage checks
83
+ if (showProgress) {
84
+ console.log(`${colors.blue}Running component usage audit...${colors.reset}`);
85
+ }
86
+ const componentAuditResult = runComponentAudit(consumingAppPath);
87
+ if (componentAuditResult.error) {
88
+ // If component audit fails, just log warning and continue
89
+ if (showProgress) {
90
+ console.warn(`${colors.yellow}Warning: Component audit failed: ${componentAuditResult.error}${colors.reset}`);
91
+ }
92
+ baseResult.issues.formIssues = [];
93
+ } else {
94
+ baseResult.issues.formIssues = componentAuditResult.issues.formIssues || [];
95
+ }
96
+
97
+ // Add compliance checks
98
+ if (showProgress) {
99
+ console.log(`${colors.blue}Running compliance audit...${colors.reset}`);
100
+ }
101
+ const complianceAuditResult = runComplianceAudit(consumingAppPath);
102
+ if (complianceAuditResult.error) {
103
+ // If compliance audit fails, just log warning and continue
104
+ if (showProgress) {
105
+ console.warn(`${colors.yellow}Warning: Compliance audit failed: ${complianceAuditResult.error}${colors.reset}`);
106
+ }
107
+ baseResult.issues.complianceIssues = {
108
+ nativeElements: [],
109
+ restrictedImports: [],
110
+ customHooks: [],
111
+ customUtils: [],
112
+ supabaseClient: [],
113
+ rbacSetup: [],
114
+ rbacPermission: [],
115
+ providers: [],
116
+ coreStyles: [],
117
+ inlineStyles: [],
118
+ };
119
+ } else {
120
+ baseResult.issues.complianceIssues = complianceAuditResult.issues || {
121
+ nativeElements: [],
122
+ restrictedImports: [],
123
+ customHooks: [],
124
+ customUtils: [],
125
+ supabaseClient: [],
126
+ rbacSetup: [],
127
+ rbacPermission: [],
128
+ providers: [],
129
+ coreStyles: [],
130
+ inlineStyles: [],
131
+ };
132
+ }
133
+
134
+ // Add standards checks
135
+ if (showProgress) {
136
+ console.log(`${colors.blue}Running standards audit...${colors.reset}`);
137
+ }
138
+ try {
139
+ const standardsAuditResult = runStandardsAudit(consumingAppPath, showProgress);
140
+ if (standardsAuditResult.error) {
141
+ // If standards audit fails, just log warning and continue
142
+ if (showProgress) {
143
+ console.warn(`\n${colors.yellow}Warning: Standards audit failed: ${standardsAuditResult.error}${colors.reset}`);
144
+ }
145
+ baseResult.issues.standardsIssues = {
146
+ cursorRuleset: [],
147
+ typescriptConfig: [],
148
+ anyTypes: [],
149
+ namingConvention: [],
150
+ rlsPolicy: [],
151
+ rpcNaming: [],
152
+ testingConfig: [],
153
+ inputValidation: [],
154
+ loggingSecurity: [],
155
+ errorMessages: [],
156
+ };
157
+ } else {
158
+ baseResult.issues.standardsIssues = standardsAuditResult.issues || {
159
+ cursorRuleset: [],
160
+ typescriptConfig: [],
161
+ anyTypes: [],
162
+ namingConvention: [],
163
+ rlsPolicy: [],
164
+ rpcNaming: [],
165
+ testingConfig: [],
166
+ inputValidation: [],
167
+ loggingSecurity: [],
168
+ errorMessages: [],
169
+ };
170
+ }
171
+ } catch (error) {
172
+ // Catch any unexpected errors
173
+ if (showProgress) {
174
+ console.warn(`\n${colors.yellow}Warning: Standards audit encountered an error: ${error.message}${colors.reset}`);
175
+ console.warn(`${colors.yellow}Stack: ${error.stack}${colors.reset}`);
176
+ }
177
+ baseResult.issues.standardsIssues = {
178
+ cursorRuleset: [],
179
+ typescriptConfig: [],
180
+ anyTypes: [],
181
+ namingConvention: [],
182
+ rlsPolicy: [],
183
+ rpcNaming: [],
184
+ testingConfig: [],
185
+ inputValidation: [],
186
+ loggingSecurity: [],
187
+ errorMessages: [],
188
+ };
189
+ }
190
+
191
+ // Add RBAC audit checks
192
+ if (showProgress) {
193
+ console.log(`${colors.blue}Running RBAC audit...${colors.reset}`);
194
+ }
195
+ try {
196
+ const rbacAuditResult = runRBACAudit(consumingAppPath);
197
+ if (rbacAuditResult.error) {
198
+ // If RBAC audit fails, just log warning and continue
199
+ if (showProgress) {
200
+ console.warn(`\n${colors.yellow}Warning: RBAC audit failed: ${rbacAuditResult.error}${colors.reset}`);
201
+ }
202
+ baseResult.issues.rbacIssues = {
203
+ rbacPageGuard: [],
204
+ rbacWrapperComponent: [],
205
+ rbacWrapperFunction: [],
206
+ rbacResourceNames: [],
207
+ rbacAccessDenied: [],
208
+ rbacDirectRPC: [],
209
+ rbacDirectTable: [],
210
+ rbacHardcodedRole: [],
211
+ rbacEnforcePermissions: [],
212
+ rbacEdgeFunction: [],
213
+ };
214
+ } else {
215
+ baseResult.issues.rbacIssues = rbacAuditResult.issues || {
216
+ rbacPageGuard: [],
217
+ rbacWrapperComponent: [],
218
+ rbacWrapperFunction: [],
219
+ rbacResourceNames: [],
220
+ rbacAccessDenied: [],
221
+ rbacDirectRPC: [],
222
+ rbacDirectTable: [],
223
+ rbacHardcodedRole: [],
224
+ rbacEnforcePermissions: [],
225
+ rbacEdgeFunction: [],
226
+ };
227
+ }
228
+ } catch (error) {
229
+ // Catch any unexpected errors
230
+ if (showProgress) {
231
+ console.warn(`\n${colors.yellow}Warning: RBAC audit encountered an error: ${error.message}${colors.reset}`);
232
+ console.warn(`${colors.yellow}Stack: ${error.stack}${colors.reset}`);
233
+ }
234
+ baseResult.issues.rbacIssues = {
235
+ rbacPageGuard: [],
236
+ rbacWrapperComponent: [],
237
+ rbacWrapperFunction: [],
238
+ rbacResourceNames: [],
239
+ rbacAccessDenied: [],
240
+ rbacDirectRPC: [],
241
+ rbacDirectTable: [],
242
+ rbacHardcodedRole: [],
243
+ rbacEnforcePermissions: [],
244
+ rbacEdgeFunction: [],
245
+ };
246
+ }
247
+
248
+ return baseResult;
249
+ }
93
250
 
94
- Available Checks:
95
- - compliance pace-core compliance checks
96
- - structure File structure checks
97
- - config Config file checks
98
- - typescript TypeScript config checks
99
- - coverage Test coverage checks
100
- - heuristics Code quality heuristics
101
- - imports Import pattern analysis
102
- - hooks React hooks compliance
103
- - performance Performance anti-patterns
104
- - dependencies Dependency analysis
105
- - error-handling Error handling patterns
106
- - bundle Bundle analysis
107
- - accessibility Accessibility checks
108
- - style Code style issues
109
- - state State management patterns
110
- - api-usage API usage patterns
111
- - documentation Documentation coverage
112
- - testing Testing patterns
113
- - environment Environment variable usage
114
- - routes Route protection
115
- - forms Form validation patterns
251
+ // Check Vite config for problematic aliases
252
+ function checkViteAliases(consumingAppPath, includedDeps) {
253
+ const issues = [];
254
+
255
+ // Find vite config file
256
+ const viteConfigPaths = [
257
+ path.join(consumingAppPath, 'vite.config.ts'),
258
+ path.join(consumingAppPath, 'vite.config.js'),
259
+ path.join(consumingAppPath, 'vite.config.mjs'),
260
+ path.join(consumingAppPath, 'vite.config.cjs'),
261
+ ];
262
+
263
+ let viteConfigPath = null;
264
+ for (const configPath of viteConfigPaths) {
265
+ if (fs.existsSync(configPath)) {
266
+ viteConfigPath = configPath;
267
+ break;
268
+ }
269
+ }
270
+
271
+ if (!viteConfigPath) {
272
+ return issues; // No vite config found, skip check
273
+ }
274
+
275
+ try {
276
+ const configContent = fs.readFileSync(viteConfigPath, 'utf8');
277
+
278
+ // Check for problematic aliases that bypass pace-core exports
279
+ // Pattern: "@jmruthers/pace-core/icons": "lucide-react" or similar
280
+ const paceCoreAliasPattern = /['"]@jmruthers\/pace-core\/(icons|utils|components|hooks|rbac)['"]\s*:\s*['"]([^'"]+)['"]/g;
281
+ let match;
282
+
283
+ while ((match = paceCoreAliasPattern.exec(configContent)) !== null) {
284
+ const paceCoreExport = match[1];
285
+ const aliasTarget = match[2];
286
+
287
+ // Check if alias targets an included dependency
288
+ if (includedDeps.includes(aliasTarget)) {
289
+ issues.push({
290
+ alias: `@jmruthers/pace-core/${paceCoreExport}`,
291
+ target: aliasTarget,
292
+ file: path.basename(viteConfigPath),
293
+ line: getLineNumber(configContent, match.index),
294
+ });
295
+ }
296
+ }
297
+
298
+ } catch (error) {
299
+ // If we can't parse the config, that's okay - just skip this check
300
+ // Could be TypeScript, complex config, etc.
301
+ }
302
+
303
+ return issues;
304
+ }
116
305
 
117
- Examples:
118
- node audit/index.cjs
119
- node audit/index.cjs --check compliance --check structure
120
- node audit/index.cjs --exclude heuristics --reporter markdown
121
- node audit/index.cjs --json
122
- `);
306
+ // Get line number from index in content
307
+ function getLineNumber(content, index) {
308
+ return content.substring(0, index).split('\n').length;
123
309
  }
124
310
 
125
- /**
126
- * Main function
127
- */
128
- async function main() {
129
- const options = parseArgs();
130
- const projectRoot = findProjectRoot();
131
- const packageJson = getPackageInfo(projectRoot);
132
- const paceCoreVersion = getPaceCoreVersion(packageJson);
133
-
134
- console.log(`${colors.cyan}${colors.bold}═══════════════════════════════════════════════════════════${colors.reset}`);
135
- console.log(`${colors.cyan}${colors.bold} pace-core Comprehensive Audit${colors.reset}`);
136
- console.log(`${colors.cyan}${colors.bold}═══════════════════════════════════════════════════════════${colors.reset}\n`);
137
- console.log(`${colors.cyan}Project:${colors.reset} ${packageJson?.name || 'Unknown'}`);
138
- console.log(`${colors.cyan}Root:${colors.reset} ${projectRoot}`);
139
- console.log(`${colors.cyan}pace-core Version:${colors.reset} ${paceCoreVersion}\n`);
140
-
141
- // Initialize scanner
142
- const scanner = new Scanner(projectRoot);
143
-
144
- // Register all checks
145
- scanner.registerChecks([
146
- complianceCheck,
147
- structureCheck,
148
- configCheck,
149
- typescriptCheck,
150
- coverageCheck,
151
- heuristicsCheck,
152
- importsCheck,
153
- hooksCheck,
154
- performanceCheck,
155
- dependenciesCheck,
156
- errorHandlingCheck,
157
- bundleCheck,
158
- accessibilityCheck,
159
- styleCheck,
160
- stateCheck,
161
- apiUsageCheck,
162
- documentationCheck,
163
- testingCheck,
164
- environmentCheck,
165
- routesCheck,
166
- formsCheck
167
- ]);
168
-
169
- // Initialize scanner context
170
- await scanner.initialize();
171
-
172
- // Run checks
173
- console.log(`${colors.cyan}Running checks...${colors.reset}\n`);
174
- const results = await scanner.run({
175
- checkNames: options.checks.length > 0 ? options.checks : null,
176
- excludeChecks: options.exclude,
177
- parallel: false
178
- });
179
-
180
- // Generate reports
181
- const reportOptions = {
182
- projectRoot,
183
- packageJson,
184
- paceCoreVersion,
185
- verbose: options.verbose
311
+ // Generate markdown report
312
+ function generateMarkdownReport(auditResults) {
313
+ const { projectName, paceCoreVersion, issues } = auditResults;
314
+ const timestamp = new Date().toISOString();
315
+
316
+ // Calculate issue counts
317
+ const dependencyIssuesCount = issues.includedDeps.length +
318
+ issues.missingRequired.length +
319
+ issues.versionIssues.length +
320
+ issues.wrongLocation.length +
321
+ issues.viteAliases.length;
322
+
323
+ const componentIssuesCount = issues.formIssues?.length || 0;
324
+
325
+ const complianceIssues = issues.complianceIssues || {
326
+ nativeElements: [],
327
+ restrictedImports: [],
328
+ customHooks: [],
329
+ customUtils: [],
330
+ supabaseClient: [],
331
+ rbacSetup: [],
332
+ providers: [],
333
+ coreStyles: [],
334
+ inlineStyles: [],
186
335
  };
187
336
 
188
- for (const reporterName of options.reporters) {
189
- try {
190
- if (reporterName === 'console') {
191
- consoleReporter.generateReport(results, reportOptions);
192
- } else if (reporterName === 'markdown') {
193
- const { filename } = markdownReporter.generateReport(results, reportOptions);
194
- console.log(`${colors.green}Markdown report saved to: audit/${filename}${colors.reset}`);
195
- } else if (reporterName === 'json') {
196
- const outputPath = options.json ? undefined : path.join(projectRoot, 'audit', `audit-${Date.now()}.json`);
197
- const jsonOutput = jsonReporter.generateReport(results, { ...reportOptions, outputPath });
198
- if (options.json) {
199
- console.log(jsonOutput);
200
- } else {
201
- console.log(`${colors.green}JSON report saved${colors.reset}`);
202
- }
337
+ const complianceIssuesCount = Object.values(complianceIssues).reduce((sum, arr) => sum + arr.length, 0);
338
+
339
+ const standardsIssues = issues.standardsIssues || {
340
+ cursorRuleset: [],
341
+ typescriptConfig: [],
342
+ anyTypes: [],
343
+ namingConvention: [],
344
+ rlsPolicy: [],
345
+ rpcNaming: [],
346
+ testingConfig: [],
347
+ inputValidation: [],
348
+ loggingSecurity: [],
349
+ errorMessages: [],
350
+ };
351
+
352
+ const standardsIssuesCount = Object.values(standardsIssues).reduce((sum, arr) => sum + arr.length, 0);
353
+
354
+ const rbacIssues = issues.rbacIssues || {
355
+ rbacPageGuard: [],
356
+ rbacWrapperComponent: [],
357
+ rbacWrapperFunction: [],
358
+ rbacResourceNames: [],
359
+ rbacAccessDenied: [],
360
+ rbacDirectRPC: [],
361
+ rbacDirectTable: [],
362
+ rbacHardcodedRole: [],
363
+ rbacEnforcePermissions: [],
364
+ rbacEdgeFunction: [],
365
+ };
366
+
367
+ const rbacIssuesCount = Object.values(rbacIssues).reduce((sum, arr) => sum + arr.length, 0);
368
+
369
+ const totalIssues = dependencyIssuesCount + componentIssuesCount + complianceIssuesCount + standardsIssuesCount + rbacIssuesCount;
370
+
371
+ let report = `# pace-core Audit Report\n\n`;
372
+ report += `**Project:** ${projectName}\n`;
373
+ report += `**pace-core Version:** ${paceCoreVersion}\n`;
374
+ report += `**Generated:** ${timestamp}\n\n`;
375
+ report += `---\n\n`;
376
+
377
+ // Summary Section (matches terminal output)
378
+ report += `## 📊 Summary\n\n`;
379
+
380
+ if (totalIssues === 0 && issues.missingOptional.length === 0) {
381
+ report += `✅ **All Checks Passed**\n\n`;
382
+ report += `- ✅ Dependency Audit: 0 issues\n`;
383
+ report += `- ✅ Component Usage Audit: 0 issues\n`;
384
+ report += `- ✅ Compliance Audit: 0 issues\n`;
385
+ report += `- ✅ Standards Audit: 0 issues\n`;
386
+ report += `- ✅ RBAC Audit: 0 issues\n`;
387
+ report += `- ✅ **Total Issues: 0**\n\n`;
388
+ } else {
389
+ report += `⚠️ **Issues Found**\n\n`;
390
+
391
+ // Dependency Audit Summary
392
+ if (dependencyIssuesCount === 0) {
393
+ report += `- ✅ Dependency Audit: 0 issues\n`;
394
+ } else {
395
+ report += `- ❌ Dependency Audit: ${dependencyIssuesCount} issue(s)\n`;
396
+ if (issues.includedDeps.length > 0) {
397
+ report += ` - Included Dependencies: ${issues.includedDeps.length}\n`;
398
+ }
399
+ if (issues.missingRequired.length > 0) {
400
+ report += ` - Missing Required: ${issues.missingRequired.length}\n`;
401
+ }
402
+ if (issues.versionIssues.length > 0) {
403
+ report += ` - Version Issues: ${issues.versionIssues.length}\n`;
404
+ }
405
+ if (issues.wrongLocation.length > 0) {
406
+ report += ` - Wrong Location: ${issues.wrongLocation.length}\n`;
407
+ }
408
+ if (issues.viteAliases.length > 0) {
409
+ report += ` - Vite Aliases: ${issues.viteAliases.length}\n`;
410
+ }
411
+ }
412
+
413
+ // Component Usage Audit Summary
414
+ if (componentIssuesCount === 0) {
415
+ report += `- ✅ Component Usage Audit: 0 issues\n`;
416
+ } else {
417
+ const plainFormTagsCount = issues.formIssues.filter(i => i.type === 'plainFormTag').length;
418
+ const reactHookFormImportsCount = issues.formIssues.filter(i => i.type === 'reactHookFormImport').length;
419
+ report += `- ❌ Component Usage Audit: ${componentIssuesCount} issue(s)\n`;
420
+ if (plainFormTagsCount > 0) {
421
+ report += ` - Plain <form> tags: ${plainFormTagsCount}\n`;
422
+ }
423
+ if (reactHookFormImportsCount > 0) {
424
+ report += ` - Direct react-hook-form imports: ${reactHookFormImportsCount}\n`;
425
+ }
426
+ }
427
+
428
+ // Compliance Audit Summary
429
+ if (complianceIssuesCount === 0) {
430
+ report += `- ✅ Compliance Audit: 0 issues\n`;
431
+ } else {
432
+ report += `- ❌ Compliance Audit: ${complianceIssuesCount} issue(s)\n`;
433
+ if (complianceIssues.nativeElements.length > 0) {
434
+ report += ` - Native HTML elements: ${complianceIssues.nativeElements.length}\n`;
435
+ }
436
+ if (complianceIssues.restrictedImports.length > 0) {
437
+ report += ` - Restricted imports: ${complianceIssues.restrictedImports.length}\n`;
438
+ }
439
+ if (complianceIssues.customHooks.length > 0) {
440
+ report += ` - Custom hooks: ${complianceIssues.customHooks.length}\n`;
441
+ }
442
+ if (complianceIssues.customUtils.length > 0) {
443
+ report += ` - Custom utilities: ${complianceIssues.customUtils.length}\n`;
444
+ }
445
+ if (complianceIssues.supabaseClient.length > 0) {
446
+ report += ` - Supabase client issues: ${complianceIssues.supabaseClient.length}\n`;
447
+ }
448
+ if (complianceIssues.rbacSetup.length > 0) {
449
+ report += ` - RBAC setup issues: ${complianceIssues.rbacSetup.length}\n`;
203
450
  }
204
- } catch (error) {
205
- console.error(`${colors.red}Error generating ${reporterName} report: ${error.message}${colors.reset}`);
451
+ if (complianceIssues.rbacPermission && complianceIssues.rbacPermission.length > 0) {
452
+ report += ` - RBAC permission usage issues: ${complianceIssues.rbacPermission.length}\n`;
453
+ }
454
+ if (complianceIssues.providers.length > 0) {
455
+ report += ` - Provider issues: ${complianceIssues.providers.length}\n`;
456
+ }
457
+ if (complianceIssues.coreStyles.length > 0) {
458
+ report += ` - Core styles issues: ${complianceIssues.coreStyles.length}\n`;
459
+ }
460
+ if (complianceIssues.inlineStyles.length > 0) {
461
+ report += ` - Inline styles: ${complianceIssues.inlineStyles.length}\n`;
462
+ }
463
+ }
464
+
465
+ // Standards Audit Summary
466
+ if (standardsIssuesCount === 0) {
467
+ report += `- ✅ Standards Audit: 0 issues\n`;
468
+ } else {
469
+ report += `- ❌ Standards Audit: ${standardsIssuesCount} issue(s)\n`;
470
+ if (standardsIssues.cursorRuleset.length > 0) {
471
+ report += ` - Cursor ruleset: ${standardsIssues.cursorRuleset.length}\n`;
472
+ }
473
+ if (standardsIssues.typescriptConfig.length > 0) {
474
+ report += ` - TypeScript config: ${standardsIssues.typescriptConfig.length}\n`;
475
+ }
476
+ if (standardsIssues.anyTypes.length > 0) {
477
+ report += ` - Any types: ${standardsIssues.anyTypes.length}\n`;
478
+ }
479
+ if (standardsIssues.namingConvention.length > 0) {
480
+ report += ` - Naming conventions: ${standardsIssues.namingConvention.length}\n`;
481
+ }
482
+ if (standardsIssues.rlsPolicy.length > 0) {
483
+ report += ` - RLS policies: ${standardsIssues.rlsPolicy.length}\n`;
484
+ }
485
+ if (standardsIssues.rpcNaming.length > 0) {
486
+ report += ` - RPC naming: ${standardsIssues.rpcNaming.length}\n`;
487
+ }
488
+ if (standardsIssues.testingConfig.length > 0) {
489
+ report += ` - Testing config: ${standardsIssues.testingConfig.length}\n`;
490
+ }
491
+ if (standardsIssues.inputValidation.length > 0) {
492
+ report += ` - Input validation: ${standardsIssues.inputValidation.length}\n`;
493
+ }
494
+ if (standardsIssues.loggingSecurity.length > 0) {
495
+ report += ` - Logging security: ${standardsIssues.loggingSecurity.length}\n`;
496
+ }
497
+ if (standardsIssues.errorMessages.length > 0) {
498
+ report += ` - Error messages: ${standardsIssues.errorMessages.length}\n`;
499
+ }
500
+ }
501
+
502
+ // RBAC Audit Summary
503
+ if (rbacIssuesCount === 0) {
504
+ report += `- ✅ RBAC Audit: 0 issues\n`;
505
+ } else {
506
+ report += `- ❌ RBAC Audit: ${rbacIssuesCount} issue(s)\n`;
507
+ if (rbacIssues.rbacPageGuard.length > 0) {
508
+ report += ` - PagePermissionGuard issues: ${rbacIssues.rbacPageGuard.length}\n`;
509
+ }
510
+ if (rbacIssues.rbacWrapperComponent.length > 0) {
511
+ report += ` - Wrapper components: ${rbacIssues.rbacWrapperComponent.length}\n`;
512
+ }
513
+ if (rbacIssues.rbacWrapperFunction.length > 0) {
514
+ report += ` - Wrapper functions: ${rbacIssues.rbacWrapperFunction.length}\n`;
515
+ }
516
+ if (rbacIssues.rbacResourceNames.length > 0) {
517
+ report += ` - RESOURCE_NAMES constants: ${rbacIssues.rbacResourceNames.length}\n`;
518
+ }
519
+ if (rbacIssues.rbacAccessDenied.length > 0) {
520
+ report += ` - AccessDenied component: ${rbacIssues.rbacAccessDenied.length}\n`;
521
+ }
522
+ if (rbacIssues.rbacDirectRPC.length > 0) {
523
+ report += ` - Direct RBAC RPC calls: ${rbacIssues.rbacDirectRPC.length}\n`;
524
+ }
525
+ if (rbacIssues.rbacDirectTable.length > 0) {
526
+ report += ` - Direct RBAC table queries: ${rbacIssues.rbacDirectTable.length}\n`;
527
+ }
528
+ if (rbacIssues.rbacHardcodedRole.length > 0) {
529
+ report += ` - Hardcoded role checks: ${rbacIssues.rbacHardcodedRole.length}\n`;
530
+ }
531
+ if (rbacIssues.rbacEnforcePermissions.length > 0) {
532
+ report += ` - enforcePermissions config: ${rbacIssues.rbacEnforcePermissions.length}\n`;
533
+ }
534
+ if (rbacIssues.rbacEdgeFunction.length > 0) {
535
+ report += ` - Edge Functions RBAC: ${rbacIssues.rbacEdgeFunction.length}\n`;
536
+ }
537
+ }
538
+
539
+ report += `\n- **Total Issues: ${totalIssues}**\n\n`;
540
+ }
541
+
542
+ report += `---\n\n`;
543
+
544
+ // Detailed Issues - Grouped by Audit Type
545
+
546
+ // Dependency Audit Issues
547
+ if (dependencyIssuesCount > 0) {
548
+ report += `## 📦 Dependency Audit Issues\n\n`;
549
+
550
+ // Included Dependencies
551
+ if (issues.includedDeps.length > 0) {
552
+ report += `### ❌ Included Dependencies (MUST REMOVE)\n\n`;
553
+ report += `These packages are already included in pace-core and should NOT be installed:\n\n`;
554
+ issues.includedDeps.forEach(issue => {
555
+ report += `- **${issue.package}**@${issue.installed} (in ${issue.location})\n`;
556
+ report += ` - **Problem:** Should NOT be installed (already included in pace-core)\n`;
557
+ report += ` - **Fix:** Run \`npm uninstall ${issue.package}\`\n\n`;
558
+ });
559
+ }
560
+
561
+ // Missing Required
562
+ if (issues.missingRequired.length > 0) {
563
+ report += `### ❌ Missing Required Dependencies\n\n`;
564
+ report += `These packages are required for pace-core to function:\n\n`;
565
+ issues.missingRequired.forEach(issue => {
566
+ report += `- **${issue.package}** (required: ${issue.required})\n`;
567
+ report += ` - **Problem:** Required dependency is missing\n`;
568
+ report += ` - **Fix:** Run \`npm install ${issue.package}@${issue.required}\`\n\n`;
569
+ });
570
+ }
571
+
572
+ // Version Issues
573
+ if (issues.versionIssues.length > 0) {
574
+ report += `### ⚠️ Version Issues\n\n`;
575
+ issues.versionIssues.forEach(issue => {
576
+ report += `- **${issue.package}**\n`;
577
+ report += ` - **Installed:** ${issue.installed}\n`;
578
+ report += ` - **Required:** ${issue.required}\n`;
579
+ report += ` - **Problem:** Version mismatch\n`;
580
+ report += ` - **Fix:** Run \`npm install ${issue.package}@${issue.required}\`\n\n`;
581
+ });
582
+ }
583
+
584
+ // Wrong Location
585
+ if (issues.wrongLocation.length > 0) {
586
+ report += `### ⚠️ Dependencies in Wrong Location\n\n`;
587
+ issues.wrongLocation.forEach(issue => {
588
+ report += `- **${issue.package}**\n`;
589
+ report += ` - **Currently in:** ${issue.current}\n`;
590
+ report += ` - **Should be in:** ${issue.shouldBe}\n`;
591
+ report += ` - **Problem:** Dependency is in the wrong section of package.json\n`;
592
+ report += ` - **Fix:**\n`;
593
+ report += ` \`\`\`bash\n`;
594
+ report += ` npm uninstall ${issue.package}\n`;
595
+ report += ` npm install -D ${issue.package}\n`;
596
+ report += ` \`\`\`\n\n`;
597
+ });
598
+ }
599
+
600
+ // Vite Aliases
601
+ if (issues.viteAliases.length > 0) {
602
+ report += `### ❌ Vite Aliases Bypassing pace-core Exports\n\n`;
603
+ report += `These Vite aliases bypass pace-core's exports and should be removed:\n\n`;
604
+ issues.viteAliases.forEach(issue => {
605
+ report += `- **${issue.alias}** → **${issue.target}**\n`;
606
+ report += ` - **File:** ${issue.file}${issue.line ? ` (line ${issue.line})` : ''}\n`;
607
+ report += ` - **Problem:** This alias bypasses pace-core's export for \`${issue.alias}\`\n`;
608
+ report += ` - **Fix:** Remove \`"${issue.alias}": "${issue.target}"\` from your Vite config and use \`${issue.alias}\` directly from pace-core\n\n`;
609
+ });
610
+ }
611
+ }
612
+
613
+ // Component Usage Audit Issues
614
+ if (componentIssuesCount > 0) {
615
+ report += `## 🧩 Component Usage Audit Issues\n\n`;
616
+
617
+ // Form Usage Issues
618
+ if (issues.formIssues && issues.formIssues.length > 0) {
619
+ report += `### ❌ Form Usage Issues (MUST FIX)\n\n`;
620
+ report += `These files use plain \`<form>\` tags or direct \`react-hook-form\` imports instead of pace-core Form component:\n\n`;
621
+
622
+ // Group by file for better readability
623
+ const issuesByFile = {};
624
+ issues.formIssues.forEach(issue => {
625
+ if (!issuesByFile[issue.file]) {
626
+ issuesByFile[issue.file] = [];
627
+ }
628
+ issuesByFile[issue.file].push(issue);
629
+ });
630
+
631
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
632
+ report += `#### ${file}\n\n`;
633
+ fileIssues.forEach(issue => {
634
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
635
+ if (issue.code) {
636
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
637
+ }
638
+ });
639
+ report += `\n`;
640
+ });
641
+
642
+ report += `**Fix:** Replace with pace-core Form component. See migration guide below.\n\n`;
206
643
  }
207
644
  }
208
645
 
209
- // Exit with appropriate code
210
- const exitCode = results.summary.errors > 0 ? 1 : 0;
211
- process.exit(exitCode);
646
+ // Compliance Audit Issues
647
+ if (complianceIssuesCount > 0) {
648
+ report += `## 🔒 Compliance Audit Issues\n\n`;
649
+
650
+ // Native Elements
651
+ if (complianceIssues.nativeElements.length > 0) {
652
+ report += `### ❌ Native HTML Elements (MUST FIX)\n\n`;
653
+ report += `These files use native HTML elements when pace-core components exist:\n\n`;
654
+
655
+ const issuesByFile = {};
656
+ complianceIssues.nativeElements.forEach(issue => {
657
+ if (!issuesByFile[issue.file]) {
658
+ issuesByFile[issue.file] = [];
659
+ }
660
+ issuesByFile[issue.file].push(issue);
661
+ });
662
+
663
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
664
+ report += `#### ${file}\n\n`;
665
+ fileIssues.forEach(issue => {
666
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
667
+ if (issue.code) {
668
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
669
+ }
670
+ report += ` - **Fix:** ${issue.fix}\n\n`;
671
+ });
672
+ });
673
+ }
674
+
675
+ // Restricted Imports
676
+ if (complianceIssues.restrictedImports.length > 0) {
677
+ report += `### ❌ Restricted Library Imports (MUST FIX)\n\n`;
678
+ report += `These files import directly from restricted libraries. Use pace-core wrappers instead:\n\n`;
679
+
680
+ const issuesByFile = {};
681
+ complianceIssues.restrictedImports.forEach(issue => {
682
+ if (!issuesByFile[issue.file]) {
683
+ issuesByFile[issue.file] = [];
684
+ }
685
+ issuesByFile[issue.file].push(issue);
686
+ });
687
+
688
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
689
+ report += `#### ${file}\n\n`;
690
+ fileIssues.forEach(issue => {
691
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
692
+ if (issue.code) {
693
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
694
+ }
695
+ report += ` - **Fix:** ${issue.fix}\n\n`;
696
+ });
697
+ });
698
+ }
699
+
700
+ // Custom Hooks
701
+ if (complianceIssues.customHooks.length > 0) {
702
+ report += `### ⚠️ Custom Hooks (Consider Using pace-core)\n\n`;
703
+ report += `These files define custom hooks that pace-core provides:\n\n`;
704
+
705
+ const issuesByFile = {};
706
+ complianceIssues.customHooks.forEach(issue => {
707
+ if (!issuesByFile[issue.file]) {
708
+ issuesByFile[issue.file] = [];
709
+ }
710
+ issuesByFile[issue.file].push(issue);
711
+ });
712
+
713
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
714
+ report += `#### ${file}\n\n`;
715
+ fileIssues.forEach(issue => {
716
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
717
+ if (issue.code) {
718
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
719
+ }
720
+ report += ` - **Fix:** ${issue.fix}\n\n`;
721
+ });
722
+ });
723
+ }
724
+
725
+ // Custom Utils
726
+ if (complianceIssues.customUtils.length > 0) {
727
+ report += `### ⚠️ Custom Utilities (Consider Using pace-core)\n\n`;
728
+ report += `These files define custom utilities that pace-core provides:\n\n`;
729
+
730
+ const issuesByFile = {};
731
+ complianceIssues.customUtils.forEach(issue => {
732
+ if (!issuesByFile[issue.file]) {
733
+ issuesByFile[issue.file] = [];
734
+ }
735
+ issuesByFile[issue.file].push(issue);
736
+ });
737
+
738
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
739
+ report += `#### ${file}\n\n`;
740
+ fileIssues.forEach(issue => {
741
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
742
+ if (issue.code) {
743
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
744
+ }
745
+ report += ` - **Fix:** ${issue.fix}\n\n`;
746
+ });
747
+ });
748
+ }
749
+
750
+ // Supabase Client
751
+ if (complianceIssues.supabaseClient.length > 0) {
752
+ report += `### ❌ Supabase Client Usage Issues (MUST FIX)\n\n`;
753
+ report += `These files have Supabase client usage issues:\n\n`;
754
+
755
+ const issuesByFile = {};
756
+ complianceIssues.supabaseClient.forEach(issue => {
757
+ if (!issuesByFile[issue.file]) {
758
+ issuesByFile[issue.file] = [];
759
+ }
760
+ issuesByFile[issue.file].push(issue);
761
+ });
762
+
763
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
764
+ report += `#### ${file}\n\n`;
765
+ fileIssues.forEach(issue => {
766
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
767
+ if (issue.code) {
768
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
769
+ }
770
+ report += ` - **Fix:** ${issue.fix}\n\n`;
771
+ });
772
+ });
773
+ }
774
+
775
+ // RBAC Permission Usage
776
+ if (complianceIssues.rbacPermission && complianceIssues.rbacPermission.length > 0) {
777
+ report += `### ❌ RBAC Permission Usage Issues (MUST FIX)\n\n`;
778
+ report += `These issues can cause false negative permission checks when scope resolution is still in progress.\n\n`;
779
+ complianceIssues.rbacPermission.forEach(issue => {
780
+ report += `**${issue.file}:${issue.line}** - ${issue.severity.toUpperCase()}\n`;
781
+ report += `${issue.message}\n\n`;
782
+ if (issue.fix) {
783
+ report += `**Fix:** ${issue.fix}\n\n`;
784
+ }
785
+ if (issue.code) {
786
+ report += `\`\`\`typescript\n${issue.code}\n\`\`\`\n\n`;
787
+ }
788
+ report += `---\n\n`;
789
+ });
790
+ }
791
+
792
+ // RBAC Setup
793
+ if (complianceIssues.rbacSetup.length > 0) {
794
+ report += `### ❌ RBAC Setup Issues (MUST FIX)\n\n`;
795
+ complianceIssues.rbacSetup.forEach(issue => {
796
+ report += `- **${issue.file}** (Line ${issue.line}): ${issue.message}\n`;
797
+ report += ` - **Fix:** ${issue.fix}\n\n`;
798
+ });
799
+ }
800
+
801
+ // Providers
802
+ if (complianceIssues.providers.length > 0) {
803
+ report += `### ❌ Provider Usage Issues (MUST FIX)\n\n`;
804
+ complianceIssues.providers.forEach(issue => {
805
+ report += `- **${issue.file}** (Line ${issue.line}): ${issue.message}\n`;
806
+ report += ` - **Fix:** ${issue.fix}\n\n`;
807
+ });
808
+ }
809
+
810
+ // Core Styles
811
+ if (complianceIssues.coreStyles.length > 0) {
812
+ report += `### ❌ Core Styles Import Issues (MUST FIX)\n\n`;
813
+ complianceIssues.coreStyles.forEach(issue => {
814
+ report += `- **${issue.file}** (Line ${issue.line}): ${issue.message}\n`;
815
+ report += ` - **Fix:** ${issue.fix}\n\n`;
816
+ });
817
+ }
818
+
819
+ // Inline Styles
820
+ if (complianceIssues.inlineStyles.length > 0) {
821
+ report += `### ❌ Inline Styles (MUST FIX)\n\n`;
822
+ report += `These files use inline styles. Use pace-core components or Tailwind classes instead:\n\n`;
823
+
824
+ const issuesByFile = {};
825
+ complianceIssues.inlineStyles.forEach(issue => {
826
+ if (!issuesByFile[issue.file]) {
827
+ issuesByFile[issue.file] = [];
828
+ }
829
+ issuesByFile[issue.file].push(issue);
830
+ });
831
+
832
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
833
+ report += `#### ${file}\n\n`;
834
+ fileIssues.forEach(issue => {
835
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
836
+ if (issue.code) {
837
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
838
+ }
839
+ report += ` - **Fix:** ${issue.fix}\n\n`;
840
+ });
841
+ });
842
+ }
843
+ }
844
+
845
+ // Standards Audit Issues
846
+ if (standardsIssuesCount > 0) {
847
+ report += `## 📋 Standards Audit Issues\n\n`;
848
+
849
+ // Cursor Ruleset
850
+ if (standardsIssues.cursorRuleset.length > 0) {
851
+ report += `### ❌ Cursor Ruleset Issues (MUST FIX)\n\n`;
852
+ report += `Required rule files are missing from .cursor/rules/ directory:\n\n`;
853
+ standardsIssues.cursorRuleset.forEach(issue => {
854
+ report += `- **${issue.file}** (Line ${issue.line}): ${issue.message}\n`;
855
+ report += ` - **Fix:** ${issue.fix}\n\n`;
856
+ });
857
+ }
858
+
859
+ // TypeScript Config
860
+ if (standardsIssues.typescriptConfig.length > 0) {
861
+ report += `### ❌ TypeScript Configuration Issues (MUST FIX)\n\n`;
862
+ standardsIssues.typescriptConfig.forEach(issue => {
863
+ report += `- **${issue.file}** (Line ${issue.line}): ${issue.message}\n`;
864
+ if (issue.code) {
865
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
866
+ }
867
+ report += ` - **Fix:** ${issue.fix}\n\n`;
868
+ });
869
+ }
870
+
871
+ // Any Types
872
+ if (standardsIssues.anyTypes.length > 0) {
873
+ report += `### ⚠️ Any Types Detected\n\n`;
874
+ report += `These files use 'any' types. Consider using 'unknown' or proper types:\n\n`;
875
+
876
+ const issuesByFile = {};
877
+ standardsIssues.anyTypes.forEach(issue => {
878
+ if (!issuesByFile[issue.file]) {
879
+ issuesByFile[issue.file] = [];
880
+ }
881
+ issuesByFile[issue.file].push(issue);
882
+ });
883
+
884
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
885
+ report += `#### ${file}\n\n`;
886
+ fileIssues.forEach(issue => {
887
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
888
+ if (issue.code) {
889
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
890
+ }
891
+ report += ` - **Fix:** ${issue.fix}\n\n`;
892
+ });
893
+ });
894
+ }
895
+
896
+ // Naming Conventions
897
+ if (standardsIssues.namingConvention.length > 0) {
898
+ report += `### ⚠️ Naming Convention Issues\n\n`;
899
+ report += `These files have naming convention violations:\n\n`;
900
+
901
+ const issuesByFile = {};
902
+ standardsIssues.namingConvention.forEach(issue => {
903
+ if (!issuesByFile[issue.file]) {
904
+ issuesByFile[issue.file] = [];
905
+ }
906
+ issuesByFile[issue.file].push(issue);
907
+ });
908
+
909
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
910
+ report += `#### ${file}\n\n`;
911
+ fileIssues.forEach(issue => {
912
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
913
+ if (issue.code) {
914
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
915
+ }
916
+ report += ` - **Fix:** ${issue.fix}\n\n`;
917
+ });
918
+ });
919
+ }
920
+
921
+ // RLS Policies
922
+ if (standardsIssues.rlsPolicy.length > 0) {
923
+ report += `### ❌ RLS Policy Issues (MUST FIX)\n\n`;
924
+ report += `These SQL migrations have RLS policy violations:\n\n`;
925
+
926
+ const issuesByFile = {};
927
+ standardsIssues.rlsPolicy.forEach(issue => {
928
+ if (!issuesByFile[issue.file]) {
929
+ issuesByFile[issue.file] = [];
930
+ }
931
+ issuesByFile[issue.file].push(issue);
932
+ });
933
+
934
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
935
+ report += `#### ${file}\n\n`;
936
+ fileIssues.forEach(issue => {
937
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
938
+ if (issue.code) {
939
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
940
+ }
941
+ report += ` - **Fix:** ${issue.fix}\n\n`;
942
+ });
943
+ });
944
+ }
945
+
946
+ // RPC Naming
947
+ if (standardsIssues.rpcNaming.length > 0) {
948
+ report += `### ❌ RPC Naming Issues (MUST FIX)\n\n`;
949
+ report += `These SQL migrations have RPC function naming violations:\n\n`;
950
+
951
+ const issuesByFile = {};
952
+ standardsIssues.rpcNaming.forEach(issue => {
953
+ if (!issuesByFile[issue.file]) {
954
+ issuesByFile[issue.file] = [];
955
+ }
956
+ issuesByFile[issue.file].push(issue);
957
+ });
958
+
959
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
960
+ report += `#### ${file}\n\n`;
961
+ fileIssues.forEach(issue => {
962
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
963
+ if (issue.code) {
964
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
965
+ }
966
+ report += ` - **Fix:** ${issue.fix}\n\n`;
967
+ });
968
+ });
969
+ }
970
+
971
+ // Testing Config
972
+ if (standardsIssues.testingConfig.length > 0) {
973
+ report += `### ❌ Testing Configuration Issues (MUST FIX)\n\n`;
974
+ standardsIssues.testingConfig.forEach(issue => {
975
+ report += `- **${issue.file}** (Line ${issue.line}): ${issue.message}\n`;
976
+ if (issue.code) {
977
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
978
+ }
979
+ report += ` - **Fix:** ${issue.fix}\n\n`;
980
+ });
981
+ }
982
+
983
+ // Input Validation (heuristic)
984
+ if (standardsIssues.inputValidation.length > 0) {
985
+ report += `### ⚠️ Input Validation Issues (Heuristic)\n\n`;
986
+ report += `These files may be missing Zod validation. This is a heuristic check and may have false positives:\n\n`;
987
+
988
+ const issuesByFile = {};
989
+ standardsIssues.inputValidation.forEach(issue => {
990
+ if (!issuesByFile[issue.file]) {
991
+ issuesByFile[issue.file] = [];
992
+ }
993
+ issuesByFile[issue.file].push(issue);
994
+ });
995
+
996
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
997
+ report += `#### ${file}\n\n`;
998
+ fileIssues.forEach(issue => {
999
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1000
+ if (issue.code) {
1001
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1002
+ }
1003
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1004
+ });
1005
+ });
1006
+ }
1007
+
1008
+ // Logging Security (heuristic)
1009
+ if (standardsIssues.loggingSecurity.length > 0) {
1010
+ report += `### ⚠️ Logging Security Issues (Heuristic)\n\n`;
1011
+ report += `These files may be logging sensitive data. This is a heuristic check and may have false positives:\n\n`;
1012
+
1013
+ const issuesByFile = {};
1014
+ standardsIssues.loggingSecurity.forEach(issue => {
1015
+ if (!issuesByFile[issue.file]) {
1016
+ issuesByFile[issue.file] = [];
1017
+ }
1018
+ issuesByFile[issue.file].push(issue);
1019
+ });
1020
+
1021
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1022
+ report += `#### ${file}\n\n`;
1023
+ fileIssues.forEach(issue => {
1024
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1025
+ if (issue.code) {
1026
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1027
+ }
1028
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1029
+ });
1030
+ });
1031
+ }
1032
+
1033
+ // Error Messages (heuristic)
1034
+ if (standardsIssues.errorMessages.length > 0) {
1035
+ report += `### ⚠️ Error Message Safety Issues (Heuristic)\n\n`;
1036
+ report += `These files may expose internal details in error messages. This is a heuristic check and may have false positives:\n\n`;
1037
+
1038
+ const issuesByFile = {};
1039
+ standardsIssues.errorMessages.forEach(issue => {
1040
+ if (!issuesByFile[issue.file]) {
1041
+ issuesByFile[issue.file] = [];
1042
+ }
1043
+ issuesByFile[issue.file].push(issue);
1044
+ });
1045
+
1046
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1047
+ report += `#### ${file}\n\n`;
1048
+ fileIssues.forEach(issue => {
1049
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1050
+ if (issue.code) {
1051
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1052
+ }
1053
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1054
+ });
1055
+ });
1056
+ }
1057
+ }
1058
+
1059
+ // RBAC Audit Issues
1060
+ if (rbacIssuesCount > 0) {
1061
+ report += `## 🔐 RBAC Audit Issues\n\n`;
1062
+ report += `These issues relate to RBAC (Role-Based Access Control) compliance. See [09-rbac-compliance.mdc](../../packages/core/cursor-rules/09-rbac-compliance.mdc) for complete documentation.\n\n`;
1063
+
1064
+ // PagePermissionGuard Issues
1065
+ if (rbacIssues.rbacPageGuard.length > 0) {
1066
+ report += `### ❌ PagePermissionGuard Issues (MUST FIX)\n\n`;
1067
+ report += `These pages are missing PagePermissionGuard wrapper or using it incorrectly:\n\n`;
1068
+
1069
+ const issuesByFile = {};
1070
+ rbacIssues.rbacPageGuard.forEach(issue => {
1071
+ if (!issuesByFile[issue.file]) {
1072
+ issuesByFile[issue.file] = [];
1073
+ }
1074
+ issuesByFile[issue.file].push(issue);
1075
+ });
1076
+
1077
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1078
+ report += `#### ${file}\n\n`;
1079
+ fileIssues.forEach(issue => {
1080
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1081
+ if (issue.code) {
1082
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1083
+ }
1084
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1085
+ });
1086
+ });
1087
+ }
1088
+
1089
+ // Wrapper Components
1090
+ if (rbacIssues.rbacWrapperComponent.length > 0) {
1091
+ report += `### ❌ Wrapper Components Around PagePermissionGuard (MUST FIX)\n\n`;
1092
+ report += `These components wrap PagePermissionGuard, which is forbidden:\n\n`;
1093
+
1094
+ const issuesByFile = {};
1095
+ rbacIssues.rbacWrapperComponent.forEach(issue => {
1096
+ if (!issuesByFile[issue.file]) {
1097
+ issuesByFile[issue.file] = [];
1098
+ }
1099
+ issuesByFile[issue.file].push(issue);
1100
+ });
1101
+
1102
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1103
+ report += `#### ${file}\n\n`;
1104
+ fileIssues.forEach(issue => {
1105
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1106
+ if (issue.code) {
1107
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1108
+ }
1109
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1110
+ });
1111
+ });
1112
+ }
1113
+
1114
+ // Wrapper Functions
1115
+ if (rbacIssues.rbacWrapperFunction.length > 0) {
1116
+ report += `### ❌ Wrapper Functions Around Permission Hooks (MUST FIX)\n\n`;
1117
+ report += `These functions wrap permission hooks, which is forbidden:\n\n`;
1118
+
1119
+ const issuesByFile = {};
1120
+ rbacIssues.rbacWrapperFunction.forEach(issue => {
1121
+ if (!issuesByFile[issue.file]) {
1122
+ issuesByFile[issue.file] = [];
1123
+ }
1124
+ issuesByFile[issue.file].push(issue);
1125
+ });
1126
+
1127
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1128
+ report += `#### ${file}\n\n`;
1129
+ fileIssues.forEach(issue => {
1130
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1131
+ if (issue.code) {
1132
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1133
+ }
1134
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1135
+ });
1136
+ });
1137
+ }
1138
+
1139
+ // RESOURCE_NAMES Constants
1140
+ if (rbacIssues.rbacResourceNames.length > 0) {
1141
+ report += `### ❌ RESOURCE_NAMES Constants Usage (MUST FIX)\n\n`;
1142
+ report += `These files use string literals in useResourcePermissions instead of RESOURCE_NAMES constants:\n\n`;
1143
+
1144
+ const issuesByFile = {};
1145
+ rbacIssues.rbacResourceNames.forEach(issue => {
1146
+ if (!issuesByFile[issue.file]) {
1147
+ issuesByFile[issue.file] = [];
1148
+ }
1149
+ issuesByFile[issue.file].push(issue);
1150
+ });
1151
+
1152
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1153
+ report += `#### ${file}\n\n`;
1154
+ fileIssues.forEach(issue => {
1155
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1156
+ if (issue.code) {
1157
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1158
+ }
1159
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1160
+ });
1161
+ });
1162
+ }
1163
+
1164
+ // AccessDenied Component
1165
+ if (rbacIssues.rbacAccessDenied.length > 0) {
1166
+ report += `### ⚠️ AccessDenied Component Usage\n\n`;
1167
+ report += `These files use custom access denied components instead of pace-core AccessDenied:\n\n`;
1168
+
1169
+ const issuesByFile = {};
1170
+ rbacIssues.rbacAccessDenied.forEach(issue => {
1171
+ if (!issuesByFile[issue.file]) {
1172
+ issuesByFile[issue.file] = [];
1173
+ }
1174
+ issuesByFile[issue.file].push(issue);
1175
+ });
1176
+
1177
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1178
+ report += `#### ${file}\n\n`;
1179
+ fileIssues.forEach(issue => {
1180
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1181
+ if (issue.code) {
1182
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1183
+ }
1184
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1185
+ });
1186
+ });
1187
+ }
1188
+
1189
+ // Direct RBAC RPC Calls
1190
+ if (rbacIssues.rbacDirectRPC.length > 0) {
1191
+ report += `### ❌ Direct RBAC RPC Calls (SECURITY CRITICAL)\n\n`;
1192
+ report += `These files call RBAC RPC functions directly, bypassing security validation:\n\n`;
1193
+
1194
+ const issuesByFile = {};
1195
+ rbacIssues.rbacDirectRPC.forEach(issue => {
1196
+ if (!issuesByFile[issue.file]) {
1197
+ issuesByFile[issue.file] = [];
1198
+ }
1199
+ issuesByFile[issue.file].push(issue);
1200
+ });
1201
+
1202
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1203
+ report += `#### ${file}\n\n`;
1204
+ fileIssues.forEach(issue => {
1205
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1206
+ if (issue.code) {
1207
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1208
+ }
1209
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1210
+ });
1211
+ });
1212
+ }
1213
+
1214
+ // Direct RBAC Table Queries
1215
+ if (rbacIssues.rbacDirectTable.length > 0) {
1216
+ report += `### ❌ Direct RBAC Table Queries (SECURITY CRITICAL)\n\n`;
1217
+ report += `These files query RBAC tables directly without useSecureSupabase:\n\n`;
1218
+
1219
+ const issuesByFile = {};
1220
+ rbacIssues.rbacDirectTable.forEach(issue => {
1221
+ if (!issuesByFile[issue.file]) {
1222
+ issuesByFile[issue.file] = [];
1223
+ }
1224
+ issuesByFile[issue.file].push(issue);
1225
+ });
1226
+
1227
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1228
+ report += `#### ${file}\n\n`;
1229
+ fileIssues.forEach(issue => {
1230
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1231
+ if (issue.code) {
1232
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1233
+ }
1234
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1235
+ });
1236
+ });
1237
+ }
1238
+
1239
+ // Hardcoded Role Checks
1240
+ if (rbacIssues.rbacHardcodedRole.length > 0) {
1241
+ report += `### ❌ Hardcoded Role Checks (SECURITY CRITICAL)\n\n`;
1242
+ report += `These files use hardcoded role comparisons instead of pace-core APIs:\n\n`;
1243
+
1244
+ const issuesByFile = {};
1245
+ rbacIssues.rbacHardcodedRole.forEach(issue => {
1246
+ if (!issuesByFile[issue.file]) {
1247
+ issuesByFile[issue.file] = [];
1248
+ }
1249
+ issuesByFile[issue.file].push(issue);
1250
+ });
1251
+
1252
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1253
+ report += `#### ${file}\n\n`;
1254
+ fileIssues.forEach(issue => {
1255
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1256
+ if (issue.code) {
1257
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1258
+ }
1259
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1260
+ });
1261
+ });
1262
+ }
1263
+
1264
+ // enforcePermissions Configuration
1265
+ if (rbacIssues.rbacEnforcePermissions.length > 0) {
1266
+ report += `### ❌ enforcePermissions Configuration Issues\n\n`;
1267
+ report += `These files have incorrect enforcePermissions configuration:\n\n`;
1268
+
1269
+ rbacIssues.rbacEnforcePermissions.forEach(issue => {
1270
+ report += `- **${issue.file}** (Line ${issue.line}): ${issue.message}\n`;
1271
+ if (issue.code) {
1272
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1273
+ }
1274
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1275
+ });
1276
+ }
1277
+
1278
+ // Edge Functions RBAC
1279
+ if (rbacIssues.rbacEdgeFunction.length > 0) {
1280
+ report += `### ❌ Edge Functions RBAC Issues (SECURITY CRITICAL)\n\n`;
1281
+ report += `These Edge Functions have RBAC compliance issues:\n\n`;
1282
+
1283
+ rbacIssues.rbacEdgeFunction.forEach(issue => {
1284
+ report += `- **${issue.file}** (Line ${issue.line}): ${issue.message}\n`;
1285
+ if (issue.code) {
1286
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1287
+ }
1288
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1289
+ });
1290
+ }
1291
+ }
1292
+
1293
+ // RBAC Audit Issues
1294
+ if (rbacIssuesCount > 0) {
1295
+ report += `## 🔐 RBAC Audit Issues\n\n`;
1296
+ report += `These issues relate to RBAC (Role-Based Access Control) compliance. See [09-rbac-compliance.mdc](../../packages/core/cursor-rules/09-rbac-compliance.mdc) for complete documentation.\n\n`;
1297
+
1298
+ // PagePermissionGuard Issues
1299
+ if (rbacIssues.rbacPageGuard.length > 0) {
1300
+ report += `### ❌ PagePermissionGuard Issues (MUST FIX)\n\n`;
1301
+ report += `These pages are missing PagePermissionGuard wrapper or using it incorrectly:\n\n`;
1302
+
1303
+ const issuesByFile = {};
1304
+ rbacIssues.rbacPageGuard.forEach(issue => {
1305
+ if (!issuesByFile[issue.file]) {
1306
+ issuesByFile[issue.file] = [];
1307
+ }
1308
+ issuesByFile[issue.file].push(issue);
1309
+ });
1310
+
1311
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1312
+ report += `#### ${file}\n\n`;
1313
+ fileIssues.forEach(issue => {
1314
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1315
+ if (issue.code) {
1316
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1317
+ }
1318
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1319
+ });
1320
+ });
1321
+ }
1322
+
1323
+ // Wrapper Components
1324
+ if (rbacIssues.rbacWrapperComponent.length > 0) {
1325
+ report += `### ❌ Wrapper Components Around PagePermissionGuard (MUST FIX)\n\n`;
1326
+ report += `These components wrap PagePermissionGuard, which is forbidden:\n\n`;
1327
+
1328
+ const issuesByFile = {};
1329
+ rbacIssues.rbacWrapperComponent.forEach(issue => {
1330
+ if (!issuesByFile[issue.file]) {
1331
+ issuesByFile[issue.file] = [];
1332
+ }
1333
+ issuesByFile[issue.file].push(issue);
1334
+ });
1335
+
1336
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1337
+ report += `#### ${file}\n\n`;
1338
+ fileIssues.forEach(issue => {
1339
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1340
+ if (issue.code) {
1341
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1342
+ }
1343
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1344
+ });
1345
+ });
1346
+ }
1347
+
1348
+ // Wrapper Functions
1349
+ if (rbacIssues.rbacWrapperFunction.length > 0) {
1350
+ report += `### ❌ Wrapper Functions Around Permission Hooks (MUST FIX)\n\n`;
1351
+ report += `These functions wrap permission hooks, which is forbidden:\n\n`;
1352
+
1353
+ const issuesByFile = {};
1354
+ rbacIssues.rbacWrapperFunction.forEach(issue => {
1355
+ if (!issuesByFile[issue.file]) {
1356
+ issuesByFile[issue.file] = [];
1357
+ }
1358
+ issuesByFile[issue.file].push(issue);
1359
+ });
1360
+
1361
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1362
+ report += `#### ${file}\n\n`;
1363
+ fileIssues.forEach(issue => {
1364
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1365
+ if (issue.code) {
1366
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1367
+ }
1368
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1369
+ });
1370
+ });
1371
+ }
1372
+
1373
+ // RESOURCE_NAMES Constants
1374
+ if (rbacIssues.rbacResourceNames.length > 0) {
1375
+ report += `### ❌ RESOURCE_NAMES Constants Usage (MUST FIX)\n\n`;
1376
+ report += `These files use string literals in useResourcePermissions instead of RESOURCE_NAMES constants:\n\n`;
1377
+
1378
+ const issuesByFile = {};
1379
+ rbacIssues.rbacResourceNames.forEach(issue => {
1380
+ if (!issuesByFile[issue.file]) {
1381
+ issuesByFile[issue.file] = [];
1382
+ }
1383
+ issuesByFile[issue.file].push(issue);
1384
+ });
1385
+
1386
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1387
+ report += `#### ${file}\n\n`;
1388
+ fileIssues.forEach(issue => {
1389
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1390
+ if (issue.code) {
1391
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1392
+ }
1393
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1394
+ });
1395
+ });
1396
+ }
1397
+
1398
+ // AccessDenied Component
1399
+ if (rbacIssues.rbacAccessDenied.length > 0) {
1400
+ report += `### ⚠️ AccessDenied Component Usage\n\n`;
1401
+ report += `These files use custom access denied components instead of pace-core AccessDenied:\n\n`;
1402
+
1403
+ const issuesByFile = {};
1404
+ rbacIssues.rbacAccessDenied.forEach(issue => {
1405
+ if (!issuesByFile[issue.file]) {
1406
+ issuesByFile[issue.file] = [];
1407
+ }
1408
+ issuesByFile[issue.file].push(issue);
1409
+ });
1410
+
1411
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1412
+ report += `#### ${file}\n\n`;
1413
+ fileIssues.forEach(issue => {
1414
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1415
+ if (issue.code) {
1416
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1417
+ }
1418
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1419
+ });
1420
+ });
1421
+ }
1422
+
1423
+ // Direct RBAC RPC Calls
1424
+ if (rbacIssues.rbacDirectRPC.length > 0) {
1425
+ report += `### ❌ Direct RBAC RPC Calls (SECURITY CRITICAL)\n\n`;
1426
+ report += `These files call RBAC RPC functions directly, bypassing security validation:\n\n`;
1427
+
1428
+ const issuesByFile = {};
1429
+ rbacIssues.rbacDirectRPC.forEach(issue => {
1430
+ if (!issuesByFile[issue.file]) {
1431
+ issuesByFile[issue.file] = [];
1432
+ }
1433
+ issuesByFile[issue.file].push(issue);
1434
+ });
1435
+
1436
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1437
+ report += `#### ${file}\n\n`;
1438
+ fileIssues.forEach(issue => {
1439
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1440
+ if (issue.code) {
1441
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1442
+ }
1443
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1444
+ });
1445
+ });
1446
+ }
1447
+
1448
+ // Direct RBAC Table Queries
1449
+ if (rbacIssues.rbacDirectTable.length > 0) {
1450
+ report += `### ❌ Direct RBAC Table Queries (SECURITY CRITICAL)\n\n`;
1451
+ report += `These files query RBAC tables directly without useSecureSupabase:\n\n`;
1452
+
1453
+ const issuesByFile = {};
1454
+ rbacIssues.rbacDirectTable.forEach(issue => {
1455
+ if (!issuesByFile[issue.file]) {
1456
+ issuesByFile[issue.file] = [];
1457
+ }
1458
+ issuesByFile[issue.file].push(issue);
1459
+ });
1460
+
1461
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1462
+ report += `#### ${file}\n\n`;
1463
+ fileIssues.forEach(issue => {
1464
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1465
+ if (issue.code) {
1466
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1467
+ }
1468
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1469
+ });
1470
+ });
1471
+ }
1472
+
1473
+ // Hardcoded Role Checks
1474
+ if (rbacIssues.rbacHardcodedRole.length > 0) {
1475
+ report += `### ❌ Hardcoded Role Checks (SECURITY CRITICAL)\n\n`;
1476
+ report += `These files use hardcoded role comparisons instead of pace-core APIs:\n\n`;
1477
+
1478
+ const issuesByFile = {};
1479
+ rbacIssues.rbacHardcodedRole.forEach(issue => {
1480
+ if (!issuesByFile[issue.file]) {
1481
+ issuesByFile[issue.file] = [];
1482
+ }
1483
+ issuesByFile[issue.file].push(issue);
1484
+ });
1485
+
1486
+ Object.entries(issuesByFile).forEach(([file, fileIssues]) => {
1487
+ report += `#### ${file}\n\n`;
1488
+ fileIssues.forEach(issue => {
1489
+ report += `- **Line ${issue.line}**: ${issue.message}\n`;
1490
+ if (issue.code) {
1491
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1492
+ }
1493
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1494
+ });
1495
+ });
1496
+ }
1497
+
1498
+ // enforcePermissions Configuration
1499
+ if (rbacIssues.rbacEnforcePermissions.length > 0) {
1500
+ report += `### ❌ enforcePermissions Configuration Issues\n\n`;
1501
+ report += `These files have incorrect enforcePermissions configuration:\n\n`;
1502
+
1503
+ rbacIssues.rbacEnforcePermissions.forEach(issue => {
1504
+ report += `- **${issue.file}** (Line ${issue.line}): ${issue.message}\n`;
1505
+ if (issue.code) {
1506
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1507
+ }
1508
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1509
+ });
1510
+ }
1511
+
1512
+ // Edge Functions RBAC
1513
+ if (rbacIssues.rbacEdgeFunction.length > 0) {
1514
+ report += `### ❌ Edge Functions RBAC Issues (SECURITY CRITICAL)\n\n`;
1515
+ report += `These Edge Functions have RBAC compliance issues:\n\n`;
1516
+
1517
+ rbacIssues.rbacEdgeFunction.forEach(issue => {
1518
+ report += `- **${issue.file}** (Line ${issue.line}): ${issue.message}\n`;
1519
+ if (issue.code) {
1520
+ report += ` \`\`\`\n ${issue.code}\n \`\`\`\n`;
1521
+ }
1522
+ report += ` - **Fix:** ${issue.fix}\n\n`;
1523
+ });
1524
+ }
1525
+ }
1526
+
1527
+ // Missing Optional (informational only)
1528
+ if (issues.missingOptional.length > 0) {
1529
+ report += `---\n\n`;
1530
+ report += `## ℹ️ Missing Optional Dependencies\n\n`;
1531
+ report += `These packages are optional (only install if you use these features):\n\n`;
1532
+ issues.missingOptional.forEach(issue => {
1533
+ report += `- **${issue.package}** (required: ${issue.required})\n`;
1534
+ });
1535
+ report += `\n`;
1536
+ }
1537
+
1538
+ // Fix Commands Section
1539
+ if (totalIssues > 0) {
1540
+ report += `---\n\n`;
1541
+ report += `## 🔧 Quick Fix Commands\n\n`;
1542
+ report += `Run these commands to fix the issues found:\n\n`;
1543
+
1544
+ if (issues.includedDeps.length > 0) {
1545
+ const depsToRemove = issues.includedDeps.map(i => i.package).join(' ');
1546
+ report += `### Remove Included Dependencies\n\n`;
1547
+ report += `\`\`\`bash\n`;
1548
+ report += `npm uninstall ${depsToRemove}\n`;
1549
+ report += `\`\`\`\n\n`;
1550
+ }
1551
+
1552
+ if (issues.missingRequired.length > 0) {
1553
+ const depsToInstall = issues.missingRequired
1554
+ .map(i => `${i.package}@${i.required}`)
1555
+ .join(' ');
1556
+ report += `### Install Missing Required Dependencies\n\n`;
1557
+ report += `\`\`\`bash\n`;
1558
+ report += `npm install ${depsToInstall}\n`;
1559
+ report += `\`\`\`\n\n`;
1560
+ }
1561
+
1562
+ if (issues.versionIssues.length > 0) {
1563
+ const depsToFix = issues.versionIssues
1564
+ .map(i => `${i.package}@${i.required}`)
1565
+ .join(' ');
1566
+ report += `### Fix Version Ranges\n\n`;
1567
+ report += `\`\`\`bash\n`;
1568
+ report += `npm install ${depsToFix}\n`;
1569
+ report += `\`\`\`\n\n`;
1570
+ }
1571
+
1572
+ if (issues.wrongLocation.length > 0) {
1573
+ issues.wrongLocation.forEach(issue => {
1574
+ report += `### Move ${issue.package} to devDependencies\n\n`;
1575
+ report += `\`\`\`bash\n`;
1576
+ report += `npm uninstall ${issue.package}\n`;
1577
+ report += `npm install -D ${issue.package}\n`;
1578
+ report += `\`\`\`\n\n`;
1579
+ });
1580
+ }
1581
+
1582
+ if (issues.viteAliases.length > 0) {
1583
+ report += `### Fix Vite Aliases\n\n`;
1584
+ report += `Remove the following aliases from your \`vite.config.ts\` (or \`vite.config.js\`):\n\n`;
1585
+ issues.viteAliases.forEach(issue => {
1586
+ report += `- Remove: \`"${issue.alias}": "${issue.target}"\`\n`;
1587
+ });
1588
+ report += `\n`;
1589
+ report += `These aliases bypass pace-core's exports. Use the pace-core exports directly instead.\n\n`;
1590
+ }
1591
+
1592
+ if (issues.formIssues && issues.formIssues.length > 0) {
1593
+ report += `### Fix Form Usage\n\n`;
1594
+ report += `Replace plain \`<form>\` tags and direct \`react-hook-form\` usage with pace-core Form component:\n\n`;
1595
+ report += `**Before (❌ Wrong):**\n`;
1596
+ report += `\`\`\`tsx\n`;
1597
+ report += `import { useForm } from 'react-hook-form';\n`;
1598
+ report += `\n`;
1599
+ report += `function MyForm() {\n`;
1600
+ report += ` const { register, handleSubmit } = useForm();\n`;
1601
+ report += ` return (\n`;
1602
+ report += ` <form onSubmit={handleSubmit(onSubmit)}>\n`;
1603
+ report += ` <input {...register('name')} />\n`;
1604
+ report += ` <button type="submit">Submit</button>\n`;
1605
+ report += ` </form>\n`;
1606
+ report += ` );\n`;
1607
+ report += `}\n`;
1608
+ report += `\`\`\`\n\n`;
1609
+ report += `**After (✅ Correct):**\n`;
1610
+ report += `\`\`\`tsx\n`;
1611
+ report += `import { Form, FormField, Button } from '@jmruthers/pace-core';\n`;
1612
+ report += `import { z } from 'zod';\n`;
1613
+ report += `\n`;
1614
+ report += `const formSchema = z.object({\n`;
1615
+ report += ` name: z.string().min(1, 'Name is required'),\n`;
1616
+ report += `});\n`;
1617
+ report += `\n`;
1618
+ report += `function MyForm() {\n`;
1619
+ report += ` return (\n`;
1620
+ report += ` <Form\n`;
1621
+ report += ` schema={formSchema}\n`;
1622
+ report += ` defaultValues={{ name: '' }}\n`;
1623
+ report += ` onSubmit={(data) => console.log(data)}\n`;
1624
+ report += ` >\n`;
1625
+ report += ` <FormField name="name" label="Name" />\n`;
1626
+ report += ` <Button type="submit">Submit</Button>\n`;
1627
+ report += ` </Form>\n`;
1628
+ report += ` );\n`;
1629
+ report += `}\n`;
1630
+ report += `\`\`\`\n\n`;
1631
+ report += `**Key Benefits:**\n`;
1632
+ report += `- Built-in Zod validation with type safety\n`;
1633
+ report += `- Automatic form persistence (session draft)\n`;
1634
+ report += `- Consistent styling with pace-core design system\n`;
1635
+ report += `- Accessible form structure with error handling\n`;
1636
+ report += `- Sensitive field filtering (passwords, etc.)\n\n`;
1637
+ report += `**Documentation:** See [pace-core Forms Guide](https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/implementation-guides/forms.md) for complete examples.\n\n`;
1638
+ }
1639
+ }
1640
+
1641
+ report += `---\n\n`;
1642
+ report += `*Generated by pace-core audit script*\n`;
1643
+
1644
+ return report;
212
1645
  }
213
1646
 
214
- // Run if called directly
215
- if (require.main === module) {
216
- main().catch(error => {
217
- console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
218
- console.error(error.stack);
1647
+ // Main function
1648
+ function main() {
1649
+ const args = process.argv.slice(2);
1650
+ const consumingAppPath = args.find(arg => !arg.startsWith('--')) || process.cwd();
1651
+ const outputArg = args.find(arg => arg.startsWith('--output'));
1652
+ const outputPath = outputArg ? outputArg.split('=')[1] || 'audit-report.md' : null;
1653
+
1654
+ console.log(`${colors.bold}${colors.cyan}pace-core Comprehensive Audit${colors.reset}\n`);
1655
+ console.log(`${colors.cyan}${'='.repeat(50)}${colors.reset}\n`);
1656
+
1657
+ // Run dependency audit (with Vite alias checks and component audit)
1658
+ console.log(`${colors.blue}Running dependency audit...${colors.reset}`);
1659
+ const auditResults = runDependencyAuditWithExtras(consumingAppPath, true);
1660
+
1661
+ if (auditResults.error) {
1662
+ console.error(`${colors.red}Error: ${auditResults.error}${colors.reset}`);
219
1663
  process.exit(1);
220
- });
1664
+ }
1665
+
1666
+ // Display results
1667
+ const { projectName, paceCoreVersion, issues } = auditResults;
1668
+
1669
+ // Calculate issue counts
1670
+ const dependencyIssuesCount = issues.includedDeps.length +
1671
+ issues.missingRequired.length +
1672
+ issues.versionIssues.length +
1673
+ issues.wrongLocation.length +
1674
+ issues.viteAliases.length;
1675
+
1676
+ const componentIssuesCount = issues.formIssues?.length || 0;
1677
+
1678
+ const complianceIssues = issues.complianceIssues || {
1679
+ nativeElements: [],
1680
+ restrictedImports: [],
1681
+ customHooks: [],
1682
+ customUtils: [],
1683
+ supabaseClient: [],
1684
+ rbacSetup: [],
1685
+ providers: [],
1686
+ coreStyles: [],
1687
+ inlineStyles: [],
1688
+ };
1689
+
1690
+ const complianceIssuesCount = Object.values(complianceIssues).reduce((sum, arr) => sum + arr.length, 0);
1691
+
1692
+ const standardsIssues = issues.standardsIssues || {
1693
+ cursorRuleset: [],
1694
+ typescriptConfig: [],
1695
+ anyTypes: [],
1696
+ namingConvention: [],
1697
+ rlsPolicy: [],
1698
+ rpcNaming: [],
1699
+ testingConfig: [],
1700
+ inputValidation: [],
1701
+ loggingSecurity: [],
1702
+ errorMessages: [],
1703
+ };
1704
+
1705
+ const standardsIssuesCount = Object.values(standardsIssues).reduce((sum, arr) => sum + arr.length, 0);
1706
+
1707
+ const rbacIssues = issues.rbacIssues || {
1708
+ rbacPageGuard: [],
1709
+ rbacWrapperComponent: [],
1710
+ rbacWrapperFunction: [],
1711
+ rbacResourceNames: [],
1712
+ rbacAccessDenied: [],
1713
+ rbacDirectRPC: [],
1714
+ rbacDirectTable: [],
1715
+ rbacHardcodedRole: [],
1716
+ rbacEnforcePermissions: [],
1717
+ rbacEdgeFunction: [],
1718
+ };
1719
+
1720
+ const rbacIssuesCount = Object.values(rbacIssues).reduce((sum, arr) => sum + arr.length, 0);
1721
+
1722
+ const totalIssues = dependencyIssuesCount + componentIssuesCount + complianceIssuesCount + standardsIssuesCount + rbacIssuesCount;
1723
+
1724
+ // Display project info
1725
+ console.log(`Project: ${colors.bold}${projectName}${colors.reset}`);
1726
+ console.log(`pace-core: ${colors.bold}${paceCoreVersion}${colors.reset}\n`);
1727
+
1728
+ // Display audit results summary
1729
+ console.log(`${colors.bold}Audit Results:${colors.reset}\n`);
1730
+
1731
+ // Dependency Audit
1732
+ if (dependencyIssuesCount === 0) {
1733
+ console.log(` ${colors.green}✅ Dependency Audit: 0 issues${colors.reset}`);
1734
+ } else {
1735
+ console.log(` ${colors.red}❌ Dependency Audit: ${dependencyIssuesCount} issue(s)${colors.reset}`);
1736
+ if (issues.includedDeps.length > 0) {
1737
+ console.log(` - Included Dependencies: ${issues.includedDeps.length}`);
1738
+ }
1739
+ if (issues.missingRequired.length > 0) {
1740
+ console.log(` - Missing Required: ${issues.missingRequired.length}`);
1741
+ }
1742
+ if (issues.versionIssues.length > 0) {
1743
+ console.log(` - Version Issues: ${issues.versionIssues.length}`);
1744
+ }
1745
+ if (issues.wrongLocation.length > 0) {
1746
+ console.log(` - Wrong Location: ${issues.wrongLocation.length}`);
1747
+ }
1748
+ if (issues.viteAliases.length > 0) {
1749
+ console.log(` - Vite Aliases: ${issues.viteAliases.length}`);
1750
+ }
1751
+ }
1752
+
1753
+ // Component Usage Audit
1754
+ if (componentIssuesCount === 0) {
1755
+ console.log(` ${colors.green}✅ Component Usage Audit: 0 issues${colors.reset}`);
1756
+ } else {
1757
+ const plainFormTagsCount = issues.formIssues.filter(i => i.type === 'plainFormTag').length;
1758
+ const reactHookFormImportsCount = issues.formIssues.filter(i => i.type === 'reactHookFormImport').length;
1759
+ console.log(` ${colors.red}❌ Component Usage Audit: ${componentIssuesCount} issue(s)${colors.reset}`);
1760
+ if (plainFormTagsCount > 0) {
1761
+ console.log(` - Plain <form> tags: ${plainFormTagsCount}`);
1762
+ }
1763
+ if (reactHookFormImportsCount > 0) {
1764
+ console.log(` - Direct react-hook-form imports: ${reactHookFormImportsCount}`);
1765
+ }
1766
+ }
1767
+
1768
+ // Compliance Audit
1769
+ if (complianceIssuesCount === 0) {
1770
+ console.log(` ${colors.green}✅ Compliance Audit: 0 issues${colors.reset}`);
1771
+ } else {
1772
+ console.log(` ${colors.red}❌ Compliance Audit: ${complianceIssuesCount} issue(s)${colors.reset}`);
1773
+ if (complianceIssues.nativeElements.length > 0) {
1774
+ console.log(` - Native HTML elements: ${complianceIssues.nativeElements.length}`);
1775
+ }
1776
+ if (complianceIssues.restrictedImports.length > 0) {
1777
+ console.log(` - Restricted imports: ${complianceIssues.restrictedImports.length}`);
1778
+ }
1779
+ if (complianceIssues.customHooks.length > 0) {
1780
+ console.log(` - Custom hooks: ${complianceIssues.customHooks.length}`);
1781
+ }
1782
+ if (complianceIssues.customUtils.length > 0) {
1783
+ console.log(` - Custom utilities: ${complianceIssues.customUtils.length}`);
1784
+ }
1785
+ if (complianceIssues.supabaseClient.length > 0) {
1786
+ console.log(` - Supabase client issues: ${complianceIssues.supabaseClient.length}`);
1787
+ }
1788
+ if (complianceIssues.rbacSetup.length > 0) {
1789
+ console.log(` - RBAC setup issues: ${complianceIssues.rbacSetup.length}`);
1790
+ }
1791
+ if (complianceIssues.rbacPermission && complianceIssues.rbacPermission.length > 0) {
1792
+ console.log(` - RBAC permission usage issues: ${complianceIssues.rbacPermission.length}`);
1793
+ }
1794
+ if (complianceIssues.providers.length > 0) {
1795
+ console.log(` - Provider issues: ${complianceIssues.providers.length}`);
1796
+ }
1797
+ if (complianceIssues.coreStyles.length > 0) {
1798
+ console.log(` - Core styles issues: ${complianceIssues.coreStyles.length}`);
1799
+ }
1800
+ if (complianceIssues.inlineStyles.length > 0) {
1801
+ console.log(` - Inline styles: ${complianceIssues.inlineStyles.length}`);
1802
+ }
1803
+ }
1804
+
1805
+ // Standards Audit
1806
+ if (standardsIssuesCount === 0) {
1807
+ console.log(` ${colors.green}✅ Standards Audit: 0 issues${colors.reset}`);
1808
+ } else {
1809
+ console.log(` ${colors.red}❌ Standards Audit: ${standardsIssuesCount} issue(s)${colors.reset}`);
1810
+ if (standardsIssues.cursorRuleset.length > 0) {
1811
+ console.log(` - Cursor ruleset: ${standardsIssues.cursorRuleset.length}`);
1812
+ }
1813
+ if (standardsIssues.typescriptConfig.length > 0) {
1814
+ console.log(` - TypeScript config: ${standardsIssues.typescriptConfig.length}`);
1815
+ }
1816
+ if (standardsIssues.anyTypes.length > 0) {
1817
+ console.log(` - Any types: ${standardsIssues.anyTypes.length}`);
1818
+ }
1819
+ if (standardsIssues.namingConvention.length > 0) {
1820
+ console.log(` - Naming conventions: ${standardsIssues.namingConvention.length}`);
1821
+ }
1822
+ if (standardsIssues.rlsPolicy.length > 0) {
1823
+ console.log(` - RLS policies: ${standardsIssues.rlsPolicy.length}`);
1824
+ }
1825
+ if (standardsIssues.rpcNaming.length > 0) {
1826
+ console.log(` - RPC naming: ${standardsIssues.rpcNaming.length}`);
1827
+ }
1828
+ if (standardsIssues.testingConfig.length > 0) {
1829
+ console.log(` - Testing config: ${standardsIssues.testingConfig.length}`);
1830
+ }
1831
+ if (standardsIssues.inputValidation.length > 0) {
1832
+ console.log(` - Input validation: ${standardsIssues.inputValidation.length}`);
1833
+ }
1834
+ if (standardsIssues.loggingSecurity.length > 0) {
1835
+ console.log(` - Logging security: ${standardsIssues.loggingSecurity.length}`);
1836
+ }
1837
+ if (standardsIssues.errorMessages.length > 0) {
1838
+ console.log(` - Error messages: ${standardsIssues.errorMessages.length}`);
1839
+ }
1840
+ }
1841
+
1842
+ // RBAC Audit
1843
+ if (rbacIssuesCount === 0) {
1844
+ console.log(` ${colors.green}✅ RBAC Audit: 0 issues${colors.reset}`);
1845
+ } else {
1846
+ console.log(` ${colors.red}❌ RBAC Audit: ${rbacIssuesCount} issue(s)${colors.reset}`);
1847
+ if (rbacIssues.rbacPageGuard.length > 0) {
1848
+ console.log(` - PagePermissionGuard issues: ${rbacIssues.rbacPageGuard.length}`);
1849
+ }
1850
+ if (rbacIssues.rbacWrapperComponent.length > 0) {
1851
+ console.log(` - Wrapper components: ${rbacIssues.rbacWrapperComponent.length}`);
1852
+ }
1853
+ if (rbacIssues.rbacWrapperFunction.length > 0) {
1854
+ console.log(` - Wrapper functions: ${rbacIssues.rbacWrapperFunction.length}`);
1855
+ }
1856
+ if (rbacIssues.rbacResourceNames.length > 0) {
1857
+ console.log(` - RESOURCE_NAMES constants: ${rbacIssues.rbacResourceNames.length}`);
1858
+ }
1859
+ if (rbacIssues.rbacAccessDenied.length > 0) {
1860
+ console.log(` - AccessDenied component: ${rbacIssues.rbacAccessDenied.length}`);
1861
+ }
1862
+ if (rbacIssues.rbacDirectRPC.length > 0) {
1863
+ console.log(` - Direct RBAC RPC calls: ${rbacIssues.rbacDirectRPC.length}`);
1864
+ }
1865
+ if (rbacIssues.rbacDirectTable.length > 0) {
1866
+ console.log(` - Direct RBAC table queries: ${rbacIssues.rbacDirectTable.length}`);
1867
+ }
1868
+ if (rbacIssues.rbacHardcodedRole.length > 0) {
1869
+ console.log(` - Hardcoded role checks: ${rbacIssues.rbacHardcodedRole.length}`);
1870
+ }
1871
+ if (rbacIssues.rbacEnforcePermissions.length > 0) {
1872
+ console.log(` - enforcePermissions config: ${rbacIssues.rbacEnforcePermissions.length}`);
1873
+ }
1874
+ if (rbacIssues.rbacEdgeFunction.length > 0) {
1875
+ console.log(` - Edge Functions RBAC: ${rbacIssues.rbacEdgeFunction.length}`);
1876
+ }
1877
+ }
1878
+
1879
+ // Total summary
1880
+ console.log(`\n${colors.bold}Total Issues: ${totalIssues === 0 ? colors.green + '0 (All checks passed!)' : colors.red + totalIssues}${colors.reset}\n`);
1881
+
1882
+ // Generate and save markdown report
1883
+ const markdownReport = generateMarkdownReport(auditResults);
1884
+
1885
+ // Generate timestamp in yyyymmddHHMM format
1886
+ const now = new Date();
1887
+ const year = now.getFullYear();
1888
+ const month = String(now.getMonth() + 1).padStart(2, '0');
1889
+ const day = String(now.getDate()).padStart(2, '0');
1890
+ const hours = String(now.getHours()).padStart(2, '0');
1891
+ const minutes = String(now.getMinutes()).padStart(2, '0');
1892
+ const timestamp = `${year}${month}${day}${hours}${minutes}`;
1893
+
1894
+ // Helper function to add timestamp to filename
1895
+ function addTimestampToFilename(filePath) {
1896
+ const dir = path.dirname(filePath);
1897
+ const ext = path.extname(filePath);
1898
+ const name = path.basename(filePath, ext);
1899
+ return path.join(dir, `${name}-${timestamp}${ext}`);
1900
+ }
1901
+
1902
+ // Determine report path
1903
+ let reportPath;
1904
+ if (outputPath) {
1905
+ reportPath = addTimestampToFilename(path.join(consumingAppPath, outputPath));
1906
+ } else {
1907
+ // Default: save to audit/ directory
1908
+ const auditDir = path.join(consumingAppPath, 'audit');
1909
+ if (!fs.existsSync(auditDir)) {
1910
+ fs.mkdirSync(auditDir, { recursive: true });
1911
+ }
1912
+ reportPath = path.join(auditDir, `pace-core-audit-${timestamp}.md`);
1913
+ }
1914
+
1915
+ // Save report
1916
+ fs.writeFileSync(reportPath, markdownReport, 'utf8');
1917
+
1918
+ // Display report location
1919
+ const relativeReportPath = path.relative(consumingAppPath, reportPath);
1920
+ console.log(`${colors.bold}Report saved to:${colors.reset} ${colors.cyan}${relativeReportPath}${colors.reset}\n`);
1921
+
1922
+ // Exit with error code if issues found
1923
+ process.exit(totalIssues > 0 ? 1 : 0);
221
1924
  }
222
1925
 
223
- module.exports = { main };
1926
+ main();
1927
+