@jmruthers/pace-core 0.5.193 → 0.6.1

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 (191) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +7 -1
  3. package/cursor-rules/00-pace-core-compliance.mdc +372 -0
  4. package/cursor-rules/01-standards-compliance.mdc +275 -0
  5. package/cursor-rules/02-project-structure.mdc +200 -0
  6. package/cursor-rules/03-solid-principles.mdc +341 -0
  7. package/cursor-rules/04-testing-standards.mdc +315 -0
  8. package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
  9. package/cursor-rules/06-code-quality.mdc +392 -0
  10. package/cursor-rules/07-tech-stack-compliance.mdc +309 -0
  11. package/cursor-rules/CHANGELOG.md +101 -0
  12. package/cursor-rules/README.md +191 -0
  13. package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-CH1U5Tpy.d.ts} +1 -1
  14. package/dist/{DataTable-5FU7IESH.js → DataTable-DQ7RSOHE.js} +6 -6
  15. package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-ce4xlHYA.d.ts} +34 -155
  16. package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-ATAP5UTR.js} +2 -2
  17. package/dist/{chunk-6C4YBBJM 5.js → chunk-3QRJFVBR.js} +1 -1
  18. package/dist/chunk-3QRJFVBR.js.map +1 -0
  19. package/dist/{chunk-IIELH4DL.js → chunk-3XTALGJF.js} +2 -2
  20. package/dist/{chunk-IIELH4DL.js.map → chunk-3XTALGJF.js.map} +1 -1
  21. package/dist/{chunk-HWIIPPNI.js → chunk-4N5C5XZU.js} +20 -20
  22. package/dist/chunk-4N5C5XZU.js.map +1 -0
  23. package/dist/{chunk-7EQTDTTJ.js → chunk-4ZC4GX36.js} +5 -5
  24. package/dist/{chunk-7EQTDTTJ.js 2.map → chunk-4ZC4GX36.js.map} +1 -1
  25. package/dist/{chunk-7FLMSG37.js → chunk-BYFSK72L.js} +22 -22
  26. package/dist/chunk-BYFSK72L.js.map +1 -0
  27. package/dist/{chunk-LFNCN2SP.js → chunk-EXUD6RNJ.js} +46 -7
  28. package/dist/chunk-EXUD6RNJ.js.map +1 -0
  29. package/dist/{chunk-NOAYCWCX 5.js → chunk-GLK6VM3F.js} +167 -169
  30. package/dist/chunk-GLK6VM3F.js.map +1 -0
  31. package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
  32. package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
  33. package/dist/{chunk-BC4IJKSL.js → chunk-JBKQ3SAO.js} +2 -2
  34. package/dist/{chunk-QWWZ5CAQ.js → chunk-LXQLPRQ2.js} +2 -2
  35. package/dist/{chunk-E3SPN4VZ 5.js → chunk-T33XF5ZC.js} +119 -114
  36. package/dist/chunk-T33XF5ZC.js.map +1 -0
  37. package/dist/{chunk-XNXXZ43G.js → chunk-XM25TVIE.js} +27 -4
  38. package/dist/chunk-XM25TVIE.js.map +1 -0
  39. package/dist/components.d.ts +3 -3
  40. package/dist/components.js +8 -8
  41. package/dist/hooks.d.ts +6 -6
  42. package/dist/hooks.js +17 -22
  43. package/dist/hooks.js.map +1 -1
  44. package/dist/index.d.ts +7 -7
  45. package/dist/index.js +15 -16
  46. package/dist/index.js.map +1 -1
  47. package/dist/providers.js +1 -1
  48. package/dist/rbac/index.d.ts +1 -1
  49. package/dist/rbac/index.js +5 -5
  50. package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-BJAlWfuJ.d.ts} +3 -3
  51. package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
  52. package/dist/utils.d.ts +1 -1
  53. package/dist/utils.js +3 -3
  54. package/docs/getting-started/cursor-rules.md +262 -0
  55. package/docs/getting-started/installation-guide.md +6 -1
  56. package/docs/getting-started/quick-start.md +6 -1
  57. package/docs/migration/MIGRATION_GUIDE.md +4 -4
  58. package/docs/migration/REACT_19_MIGRATION.md +227 -0
  59. package/docs/standards/README.md +39 -0
  60. package/docs/troubleshooting/migration.md +4 -4
  61. package/examples/PublicPages/PublicEventPage.tsx +1 -1
  62. package/package.json +11 -6
  63. package/scripts/audit-consuming-app.cjs +961 -0
  64. package/scripts/check-pace-core-compliance.cjs +34 -15
  65. package/scripts/install-cursor-rules.cjs +236 -0
  66. package/src/__tests__/helpers/test-providers.tsx +1 -1
  67. package/src/__tests__/helpers/test-utils.tsx +1 -1
  68. package/src/components/Badge/Badge.tsx +2 -4
  69. package/src/components/Button/Button.tsx +5 -4
  70. package/src/components/Calendar/Calendar.tsx +1 -1
  71. package/src/components/DataTable/DataTable.test.tsx +57 -93
  72. package/src/components/DataTable/DataTable.tsx +2 -2
  73. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +13 -5
  74. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
  75. package/src/components/DataTable/components/AccessDeniedPage.tsx +1 -1
  76. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  77. package/src/components/DataTable/components/DataTableCore.tsx +4 -7
  78. package/src/components/DataTable/components/DataTableModals.tsx +1 -1
  79. package/src/components/DataTable/components/EditableRow.tsx +1 -1
  80. package/src/components/DataTable/components/UnifiedTableBody.tsx +6 -8
  81. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
  82. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
  83. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
  84. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
  85. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
  86. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
  87. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
  88. package/src/components/DataTable/hooks/useColumnReordering.ts +2 -2
  89. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
  90. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
  91. package/src/components/Dialog/Dialog.tsx +6 -5
  92. package/src/components/ErrorBoundary/ErrorBoundary.tsx +1 -1
  93. package/src/components/EventSelector/EventSelector.tsx +1 -1
  94. package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
  95. package/src/components/Footer/Footer.tsx +1 -1
  96. package/src/components/Form/Form.test.tsx +36 -15
  97. package/src/components/Form/Form.tsx +30 -26
  98. package/src/components/Header/Header.tsx +1 -1
  99. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
  100. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
  101. package/src/components/Input/Input.tsx +28 -30
  102. package/src/components/Label/Label.tsx +1 -1
  103. package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
  104. package/src/components/LoginForm/LoginForm.test.tsx +42 -42
  105. package/src/components/LoginForm/LoginForm.tsx +8 -8
  106. package/src/components/NavigationMenu/NavigationMenu.tsx +1 -1
  107. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
  108. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +50 -50
  109. package/src/components/PaceAppLayout/PaceAppLayout.tsx +1 -1
  110. package/src/components/PaceAppLayout/README.md +1 -1
  111. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
  112. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
  113. package/src/components/PasswordChange/PasswordChangeForm.tsx +1 -1
  114. package/src/components/Progress/Progress.tsx +1 -1
  115. package/src/components/PublicLayout/PublicPageLayout.tsx +1 -1
  116. package/src/components/Select/Select.tsx +33 -22
  117. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +1 -1
  118. package/src/components/Table/Table.tsx +1 -1
  119. package/src/components/Textarea/Textarea.tsx +27 -29
  120. package/src/components/Toast/Toast.tsx +1 -1
  121. package/src/components/Tooltip/Tooltip.tsx +1 -1
  122. package/src/components/UserMenu/UserMenu.tsx +1 -1
  123. package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
  124. package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
  125. package/src/hooks/public/usePublicEvent.ts +1 -1
  126. package/src/hooks/public/usePublicEventLogo.ts +1 -1
  127. package/src/hooks/public/usePublicRouteParams.ts +1 -1
  128. package/src/hooks/useDataTableState.ts +8 -18
  129. package/src/hooks/useFocusManagement.ts +2 -2
  130. package/src/hooks/useFocusTrap.ts +4 -4
  131. package/src/hooks/useFormDialog.ts +8 -7
  132. package/src/hooks/useInactivityTracker.ts +1 -1
  133. package/src/hooks/usePermissionCache.ts +1 -1
  134. package/src/hooks/useSecureDataAccess.ts +19 -4
  135. package/src/hooks/useToast.ts +2 -2
  136. package/src/providers/__tests__/OrganisationProvider.test.tsx +57 -13
  137. package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
  138. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
  139. package/src/providers/services/UnifiedAuthProvider.tsx +22 -22
  140. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +13 -3
  141. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +24 -24
  142. package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
  143. package/src/rbac/components/NavigationGuard.tsx +1 -1
  144. package/src/rbac/components/NavigationProvider.tsx +1 -1
  145. package/src/rbac/components/PagePermissionGuard.tsx +1 -1
  146. package/src/rbac/components/PagePermissionProvider.tsx +1 -1
  147. package/src/rbac/components/PermissionEnforcer.tsx +1 -1
  148. package/src/rbac/components/RoleBasedRouter.tsx +1 -1
  149. package/src/rbac/components/SecureDataProvider.tsx +1 -1
  150. package/src/rbac/secureClient.ts +12 -0
  151. package/src/utils/security/secureDataAccess.test.ts +31 -20
  152. package/src/utils/security/secureDataAccess.ts +4 -3
  153. package/dist/chunk-6C4YBBJM.js +0 -628
  154. package/dist/chunk-6C4YBBJM.js.map +0 -1
  155. package/dist/chunk-7D4SUZUM.js 2.map +0 -1
  156. package/dist/chunk-7EQTDTTJ.js.map +0 -1
  157. package/dist/chunk-7FLMSG37.js 2.map +0 -1
  158. package/dist/chunk-7FLMSG37.js.map +0 -1
  159. package/dist/chunk-E3SPN4VZ.js +0 -12917
  160. package/dist/chunk-E3SPN4VZ.js.map +0 -1
  161. package/dist/chunk-E66EQZE6 5.js +0 -37
  162. package/dist/chunk-E66EQZE6.js 2.map +0 -1
  163. package/dist/chunk-HWIIPPNI.js.map +0 -1
  164. package/dist/chunk-I7PSE6JW 5.js +0 -191
  165. package/dist/chunk-I7PSE6JW.js 2.map +0 -1
  166. package/dist/chunk-KNC55RTG.js 5.map +0 -1
  167. package/dist/chunk-KQCRWDSA.js 5.map +0 -1
  168. package/dist/chunk-LFNCN2SP.js 2.map +0 -1
  169. package/dist/chunk-LFNCN2SP.js.map +0 -1
  170. package/dist/chunk-LMC26NLJ 2.js +0 -84
  171. package/dist/chunk-NOAYCWCX.js +0 -4993
  172. package/dist/chunk-NOAYCWCX.js.map +0 -1
  173. package/dist/chunk-QWWZ5CAQ.js.map +0 -1
  174. package/dist/chunk-QXHPKYJV 3.js +0 -113
  175. package/dist/chunk-R77UEZ4E 3.js +0 -68
  176. package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
  177. package/dist/chunk-XNXXZ43G.js.map +0 -1
  178. package/dist/chunk-ZSAAAMVR 6.js +0 -25
  179. package/dist/components.js 5.map +0 -1
  180. package/dist/styles/index 2.js +0 -12
  181. package/dist/styles/index.js 5.map +0 -1
  182. package/dist/theming/runtime 5.js +0 -19
  183. package/dist/theming/runtime.js 5.map +0 -1
  184. /package/dist/{DataTable-5FU7IESH.js.map → DataTable-DQ7RSOHE.js.map} +0 -0
  185. /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-ATAP5UTR.js.map} +0 -0
  186. /package/dist/{chunk-BC4IJKSL.js.map → chunk-JBKQ3SAO.js.map} +0 -0
  187. /package/dist/{chunk-QWWZ5CAQ.js 3.map → chunk-LXQLPRQ2.js.map} +0 -0
  188. /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
  189. /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
  190. /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
  191. /package/examples/{rbac → RBAC}/index.ts +0 -0
