@chemx/starter-kit 26.9.12-93 → 26.9.13-272
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.
- package/cli/audit/ai-slop-detector.js +25 -41
- package/cli/audit/ast-visitors.js +10 -13
- package/cli/audit/history.js +45 -11
- package/cli/audit/pattern-detector.js +182 -0
- package/cli/audit/prompts.js +129 -72
- package/cli/audit/reporter-banner.js +4 -16
- package/cli/audit/reporter-grades.js +28 -16
- package/cli/audit/reporter-grouping.d.ts +6 -0
- package/cli/audit/reporter-grouping.js +48 -5
- package/cli/audit/reporter-markdown.js +31 -16
- package/cli/audit/reporter-sections.js +28 -12
- package/cli/audit/reporter-utils.js +88 -1
- package/cli/audit/reporter.d.ts +13 -4
- package/cli/audit/reporter.js +32 -5
- package/cli/audit/roadmap.js +236 -0
- package/cli/audit/rules-predicates.d.ts +17 -0
- package/cli/audit/rules-predicates.js +107 -0
- package/cli/audit/rules.js +6 -2
- package/cli/audit/social-gh.js +2 -6
- package/cli/audit/social-publisher.js +1 -1
- package/cli/audit/social.js +25 -9
- package/cli/audit/types.d.ts +52 -5
- package/cli/audit.js +76 -7
- package/cli/badge.js +9 -2
- package/cli/build/detector.js +68 -0
- package/cli/build/executor.js +58 -0
- package/cli/build/grouper.js +78 -0
- package/cli/build/parser-matchers.js +116 -0
- package/cli/build/parser.js +80 -0
- package/cli/build/reporter.js +66 -0
- package/cli/build/types.d.ts +58 -0
- package/cli/build/types.js +1 -0
- package/cli/build.d.ts +16 -0
- package/cli/build.js +55 -0
- package/cli/help.js +14 -0
- package/cli/index.js +39 -14
- package/cli/installer-templates.js +43 -4
- package/cli/license.js +10 -7
- package/cli/navigator-actions.js +34 -6
- package/cli/navigator-banner.js +4 -2
- package/cli/navigator-menu.js +18 -0
- package/cli/navigator-share.js +1 -1
- package/cli/navigator.d.ts +8 -1
- package/cli/navigator.js +3 -3
- package/cli/terminal.js +6 -5
- package/cli/theme.d.ts +41 -0
- package/cli/theme.js +84 -0
- package/docs/CHANGELOG.md +46 -0
- package/package.json +3 -2
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
resolveHealthHearts,
|
|
14
14
|
resolveRiskColor,
|
|
15
15
|
resolveHotspotBadge,
|
|
16
|
-
resolveTopSectionColor
|
|
16
|
+
resolveTopSectionColor,
|
|
17
|
+
PILLAR_EMOJIS,
|
|
18
|
+
formatPillarReactionBadgesTerminal
|
|
17
19
|
} from './reporter-utils.js';
|
|
18
20
|
import { getAsciiGradeLines, getReportCardAsciiLines } from './reporter-ascii.js';
|
|
19
21
|
import { renderGroupedViolationsTerminal } from './reporter-grouping.js';
|
|
@@ -153,10 +155,18 @@ export const formatPillarsSection = (report, themeColor = null) => {
|
|
|
153
155
|
lines.push(`${sectionColor}======================================================================${RESET}`);
|
|
154
156
|
lines.push(`${BOLD}${sectionColor} 7-PILLAR ARCHITECTURAL COMPLIANCE MATRIX${RESET}`);
|
|
155
157
|
lines.push(`${sectionColor}======================================================================${RESET}`);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
|
|
159
|
+
const reactionStrip = formatPillarReactionBadgesTerminal(pillars);
|
|
160
|
+
if (reactionStrip) {
|
|
161
|
+
lines.push(` ${BOLD}Reaction Badges:${RESET} ${reactionStrip}`);
|
|
162
|
+
lines.push(`${sectionColor}----------------------------------------------------------------------${RESET}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
lines.push(` ${DIM}Pillar Name Status Violations${RESET}`);
|
|
166
|
+
lines.push(` ${DIM}------------------------------------------------------------------------${RESET}`);
|
|
158
167
|
for (const [pillarName, data] of Object.entries(pillars)) {
|
|
159
|
-
const
|
|
168
|
+
const icon = PILLAR_EMOJIS[pillarName] || '🏛️';
|
|
169
|
+
const padName = `[ ${icon} ] ${pillarName}`.padEnd(51, ' ');
|
|
160
170
|
const badge = getStatusBadge(data.status).padEnd(16, ' ');
|
|
161
171
|
const countStr = data.violations === 0 ? `${GREEN}0${RESET}` : `${RED}${data.violations}${RESET}`;
|
|
162
172
|
lines.push(` ${padName} ${badge} ${countStr}`);
|
|
@@ -166,7 +176,8 @@ export const formatPillarsSection = (report, themeColor = null) => {
|
|
|
166
176
|
return lines.join('\n');
|
|
167
177
|
};
|
|
168
178
|
|
|
169
|
-
export const formatHotspotsSection = (report, themeColor = null) => {
|
|
179
|
+
export const formatHotspotsSection = (report, themeColor = null, options = {}) => {
|
|
180
|
+
const { includePrompt = true } = typeof options === 'boolean' ? { includePrompt: options } : options;
|
|
170
181
|
const { hotspots } = report;
|
|
171
182
|
const sectionColor = themeColor || resolveTopSectionColor(report);
|
|
172
183
|
const lines = [];
|
|
@@ -184,9 +195,11 @@ export const formatHotspotsSection = (report, themeColor = null) => {
|
|
|
184
195
|
}
|
|
185
196
|
}
|
|
186
197
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
198
|
+
if (includePrompt) {
|
|
199
|
+
const promptHotspots = buildHotspotsPrompt(report);
|
|
200
|
+
if (promptHotspots) {
|
|
201
|
+
lines.push(formatPromptBox('🤖 AI AGENT REFACTORING PROMPT (MONOLITH DECOMPOSITION)', promptHotspots));
|
|
202
|
+
}
|
|
190
203
|
}
|
|
191
204
|
|
|
192
205
|
lines.push(`${sectionColor}======================================================================${RESET}\n`);
|
|
@@ -221,7 +234,8 @@ export const formatContextAnalysisSection = (report, themeColor = null) => {
|
|
|
221
234
|
return lines.join('\n');
|
|
222
235
|
};
|
|
223
236
|
|
|
224
|
-
export const formatAiSlopSection = (report, themeColor = null) => {
|
|
237
|
+
export const formatAiSlopSection = (report, themeColor = null, options = {}) => {
|
|
238
|
+
const { includePrompt = true } = typeof options === 'boolean' ? { includePrompt: options } : options;
|
|
225
239
|
const { aiSlop, violations = [] } = report;
|
|
226
240
|
const slopViolations = violations.filter(isSlopViolation);
|
|
227
241
|
const sectionColor = themeColor || resolveTopSectionColor(report);
|
|
@@ -243,9 +257,11 @@ export const formatAiSlopSection = (report, themeColor = null) => {
|
|
|
243
257
|
lines.push(renderGroupedViolationsTerminal(slopViolations));
|
|
244
258
|
}
|
|
245
259
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
260
|
+
if (includePrompt) {
|
|
261
|
+
const promptSlop = buildAiSlopPrompt(report);
|
|
262
|
+
if (promptSlop) {
|
|
263
|
+
lines.push(formatPromptBox('🤖 AI AGENT REFACTORING PROMPT (AI SLOP & AUTHENTICITY)', promptSlop));
|
|
264
|
+
}
|
|
249
265
|
}
|
|
250
266
|
|
|
251
267
|
lines.push(`${sectionColor}======================================================================${RESET}\n`);
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
export
|
|
1
|
+
export {
|
|
2
|
+
CHEMX_COLORS,
|
|
3
|
+
CHEMX_RGB,
|
|
4
|
+
getChemicalXGradientColor,
|
|
5
|
+
formatChemicalXGradient
|
|
6
|
+
} from '../theme.js';
|
|
7
|
+
|
|
8
|
+
export const CYAN = '\x1b[38;2;56;189;248m';
|
|
9
|
+
export const PINK = '\x1b[38;2;244;63;133m';
|
|
10
|
+
export const PURPLE = '\x1b[38;2;168;85;247m';
|
|
11
|
+
export const MINT = '\x1b[38;2;45;212;191m';
|
|
12
|
+
export const LIME = '\x1b[38;2;163;230;53m';
|
|
13
|
+
export const GOLD = '\x1b[38;2;251;191;36m';
|
|
2
14
|
export const GREEN = '\x1b[32m';
|
|
3
15
|
export const YELLOW = '\x1b[33m';
|
|
4
16
|
export const RED = '\x1b[31m';
|
|
@@ -117,6 +129,49 @@ export const PILLAR_EMOJIS = {
|
|
|
117
129
|
'Naming Conventions': '🏷️'
|
|
118
130
|
};
|
|
119
131
|
|
|
132
|
+
export const PILLAR_SHORT_NAMES = {
|
|
133
|
+
'Line Budgets & Monolith Decomposition': 'Monoliths',
|
|
134
|
+
'Control Flow & Boolean Logic': 'Logic',
|
|
135
|
+
'Reactivity & Composable Contracts': 'Reactivity',
|
|
136
|
+
'Type Architecture & Data Integrity': 'Types',
|
|
137
|
+
'Design System & Styling Hygiene': 'Styling',
|
|
138
|
+
'Timers & Macro-Task Discipline': 'Timers',
|
|
139
|
+
'Global Hygiene & Typography': 'Hygiene',
|
|
140
|
+
'Accessibility & Semantic Integrity': 'A11y',
|
|
141
|
+
'Security & Content Safety': 'Security',
|
|
142
|
+
'Testing Discipline': 'Testing',
|
|
143
|
+
'Naming Conventions': 'Naming'
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const resolveBadgeColor = (score) => {
|
|
147
|
+
if (score >= 90) return '06b6d4';
|
|
148
|
+
if (score >= 70) return 'f59e0b';
|
|
149
|
+
return 'ef4444';
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const formatPillarShieldBadges = (pillars = {}) => {
|
|
153
|
+
const badges = [];
|
|
154
|
+
for (const [pillarName, data] of Object.entries(pillars)) {
|
|
155
|
+
const icon = PILLAR_EMOJIS[pillarName] || '🏛️';
|
|
156
|
+
const shortName = PILLAR_SHORT_NAMES[pillarName] || pillarName.split(' ')[0];
|
|
157
|
+
const rawLabel = `${icon} ${shortName}`.replace(/-/g, '--').replace(/ /g, '_');
|
|
158
|
+
const label = encodeURIComponent(rawLabel);
|
|
159
|
+
let statusText = 'PASS';
|
|
160
|
+
let color = '06b6d4';
|
|
161
|
+
if (data?.status === 'WARN') {
|
|
162
|
+
const v = data?.violations || 0;
|
|
163
|
+
statusText = encodeURIComponent(`WARN ${v}`.replace(/-/g, '--').replace(/ /g, '_'));
|
|
164
|
+
color = 'f59e0b';
|
|
165
|
+
} else if (data?.status === 'FAILED') {
|
|
166
|
+
const v = data?.violations || 0;
|
|
167
|
+
statusText = encodeURIComponent(`FAIL ${v}`.replace(/-/g, '--').replace(/ /g, '_'));
|
|
168
|
+
color = 'ef4444';
|
|
169
|
+
}
|
|
170
|
+
badges.push(`[](https://chemicalx.xophz.com)`);
|
|
171
|
+
}
|
|
172
|
+
return badges.join(' ');
|
|
173
|
+
};
|
|
174
|
+
|
|
120
175
|
export const NUMBER_EMOJIS = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟'];
|
|
121
176
|
|
|
122
177
|
export const resolvePriorityBadge = (idx) => {
|
|
@@ -135,3 +190,35 @@ export const resolveMarkdownMonolithText = (lineCount) => {
|
|
|
135
190
|
if (lineCount > 500) return '🟡 **WARNING (> 500 lines of code)**';
|
|
136
191
|
return '🟢 Compliant';
|
|
137
192
|
};
|
|
193
|
+
|
|
194
|
+
export const formatPillarReactionBadgesTerminal = (pillars = {}) => {
|
|
195
|
+
const badges = [];
|
|
196
|
+
for (const [pillarName, data] of Object.entries(pillars)) {
|
|
197
|
+
const icon = PILLAR_EMOJIS[pillarName] || '🏛️';
|
|
198
|
+
const violations = data?.violations || 0;
|
|
199
|
+
if (data?.status === 'PASSED') {
|
|
200
|
+
badges.push(`${GREEN}[ ${icon} PASS ]${RESET}`);
|
|
201
|
+
} else if (data?.status === 'WARN') {
|
|
202
|
+
badges.push(`${YELLOW}[ ${icon} WARN (${violations}) ]${RESET}`);
|
|
203
|
+
} else {
|
|
204
|
+
badges.push(`${RED}[ ${icon} FAIL (${violations}) ]${RESET}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return badges.join(' ');
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const formatPillarReactionBadgesMarkdown = (pillars = {}) => {
|
|
211
|
+
const badges = [];
|
|
212
|
+
for (const [pillarName, data] of Object.entries(pillars)) {
|
|
213
|
+
const icon = PILLAR_EMOJIS[pillarName] || '🏛️';
|
|
214
|
+
const violations = data?.violations || 0;
|
|
215
|
+
if (data?.status === 'PASSED') {
|
|
216
|
+
badges.push(`\`[ ${icon} PASS ]\``);
|
|
217
|
+
} else if (data?.status === 'WARN') {
|
|
218
|
+
badges.push(`\`[ ${icon} WARN: ${violations} ]\``);
|
|
219
|
+
} else {
|
|
220
|
+
badges.push(`\`[ ${icon} FAIL: ${violations} ]\``);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return badges.join(' ');
|
|
224
|
+
};
|
package/cli/audit/reporter.d.ts
CHANGED
|
@@ -6,11 +6,20 @@ export * from './reporter-sections';
|
|
|
6
6
|
export * from './reporter-summary';
|
|
7
7
|
export * from './reporter-grouping';
|
|
8
8
|
|
|
9
|
+
export interface FormatGradeOptions {
|
|
10
|
+
readonly includePrompt?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
export declare function formatTerminalReport(report: AuditReport): string;
|
|
10
14
|
export declare function generateMarkdownReport(report: AuditReport): string;
|
|
11
15
|
export declare function resolveTopSectionColor(report: AuditReport): string;
|
|
12
|
-
export declare function formatGradeFSection(report: AuditReport): string;
|
|
13
|
-
export declare function formatGradeDSection(report: AuditReport): string;
|
|
14
|
-
export declare function formatGradeCSection(report: AuditReport): string;
|
|
15
|
-
export declare function formatGradeBSection(report: AuditReport): string;
|
|
16
|
+
export declare function formatGradeFSection(report: AuditReport, options?: FormatGradeOptions): string;
|
|
17
|
+
export declare function formatGradeDSection(report: AuditReport, options?: FormatGradeOptions): string;
|
|
18
|
+
export declare function formatGradeCSection(report: AuditReport, options?: FormatGradeOptions): string;
|
|
19
|
+
export declare function formatGradeBSection(report: AuditReport, options?: FormatGradeOptions): string;
|
|
16
20
|
export declare function formatGradeASection(report: AuditReport): string;
|
|
21
|
+
export declare function formatPillarReactionBadgesTerminal(pillars?: Record<string, unknown>): string;
|
|
22
|
+
export declare function formatPillarReactionBadgesMarkdown(pillars?: Record<string, unknown>): string;
|
|
23
|
+
export declare const PILLAR_SHORT_NAMES: Record<string, string>;
|
|
24
|
+
export declare function resolveBadgeColor(score: number): string;
|
|
25
|
+
export declare function formatPillarShieldBadges(pillars?: Record<string, unknown>): string;
|
package/cli/audit/reporter.js
CHANGED
|
@@ -20,6 +20,24 @@ import {
|
|
|
20
20
|
formatContextAnalysisSection,
|
|
21
21
|
formatAiSlopSection
|
|
22
22
|
} from './reporter-sections.js';
|
|
23
|
+
import {
|
|
24
|
+
formatRoadmapSection,
|
|
25
|
+
formatRoadmapMarkdown,
|
|
26
|
+
buildSelfHealingRoadmapPrompt
|
|
27
|
+
} from './roadmap.js';
|
|
28
|
+
import {
|
|
29
|
+
formatGradeASection,
|
|
30
|
+
formatGradeBSection,
|
|
31
|
+
formatGradeCSection,
|
|
32
|
+
formatGradeDSection,
|
|
33
|
+
formatGradeFSection
|
|
34
|
+
} from './reporter-grades.js';
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
formatRoadmapSection,
|
|
38
|
+
formatRoadmapMarkdown,
|
|
39
|
+
buildSelfHealingRoadmapPrompt
|
|
40
|
+
};
|
|
23
41
|
|
|
24
42
|
export {
|
|
25
43
|
groupViolationsBySeverity,
|
|
@@ -32,7 +50,12 @@ export {
|
|
|
32
50
|
resolveTopSectionColor,
|
|
33
51
|
resolveMarkdownStatusIcon,
|
|
34
52
|
resolveMarkdownMonolithText,
|
|
53
|
+
formatPillarReactionBadgesTerminal,
|
|
54
|
+
formatPillarReactionBadgesMarkdown,
|
|
55
|
+
formatPillarShieldBadges,
|
|
56
|
+
resolveBadgeColor,
|
|
35
57
|
PILLAR_EMOJIS,
|
|
58
|
+
PILLAR_SHORT_NAMES,
|
|
36
59
|
NUMBER_EMOJIS,
|
|
37
60
|
resolvePriorityBadge,
|
|
38
61
|
CYAN,
|
|
@@ -49,6 +72,7 @@ export {
|
|
|
49
72
|
resolveDirectory,
|
|
50
73
|
groupViolationsByDirectory,
|
|
51
74
|
groupViolationsByRule,
|
|
75
|
+
buildPathTree,
|
|
52
76
|
formatCompactLocations,
|
|
53
77
|
renderGroupedViolationsTerminal,
|
|
54
78
|
formatDirectoryDistributionSection
|
|
@@ -108,12 +132,15 @@ export const formatTerminalReport = (report) => {
|
|
|
108
132
|
lines.push(formatScorecardSection(report, topColor));
|
|
109
133
|
lines.push(formatContextAnalysisSection(report, topColor));
|
|
110
134
|
lines.push(formatPillarsSection(report, topColor));
|
|
111
|
-
lines.push(formatHotspotsSection(report, topColor));
|
|
135
|
+
lines.push(formatHotspotsSection(report, topColor, { includePrompt: false }));
|
|
112
136
|
lines.push(formatDirectoryDistributionSection(report, topColor));
|
|
113
|
-
lines.push(formatAiSlopSection(report, topColor));
|
|
114
|
-
lines.push(
|
|
115
|
-
lines.push(
|
|
116
|
-
lines.push(
|
|
137
|
+
lines.push(formatAiSlopSection(report, topColor, { includePrompt: false }));
|
|
138
|
+
lines.push(formatRoadmapSection(report, topColor));
|
|
139
|
+
lines.push(formatGradeASection(report));
|
|
140
|
+
lines.push(formatGradeBSection(report, { includePrompt: false }));
|
|
141
|
+
lines.push(formatGradeCSection(report, { includePrompt: false }));
|
|
142
|
+
lines.push(formatGradeDSection(report, { includePrompt: false }));
|
|
143
|
+
lines.push(formatGradeFSection(report, { includePrompt: false }));
|
|
117
144
|
|
|
118
145
|
const masterPrompt = buildMasterPrompt(report);
|
|
119
146
|
if (masterPrompt) {
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chemical X Protocol: Self-Healing Remediation Roadmap
|
|
3
|
+
* Calculates and formats the optimal sequence of architectural fixes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
CYAN,
|
|
8
|
+
GREEN,
|
|
9
|
+
YELLOW,
|
|
10
|
+
RED,
|
|
11
|
+
ORANGE,
|
|
12
|
+
DIM,
|
|
13
|
+
BOLD,
|
|
14
|
+
RESET,
|
|
15
|
+
resolveTopSectionColor,
|
|
16
|
+
groupViolationsBySeverity
|
|
17
|
+
} from './reporter-utils.js';
|
|
18
|
+
|
|
19
|
+
export const buildRemediationRoadmap = (report) => {
|
|
20
|
+
const { hotspots = [], violations = [], patterns = [] } = report;
|
|
21
|
+
const { critical, high, medium, low } = groupViolationsBySeverity(violations);
|
|
22
|
+
|
|
23
|
+
const extremeMonoliths = hotspots.filter((h) => h.lineCount >= 2000);
|
|
24
|
+
const severeMonoliths = hotspots.filter((h) => h.lineCount >= 1000 && h.lineCount < 2000);
|
|
25
|
+
const warningMonoliths = hotspots.filter((h) => h.lineCount >= 500 && h.lineCount < 1000);
|
|
26
|
+
|
|
27
|
+
const phases = [];
|
|
28
|
+
|
|
29
|
+
// Phase 1: Pattern Recognition & Canonical Harvesting
|
|
30
|
+
phases.push({
|
|
31
|
+
step: 1,
|
|
32
|
+
phase: 'PHASE 1: PATTERN HARVESTING & CANONICAL EXTRACTION',
|
|
33
|
+
roi: 'MAXIMUM (Compounding Reuse)',
|
|
34
|
+
rationale: 'Extract reusable UI structures and shared domain types before slicing monoliths to eliminate redundant bespoke code.',
|
|
35
|
+
items: patterns.length > 0
|
|
36
|
+
? patterns.map((p) => ({
|
|
37
|
+
title: p.label,
|
|
38
|
+
target: p.suggestedCapsule,
|
|
39
|
+
action: p.recommendation,
|
|
40
|
+
locations: p.occurrences.slice(0, 3).map((o) => `${o.filePath}:${o.line}`)
|
|
41
|
+
}))
|
|
42
|
+
: [
|
|
43
|
+
{
|
|
44
|
+
title: 'Cross-Monolith Pattern Survey',
|
|
45
|
+
target: 'src/components/molecules/',
|
|
46
|
+
action: 'Audit shared card layouts, common status unions, and repeated filter predicates across candidate files before code splitting.',
|
|
47
|
+
locations: []
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Phase 2: Domain Foundations & Type Architecture
|
|
53
|
+
const typeViolations = violations.filter((v) => v.rule === 'TYPE_MONOLITH' || v.pillar?.includes('Pillar 4'));
|
|
54
|
+
phases.push({
|
|
55
|
+
step: 2,
|
|
56
|
+
phase: 'PHASE 2: DOMAIN FOUNDATIONS & TYPE INTEGRITY',
|
|
57
|
+
roi: 'HIGH (Compile-Time Guardrails)',
|
|
58
|
+
rationale: 'Co-locate granular types/*.d.ts capsules to provide strict discriminated unions for decomposing components.',
|
|
59
|
+
items: typeViolations.length > 0
|
|
60
|
+
? typeViolations.slice(0, 5).map((v) => ({
|
|
61
|
+
title: v.hazard,
|
|
62
|
+
target: `${v.filePath} -> types/*.d.ts`,
|
|
63
|
+
action: v.directive,
|
|
64
|
+
locations: [`${v.filePath}:${v.line}`]
|
|
65
|
+
}))
|
|
66
|
+
: [
|
|
67
|
+
{
|
|
68
|
+
title: 'Domain Type Co-location',
|
|
69
|
+
target: 'types/*.d.ts',
|
|
70
|
+
action: 'Ensure molecule capsules own co-located type declarations (< 100 lines each) with zero root type dumping.',
|
|
71
|
+
locations: []
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Phase 3: Monolith Decomposition (Grade F & D)
|
|
77
|
+
const topMonoliths = [...extremeMonoliths, ...severeMonoliths, ...warningMonoliths].slice(0, 6);
|
|
78
|
+
phases.push({
|
|
79
|
+
step: 3,
|
|
80
|
+
phase: 'PHASE 3: MONOLITH DECOMPOSITION (HOTSPOTS)',
|
|
81
|
+
roi: 'VERY HIGH (Context Window Unlocking)',
|
|
82
|
+
rationale: 'Decompose giant views into 10-20 line Table-of-Contents templates by importing canonical capsules harvested in Phase 1.',
|
|
83
|
+
items: topMonoliths.map((m) => ({
|
|
84
|
+
title: `${m.filePath} (${m.lineCount} lines, ${m.violationCount} hazards)`,
|
|
85
|
+
target: m.filePath,
|
|
86
|
+
action: 'Decompose into crystalline single-responsibility capsules (< 100 lines per molecule) reusing Phase 1 components.',
|
|
87
|
+
locations: [`${m.filePath}:1`]
|
|
88
|
+
}))
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Phase 4: Component Hygiene & Polish (Grade C & B)
|
|
92
|
+
phases.push({
|
|
93
|
+
step: 4,
|
|
94
|
+
phase: 'PHASE 4: COMPONENT HYGIENE & DESIGN SYSTEM POLISH',
|
|
95
|
+
roi: 'MEDIUM (Visual & Syntax Standardization)',
|
|
96
|
+
rationale: 'Clean up inline styles into mixins (@include glass), two-stage booleans, typography, and logging on the newly lean capsules.',
|
|
97
|
+
items: [
|
|
98
|
+
{
|
|
99
|
+
title: `Medium Severity Debts (${medium.length} items)`,
|
|
100
|
+
target: 'Component stylesheets & logic',
|
|
101
|
+
action: 'Replace raw inline styles with atom props or SCSS mixins (@include glass); extract anonymous callbacks into named handlers.',
|
|
102
|
+
locations: []
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
title: `Hygiene & Typography Debts (${low.length} items)`,
|
|
106
|
+
target: 'Copy, comments & logs',
|
|
107
|
+
action: 'Eliminate em dashes (use hyphens or colons), route console logs through debug proxy, and ensure FontAwesome SVG compliance.',
|
|
108
|
+
locations: []
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Phase 5: Verification & Gatekeeping
|
|
114
|
+
phases.push({
|
|
115
|
+
step: 5,
|
|
116
|
+
phase: 'PHASE 5: AUTOMATED GATEKEEPING & CI ENFORCEMENT',
|
|
117
|
+
roi: 'PERMANENT (Zero-Entropy Protection)',
|
|
118
|
+
rationale: 'Verify the newly refactored codebase passes with Grade A / Score >= 90 and install pre-commit guardrails.',
|
|
119
|
+
items: [
|
|
120
|
+
{
|
|
121
|
+
title: 'Run Architectural Re-Audit',
|
|
122
|
+
target: 'chemx audit',
|
|
123
|
+
action: 'Execute npx chemx audit --strict to confirm zero monolith hazards remain.',
|
|
124
|
+
locations: []
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
title: 'Install Pre-Commit & CI Gatekeepers',
|
|
128
|
+
target: '.git/hooks/pre-commit & .github/workflows/',
|
|
129
|
+
action: 'Run npx chemx install to lock in line budgets and prevent future monolith regressions.',
|
|
130
|
+
locations: []
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
title: 'Branch, Commit & Pull Request Workflow',
|
|
134
|
+
target: 'git checkout -b chem-x/NAMEOFIMPROVEMENT',
|
|
135
|
+
action: 'Change to a branch prefixed with chem-x/NAMEOFIMPROVEMENT to make changes, commit the branch, and create a PR to main.',
|
|
136
|
+
locations: []
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return phases;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const formatRoadmapSection = (report, themeColor = null) => {
|
|
145
|
+
const sectionColor = themeColor || resolveTopSectionColor(report);
|
|
146
|
+
const phases = buildRemediationRoadmap(report);
|
|
147
|
+
const lines = [];
|
|
148
|
+
|
|
149
|
+
lines.push('');
|
|
150
|
+
lines.push(`${sectionColor}======================================================================${RESET}`);
|
|
151
|
+
lines.push(`${BOLD}${sectionColor} SELF-HEALING REMEDIATION ROADMAP (OPTIMAL FIX SEQUENCE)${RESET}`);
|
|
152
|
+
lines.push(`${DIM} Fix in this sequence to harvest patterns first and compound code reuse${RESET}`);
|
|
153
|
+
lines.push(`${sectionColor}======================================================================${RESET}`);
|
|
154
|
+
|
|
155
|
+
phases.forEach((p) => {
|
|
156
|
+
lines.push(`\n ${CYAN}${BOLD}[STEP ${p.step}] ${p.phase}${RESET}`);
|
|
157
|
+
lines.push(` ${DIM}ROI: ${p.roi}${RESET}`);
|
|
158
|
+
lines.push(` ${YELLOW}Strategy: ${p.rationale}${RESET}`);
|
|
159
|
+
|
|
160
|
+
p.items.forEach((item, idx) => {
|
|
161
|
+
lines.push(` ${idx + 1}. ${BOLD}${item.title}${RESET}`);
|
|
162
|
+
lines.push(` Target: ${CYAN}${item.target}${RESET}`);
|
|
163
|
+
lines.push(` Action: ${item.action}`);
|
|
164
|
+
if (item.locations.length > 0) {
|
|
165
|
+
lines.push(` Locations: ${DIM}${item.locations.join(', ')}${RESET}`);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
lines.push(`\n${sectionColor}======================================================================${RESET}\n`);
|
|
171
|
+
return lines.join('\n');
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const formatRoadmapMarkdown = (report) => {
|
|
175
|
+
const phases = buildRemediationRoadmap(report);
|
|
176
|
+
const lines = [];
|
|
177
|
+
|
|
178
|
+
lines.push('## 🗺 Recommended Remediation Sequence (Self-Healing Roadmap)\n');
|
|
179
|
+
lines.push('> Follow this sequence strictly: harvesting patterns first creates reusable building blocks that eliminate duplicate work during subsequent monolith slicing.\n');
|
|
180
|
+
|
|
181
|
+
phases.forEach((p) => {
|
|
182
|
+
lines.push(`### Step ${p.step}: ${p.phase}`);
|
|
183
|
+
lines.push(`**ROI**: ${p.roi} | **Strategy**: ${p.rationale}\n`);
|
|
184
|
+
|
|
185
|
+
p.items.forEach((item) => {
|
|
186
|
+
lines.push(`- [ ] **${item.title}**`);
|
|
187
|
+
lines.push(` - **Target**: \`${item.target}\``);
|
|
188
|
+
lines.push(` - **Action**: ${item.action}`);
|
|
189
|
+
if (item.locations.length > 0) {
|
|
190
|
+
lines.push(` - **Locations**: \`${item.locations.join('`, `')}\``);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
lines.push('');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return lines.join('\n');
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export const buildSelfHealingRoadmapPrompt = (report) => {
|
|
200
|
+
const phases = buildRemediationRoadmap(report);
|
|
201
|
+
const lines = [];
|
|
202
|
+
|
|
203
|
+
lines.push('Act as a Principal Systems Architect. Execute a self-healing architectural remediation of our codebase by following this strict multi-phase sequence in order:\n');
|
|
204
|
+
|
|
205
|
+
lines.push('### MANDATORY GIT BRANCHING & PR WORKFLOW:');
|
|
206
|
+
lines.push('1. Before modifying any files, change to a dedicated feature branch prefixed with `chem-x/NAMEOFIMPROVEMENT`:');
|
|
207
|
+
lines.push(' `git checkout -b chem-x/NAMEOFIMPROVEMENT`');
|
|
208
|
+
lines.push('2. Execute each remediation phase sequentially on this branch.');
|
|
209
|
+
lines.push('3. Commit the branch with atomic, intention-revealing commit messages.');
|
|
210
|
+
lines.push('4. Create a Pull Request (PR) to `main` upon completing the remediation phases.\n');
|
|
211
|
+
|
|
212
|
+
phases.forEach((p) => {
|
|
213
|
+
lines.push(`### STEP ${p.step}: ${p.phase}`);
|
|
214
|
+
lines.push(`Goal: ${p.rationale}\n`);
|
|
215
|
+
p.items.forEach((item, idx) => {
|
|
216
|
+
lines.push(`${idx + 1}. Task: ${item.title}`);
|
|
217
|
+
lines.push(` Target: ${item.target}`);
|
|
218
|
+
lines.push(` Execution: ${item.action}`);
|
|
219
|
+
if (item.locations.length > 0) {
|
|
220
|
+
lines.push(` Files: ${item.locations.join(', ')}`);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
lines.push('');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
lines.push('### EXECUTION DISCIPLINE:');
|
|
227
|
+
lines.push('1. Branch First: Change to a new branch prefixed with `chem-x/NAMEOFIMPROVEMENT` (e.g. `git checkout -b chem-x/<name-of-improvement>`) before making any changes.');
|
|
228
|
+
lines.push('2. Complete Step 1 (Pattern Harvesting) completely before touching any monolithic file in Step 3.');
|
|
229
|
+
lines.push('3. Spliced views in Step 3 must directly import and bind to the canonical capsules created in Step 1.');
|
|
230
|
+
lines.push('4. Every new molecule component must stay under 100 lines.');
|
|
231
|
+
lines.push('5. Top-level page views must be 10-20 line declarative Table-of-Contents templates assembling molecules via named slots.');
|
|
232
|
+
lines.push('6. Zero synthetic or mock data; zero em dashes (use hyphens or colons).');
|
|
233
|
+
lines.push('7. PR to Main: Commit the branch and create a Pull Request to main.');
|
|
234
|
+
|
|
235
|
+
return lines.join('\n');
|
|
236
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type * as babelTypes from '@babel/types';
|
|
2
|
+
|
|
3
|
+
export declare function isComponentPath(path: string): boolean;
|
|
4
|
+
export declare function isCodeLine(line: string): boolean;
|
|
5
|
+
export declare function isConsoleCallStatement(stmt: unknown, t: typeof babelTypes): boolean;
|
|
6
|
+
export declare function isShallowCatchBody(body: readonly unknown[], t: typeof babelTypes): boolean;
|
|
7
|
+
export declare function hasAnyTypeAnnotation(param: unknown, t: typeof babelTypes): boolean;
|
|
8
|
+
export declare function isRedundantPassthroughReturn(curr: unknown, next: unknown, t: typeof babelTypes): boolean;
|
|
9
|
+
export declare function isHookIdentifier(idNode: unknown): boolean;
|
|
10
|
+
export declare function isCustomHookFunction(astPath: unknown): boolean;
|
|
11
|
+
export declare function resolveStartLine(primaryNode?: unknown, fallbackNode?: unknown, defaultLine?: number): number;
|
|
12
|
+
export declare function isZeroDelayTimeout(callee: unknown, delayArg: unknown, t: typeof babelTypes): boolean;
|
|
13
|
+
export declare function isUnguardedConsoleCall(callee: unknown, t: typeof babelTypes): boolean;
|
|
14
|
+
export declare function matchesDiscussionTitle(titleLower: string, targetLower: string): boolean;
|
|
15
|
+
export declare function isGradeBelowMinimum(currentGrade: string, minGrade?: string | null): boolean;
|
|
16
|
+
export declare function evaluateAuditFailure(conditions?: readonly boolean[]): boolean;
|
|
17
|
+
export declare function isNonInteractiveSession(rawArgs: readonly string[], env?: Record<string, string | undefined>): boolean;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chemical X Protocol: Canonical Predicates for AST & CLI Control Flow
|
|
3
|
+
* Reusable single-concept booleans and higher-order predicate helpers.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const COMPONENT_PATH_SEGMENTS = ['molecules', 'components', '/m-', '/views/', '/pages/'];
|
|
7
|
+
const CONSOLE_LOG_METHODS = ['log', 'info', 'warn'];
|
|
8
|
+
const NON_INTERACTIVE_FLAGS = ['--non-interactive', '--no-interactive', '--ci'];
|
|
9
|
+
const GRADE_RANKS = { 'A+': 5, 'A': 4, 'B': 3, 'C': 2, 'D': 1, 'F': 0 };
|
|
10
|
+
|
|
11
|
+
export const isComponentPath = (path) => {
|
|
12
|
+
return COMPONENT_PATH_SEGMENTS.some((segment) => path.includes(segment));
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const isCodeLine = (line) => {
|
|
16
|
+
if (!line) return false;
|
|
17
|
+
const isLineComment = line.startsWith('//');
|
|
18
|
+
const isBlockComment = line.startsWith('/*');
|
|
19
|
+
return !isLineComment && !isBlockComment;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const isConsoleCallStatement = (stmt, t) => {
|
|
23
|
+
if (!t.isExpressionStatement(stmt)) return false;
|
|
24
|
+
const expr = stmt.expression;
|
|
25
|
+
if (!t.isCallExpression(expr)) return false;
|
|
26
|
+
const callee = expr.callee;
|
|
27
|
+
if (!t.isMemberExpression(callee)) return false;
|
|
28
|
+
return t.isIdentifier(callee.object, { name: 'console' });
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const isShallowCatchBody = (body, t) => {
|
|
32
|
+
if (body.length === 0) return true;
|
|
33
|
+
if (body.length === 1) return isConsoleCallStatement(body[0], t);
|
|
34
|
+
return false;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const hasAnyTypeAnnotation = (param, t) => {
|
|
38
|
+
if (!param || !t.isIdentifier(param)) return false;
|
|
39
|
+
const typeAnn = param.typeAnnotation;
|
|
40
|
+
if (!typeAnn || !t.isTSTypeAnnotation(typeAnn)) return false;
|
|
41
|
+
return t.isTSAnyKeyword(typeAnn.typeAnnotation);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const isRedundantPassthroughReturn = (curr, next, t) => {
|
|
45
|
+
if (!t.isVariableDeclaration(curr) || curr.declarations.length !== 1) return false;
|
|
46
|
+
const decl = curr.declarations[0];
|
|
47
|
+
if (!t.isIdentifier(decl.id)) return false;
|
|
48
|
+
if (!t.isReturnStatement(next) || !t.isIdentifier(next.argument)) return false;
|
|
49
|
+
return decl.id.name === next.argument.name;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const isHookIdentifier = (idNode) => {
|
|
53
|
+
return Boolean(idNode?.name && /^use[A-Z0-9]/.test(idNode.name));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const isCustomHookFunction = (astPath) => {
|
|
57
|
+
const hasHookId = isHookIdentifier(astPath.node.id);
|
|
58
|
+
const hasParentHookId = isHookIdentifier(astPath.parentPath?.node?.id);
|
|
59
|
+
return hasHookId || hasParentHookId;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const resolveStartLine = (primaryNode, fallbackNode, defaultLine = 1) => {
|
|
63
|
+
return primaryNode?.loc?.start?.line ?? fallbackNode?.loc?.start?.line ?? defaultLine;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const isZeroDelayTimeout = (callee, delayArg, t) => {
|
|
67
|
+
const isTimeoutId = t.isIdentifier(callee, { name: 'setTimeout' });
|
|
68
|
+
const isZeroNumeric = t.isNumericLiteral(delayArg) && delayArg.value === 0;
|
|
69
|
+
return isTimeoutId && isZeroNumeric;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const isUnguardedConsoleCall = (callee, t) => {
|
|
73
|
+
if (!t.isMemberExpression(callee)) return false;
|
|
74
|
+
if (!t.isIdentifier(callee.object, { name: 'console' })) return false;
|
|
75
|
+
if (!t.isIdentifier(callee.property)) return false;
|
|
76
|
+
return CONSOLE_LOG_METHODS.includes(callee.property.name);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const matchesDiscussionTitle = (titleLower, targetLower) => {
|
|
80
|
+
const patterns = [
|
|
81
|
+
` ${targetLower} :`,
|
|
82
|
+
` ${targetLower} [`,
|
|
83
|
+
`: ${targetLower}`,
|
|
84
|
+
`${targetLower} audit`
|
|
85
|
+
];
|
|
86
|
+
return patterns.some((p) => titleLower.includes(p));
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const isGradeBelowMinimum = (currentGrade, minGrade) => {
|
|
90
|
+
if (!minGrade) return false;
|
|
91
|
+
const currentRank = GRADE_RANKS[currentGrade];
|
|
92
|
+
const minRank = GRADE_RANKS[minGrade];
|
|
93
|
+
const hasValidRanks = currentRank !== undefined && minRank !== undefined;
|
|
94
|
+
return hasValidRanks && currentRank < minRank;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const evaluateAuditFailure = (conditions = []) => {
|
|
98
|
+
return conditions.some(Boolean);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const isNonInteractiveSession = (rawArgs, env = process.env) => {
|
|
102
|
+
const hasCliFlag = NON_INTERACTIVE_FLAGS.some((flag) => rawArgs.includes(flag));
|
|
103
|
+
const hasOutputFlag = rawArgs.some((arg) => arg.startsWith('--output=') || arg.startsWith('-o='));
|
|
104
|
+
const hasFormatFlag = rawArgs.includes('--markdown') || rawArgs.includes('--md') || rawArgs.includes('--json');
|
|
105
|
+
const hasCiEnv = Boolean(env.CI || env.GIT_DIR);
|
|
106
|
+
return hasCliFlag || hasOutputFlag || hasFormatFlag || hasCiEnv;
|
|
107
|
+
};
|
package/cli/audit/rules.js
CHANGED
|
@@ -18,10 +18,11 @@ import {
|
|
|
18
18
|
checkExtendedTextPatterns,
|
|
19
19
|
createExtendedVisitors
|
|
20
20
|
} from './extended-visitors.js';
|
|
21
|
+
import { createPatternVisitors } from './pattern-detector.js';
|
|
21
22
|
|
|
22
23
|
export { PILLARS, RULE_REGISTRY };
|
|
23
24
|
|
|
24
|
-
export const auditCode = (content, filePath, relativePath) => {
|
|
25
|
+
export const auditCode = (content, filePath, relativePath, options = {}) => {
|
|
25
26
|
const violations = [];
|
|
26
27
|
const lines = content.split('\n');
|
|
27
28
|
const lineCount = lines.length;
|
|
@@ -132,9 +133,12 @@ export const auditCode = (content, filePath, relativePath) => {
|
|
|
132
133
|
const visitors = createAstVisitors({ relativePath, violations });
|
|
133
134
|
const slopVisitors = createAiSlopVisitors({ relativePath, violations });
|
|
134
135
|
const extendedVisitors = createExtendedVisitors({ relativePath, violations });
|
|
136
|
+
const patternVisitors = options.patternRegistry
|
|
137
|
+
? createPatternVisitors(options.patternRegistry, relativePath)
|
|
138
|
+
: {};
|
|
135
139
|
|
|
136
140
|
const mergedVisitors = { ...visitors };
|
|
137
|
-
for (const visitorSet of [slopVisitors, extendedVisitors]) {
|
|
141
|
+
for (const visitorSet of [slopVisitors, extendedVisitors, patternVisitors]) {
|
|
138
142
|
for (const [key, fn] of Object.entries(visitorSet)) {
|
|
139
143
|
if (mergedVisitors[key]) {
|
|
140
144
|
const orig = mergedVisitors[key];
|