@chemx/starter-kit 26.9.11-481 → 26.9.12-107

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 (46) hide show
  1. package/cli/audit/ai-slop-detector.js +262 -0
  2. package/cli/audit/extended-visitors.js +359 -0
  3. package/cli/audit/history.js +19 -2
  4. package/cli/audit/metrics.js +69 -0
  5. package/cli/audit/prompts.js +175 -48
  6. package/cli/audit/reporter-banner.d.ts +3 -0
  7. package/cli/audit/reporter-banner.js +74 -0
  8. package/cli/audit/reporter-grades.js +11 -26
  9. package/cli/audit/reporter-grouping-markdown.js +83 -0
  10. package/cli/audit/reporter-grouping.d.ts +42 -0
  11. package/cli/audit/reporter-grouping.js +243 -0
  12. package/cli/audit/reporter-markdown.js +34 -22
  13. package/cli/audit/reporter-sections.d.ts +10 -0
  14. package/cli/audit/reporter-sections.js +259 -0
  15. package/cli/audit/reporter-summary.d.ts +4 -0
  16. package/cli/audit/reporter-summary.js +142 -0
  17. package/cli/audit/reporter-utils.js +13 -1
  18. package/cli/audit/reporter.d.ts +16 -0
  19. package/cli/audit/reporter.js +47 -420
  20. package/cli/audit/rules-helpers.js +16 -0
  21. package/cli/audit/rules-registry.js +88 -1
  22. package/cli/audit/rules.js +32 -1
  23. package/cli/audit/social-git.js +133 -131
  24. package/cli/audit/social.js +15 -2
  25. package/cli/audit/types.d.ts +28 -27
  26. package/cli/audit.js +56 -3
  27. package/cli/badge.js +262 -0
  28. package/cli/generator-templates.js +264 -0
  29. package/cli/generator.d.ts +10 -0
  30. package/cli/generator.js +151 -0
  31. package/cli/help.js +17 -7
  32. package/cli/index.js +26 -16
  33. package/cli/installer.d.ts +2 -0
  34. package/cli/installer.js +52 -38
  35. package/cli/license.js +17 -4
  36. package/cli/navigator-actions.js +63 -1
  37. package/cli/navigator-banner.js +17 -6
  38. package/cli/navigator-grades.js +9 -0
  39. package/cli/navigator-menu.js +36 -0
  40. package/cli/navigator-share.js +7 -4
  41. package/cli/navigator.d.ts +5 -1
  42. package/cli/navigator.js +49 -12
  43. package/cli/scaffold.js +3 -54
  44. package/cli/terminal.js +4 -1
  45. package/docs/CHANGELOG.md +50 -0
  46. package/package.json +1 -1