@@ -0,0 +1,961 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Comprehensive Audit Tool for Consuming Apps
5
+ * @package @jmruthers/pace-core
6
+ * @module Scripts/audit-consuming-app
7
+ *
8
+ * Performs comprehensive audit of consuming app including:
9
+ * - Phase 1: Deterministic checks (file structure, configs, coverage)
10
+ * - Phase 2: Heuristic checks (SOLID principles, code complexity)
11
+ * - Standards compliance
12
+ * - Testing compliance
13
+ * - Code quality metrics
14
+ *
15
+ * Generates timestamped markdown report in audit/ directory.
16
+ */
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ // Import existing compliance checker
22
+ let complianceChecker;
23
+ try {
24
+ complianceChecker = require('./check-pace-core-compliance.cjs');
25
+ } catch (error) {
26
+ console.error('Error loading compliance checker:', error.message);
27
+ process.exit(1);
28
+ }
29
+
30
+ // Run compliance check and get results
31
+ function runComplianceCheck(projectRoot) {
32
+ const manifest = complianceChecker.loadManifest();
33
+ const files = complianceChecker.findSourceFiles(projectRoot, []);
34
+
35
+ console.log(` Scanning ${files.length} files for compliance issues...`);
36
+
37
+ // Scan all files
38
+ const allViolations = {
39
+ restrictedImports: [],
40
+ duplicateComponents: [],
41
+ duplicateHooks: [],
42
+ duplicateUtils: [],
43
+ suggestions: [],
44
+ customAuthCode: [],
45
+ duplicateConfig: [],
46
+ unprotectedPages: [],
47
+ directSupabaseAuth: [],
48
+ providerSetupIssues: [],
49
+ viteConfigIssues: [],
50
+ routerSetupIssues: [],
51
+ unnecessaryWrappers: [],
52
+ appDiscoveryIssues: []
53
+ };
54
+
55
+ let scanned = 0;
56
+ files.forEach(file => {
57
+ try {
58
+ const violations = complianceChecker.scanFile(file, manifest);
59
+ allViolations.restrictedImports.push(...violations.restrictedImports);
60
+ allViolations.duplicateComponents.push(...violations.duplicateComponents);
61
+ allViolations.duplicateHooks.push(...violations.duplicateHooks);
62
+ allViolations.duplicateUtils.push(...violations.duplicateUtils);
63
+ allViolations.suggestions.push(...violations.suggestions);
64
+ allViolations.customAuthCode.push(...violations.customAuthCode);
65
+ allViolations.duplicateConfig.push(...violations.duplicateConfig);
66
+ allViolations.unprotectedPages.push(...violations.unprotectedPages);
67
+ allViolations.directSupabaseAuth.push(...violations.directSupabaseAuth);
68
+ allViolations.providerSetupIssues.push(...violations.providerSetupIssues);
69
+ allViolations.viteConfigIssues.push(...violations.viteConfigIssues);
70
+ allViolations.routerSetupIssues.push(...violations.routerSetupIssues);
71
+ allViolations.unnecessaryWrappers.push(...violations.unnecessaryWrappers);
72
+ allViolations.appDiscoveryIssues.push(...violations.appDiscoveryIssues);
73
+ scanned++;
74
+ if (scanned % 50 === 0) {
75
+ process.stdout.write(` Scanned ${scanned}/${files.length} files...\r`);
76
+ }
77
+ } catch (error) {
78
+ // Skip files with errors
79
+ scanned++;
80
+ }
81
+ });
82
+
83
+ if (scanned > 0) {
84
+ process.stdout.write(` Scanned ${scanned}/${files.length} files...\n`);
85
+ }
86
+
87
+ return allViolations;
88
+ }
89
+
90
+ // Convert compliance violations to markdown
91
+ function complianceViolationsToMarkdown(allViolations) {
92
+ let markdown = '';
93
+
94
+ const totalRestricted = allViolations.restrictedImports.length;
95
+ const totalDuplicates =
96
+ allViolations.duplicateComponents.length +
97
+ allViolations.duplicateHooks.length +
98
+ allViolations.duplicateUtils.length;
99
+ const totalSuggestions = allViolations.suggestions.length;
100
+ const totalRbacAuth =
101
+ allViolations.customAuthCode.length +
102
+ allViolations.duplicateConfig.length +
103
+ allViolations.unprotectedPages.length +
104
+ allViolations.directSupabaseAuth.length;
105
+ const totalSetupIssues =
106
+ allViolations.providerSetupIssues.length +
107
+ allViolations.viteConfigIssues.length +
108
+ allViolations.routerSetupIssues.length;
109
+ const totalUnnecessaryWrappers = allViolations.unnecessaryWrappers.length;
110
+ const totalAppDiscovery = allViolations.appDiscoveryIssues.length;
111
+ const totalIssues = totalRestricted + totalDuplicates + totalSuggestions + totalRbacAuth + totalSetupIssues + totalUnnecessaryWrappers + totalAppDiscovery;
112
+
113
+ markdown += `### Summary\n\n`;
114
+ markdown += `- **Total Issues:** ${totalIssues}\n`;
115
+ markdown += `- **Restricted Imports:** ${totalRestricted}\n`;
116
+ markdown += `- **Duplicate Components/Hooks/Utils:** ${totalDuplicates}\n`;
117
+ markdown += `- **RBAC/Auth Issues:** ${totalRbacAuth}\n`;
118
+ markdown += `- **Setup Issues:** ${totalSetupIssues}\n`;
119
+ markdown += `- **Unnecessary Wrappers:** ${totalUnnecessaryWrappers}\n`;
120
+ markdown += `- **App Discovery Issues:** ${totalAppDiscovery}\n`;
121
+ markdown += `- **Suggestions:** ${totalSuggestions}\n\n`;
122
+
123
+ // Restricted Imports
124
+ if (totalRestricted > 0) {
125
+ markdown += `#### ❌ Restricted Imports (${totalRestricted})\n\n`;
126
+ allViolations.restrictedImports.forEach(({ module, reason, file, line }) => {
127
+ markdown += `- **${file}:${line}**\n`;
128
+ markdown += ` - Import: \`${module}\`\n`;
129
+ markdown += ` - Reason: ${reason}\n\n`;
130
+ });
131
+ } else {
132
+ markdown += `#### ✅ No Restricted Imports\n\n`;
133
+ }
134
+
135
+ // Duplicate Components
136
+ if (allViolations.duplicateComponents.length > 0) {
137
+ markdown += `#### ❌ Duplicate Components (${allViolations.duplicateComponents.length})\n\n`;
138
+ allViolations.duplicateComponents.forEach(({ component, file }) => {
139
+ markdown += `- **${file}**\n`;
140
+ markdown += ` - Component \`${component}\` conflicts with pace-core component\n`;
141
+ markdown += ` - Suggestion: Use \`${component}\` from '@jmruthers/pace-core' instead\n\n`;
142
+ });
143
+ }
144
+
145
+ // Duplicate Hooks
146
+ if (allViolations.duplicateHooks.length > 0) {
147
+ markdown += `#### ❌ Duplicate Hooks (${allViolations.duplicateHooks.length})\n\n`;
148
+ allViolations.duplicateHooks.forEach(({ hook, file }) => {
149
+ markdown += `- **${file}**\n`;
150
+ markdown += ` - Hook \`${hook}\` conflicts with pace-core hook\n`;
151
+ markdown += ` - Suggestion: Use \`${hook}\` from '@jmruthers/pace-core' instead\n\n`;
152
+ });
153
+ }
154
+
155
+ // Duplicate Utils
156
+ if (allViolations.duplicateUtils.length > 0) {
157
+ markdown += `#### ❌ Duplicate Utils (${allViolations.duplicateUtils.length})\n\n`;
158
+ allViolations.duplicateUtils.forEach(({ util, file }) => {
159
+ markdown += `- **${file}**\n`;
160
+ markdown += ` - Util \`${util}\` conflicts with pace-core util\n`;
161
+ markdown += ` - Suggestion: Use \`${util}\` from '@jmruthers/pace-core' instead\n\n`;
162
+ });
163
+ }
164
+
165
+ // Unnecessary Wrappers
166
+ if (totalUnnecessaryWrappers > 0) {
167
+ markdown += `#### ⚠️ Unnecessary Wrappers (${totalUnnecessaryWrappers})\n\n`;
168
+ allViolations.unnecessaryWrappers.forEach(({ component, wrappedComponent, file, line, reason, recommendation }) => {
169
+ markdown += `- **${file}:${line}**\n`;
170
+ markdown += ` - Component: \`${component}\`\n`;
171
+ markdown += ` - Wraps: \`${wrappedComponent}\`\n`;
172
+ markdown += ` - Issue: ${reason}\n`;
173
+ if (recommendation) {
174
+ markdown += ` - Recommendation: ${recommendation}\n\n`;
175
+ } else {
176
+ markdown += ` - Recommendation: Remove the wrapper and use the component directly.\n\n`;
177
+ }
178
+ });
179
+ }
180
+
181
+ // Custom Auth/RBAC Code
182
+ if (allViolations.customAuthCode.length > 0) {
183
+ // Separate by severity
184
+ const errors = allViolations.customAuthCode.filter(v => !v.severity || v.severity === 'error');
185
+ const warnings = allViolations.customAuthCode.filter(v => v.severity === 'warning');
186
+
187
+ if (errors.length > 0) {
188
+ markdown += `#### ❌ Custom Auth/RBAC Code (${errors.length})\n\n`;
189
+ errors.forEach(({ name, type, file, line, reason, replacement, example }) => {
190
+ markdown += `- **${file}:${line}**\n`;
191
+ markdown += ` - ${type}: \`${name}\`\n`;
192
+ markdown += ` - Reason: ${reason}\n`;
193
+ if (replacement) {
194
+ markdown += ` - Fix: ${replacement}\n`;
195
+ }
196
+ if (example) {
197
+ markdown += ` - Example:\n\`\`\`typescript\n${example}\n\`\`\`\n`;
198
+ }
199
+ markdown += `\n`;
200
+ });
201
+ }
202
+
203
+ if (warnings.length > 0) {
204
+ markdown += `#### ⚠️ Custom Auth/RBAC Warnings (${warnings.length})\n\n`;
205
+ warnings.forEach(({ name, type, file, line, reason, replacement, example }) => {
206
+ markdown += `- **${file}:${line}**\n`;
207
+ markdown += ` - ${type}: \`${name}\`\n`;
208
+ markdown += ` - Note: ${reason}\n`;
209
+ if (replacement) {
210
+ markdown += ` - Recommendation: ${replacement}\n`;
211
+ }
212
+ if (example) {
213
+ markdown += ` - Example:\n\`\`\`typescript\n${example}\n\`\`\`\n`;
214
+ }
215
+ markdown += `\n`;
216
+ });
217
+ }
218
+ }
219
+
220
+ // Duplicate Configurations
221
+ if (allViolations.duplicateConfig.length > 0) {
222
+ markdown += `#### ❌ Duplicate Configurations (${allViolations.duplicateConfig.length})\n\n`;
223
+ allViolations.duplicateConfig.forEach(({ type, file, count, reason }) => {
224
+ markdown += `- **${file}**\n`;
225
+ markdown += ` - Type: \`${type}\`${count ? ` (${count} instances)` : ''}\n`;
226
+ markdown += ` - Reason: ${reason}\n\n`;
227
+ });
228
+ }
229
+
230
+ // Provider Setup Issues
231
+ if (allViolations.providerSetupIssues.length > 0) {
232
+ markdown += `#### ❌ Provider Setup Issues (${allViolations.providerSetupIssues.length})\n\n`;
233
+ allViolations.providerSetupIssues.forEach(({ issue, file, line, recommendation }) => {
234
+ markdown += `- **${file}:${line}**\n`;
235
+ markdown += ` - Issue: ${issue}\n`;
236
+ if (recommendation) {
237
+ markdown += ` - Recommendation: ${recommendation}\n\n`;
238
+ }
239
+ });
240
+ }
241
+
242
+ // Vite Config Issues
243
+ if (allViolations.viteConfigIssues.length > 0) {
244
+ markdown += `#### ❌ Vite Config Issues (${allViolations.viteConfigIssues.length})\n\n`;
245
+ allViolations.viteConfigIssues.forEach(({ issue, file, line, recommendation }) => {
246
+ markdown += `- **${file}:${line}**\n`;
247
+ markdown += ` - Issue: ${issue}\n`;
248
+ if (recommendation) {
249
+ markdown += ` - Recommendation: ${recommendation}\n\n`;
250
+ }
251
+ });
252
+ }
253
+
254
+ // Router Setup Issues
255
+ if (allViolations.routerSetupIssues.length > 0) {
256
+ markdown += `#### ❌ Router Setup Issues (${allViolations.routerSetupIssues.length})\n\n`;
257
+ allViolations.routerSetupIssues.forEach(({ issue, file, line, recommendation }) => {
258
+ markdown += `- **${file}:${line}**\n`;
259
+ markdown += ` - Issue: ${issue}\n`;
260
+ if (recommendation) {
261
+ markdown += ` - Recommendation: ${recommendation}\n\n`;
262
+ }
263
+ });
264
+ }
265
+
266
+ // Unprotected Pages
267
+ if (allViolations.unprotectedPages.length > 0) {
268
+ markdown += `#### ❌ Unprotected Pages (${allViolations.unprotectedPages.length})\n\n`;
269
+ allViolations.unprotectedPages.forEach(({ page, file, recommendation }) => {
270
+ markdown += `- **${file}**\n`;
271
+ markdown += ` - Page: ${page}\n`;
272
+ if (recommendation) {
273
+ markdown += ` - Recommendation: ${recommendation}\n\n`;
274
+ }
275
+ });
276
+ }
277
+
278
+ // Direct Supabase Auth
279
+ if (allViolations.directSupabaseAuth.length > 0) {
280
+ markdown += `#### ❌ Direct Supabase Auth Usage (${allViolations.directSupabaseAuth.length})\n\n`;
281
+ allViolations.directSupabaseAuth.forEach(({ file, line, recommendation }) => {
282
+ markdown += `- **${file}:${line}**\n`;
283
+ if (recommendation) {
284
+ markdown += ` - Recommendation: ${recommendation}\n\n`;
285
+ }
286
+ });
287
+ }
288
+
289
+ // App Discovery Issues
290
+ if (totalAppDiscovery > 0) {
291
+ markdown += `#### ⚠️ App Discovery Issues (${totalAppDiscovery})\n\n`;
292
+ allViolations.appDiscoveryIssues.forEach(({ issue, file, recommendation }) => {
293
+ markdown += `- **${file}**\n`;
294
+ markdown += ` - Issue: ${issue}\n`;
295
+ if (recommendation) {
296
+ markdown += ` - Recommendation: ${recommendation}\n\n`;
297
+ }
298
+ });
299
+ }
300
+
301
+ // Suggestions
302
+ if (totalSuggestions > 0) {
303
+ markdown += `#### 💡 Suggestions (${totalSuggestions})\n\n`;
304
+ const grouped = {};
305
+ allViolations.suggestions.forEach(s => {
306
+ if (!grouped[s.file]) grouped[s.file] = [];
307
+ grouped[s.file].push(s);
308
+ });
309
+ Object.entries(grouped).forEach(([file, suggestions]) => {
310
+ markdown += `- **${file}**\n`;
311
+ suggestions.forEach(s => {
312
+ markdown += ` - ${s.suggestion}\n`;
313
+ });
314
+ markdown += `\n`;
315
+ });
316
+ }
317
+
318
+ if (totalIssues === 0) {
319
+ markdown += `✅ **Excellent!** No compliance issues found.\n\n`;
320
+ }
321
+
322
+ return markdown;
323
+ }
324
+
325
+ // ANSI color codes
326
+ const colors = {
327
+ reset: '\x1b[0m',
328
+ green: '\x1b[32m',
329
+ yellow: '\x1b[33m',
330
+ blue: '\x1b[34m',
331
+ cyan: '\x1b[36m',
332
+ bold: '\x1b[1m',
333
+ red: '\x1b[31m'
334
+ };
335
+
336
+ // Find project root
337
+ function findProjectRoot() {
338
+ let current = path.resolve(process.cwd());
339
+ while (current !== path.dirname(current)) {
340
+ if (fs.existsSync(path.join(current, 'package.json'))) {
341
+ return current;
342
+ }
343
+ current = path.dirname(current);
344
+ }
345
+ return process.cwd();
346
+ }
347
+
348
+ // Get package.json info
349
+ function getPackageInfo(projectRoot) {
350
+ const packagePath = path.join(projectRoot, 'package.json');
351
+ if (!fs.existsSync(packagePath)) {
352
+ return null;
353
+ }
354
+ try {
355
+ return JSON.parse(fs.readFileSync(packagePath, 'utf8'));
356
+ } catch (error) {
357
+ return null;
358
+ }
359
+ }
360
+
361
+ // Get pace-core version
362
+ function getPaceCoreVersion(packageJson) {
363
+ const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
364
+ return deps['@jmruthers/pace-core'] || 'unknown';
365
+ }
366
+
367
+ // Phase 1: Deterministic Checks
368
+
369
+ // Check file structure
370
+ function checkFileStructure(projectRoot) {
371
+ const issues = [];
372
+ const warnings = [];
373
+
374
+ const requiredDirs = ['src'];
375
+ const recommendedDirs = ['src/components', 'src/hooks', 'src/utils', 'src/pages'];
376
+
377
+ requiredDirs.forEach(dir => {
378
+ const dirPath = path.join(projectRoot, dir);
379
+ if (!fs.existsSync(dirPath)) {
380
+ issues.push({
381
+ type: 'missing-directory',
382
+ directory: dir,
383
+ severity: 'error',
384
+ message: `Required directory '${dir}' is missing`
385
+ });
386
+ }
387
+ });
388
+
389
+ recommendedDirs.forEach(dir => {
390
+ const dirPath = path.join(projectRoot, dir);
391
+ if (!fs.existsSync(dirPath)) {
392
+ warnings.push({
393
+ type: 'missing-directory',
394
+ directory: dir,
395
+ severity: 'warning',
396
+ message: `Recommended directory '${dir}' is missing`
397
+ });
398
+ }
399
+ });
400
+
401
+ return { issues, warnings };
402
+ }
403
+
404
+ // Check config files
405
+ function checkConfigFiles(projectRoot) {
406
+ const issues = [];
407
+ const warnings = [];
408
+
409
+ const requiredConfigs = [
410
+ { file: 'package.json', severity: 'error' },
411
+ { file: 'tsconfig.json', severity: 'error' },
412
+ { file: 'vite.config.ts', severity: 'warning' },
413
+ { file: 'vitest.config.ts', severity: 'warning' }
414
+ ];
415
+
416
+ requiredConfigs.forEach(({ file, severity }) => {
417
+ const filePath = path.join(projectRoot, file);
418
+ if (!fs.existsSync(filePath)) {
419
+ if (severity === 'error') {
420
+ issues.push({
421
+ type: 'missing-config',
422
+ file,
423
+ severity,
424
+ message: `Required config file '${file}' is missing`
425
+ });
426
+ } else {
427
+ warnings.push({
428
+ type: 'missing-config',
429
+ file,
430
+ severity,
431
+ message: `Recommended config file '${file}' is missing`
432
+ });
433
+ }
434
+ }
435
+ });
436
+
437
+ return { issues, warnings };
438
+ }
439
+
440
+ // Check TypeScript strict mode
441
+ function checkTypeScriptConfig(projectRoot) {
442
+ const issues = [];
443
+ const warnings = [];
444
+
445
+ const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
446
+ if (!fs.existsSync(tsconfigPath)) {
447
+ return { issues, warnings };
448
+ }
449
+
450
+ try {
451
+ const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8'));
452
+ const compilerOptions = tsconfig.compilerOptions || {};
453
+
454
+ if (!compilerOptions.strict) {
455
+ issues.push({
456
+ type: 'typescript-config',
457
+ severity: 'error',
458
+ message: 'TypeScript strict mode is not enabled',
459
+ recommendation: 'Set "strict": true in tsconfig.json'
460
+ });
461
+ }
462
+
463
+ if (compilerOptions.noImplicitAny === false) {
464
+ issues.push({
465
+ type: 'typescript-config',
466
+ severity: 'error',
467
+ message: 'noImplicitAny is disabled',
468
+ recommendation: 'Set "noImplicitAny": true in tsconfig.json'
469
+ });
470
+ }
471
+ } catch (error) {
472
+ warnings.push({
473
+ type: 'typescript-config',
474
+ severity: 'warning',
475
+ message: `Could not parse tsconfig.json: ${error.message}`
476
+ });
477
+ }
478
+
479
+ return { issues, warnings };
480
+ }
481
+
482
+ // Check test coverage
483
+ function checkTestCoverage(projectRoot) {
484
+ const issues = [];
485
+ const warnings = [];
486
+
487
+ // Try to find coverage report
488
+ const coveragePaths = [
489
+ path.join(projectRoot, 'coverage', 'coverage-summary.json'),
490
+ path.join(projectRoot, 'coverage', 'coverage-final.json')
491
+ ];
492
+
493
+ let coverageData = null;
494
+ for (const coveragePath of coveragePaths) {
495
+ if (fs.existsSync(coveragePath)) {
496
+ try {
497
+ coverageData = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
498
+ break;
499
+ } catch (error) {
500
+ // Continue to next path
501
+ }
502
+ }
503
+ }
504
+
505
+ if (!coverageData) {
506
+ warnings.push({
507
+ type: 'coverage',
508
+ severity: 'warning',
509
+ message: 'No coverage report found. Run tests with coverage to get accurate metrics.'
510
+ });
511
+ return { issues, warnings };
512
+ }
513
+
514
+ // Check coverage thresholds
515
+ const total = coverageData.total || {};
516
+ const lines = total.lines?.pct || 0;
517
+ const functions = total.functions?.pct || 0;
518
+ const branches = total.branches?.pct || 0;
519
+ const statements = total.statements?.pct || 0;
520
+
521
+ if (lines < 70) {
522
+ warnings.push({
523
+ type: 'coverage',
524
+ severity: 'warning',
525
+ message: `Line coverage is ${lines.toFixed(1)}%, below recommended 70%`
526
+ });
527
+ }
528
+
529
+ if (functions < 70) {
530
+ warnings.push({
531
+ type: 'coverage',
532
+ severity: 'warning',
533
+ message: `Function coverage is ${functions.toFixed(1)}%, below recommended 70%`
534
+ });
535
+ }
536
+
537
+ return { issues, warnings, metrics: { lines, functions, branches, statements } };
538
+ }
539
+
540
+ // Phase 2: Heuristic Checks
541
+
542
+ // Check for large files
543
+ function checkLargeFiles(projectRoot) {
544
+ const suggestions = [];
545
+ const maxLines = 500;
546
+
547
+ function scanDirectory(dir, relativePath = '') {
548
+ const items = fs.readdirSync(dir);
549
+
550
+ items.forEach(item => {
551
+ const fullPath = path.join(dir, item);
552
+ const stat = fs.statSync(fullPath);
553
+
554
+ if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules' && item !== 'dist') {
555
+ scanDirectory(fullPath, path.join(relativePath, item));
556
+ } else if (stat.isFile() && /\.(ts|tsx|js|jsx)$/.test(item)) {
557
+ const content = fs.readFileSync(fullPath, 'utf8');
558
+ const lines = content.split('\n').length;
559
+
560
+ if (lines > maxLines) {
561
+ suggestions.push({
562
+ type: 'large-file',
563
+ file: path.join(relativePath, item),
564
+ lines,
565
+ severity: 'suggestion',
566
+ message: `File has ${lines} lines, consider splitting into smaller modules`
567
+ });
568
+ }
569
+ }
570
+ });
571
+ }
572
+
573
+ const srcPath = path.join(projectRoot, 'src');
574
+ if (fs.existsSync(srcPath)) {
575
+ scanDirectory(srcPath, 'src');
576
+ }
577
+
578
+ return suggestions;
579
+ }
580
+
581
+ // Check for god objects (heuristic)
582
+ function checkGodObjects(projectRoot) {
583
+ const suggestions = [];
584
+
585
+ // This is a simplified heuristic - in practice, you'd use AST parsing
586
+ function scanFile(filePath, relativePath) {
587
+ try {
588
+ const content = fs.readFileSync(filePath, 'utf8');
589
+
590
+ // Heuristic: Files with many exports might be god objects
591
+ const exportCount = (content.match(/export\s+(const|function|class|interface|type)/g) || []).length;
592
+
593
+ if (exportCount > 10) {
594
+ suggestions.push({
595
+ type: 'god-object',
596
+ file: relativePath,
597
+ exportCount,
598
+ severity: 'suggestion',
599
+ message: `File exports ${exportCount} items, consider splitting into smaller modules`
600
+ });
601
+ }
602
+ } catch (error) {
603
+ // Skip files we can't read
604
+ }
605
+ }
606
+
607
+ function scanDirectory(dir, relativePath = '') {
608
+ const items = fs.readdirSync(dir);
609
+
610
+ items.forEach(item => {
611
+ const fullPath = path.join(dir, item);
612
+ const stat = fs.statSync(fullPath);
613
+
614
+ if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules' && item !== 'dist') {
615
+ scanDirectory(fullPath, path.join(relativePath, item));
616
+ } else if (stat.isFile() && /\.(ts|tsx|js|jsx)$/.test(item)) {
617
+ scanFile(fullPath, path.join(relativePath, item));
618
+ }
619
+ });
620
+ }
621
+
622
+ const srcPath = path.join(projectRoot, 'src');
623
+ if (fs.existsSync(srcPath)) {
624
+ scanDirectory(srcPath, 'src');
625
+ }
626
+
627
+ return suggestions;
628
+ }
629
+
630
+ // Generate markdown report
631
+ function generateMarkdownReport(auditResults, projectRoot, packageJson, paceCoreVersion) {
632
+ const timestamp = new Date();
633
+ // Format: yyyymmddhhmmss
634
+ const year = timestamp.getFullYear();
635
+ const month = String(timestamp.getMonth() + 1).padStart(2, '0');
636
+ const day = String(timestamp.getDate()).padStart(2, '0');
637
+ const hours = String(timestamp.getHours()).padStart(2, '0');
638
+ const minutes = String(timestamp.getMinutes()).padStart(2, '0');
639
+ const seconds = String(timestamp.getSeconds()).padStart(2, '0');
640
+ const timestampStr = `${year}${month}${day}${hours}${minutes}${seconds}`;
641
+ const filename = `audit-${timestampStr}.md`;
642
+ const auditDir = path.join(projectRoot, 'audit');
643
+
644
+ // Create audit directory if it doesn't exist
645
+ if (!fs.existsSync(auditDir)) {
646
+ fs.mkdirSync(auditDir, { recursive: true });
647
+ }
648
+
649
+ const reportPath = path.join(auditDir, filename);
650
+
651
+ // Calculate totals
652
+ const phase1Errors = auditResults.phase1.issues.length;
653
+ const phase1Warnings = auditResults.phase1.warnings.length;
654
+ const phase2Suggestions = auditResults.phase2.suggestions.length;
655
+
656
+ // Calculate compliance issues
657
+ const complianceIssues = auditResults.compliance
658
+ ? (auditResults.compliance.customAuthCode.length +
659
+ auditResults.compliance.restrictedImports.length +
660
+ auditResults.compliance.duplicateComponents.length +
661
+ auditResults.compliance.duplicateHooks.length +
662
+ auditResults.compliance.duplicateUtils.length +
663
+ auditResults.compliance.unprotectedPages.length +
664
+ auditResults.compliance.directSupabaseAuth.length +
665
+ auditResults.compliance.providerSetupIssues.length +
666
+ auditResults.compliance.viteConfigIssues.length +
667
+ auditResults.compliance.routerSetupIssues.length +
668
+ auditResults.compliance.duplicateConfig.length)
669
+ : 0;
670
+
671
+ const totalIssues = phase1Errors + phase1Warnings + phase2Suggestions + complianceIssues;
672
+
673
+ // Calculate compliance score (0-100)
674
+ const maxIssues = 100; // Arbitrary max for scoring
675
+ const complianceScore = Math.max(0, 100 - Math.min(100, (totalIssues / maxIssues) * 100));
676
+
677
+ // Generate markdown
678
+ let markdown = `# pace-core Compliance Audit Report\n\n`;
679
+ markdown += `**Generated:** ${timestamp.toISOString()}\n`;
680
+ markdown += `**Project:** ${packageJson?.name || 'Unknown'}\n`;
681
+ markdown += `**pace-core Version:** ${paceCoreVersion}\n`;
682
+ markdown += `**Audit Tool Version:** 1.0.0\n\n`;
683
+
684
+ markdown += `## Executive Summary\n\n`;
685
+ markdown += `- **Total Issues:** ${totalIssues}\n`;
686
+ markdown += `- **Critical Failures:** ${phase1Errors} (Phase 1 - Must Fix)\n`;
687
+ markdown += `- **Warnings:** ${phase1Warnings} (Phase 1 - Should Fix)\n`;
688
+ markdown += `- **Suggestions:** ${phase2Suggestions} (Phase 2 - Consider)\n`;
689
+ markdown += `- **Compliance Issues:** ${complianceIssues} (pace-core Compliance Check)\n`;
690
+ markdown += `- **Compliance Score:** ${complianceScore.toFixed(1)}%\n\n`;
691
+
692
+ // Phase 1: Deterministic Checks
693
+ markdown += `## Phase 1: Deterministic Checks\n\n`;
694
+
695
+ // File Structure
696
+ if (auditResults.phase1.fileStructure.issues.length > 0 || auditResults.phase1.fileStructure.warnings.length > 0) {
697
+ markdown += `### File Structure\n\n`;
698
+ auditResults.phase1.fileStructure.issues.forEach(issue => {
699
+ markdown += `- [ ] ❌ **${issue.directory}**: ${issue.message}\n`;
700
+ });
701
+ auditResults.phase1.fileStructure.warnings.forEach(warning => {
702
+ markdown += `- [ ] ⚠️ **${warning.directory}**: ${warning.message}\n`;
703
+ });
704
+ markdown += `\n`;
705
+ } else {
706
+ markdown += `### File Structure\n\n`;
707
+ markdown += `- [x] ✅ All required directories present\n\n`;
708
+ }
709
+
710
+ // Config Files
711
+ if (auditResults.phase1.configFiles.issues.length > 0 || auditResults.phase1.configFiles.warnings.length > 0) {
712
+ markdown += `### Config Files\n\n`;
713
+ auditResults.phase1.configFiles.issues.forEach(issue => {
714
+ markdown += `- [ ] ❌ **${issue.file}**: ${issue.message}\n`;
715
+ });
716
+ auditResults.phase1.configFiles.warnings.forEach(warning => {
717
+ markdown += `- [ ] ⚠️ **${warning.file}**: ${warning.message}\n`;
718
+ });
719
+ markdown += `\n`;
720
+ } else {
721
+ markdown += `### Config Files\n\n`;
722
+ markdown += `- [x] ✅ All required config files present\n\n`;
723
+ }
724
+
725
+ // TypeScript Config
726
+ if (auditResults.phase1.typescript.issues.length > 0 || auditResults.phase1.typescript.warnings.length > 0) {
727
+ markdown += `### TypeScript Configuration\n\n`;
728
+ auditResults.phase1.typescript.issues.forEach(issue => {
729
+ markdown += `- [ ] ❌ ${issue.message}\n`;
730
+ if (issue.recommendation) {
731
+ markdown += ` - Recommendation: ${issue.recommendation}\n`;
732
+ }
733
+ });
734
+ auditResults.phase1.typescript.warnings.forEach(warning => {
735
+ markdown += `- [ ] ⚠️ ${warning.message}\n`;
736
+ });
737
+ markdown += `\n`;
738
+ } else {
739
+ markdown += `### TypeScript Configuration\n\n`;
740
+ markdown += `- [x] ✅ TypeScript strict mode enabled\n\n`;
741
+ }
742
+
743
+ // Test Coverage
744
+ if (auditResults.phase1.coverage.warnings.length > 0) {
745
+ markdown += `### Test Coverage\n\n`;
746
+ if (auditResults.phase1.coverage.metrics) {
747
+ const m = auditResults.phase1.coverage.metrics;
748
+ markdown += `**Current Coverage:**\n`;
749
+ markdown += `- Lines: ${m.lines.toFixed(1)}%\n`;
750
+ markdown += `- Functions: ${m.functions.toFixed(1)}%\n`;
751
+ markdown += `- Branches: ${m.branches.toFixed(1)}%\n`;
752
+ markdown += `- Statements: ${m.statements.toFixed(1)}%\n\n`;
753
+ }
754
+ auditResults.phase1.coverage.warnings.forEach(warning => {
755
+ markdown += `- [ ] ⚠️ ${warning.message}\n`;
756
+ });
757
+ markdown += `\n`;
758
+ } else if (auditResults.phase1.coverage.metrics) {
759
+ markdown += `### Test Coverage\n\n`;
760
+ const m = auditResults.phase1.coverage.metrics;
761
+ markdown += `- [x] ✅ Coverage meets requirements\n`;
762
+ markdown += ` - Lines: ${m.lines.toFixed(1)}%\n`;
763
+ markdown += ` - Functions: ${m.functions.toFixed(1)}%\n`;
764
+ markdown += ` - Branches: ${m.branches.toFixed(1)}%\n`;
765
+ markdown += ` - Statements: ${m.statements.toFixed(1)}%\n\n`;
766
+ }
767
+
768
+ // Phase 2: Heuristic Checks
769
+ markdown += `## Phase 2: Heuristic Checks (Suggestions)\n\n`;
770
+
771
+ if (auditResults.phase2.suggestions.length === 0) {
772
+ markdown += `- ✅ No suggestions at this time\n\n`;
773
+ } else {
774
+ // Group by type
775
+ const byType = {};
776
+ auditResults.phase2.suggestions.forEach(suggestion => {
777
+ if (!byType[suggestion.type]) {
778
+ byType[suggestion.type] = [];
779
+ }
780
+ byType[suggestion.type].push(suggestion);
781
+ });
782
+
783
+ Object.entries(byType).forEach(([type, suggestions]) => {
784
+ const typeName = type.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
785
+ markdown += `### ${typeName}\n\n`;
786
+ suggestions.forEach(suggestion => {
787
+ markdown += `- 💡 **${suggestion.file}**: ${suggestion.message}\n`;
788
+ if (suggestion.lines) {
789
+ markdown += ` - Lines: ${suggestion.lines}\n`;
790
+ }
791
+ if (suggestion.exportCount) {
792
+ markdown += ` - Exports: ${suggestion.exportCount}\n`;
793
+ }
794
+ });
795
+ markdown += `\n`;
796
+ });
797
+ }
798
+
799
+ // Compliance Check Results
800
+ markdown += `## pace-core Compliance Check\n\n`;
801
+ if (auditResults.compliance) {
802
+ markdown += complianceViolationsToMarkdown(auditResults.compliance);
803
+ } else {
804
+ markdown += `⚠️ Compliance check was not run. Run the audit script to include compliance results.\n\n`;
805
+ }
806
+
807
+ // Recommendations
808
+ markdown += `## Recommendations\n\n`;
809
+
810
+ if (phase1Errors > 0) {
811
+ markdown += `### Must Fix (Phase 1 Failures)\n\n`;
812
+ auditResults.phase1.issues.forEach(issue => {
813
+ markdown += `1. **Priority: High** - ${issue.message || issue.type}\n`;
814
+ if (issue.recommendation) {
815
+ markdown += ` - ${issue.recommendation}\n`;
816
+ }
817
+ });
818
+ markdown += `\n`;
819
+ }
820
+
821
+ if (phase1Warnings > 0) {
822
+ markdown += `### Should Fix (Phase 1 Warnings)\n\n`;
823
+ auditResults.phase1.warnings.forEach(warning => {
824
+ markdown += `1. **Priority: Medium** - ${warning.message || warning.type}\n`;
825
+ if (warning.recommendation) {
826
+ markdown += ` - ${warning.recommendation}\n`;
827
+ }
828
+ });
829
+ markdown += `\n`;
830
+ }
831
+
832
+ if (phase2Suggestions > 0) {
833
+ markdown += `### Consider (Phase 2 Suggestions)\n\n`;
834
+ auditResults.phase2.suggestions.slice(0, 10).forEach(suggestion => {
835
+ markdown += `1. **Priority: Low** - ${suggestion.message}\n`;
836
+ markdown += ` - File: ${suggestion.file}\n`;
837
+ });
838
+ if (phase2Suggestions > 10) {
839
+ markdown += `\n*... and ${phase2Suggestions - 10} more suggestions*\n`;
840
+ }
841
+ markdown += `\n`;
842
+ }
843
+
844
+ if (totalIssues === 0) {
845
+ markdown += `✅ **Excellent!** No issues found. Your codebase is fully compliant.\n\n`;
846
+ }
847
+
848
+ // Write report
849
+ fs.writeFileSync(reportPath, markdown, 'utf8');
850
+
851
+ return { reportPath, filename };
852
+ }
853
+
854
+ // Main function
855
+ function main() {
856
+ const projectRoot = findProjectRoot();
857
+ const packageJson = getPackageInfo(projectRoot);
858
+ const paceCoreVersion = getPaceCoreVersion(packageJson);
859
+
860
+ console.log(`${colors.cyan}${colors.bold}═══════════════════════════════════════════════════════════${colors.reset}`);
861
+ console.log(`${colors.cyan}${colors.bold} pace-core Comprehensive Audit${colors.reset}`);
862
+ console.log(`${colors.cyan}${colors.bold}═══════════════════════════════════════════════════════════${colors.reset}\n`);
863
+ console.log(`${colors.cyan}Project:${colors.reset} ${packageJson?.name || 'Unknown'}`);
864
+ console.log(`${colors.cyan}Root:${colors.reset} ${projectRoot}`);
865
+ console.log(`${colors.cyan}pace-core Version:${colors.reset} ${paceCoreVersion}\n`);
866
+
867
+ // Note: We don't call complianceChecker.main() because it calls process.exit()
868
+ // The compliance check output you see is from a previous run or separate execution
869
+ // We'll continue with Phase 1 and Phase 2 checks and generate markdown report
870
+
871
+ // Phase 1: Deterministic Checks
872
+ console.log(`\n${colors.cyan}Phase 1: Running deterministic checks...${colors.reset}`);
873
+ const fileStructure = checkFileStructure(projectRoot);
874
+ const configFiles = checkConfigFiles(projectRoot);
875
+ const typescript = checkTypeScriptConfig(projectRoot);
876
+ const coverage = checkTestCoverage(projectRoot);
877
+
878
+ const phase1 = {
879
+ issues: [
880
+ ...fileStructure.issues,
881
+ ...configFiles.issues,
882
+ ...typescript.issues,
883
+ ...coverage.issues
884
+ ],
885
+ warnings: [
886
+ ...fileStructure.warnings,
887
+ ...configFiles.warnings,
888
+ ...typescript.warnings,
889
+ ...coverage.warnings
890
+ ],
891
+ fileStructure,
892
+ configFiles,
893
+ typescript,
894
+ coverage
895
+ };
896
+
897
+ // Phase 2: Heuristic Checks
898
+ console.log(`${colors.cyan}Phase 2: Running heuristic checks...${colors.reset}`);
899
+ const largeFiles = checkLargeFiles(projectRoot);
900
+ const godObjects = checkGodObjects(projectRoot);
901
+
902
+ const phase2 = {
903
+ suggestions: [
904
+ ...largeFiles,
905
+ ...godObjects
906
+ ]
907
+ };
908
+
909
+ // Compliance Check
910
+ console.log(`${colors.cyan}Running pace-core compliance check...${colors.reset}`);
911
+ let complianceResults = null;
912
+ try {
913
+ complianceResults = runComplianceCheck(projectRoot);
914
+ console.log(`${colors.green}✓ Compliance check complete${colors.reset}`);
915
+ } catch (error) {
916
+ console.error(`${colors.red}Compliance check failed: ${error.message}${colors.reset}`);
917
+ if (error.stack) {
918
+ console.error(`${colors.red}Stack: ${error.stack}${colors.reset}`);
919
+ }
920
+ console.error(`${colors.yellow}Continuing with other audit checks...${colors.reset}`);
921
+ }
922
+
923
+ // Generate report
924
+ console.log(`\n${colors.cyan}Generating audit report...${colors.reset}`);
925
+ const auditResults = { phase1, phase2, compliance: complianceResults };
926
+ const { reportPath, filename } = generateMarkdownReport(
927
+ auditResults,
928
+ projectRoot,
929
+ packageJson,
930
+ paceCoreVersion
931
+ );
932
+
933
+ console.log(`\n${colors.green}${colors.bold}✅ Audit complete!${colors.reset}`);
934
+ console.log(`${colors.green}Report saved to: ${reportPath}${colors.reset}\n`);
935
+
936
+ // Summary
937
+ const totalErrors = phase1.issues.length;
938
+ const totalWarnings = phase1.warnings.length;
939
+ const totalSuggestions = phase2.suggestions.length;
940
+
941
+ console.log(`${colors.bold}Summary:${colors.reset}`);
942
+ console.log(` ${colors.red}Critical Failures:${colors.reset} ${totalErrors}`);
943
+ console.log(` ${colors.yellow}Warnings:${colors.reset} ${totalWarnings}`);
944
+ console.log(` ${colors.blue}Suggestions:${colors.reset} ${totalSuggestions}`);
945
+ console.log(`\n${colors.cyan}See ${filename} for detailed report.${colors.reset}\n`);
946
+
947
+ process.exit(totalErrors > 0 ? 1 : 0);
948
+ }
949
+
950
+ // Run if called directly
951
+ if (require.main === module) {
952
+ try {
953
+ main();
954
+ } catch (error) {
955
+ console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
956
+ console.error(error.stack);
957
+ process.exit(1);
958
+ }
959
+ }
960
+
961
+ module.exports = { main };