@@ -7,6 +7,7 @@ export const calculateMolecularHealthScore = (violations, totalFiles) => {
7
7
 
8
8
  let penalty = 0;
9
9
  for (const v of violations) {
10
+ if (v.isAiSlop) continue;
10
11
  if (v.severity === 'CRITICAL') penalty += 8;
11
12
  else if (v.severity === 'HIGH') penalty += 4;
12
13
  else if (v.severity === 'MEDIUM') penalty += 2;
@@ -56,6 +57,7 @@ export const calculatePillarBreakdown = (violations) => {
56
57
  }
57
58
 
58
59
  for (const v of violations) {
60
+ if (v.isAiSlop) continue;
59
61
  const pillarName = v.pillar || PILLARS.PILLAR_1;
60
62
  if (!breakdown[pillarName]) {
61
63
  breakdown[pillarName] = {
@@ -177,3 +179,70 @@ export const calculateHotspots = (violations, fileStats, limit = 5) => {
177
179
  };
178
180
 
179
181
  export const calculateQuantumHealthScore = calculateMolecularHealthScore;
182
+
183
+ export const calculateAiSlopScore = (violations, totalFiles) => {
184
+ const slopViolations = violations.filter((v) => v.isAiSlop);
185
+ if (totalFiles === 0) {
186
+ return {
187
+ score: 100,
188
+ grade: 'A+',
189
+ label: 'Pure Artisanal',
190
+ violationsCount: 0,
191
+ breakdown: { critical: 0, high: 0, medium: 0, low: 0 }
192
+ };
193
+ }
194
+
195
+ let penalty = 0;
196
+ let critical = 0;
197
+ let high = 0;
198
+ let medium = 0;
199
+ let low = 0;
200
+
201
+ for (const v of slopViolations) {
202
+ if (v.severity === 'CRITICAL') {
203
+ penalty += 10;
204
+ critical += 1;
205
+ } else if (v.severity === 'HIGH') {
206
+ penalty += 5;
207
+ high += 1;
208
+ } else if (v.severity === 'MEDIUM') {
209
+ penalty += 2;
210
+ medium += 1;
211
+ } else {
212
+ penalty += 1;
213
+ low += 1;
214
+ }
215
+ }
216
+
217
+ const scale = Math.max(1, Math.log10(totalFiles + 1));
218
+ const adjustedPenalty = penalty / scale;
219
+ const score = Math.max(0, Math.min(100, Math.round(100 - adjustedPenalty)));
220
+
221
+ let grade = 'F';
222
+ let label = 'Severe AI Slop Infection';
223
+
224
+ if (score >= 95) {
225
+ grade = 'A+';
226
+ label = 'Pure Artisanal';
227
+ } else if (score >= 90) {
228
+ grade = 'A';
229
+ label = 'Near-Zero Slop';
230
+ } else if (score >= 80) {
231
+ grade = 'B';
232
+ label = 'Low AI Residue';
233
+ } else if (score >= 70) {
234
+ grade = 'C';
235
+ label = 'Moderate Slop Drift';
236
+ } else if (score >= 60) {
237
+ grade = 'D';
238
+ label = 'High AI Slop Hazard';
239
+ }
240
+
241
+ return {
242
+ score,
243
+ grade,
244
+ label,
245
+ violationsCount: slopViolations.length,
246
+ breakdown: { critical, high, medium, low }
247
+ };
248
+ };
@@ -1,35 +1,108 @@
1
+ import { groupViolationsByRule } from './reporter-grouping.js';
2
+
1
3
  const CYAN = '\x1b[38;2;98;201;255m';
2
4
  const BOLD = '\x1b[1m';
3
5
  const DIM = '\x1b[2m';
4
6
  const RESET = '\x1b[0m';
5
7
 
6
- export const buildGradeFPrompt = (report) => {
7
- const { violations, hotspots } = report;
8
- const critical = violations.filter((v) => v.severity === 'CRITICAL');
8
+ const buildPathTree = (violations) => {
9
+ const root = { dirs: new Map(), files: new Map() };
10
+
11
+ for (const v of violations) {
12
+ const rawPath = v.filePath || '';
13
+ const normalized = rawPath.replace(/\\/g, '/').replace(/^\.\//, '');
14
+ const segments = normalized.split('/');
15
+ const fileName = segments.pop();
16
+
17
+ let current = root;
18
+ for (const seg of segments) {
19
+ const segName = seg.endsWith('/') ? seg : `${seg}/`;
20
+ if (!current.dirs.has(segName)) {
21
+ current.dirs.set(segName, { dirs: new Map(), files: new Map() });
22
+ }
23
+ current = current.dirs.get(segName);
24
+ }
25
+
26
+ if (!current.files.has(fileName)) {
27
+ current.files.set(fileName, []);
28
+ }
29
+ const loc = v.column ? `${v.line}:${v.column}` : `${v.line}`;
30
+ current.files.get(fileName).push(loc);
31
+ }
32
+
33
+ return root;
34
+ };
35
+
36
+ const renderTreeLines = (node, depth = 0) => {
37
+ const lines = [];
38
+ const indent = ' ' + ' '.repeat(depth);
39
+
40
+ const sortedDirs = Array.from(node.dirs.entries()).sort((a, b) => a[0].localeCompare(b[0]));
41
+ for (const [dirName, childNode] of sortedDirs) {
42
+ lines.push(`${indent}* ${dirName}`);
43
+ const childLines = renderTreeLines(childNode, depth + 1);
44
+ lines.push(...childLines);
45
+ }
46
+
47
+ const sortedFiles = Array.from(node.files.entries()).sort((a, b) => a[0].localeCompare(b[0]));
48
+ for (const [fileName, locs] of sortedFiles) {
49
+ const uniqueLocs = Array.from(new Set(locs)).join(', ');
50
+ lines.push(`${indent}- \`${fileName}:${uniqueLocs}\``);
51
+ }
52
+
53
+ return lines;
54
+ };
55
+
56
+ export const formatGroupedPromptViolations = (violations = []) => {
57
+ const hasNoViolations = violations.length === 0;
58
+ if (hasNoViolations) return [];
59
+
60
+ const ruleGroups = groupViolationsByRule(violations);
61
+ const lines = [];
62
+
63
+ ruleGroups.forEach((rg, idx) => {
64
+ const countLabel = rg.total === 1 ? '1 item' : `${rg.total} items`;
65
+ lines.push(`${idx + 1}. [${rg.rule}] (${countLabel})`);
66
+ lines.push(` Hazard: ${rg.hazard}`);
67
+ lines.push(` Directive: ${rg.directive}`);
68
+ lines.push(' Locations:');
69
+
70
+ const tree = buildPathTree(rg.violations);
71
+ const treeLines = renderTreeLines(tree, 0);
72
+ lines.push(...treeLines);
73
+ lines.push('');
74
+ });
75
+
76
+ return lines;
77
+ };
78
+
79
+ export const buildGradeFPrompt = (report, options = {}) => {
80
+ const { excludeAiSlop = false } = options;
81
+ const { violations = [], hotspots = [] } = report;
82
+ const critical = violations
83
+ .filter((v) => v.severity === 'CRITICAL')
84
+ .filter((v) => (excludeAiSlop ? !v.isAiSlop : true));
9
85
  const extremeMonoliths = hotspots.filter((h) => h.lineCount >= 2000);
10
86
 
11
- if (critical.length === 0 && extremeMonoliths.length === 0) return '';
87
+ const hasNoCritical = critical.length === 0;
88
+ const hasNoMonoliths = extremeMonoliths.length === 0;
89
+ if (hasNoCritical && hasNoMonoliths) return '';
12
90
 
13
91
  const lines = [];
14
92
  lines.push('Act as a Principal Systems Architect. Surgically refactor the following Grade F Critical Context Hazards in our codebase according to Chemical X Molecular Architecture Standards:\n');
15
93
 
16
94
  if (extremeMonoliths.length > 0) {
17
95
  lines.push('### EXTREME MONOLITHS (>= 2,000 lines of code) : MONOLITH DECOMPOSITION');
96
+ lines.push('Action: Decompose into crystalline single-responsibility capsules (< 100 lines per molecule). Convert top-level view into a declarative Table-of-Contents view.\n');
18
97
  extremeMonoliths.forEach((h, i) => {
19
98
  lines.push(`${i + 1}. File: \`${h.filePath}\` (${h.lineCount} lines)`);
20
- lines.push(' Action: Decompose this monolith into crystalline single-responsibility capsules (< 100 lines per molecule). Convert top-level view into a declarative Table-of-Contents view.');
21
99
  });
22
100
  lines.push('');
23
101
  }
24
102
 
25
103
  if (critical.length > 0) {
26
104
  lines.push('### CRITICAL AST VIOLATIONS');
27
- critical.forEach((v, i) => {
28
- lines.push(`${i + 1}. \`${v.filePath}:${v.line}:${v.column}\` [${v.rule}]`);
29
- lines.push(` Hazard: ${v.hazard}`);
30
- lines.push(` Directive: ${v.directive}`);
31
- });
32
- lines.push('');
105
+ lines.push(...formatGroupedPromptViolations(critical));
33
106
  }
34
107
 
35
108
  lines.push('### STRICT EXECUTION RULES:');
@@ -42,33 +115,33 @@ export const buildGradeFPrompt = (report) => {
42
115
  return lines.join('\n');
43
116
  };
44
117
 
45
- export const buildGradeDPrompt = (report) => {
46
- const { violations, hotspots } = report;
47
- const high = violations.filter((v) => v.severity === 'HIGH');
118
+ export const buildGradeDPrompt = (report, options = {}) => {
119
+ const { excludeAiSlop = false } = options;
120
+ const { violations = [], hotspots = [] } = report;
121
+ const high = violations
122
+ .filter((v) => v.severity === 'HIGH')
123
+ .filter((v) => (excludeAiSlop ? !v.isAiSlop : true));
48
124
  const severeMonoliths = hotspots.filter((h) => h.lineCount >= 1000 && h.lineCount < 2000);
49
125
 
50
- if (high.length === 0 && severeMonoliths.length === 0) return '';
126
+ const hasNoHigh = high.length === 0;
127
+ const hasNoMonoliths = severeMonoliths.length === 0;
128
+ if (hasNoHigh && hasNoMonoliths) return '';
51
129
 
52
130
  const lines = [];
53
131
  lines.push('Act as a Principal Systems Architect. Refactor the following Grade D High-Severity Architectural Debts according to Chemical X Molecular Architecture Standards:\n');
54
132
 
55
133
  if (severeMonoliths.length > 0) {
56
134
  lines.push('### SEVERE MONOLITHS (1,000 - 1,999 lines of code)');
135
+ lines.push('Action: Extract sub-features into isolated molecule capsules (< 100 lines of code) and domain composables.\n');
57
136
  severeMonoliths.forEach((h, i) => {
58
137
  lines.push(`${i + 1}. File: \`${h.filePath}\` (${h.lineCount} lines)`);
59
- lines.push(' Action: Extract sub-features into isolated molecule capsules (< 100 lines of code) and domain composables.');
60
138
  });
61
139
  lines.push('');
62
140
  }
63
141
 
64
142
  if (high.length > 0) {
65
143
  lines.push('### HIGH SEVERITY VIOLATIONS');
66
- high.forEach((v, i) => {
67
- lines.push(`${i + 1}. \`${v.filePath}:${v.line}:${v.column}\` [${v.rule}]`);
68
- lines.push(` Hazard: ${v.hazard}`);
69
- lines.push(` Directive: ${v.directive}`);
70
- });
71
- lines.push('');
144
+ lines.push(...formatGroupedPromptViolations(high));
72
145
  }
73
146
 
74
147
  lines.push('### STRICT EXECUTION RULES:');
@@ -79,33 +152,33 @@ export const buildGradeDPrompt = (report) => {
79
152
  return lines.join('\n');
80
153
  };
81
154
 
82
- export const buildGradeCPrompt = (report) => {
83
- const { violations, hotspots } = report;
84
- const medium = violations.filter((v) => v.severity === 'MEDIUM');
155
+ export const buildGradeCPrompt = (report, options = {}) => {
156
+ const { excludeAiSlop = false } = options;
157
+ const { violations = [], hotspots = [] } = report;
158
+ const medium = violations
159
+ .filter((v) => v.severity === 'MEDIUM')
160
+ .filter((v) => (excludeAiSlop ? !v.isAiSlop : true));
85
161
  const warningMonoliths = hotspots.filter((h) => h.lineCount >= 500 && h.lineCount < 1000);
86
162
 
87
- if (medium.length === 0 && warningMonoliths.length === 0) return '';
163
+ const hasNoMedium = medium.length === 0;
164
+ const hasNoMonoliths = warningMonoliths.length === 0;
165
+ if (hasNoMedium && hasNoMonoliths) return '';
88
166
 
89
167
  const lines = [];
90
168
  lines.push('Act as a Senior Frontend Engineer. Refactor the following Grade C Medium-Severity Technical Debts according to Chemical X Molecular Architecture Standards:\n');
91
169
 
92
170
  if (warningMonoliths.length > 0) {
93
171
  lines.push('### WARNING MONOLITHS (500 - 999 lines of code)');
172
+ lines.push('Action: Bring file under 500 line budget by extracting helper functions, types, and child molecules.\n');
94
173
  warningMonoliths.forEach((h, i) => {
95
174
  lines.push(`${i + 1}. File: \`${h.filePath}\` (${h.lineCount} lines)`);
96
- lines.push(' Action: Bring file under 500 line budget by extracting helper functions, types, and child molecules.');
97
175
  });
98
176
  lines.push('');
99
177
  }
100
178
 
101
179
  if (medium.length > 0) {
102
180
  lines.push('### MEDIUM SEVERITY VIOLATIONS');
103
- medium.forEach((v, i) => {
104
- lines.push(`${i + 1}. \`${v.filePath}:${v.line}:${v.column}\` [${v.rule}]`);
105
- lines.push(` Hazard: ${v.hazard}`);
106
- lines.push(` Directive: ${v.directive}`);
107
- });
108
- lines.push('');
181
+ lines.push(...formatGroupedPromptViolations(medium));
109
182
  }
110
183
 
111
184
  lines.push('### STRICT EXECUTION RULES:');
@@ -116,22 +189,21 @@ export const buildGradeCPrompt = (report) => {
116
189
  return lines.join('\n');
117
190
  };
118
191
 
119
- export const buildGradeBPrompt = (report) => {
120
- const { violations } = report;
121
- const low = violations.filter((v) => v.severity === 'LOW');
192
+ export const buildGradeBPrompt = (report, options = {}) => {
193
+ const { excludeAiSlop = false } = options;
194
+ const { violations = [] } = report;
195
+ const low = violations
196
+ .filter((v) => v.severity === 'LOW')
197
+ .filter((v) => (excludeAiSlop ? !v.isAiSlop : true));
122
198
 
123
- if (low.length === 0) return '';
199
+ const hasNoLow = low.length === 0;
200
+ if (hasNoLow) return '';
124
201
 
125
202
  const lines = [];
126
203
  lines.push('Act as a Clean Code Specialist. Clean up the following Grade B Low-Severity Hygiene Issues according to Chemical X standards:\n');
127
204
 
128
205
  lines.push('### LOW HYGIENE VIOLATIONS');
129
- low.forEach((v, i) => {
130
- lines.push(`${i + 1}. \`${v.filePath}:${v.line}:${v.column}\` [${v.rule}]`);
131
- lines.push(` Hazard: ${v.hazard}`);
132
- lines.push(` Directive: ${v.directive}`);
133
- });
134
- lines.push('');
206
+ lines.push(...formatGroupedPromptViolations(low));
135
207
 
136
208
  lines.push('### STRICT EXECUTION RULES:');
137
209
  lines.push('1. Typography Hygiene: Replace all em dashes with standard hyphens (-) or colons (:).');
@@ -141,15 +213,70 @@ export const buildGradeBPrompt = (report) => {
141
213
  return lines.join('\n');
142
214
  };
143
215
 
216
+ export const buildAiSlopPrompt = (report) => {
217
+ const { violations = [] } = report;
218
+ const slopViolations = violations.filter((v) => Boolean(v.isAiSlop));
219
+
220
+ const hasNoSlop = slopViolations.length === 0;
221
+ if (hasNoSlop) return '';
222
+
223
+ const lines = [];
224
+ lines.push('Act as a Clean Code Specialist and Code Authenticity Guardian. Eliminate the following AI Slop and conversational artifacts from our codebase according to Chemical X standards:\n');
225
+
226
+ lines.push('### AI SLOP & CODE AUTHENTICITY VIOLATIONS');
227
+ lines.push(...formatGroupedPromptViolations(slopViolations));
228
+
229
+ lines.push('### STRICT EXECUTION RULES:');
230
+ lines.push('1. Conversational Residue: Completely remove leaked AI conversational preambles, assistant markdown code fences, and pleasantry comments.');
231
+ lines.push('2. Truncation Placeholders: Replace lazy AI truncation placeholders with complete, robust, and verifiable implementations.');
232
+ lines.push('3. Echo Comments: Delete trivial parroting comments that merely restate adjacent self-documenting code.');
233
+ lines.push('4. Paranoia Catch Wrappers: Replace shallow or empty catch blocks with intentional error propagation or ResultTuple ([data, error]).');
234
+ lines.push('5. Inline Utility Reinventions: Replace reinvented inline helper utilities with shared project utilities or native methods.');
235
+ lines.push('6. Type Widening: Eliminate lazy "any" type casts on error parameters or variables; enforce strict typing.');
236
+
237
+ return lines.join('\n');
238
+ };
239
+
240
+ export const buildHotspotsPrompt = (report) => {
241
+ const { hotspots = [] } = report;
242
+ const monolithHotspots = hotspots.filter((h) => h.isMonolith || h.lineCount > 500);
243
+
244
+ const hasNoMonoliths = monolithHotspots.length === 0;
245
+ if (hasNoMonoliths) return '';
246
+
247
+ const lines = [];
248
+ lines.push('Act as a Principal Systems Architect. Surgically decompose the following monolithic hotspot files according to Chemical X Molecular Architecture Standards:\n');
249
+
250
+ lines.push('### MONOLITHIC REFACTORING HOTSPOTS');
251
+ lines.push('Action: Decompose into single-responsibility crystalline molecule capsules (< 100 lines) and dedicated domain composables.\n');
252
+ monolithHotspots.forEach((h, i) => {
253
+ const tier = h.monolithTier ? `[${h.monolithTier} MONOLITH]` : '[MONOLITH]';
254
+ lines.push(`${i + 1}. File: \`${h.filePath}\` (${h.lineCount} lines, ${h.violationCount} hazards) ${tier}`);
255
+ });
256
+ lines.push('');
257
+
258
+ lines.push('### STRICT EXECUTION RULES:');
259
+ lines.push('1. Molecular Limits: Molecule capsules must strictly remain under 100 lines per file.');
260
+ lines.push('2. Table-of-Contents Views: Top-level page views must be 10 to 20 line declarative Table of Contents assembling molecules via named slots.');
261
+ lines.push('3. Composable Return Contracts: Custom hooks/composables must strictly limit returns to 3 to 5 properties (State + Status + Actions).');
262
+ lines.push('4. Type Co-location: Co-locate granular types/*.d.ts files inside each feature capsule (< 100 lines per type file) instead of creating type monoliths.');
263
+ lines.push('5. Zero breaking changes to external component APIs, route exports, or existing props.');
264
+
265
+ return lines.join('\n');
266
+ };
267
+
144
268
  export const buildMasterPrompt = (report) => {
145
269
  const sections = [
146
- buildGradeFPrompt(report),
147
- buildGradeDPrompt(report),
148
- buildGradeCPrompt(report),
149
- buildGradeBPrompt(report)
270
+ buildGradeFPrompt(report, { excludeAiSlop: true }),
271
+ buildGradeDPrompt(report, { excludeAiSlop: true }),
272
+ buildGradeCPrompt(report, { excludeAiSlop: true }),
273
+ buildGradeBPrompt(report, { excludeAiSlop: true }),
274
+ buildAiSlopPrompt(report),
275
+ buildHotspotsPrompt(report)
150
276
  ].filter(Boolean);
151
277
 
152
- if (sections.length === 0) return '';
278
+ const hasNoSections = sections.length === 0;
279
+ if (hasNoSections) return '';
153
280
 
154
281
  const header = `Act as a Principal Systems Architect. Execute a phased architectural refactoring of our codebase according to Chemical X Molecular Architecture Standards.\n\n`;
155
282
  return header + sections.join('\n\n---\n\n');
@@ -0,0 +1,3 @@
1
+ import type { AuditReport } from './types';
2
+
3
+ export declare function getChemicalXAsciiBanner(gradeOrReport?: string | AuditReport | null): string;
@@ -0,0 +1,74 @@
1
+ import { resolveGradeColor } from './reporter-utils.js';
2
+ import { getAsciiGradeLines } from './reporter-ascii.js';
3
+
4
+ const BANNER_ART = [
5
+ ' ██████╗██╗ ██╗███████╗███╗ ███╗██╗ ██████╗ █████╗ ██╗ ██╗ ██╗',
6
+ '██╔════╝██║ ██║██╔════╝████╗ ████║██║██╔════╝██╔══██╗██║ ╚██╗██╔╝',
7
+ '██║ ███████║█████╗ ██╔████╔██║██║██║ ███████║██║ ╚███╔╝ ',
8
+ '██║ ██╔══██║██╔══╝ ██║╚██╔╝██║██║██║ ██╔══██║██║ ██╔██╗ ',
9
+ '╚██████╗██║ ██║███████╗██║ ╚═╝ ██║██║╚██████╗██║ ██║███████╗ ██╔╝ ██╗',
10
+ ' ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝'
11
+ ];
12
+
13
+ const MAX_BANNER_LEN = 74;
14
+
15
+ export const getChemicalXAsciiBanner = (gradeOrReport = null) => {
16
+ const grade = typeof gradeOrReport === 'object' && gradeOrReport !== null
17
+ ? gradeOrReport.health?.grade
18
+ : gradeOrReport;
19
+
20
+ const lines = [];
21
+ lines.push(' \x1b[1m\x1b[37mThe Secret Sauce to \x1b[38;2;98;201;255mVibe Coding\x1b[0m');
22
+
23
+ const gColor = grade ? resolveGradeColor(grade) : '';
24
+ const gradeLines = grade ? getAsciiGradeLines(grade, gColor, true) : [];
25
+ const termWidth = process.stdout.columns || 0;
26
+ const isSideBySide = Boolean(!termWidth || termWidth >= 105);
27
+
28
+ for (let lineIdx = 0; lineIdx < BANNER_ART.length; lineIdx++) {
29
+ const line = BANNER_ART[lineIdx];
30
+ let out = '';
31
+ for (let i = 0; i < line.length; i++) {
32
+ const ch = line[i];
33
+ if (ch === ' ') {
34
+ out += ' ';
35
+ continue;
36
+ }
37
+ const t = i / (MAX_BANNER_LEN - 1);
38
+ let r;
39
+ let g;
40
+ let b;
41
+ if (t < 0.5) {
42
+ const factor = t * 2;
43
+ r = Math.round(244 * (1 - factor) + 192 * factor);
44
+ g = Math.round(114 * (1 - factor) + 132 * factor);
45
+ b = Math.round(182 * (1 - factor) + 252 * factor);
46
+ } else {
47
+ const factor = (t - 0.5) * 2;
48
+ r = Math.round(192 * (1 - factor) + 129 * factor);
49
+ g = Math.round(132 * (1 - factor) + 140 * factor);
50
+ b = Math.round(252 * (1 - factor) + 248 * factor);
51
+ }
52
+ out += `\x1b[38;2;${r};${g};${b}m\x1b[1m${ch}\x1b[0m`;
53
+ }
54
+
55
+ if (isSideBySide && gradeLines.length > lineIdx) {
56
+ out += ` ${gradeLines[lineIdx]}`;
57
+ }
58
+
59
+ lines.push(out);
60
+ }
61
+
62
+ const subtitle = 'Architectural guardrails to eliminate token burn and AI hallucinations.';
63
+ const pad = ' '.repeat(Math.max(0, Math.floor((MAX_BANNER_LEN - subtitle.length) / 2)));
64
+ lines.push(`${pad}\x1b[2m${subtitle}\x1b[0m\n`);
65
+
66
+ if (!isSideBySide && gradeLines.length > 0) {
67
+ for (const gl of gradeLines) {
68
+ lines.push(` ${gl}`);
69
+ }
70
+ lines.push('');
71
+ }
72
+
73
+ return lines.join('\n');
74
+ };
@@ -17,6 +17,7 @@ import {
17
17
  groupViolationsBySeverity
18
18
  } from './reporter-utils.js';
19
19
  import { getReportCardAsciiLines } from './reporter-ascii.js';
20
+ import { renderGroupedViolationsTerminal } from './reporter-grouping.js';
20
21
 
21
22
  export const formatGradeFSection = (report) => {
22
23
  const { violations, hotspots, pillars } = report;
@@ -44,12 +45,8 @@ export const formatGradeFSection = (report) => {
44
45
  lines.push(` ${DIM}Codebase is free of extreme monoliths (>2,000 lines of code) and critical violations.${RESET}`);
45
46
  } else {
46
47
  if (critical.length > 0) {
47
- lines.push(`\n ${BOLD}${RED}🚨 CRITICAL AST VIOLATIONS (${critical.length}):${RESET}`);
48
- critical.forEach((v, idx) => {
49
- lines.push(` ${RED}[#${idx + 1} CRITICAL]${RESET} [${v.rule}] ${YELLOW}${v.filePath}:${v.line}:${v.column}${RESET}`);
50
- lines.push(` Hazard: ${v.hazard}`);
51
- lines.push(` Directive: ${CYAN}${v.directive}${RESET}\n`);
52
- });
48
+ lines.push(`\n ${BOLD}${RED}🚨 CRITICAL AST VIOLATIONS (${critical.length}):${RESET}\n`);
49
+ lines.push(renderGroupedViolationsTerminal(critical));
53
50
  }
54
51
 
55
52
  if (extremeMonoliths.length > 0) {
@@ -105,12 +102,8 @@ export const formatGradeDSection = (report) => {
105
102
  lines.push(` ${DIM}No severe monoliths (1,000-1,999 lines of code) or saturated hook anti-patterns.${RESET}`);
106
103
  } else {
107
104
  if (high.length > 0) {
108
- lines.push(`\n ${BOLD}${ORANGE}âš ī¸ HIGH SEVERITY VIOLATIONS (${high.length}):${RESET}`);
109
- high.forEach((v, idx) => {
110
- lines.push(` ${ORANGE}[#${idx + 1} HIGH]${RESET} [${v.rule}] ${YELLOW}${v.filePath}:${v.line}:${v.column}${RESET}`);
111
- lines.push(` Hazard: ${v.hazard}`);
112
- lines.push(` Directive: ${CYAN}${v.directive}${RESET}\n`);
113
- });
105
+ lines.push(`\n ${BOLD}${ORANGE}âš ī¸ HIGH SEVERITY VIOLATIONS (${high.length}):${RESET}\n`);
106
+ lines.push(renderGroupedViolationsTerminal(high));
114
107
  }
115
108
 
116
109
  if (severeMonoliths.length > 0) {
@@ -163,12 +156,8 @@ export const formatGradeCSection = (report) => {
163
156
  lines.push(` ${DIM}All files remain under 500 lines with clean styling and handlers.${RESET}`);
164
157
  } else {
165
158
  if (medium.length > 0) {
166
- lines.push(`\n ${BOLD}${CYAN}⚡ MEDIUM SEVERITY VIOLATIONS (${medium.length}):${RESET}`);
167
- medium.forEach((v, idx) => {
168
- lines.push(` ${YELLOW}[#${idx + 1} MED]${RESET} [${v.rule}] ${YELLOW}${v.filePath}:${v.line}:${v.column}${RESET}`);
169
- lines.push(` Hazard: ${v.hazard}`);
170
- lines.push(` Directive: ${CYAN}${v.directive}${RESET}\n`);
171
- });
159
+ lines.push(`\n ${BOLD}${CYAN}⚡ MEDIUM SEVERITY VIOLATIONS (${medium.length}):${RESET}\n`);
160
+ lines.push(renderGroupedViolationsTerminal(medium));
172
161
  }
173
162
 
174
163
  if (warningMonoliths.length > 0) {
@@ -209,12 +198,8 @@ export const formatGradeBSection = (report) => {
209
198
  lines.push(`\n ${GREEN}✔ Outstanding hygiene! Zero Grade B issues detected.${RESET}`);
210
199
  lines.push(` ${DIM}Zero em dashes and zero unguarded console statements found.${RESET}`);
211
200
  } else {
212
- lines.push(`\n ${BOLD}${YELLOW}â„šī¸ LOW HYGIENE VIOLATIONS (${low.length}):${RESET}`);
213
- low.forEach((v, idx) => {
214
- lines.push(` ${YELLOW}[#${idx + 1} LOW]${RESET} [${v.rule}] ${YELLOW}${v.filePath}:${v.line}:${v.column}${RESET}`);
215
- lines.push(` Hazard: ${v.hazard}`);
216
- lines.push(` Directive: ${CYAN}${v.directive}${RESET}\n`);
217
- });
201
+ lines.push(`\n ${BOLD}${YELLOW}â„šī¸ LOW HYGIENE VIOLATIONS (${low.length}):${RESET}\n`);
202
+ lines.push(renderGroupedViolationsTerminal(low));
218
203
  }
219
204
 
220
205
  const promptB = buildGradeBPrompt(report);
@@ -240,11 +225,11 @@ export const formatGradeASection = (report) => {
240
225
  lines.push(`${DIM} Molecular Architecture verified clean areas and crystalline modules${RESET}`);
241
226
  lines.push(`${GREEN}======================================================================${RESET}`);
242
227
  lines.push(` Molecular Health Score: ${BOLD}${GREEN}${health.score}/100${RESET} [Grade: ${BOLD}${GREEN}${health.grade}${RESET}]`);
243
- lines.push(` Passing Pillars: ${BOLD}${GREEN}${passedPillars.length} / 7 Pillars PASSED${RESET}`);
228
+ lines.push(` Passing Pillars: ${BOLD}${GREEN}${passedPillars.length} / ${Object.keys(pillars).length} Pillars PASSED${RESET}`);
244
229
  lines.push(` Capsule Compliance: ${BOLD}${GREEN}${metrics.moleculeCompliantPct}%${RESET} compliant (< 100 lines of code)`);
245
230
  lines.push(`${GREEN}----------------------------------------------------------------------${RESET}`);
246
231
 
247
- lines.push(`\n ${BOLD}${GREEN}✔ COMPLIANT PILLARS (${passedPillars.length} / 7):${RESET}`);
232
+ lines.push(`\n ${BOLD}${GREEN}✔ COMPLIANT PILLARS (${passedPillars.length} / ${Object.keys(pillars).length}):${RESET}`);
248
233
  if (passedPillars.length === 0) {
249
234
  lines.push(` ${YELLOW}No pillars are currently 100% compliant.${RESET}\n`);
250
235
  } else {
@@ -0,0 +1,83 @@
1
+ import path from 'node:path';
2
+ import {
3
+ groupViolationsByDirectory,
4
+ groupViolationsByRule
5
+ } from './reporter-grouping.js';
6
+
7
+ export const formatDirectoryRollupMarkdown = (violations = []) => {
8
+ const dirGroups = groupViolationsByDirectory(violations);
9
+ const lines = [];
10
+
11
+ lines.push('### Directory Hazard Rollup');
12
+ lines.push('');
13
+
14
+ const hasNoViolations = dirGroups.length === 0;
15
+ if (hasNoViolations) {
16
+ lines.push('đŸŸĸ Zero violations detected across all project directories.');
17
+ lines.push('');
18
+ return lines.join('\n');
19
+ }
20
+
21
+ lines.push('| Directory | Total Hazards | Critical | High | Med | Low | Primary Hazards |');
22
+ lines.push('| :--- | :---: | :---: | :---: | :---: | :---: | :--- |');
23
+
24
+ for (const g of dirGroups) {
25
+ const topHazards = g.topRules
26
+ .slice(0, 2)
27
+ .map((r) => `\`${r.rule}\` (${r.count})`)
28
+ .join(', ');
29
+ const displayHazards = topHazards || 'None';
30
+ lines.push(`| \`${g.directory}\` | **${g.total}** | ${g.critical} | ${g.high} | ${g.medium} | ${g.low} | ${displayHazards} |`);
31
+ }
32
+ lines.push('');
33
+
34
+ return lines.join('\n');
35
+ };
36
+
37
+ export const renderGroupedViolationsMarkdown = (violations = [], options = {}) => {
38
+ const { showSeverity = false } = options;
39
+ const hasNoViolations = violations.length === 0;
40
+ if (hasNoViolations) return '';
41
+
42
+ const ruleGroups = groupViolationsByRule(violations);
43
+ const lines = [];
44
+
45
+ const headers = showSeverity
46
+ ? '| Rule | Severity | Count | Directive | Affected Locations by Directory |'
47
+ : '| Rule | Count | Directive | Affected Locations by Directory |';
48
+ const divider = showSeverity
49
+ ? '| :--- | :---: | :---: | :--- | :--- |'
50
+ : '| :--- | :---: | :--- | :--- |';
51
+
52
+ lines.push(headers);
53
+ lines.push(divider);
54
+
55
+ for (const rg of ruleGroups) {
56
+ const dirSummaries = rg.directories.map((d) => {
57
+ const formatLocationItem = (v) => {
58
+ const base = path.basename(v.filePath);
59
+ return `\`${base}:${v.line}\``;
60
+ };
61
+
62
+ const locList = d.violations.slice(0, 3).map(formatLocationItem);
63
+ const remaining = d.violations.length - 3;
64
+ const hasRemaining = remaining > 0;
65
+ if (hasRemaining) {
66
+ locList.push(`*(+${remaining} more)*`);
67
+ }
68
+ return `📁 **\`${d.directory}\`** (${d.count}): ${locList.join(', ')}`;
69
+ });
70
+
71
+ const locationCell = dirSummaries.join('<br>');
72
+ const sanitizedDirective = rg.directive.replace(/\|/g, '\\|');
73
+
74
+ if (showSeverity) {
75
+ lines.push(`| \`${rg.rule}\` | ${rg.severity} | **${rg.total}** | ${sanitizedDirective} | ${locationCell} |`);
76
+ } else {
77
+ lines.push(`| \`${rg.rule}\` | **${rg.total}** | ${sanitizedDirective} | ${locationCell} |`);
78
+ }
79
+ }
80
+ lines.push('');
81
+
82
+ return lines.join('\n');
83
+ };