@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
package/cli/audit/social-gh.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
DISCUSSION_CATEGORY,
|
|
4
4
|
DISCUSSION_CATEGORY_SLUG
|
|
5
5
|
} from './social-constants.js';
|
|
6
|
+
import { matchesDiscussionTitle } from './rules-predicates.js';
|
|
6
7
|
|
|
7
8
|
export const publishDiscussionViaGh = (repo, title, body, category = DISCUSSION_CATEGORY) => {
|
|
8
9
|
try {
|
|
@@ -110,12 +111,7 @@ export const findExistingDiscussionViaGh = (repo, projectName) => {
|
|
|
110
111
|
const targetLower = projectName.toLowerCase();
|
|
111
112
|
const match = list.find((d) => {
|
|
112
113
|
const titleLower = (d.title || '').toLowerCase();
|
|
113
|
-
return (
|
|
114
|
-
titleLower.includes(` ${targetLower} :`) ||
|
|
115
|
-
titleLower.includes(` ${targetLower} [`) ||
|
|
116
|
-
titleLower.includes(`: ${targetLower}`) ||
|
|
117
|
-
titleLower.includes(`${targetLower} audit`)
|
|
118
|
-
);
|
|
114
|
+
return matchesDiscussionTitle(titleLower, targetLower);
|
|
119
115
|
});
|
|
120
116
|
return match || null;
|
|
121
117
|
}
|
|
@@ -59,7 +59,7 @@ export {
|
|
|
59
59
|
export const formatArchiveComment = (previousTitle, previousBody) => {
|
|
60
60
|
const archiveTimestamp = new Date().toUTCString();
|
|
61
61
|
return [
|
|
62
|
-
'##
|
|
62
|
+
'## ⏱️ Audit Checkpoint Snapshot (Archived)',
|
|
63
63
|
'',
|
|
64
64
|
`> **Archived on:** ${archiveTimestamp}`,
|
|
65
65
|
`> **Previous Status:** ${previousTitle || 'Prior Report'}`,
|
package/cli/audit/social.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { groupViolationsBySeverity } from './reporter.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
PILLAR_EMOJIS,
|
|
4
|
+
formatPillarReactionBadgesMarkdown,
|
|
5
|
+
formatPillarShieldBadges,
|
|
6
|
+
resolveBadgeColor
|
|
7
|
+
} from './reporter-utils.js';
|
|
3
8
|
|
|
4
9
|
export {
|
|
5
10
|
parseGitRemoteUrl,
|
|
@@ -31,11 +36,7 @@ export const DISCUSSION_CATEGORY_SLUG = 'npx-chemx-audit';
|
|
|
31
36
|
export const ORG_DISCUSSIONS_URL = 'https://github.com/orgs/Chemical-X-Protocol/discussions';
|
|
32
37
|
export const DEFAULT_DISCUSSION_REPO = 'Chemical-X-Protocol/.github';
|
|
33
38
|
|
|
34
|
-
export
|
|
35
|
-
if (score >= 90) return '06b6d4';
|
|
36
|
-
if (score >= 70) return 'f59e0b';
|
|
37
|
-
return 'ef4444';
|
|
38
|
-
};
|
|
39
|
+
export { resolveBadgeColor };
|
|
39
40
|
|
|
40
41
|
export const resolveHotspotTierText = (lineCount) => {
|
|
41
42
|
if (lineCount >= 2000) return '🔴 **CRITICAL (>= 2,000 lines of code)**';
|
|
@@ -101,8 +102,13 @@ export const generateDiscussionContent = (report, username, projectName = 'Codeb
|
|
|
101
102
|
const lines = [];
|
|
102
103
|
lines.push(`# ${isHighScoring ? 'Crystalline' : 'Architecture'} Audit Report: ${projectName}`);
|
|
103
104
|
lines.push('');
|
|
104
|
-
lines.push(`[](https://chemicalx.xophz.com) [](https://chemicalx.xophz.com)`);
|
|
105
|
+
lines.push(`[](https://chemicalx.xophz.com) [](https://chemicalx.xophz.com) [](https://chemicalx.xophz.com)`);
|
|
105
106
|
lines.push('');
|
|
107
|
+
const pillarShields = formatPillarShieldBadges(pillars);
|
|
108
|
+
if (pillarShields) {
|
|
109
|
+
lines.push(pillarShields);
|
|
110
|
+
lines.push('');
|
|
111
|
+
}
|
|
106
112
|
lines.push(`* **Audited by**: @${username}`);
|
|
107
113
|
const resolvedRepoUrl = repoUrl || (/^[\w\-.]+\/[\w\-.]+$/.test(projectName) ? `https://github.com/${projectName}` : '');
|
|
108
114
|
if (resolvedRepoUrl) {
|
|
@@ -241,11 +247,21 @@ export const generateTransformationDiscussionContent = (
|
|
|
241
247
|
return isGood ? `🟢 **${sign}**` : `🔴 **${sign}**`;
|
|
242
248
|
};
|
|
243
249
|
|
|
250
|
+
const beforeColor = resolveBadgeColor(scoreBefore);
|
|
251
|
+
const afterColor = resolveBadgeColor(scoreAfter);
|
|
252
|
+
const encodedBeforeGrade = encodeURIComponent(`${scoreBefore}/100 (${beforeSnapshot.health.grade})`);
|
|
253
|
+
const encodedAfterGrade = encodeURIComponent(`${scoreAfter}/100 (${afterSnapshot.health.grade})`);
|
|
254
|
+
|
|
244
255
|
const lines = [];
|
|
245
256
|
lines.push(`# 🚀 Architectural Transformation: ${projectName}`);
|
|
246
257
|
lines.push('');
|
|
247
|
-
lines.push(`[](https://chemicalx.xophz.com)`);
|
|
258
|
+
lines.push(`[](https://chemicalx.xophz.com) [](https://chemicalx.xophz.com) [](https://chemicalx.xophz.com)`);
|
|
248
259
|
lines.push('');
|
|
260
|
+
const afterPillarShields = formatPillarShieldBadges(afterSnapshot.pillars);
|
|
261
|
+
if (afterPillarShields) {
|
|
262
|
+
lines.push(afterPillarShields);
|
|
263
|
+
lines.push('');
|
|
264
|
+
}
|
|
249
265
|
lines.push(`* **Audited by**: @${username}`);
|
|
250
266
|
const resolvedRepoUrl = repoUrl || (/^[\w\-.]+\/[\w\-.]+$/.test(projectName) ? `https://github.com/${projectName}` : '');
|
|
251
267
|
if (resolvedRepoUrl) {
|
|
@@ -263,7 +279,7 @@ export const generateTransformationDiscussionContent = (
|
|
|
263
279
|
lines.push('');
|
|
264
280
|
lines.push('## 1. Before vs. After Metric Comparison');
|
|
265
281
|
lines.push('');
|
|
266
|
-
lines.push('| Metric | Before (Baseline) | After (Refactored) | Delta |');
|
|
282
|
+
lines.push('| Metric | Before (Baseline Floor) | After (Refactored) | Delta |');
|
|
267
283
|
lines.push('| :--- | :---: | :---: | :---: |');
|
|
268
284
|
lines.push(`| **Molecular Health (MHI)** | ${scoreBefore} / 100 (${beforeSnapshot.health.grade}) | ${scoreAfter} / 100 (${afterSnapshot.health.grade}) | ${formatDelta(scoreDelta)} |`);
|
|
269
285
|
const slopBefore = beforeSnapshot.aiSlop?.score ?? 100;
|
package/cli/audit/types.d.ts
CHANGED
|
@@ -87,6 +87,41 @@ export interface AiSlopScore {
|
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
export interface PatternOccurrence {
|
|
91
|
+
readonly filePath: string;
|
|
92
|
+
readonly line: number;
|
|
93
|
+
readonly column?: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface PatternCandidate {
|
|
97
|
+
readonly id: string;
|
|
98
|
+
readonly type: 'UI_STRUCTURE' | 'STATE_UNION' | 'PREDICATE_LOGIC' | 'HOOK_SIGNATURE';
|
|
99
|
+
readonly label: string;
|
|
100
|
+
readonly detail: string;
|
|
101
|
+
readonly suggestedCapsule: string;
|
|
102
|
+
readonly recommendation: string;
|
|
103
|
+
readonly fileCount: number;
|
|
104
|
+
readonly totalHits: number;
|
|
105
|
+
readonly hasHotspot: boolean;
|
|
106
|
+
readonly impactScore: number;
|
|
107
|
+
readonly occurrences: readonly PatternOccurrence[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface RoadmapItem {
|
|
111
|
+
readonly title: string;
|
|
112
|
+
readonly target: string;
|
|
113
|
+
readonly action: string;
|
|
114
|
+
readonly locations: readonly string[];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface RoadmapPhase {
|
|
118
|
+
readonly step: number;
|
|
119
|
+
readonly phase: string;
|
|
120
|
+
readonly roi: string;
|
|
121
|
+
readonly rationale: string;
|
|
122
|
+
readonly items: readonly RoadmapItem[];
|
|
123
|
+
}
|
|
124
|
+
|
|
90
125
|
export interface AuditReport {
|
|
91
126
|
readonly scannedFiles: number;
|
|
92
127
|
readonly totalViolations: number;
|
|
@@ -96,6 +131,8 @@ export interface AuditReport {
|
|
|
96
131
|
readonly pillars: Record<string, PillarData>;
|
|
97
132
|
readonly contextAnalysis: ContextTokenAnalysis;
|
|
98
133
|
readonly hotspots: readonly HotspotFile[];
|
|
134
|
+
readonly patterns?: readonly PatternCandidate[];
|
|
135
|
+
readonly roadmap?: readonly RoadmapPhase[];
|
|
99
136
|
readonly violations: readonly HazardViolation[];
|
|
100
137
|
}
|
|
101
138
|
|
|
@@ -123,14 +160,24 @@ export declare function calculateAiSlopScore(violations: readonly HazardViolatio
|
|
|
123
160
|
export * from './reporter';
|
|
124
161
|
export * from './social';
|
|
125
162
|
|
|
126
|
-
export
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
163
|
+
export interface PromptOptions {
|
|
164
|
+
readonly excludeAiSlop?: boolean;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export declare function formatGroupedPromptViolations(violations?: readonly HazardViolation[]): string[];
|
|
168
|
+
export declare function buildGradeFPrompt(report: AuditReport, options?: PromptOptions): string;
|
|
169
|
+
export declare function buildGradeDPrompt(report: AuditReport, options?: PromptOptions): string;
|
|
170
|
+
export declare function buildGradeCPrompt(report: AuditReport, options?: PromptOptions): string;
|
|
171
|
+
export declare function buildGradeBPrompt(report: AuditReport, options?: PromptOptions): string;
|
|
130
172
|
export declare function buildAiSlopPrompt(report: AuditReport): string;
|
|
131
173
|
export declare function buildHotspotsPrompt(report: AuditReport): string;
|
|
132
174
|
export declare function buildMasterPrompt(report: AuditReport): string;
|
|
133
175
|
export declare function formatPromptBox(title: string, promptText: string): string;
|
|
134
176
|
|
|
135
|
-
export
|
|
177
|
+
export declare function buildRemediationRoadmap(report: AuditReport): RoadmapPhase[];
|
|
178
|
+
export declare function formatRoadmapSection(report: AuditReport, themeColor?: string | null): string;
|
|
179
|
+
export declare function formatRoadmapMarkdown(report: AuditReport): string;
|
|
180
|
+
export declare function buildSelfHealingRoadmapPrompt(report: AuditReport): string;
|
|
136
181
|
|
|
182
|
+
export * from './history';
|
|
183
|
+
export * from './rules-predicates';
|
package/cli/audit.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { auditCode, PILLARS, RULE_REGISTRY } from './audit/rules.js';
|
|
4
|
+
import { createPatternRegistry } from './audit/pattern-detector.js';
|
|
5
|
+
import {
|
|
6
|
+
buildRemediationRoadmap,
|
|
7
|
+
formatRoadmapSection,
|
|
8
|
+
formatRoadmapMarkdown,
|
|
9
|
+
buildSelfHealingRoadmapPrompt
|
|
10
|
+
} from './audit/roadmap.js';
|
|
4
11
|
import {
|
|
5
12
|
calculateMolecularHealthScore,
|
|
6
13
|
calculateQuantumHealthScore,
|
|
@@ -38,8 +45,24 @@ import {
|
|
|
38
45
|
getAsciiGradeLines,
|
|
39
46
|
formatAsciiGrade,
|
|
40
47
|
getReportCardAsciiLines,
|
|
41
|
-
REPORT_CARD_ASCII
|
|
48
|
+
REPORT_CARD_ASCII,
|
|
49
|
+
formatPillarReactionBadgesTerminal,
|
|
50
|
+
formatPillarReactionBadgesMarkdown,
|
|
51
|
+
formatPillarShieldBadges,
|
|
52
|
+
resolveBadgeColor,
|
|
53
|
+
PILLAR_SHORT_NAMES
|
|
42
54
|
} from './audit/reporter.js';
|
|
55
|
+
import {
|
|
56
|
+
buildGradeFPrompt,
|
|
57
|
+
buildGradeDPrompt,
|
|
58
|
+
buildGradeCPrompt,
|
|
59
|
+
buildGradeBPrompt,
|
|
60
|
+
buildAiSlopPrompt,
|
|
61
|
+
buildHotspotsPrompt,
|
|
62
|
+
buildMasterPrompt,
|
|
63
|
+
formatPromptBox,
|
|
64
|
+
formatGroupedPromptViolations
|
|
65
|
+
} from './audit/prompts.js';
|
|
43
66
|
|
|
44
67
|
const IGNORED_DIRS = new Set([
|
|
45
68
|
'node_modules',
|
|
@@ -70,10 +93,11 @@ export const auditFile = (filePath, relativePath) => {
|
|
|
70
93
|
return auditCode(content, filePath, relativePath);
|
|
71
94
|
};
|
|
72
95
|
|
|
73
|
-
export const scanTree = (targetDir, baseDir) => {
|
|
96
|
+
export const scanTree = (targetDir, baseDir, scanOptions = {}) => {
|
|
74
97
|
let violations = [];
|
|
75
98
|
let fileStats = [];
|
|
76
99
|
let totalHooks = 0;
|
|
100
|
+
const { patternRegistry } = scanOptions;
|
|
77
101
|
|
|
78
102
|
if (!fs.existsSync(targetDir)) {
|
|
79
103
|
return { violations, fileStats, totalHooks };
|
|
@@ -86,7 +110,7 @@ export const scanTree = (targetDir, baseDir) => {
|
|
|
86
110
|
|
|
87
111
|
if (entry.isDirectory()) {
|
|
88
112
|
if (!IGNORED_DIRS.has(entry.name)) {
|
|
89
|
-
const sub = scanTree(fullPath, baseDir);
|
|
113
|
+
const sub = scanTree(fullPath, baseDir, scanOptions);
|
|
90
114
|
violations = violations.concat(sub.violations);
|
|
91
115
|
fileStats = fileStats.concat(sub.fileStats);
|
|
92
116
|
totalHooks += sub.totalHooks;
|
|
@@ -110,7 +134,7 @@ export const scanTree = (targetDir, baseDir) => {
|
|
|
110
134
|
totalHooks += hookMatches.length;
|
|
111
135
|
}
|
|
112
136
|
|
|
113
|
-
const fileViolations = auditCode(content, fullPath, relPath);
|
|
137
|
+
const fileViolations = auditCode(content, fullPath, relPath, { patternRegistry });
|
|
114
138
|
violations = violations.concat(fileViolations);
|
|
115
139
|
}
|
|
116
140
|
}
|
|
@@ -126,7 +150,8 @@ export const scanDirectory = (targetDir, baseDir) => {
|
|
|
126
150
|
export const runAudit = (targetDir = 'src', options = {}) => {
|
|
127
151
|
const cwd = process.cwd();
|
|
128
152
|
const absoluteTarget = path.resolve(cwd, targetDir);
|
|
129
|
-
const
|
|
153
|
+
const patternRegistry = createPatternRegistry();
|
|
154
|
+
const { violations, fileStats, totalHooks } = scanTree(absoluteTarget, cwd, { patternRegistry });
|
|
130
155
|
|
|
131
156
|
const scannedFiles = fileStats.length;
|
|
132
157
|
const totalLoc = fileStats.reduce((acc, f) => acc + f.lineCount, 0);
|
|
@@ -168,6 +193,8 @@ export const runAudit = (targetDir = 'src', options = {}) => {
|
|
|
168
193
|
const contextAnalysis = calculateTokenBurnAnalytics(fileStats, options);
|
|
169
194
|
const hotspots = calculateHotspots(violations, fileStats, 5);
|
|
170
195
|
const aiSlop = calculateAiSlopScore(violations, scannedFiles);
|
|
196
|
+
const patterns = patternRegistry.resolveHarmonizationCandidates(hotspots);
|
|
197
|
+
const roadmap = buildRemediationRoadmap({ hotspots, violations, patterns });
|
|
171
198
|
|
|
172
199
|
const report = {
|
|
173
200
|
targetDir,
|
|
@@ -180,6 +207,8 @@ export const runAudit = (targetDir = 'src', options = {}) => {
|
|
|
180
207
|
pillars,
|
|
181
208
|
contextAnalysis,
|
|
182
209
|
hotspots,
|
|
210
|
+
patterns,
|
|
211
|
+
roadmap,
|
|
183
212
|
violations
|
|
184
213
|
};
|
|
185
214
|
|
|
@@ -233,9 +262,30 @@ export {
|
|
|
233
262
|
getAsciiGradeLines,
|
|
234
263
|
formatAsciiGrade,
|
|
235
264
|
getReportCardAsciiLines,
|
|
236
|
-
REPORT_CARD_ASCII
|
|
265
|
+
REPORT_CARD_ASCII,
|
|
266
|
+
formatPillarReactionBadgesTerminal,
|
|
267
|
+
formatPillarReactionBadgesMarkdown,
|
|
268
|
+
formatPillarShieldBadges,
|
|
269
|
+
resolveBadgeColor,
|
|
270
|
+
PILLAR_SHORT_NAMES,
|
|
271
|
+
buildGradeFPrompt,
|
|
272
|
+
buildGradeDPrompt,
|
|
273
|
+
buildGradeCPrompt,
|
|
274
|
+
buildGradeBPrompt,
|
|
275
|
+
buildAiSlopPrompt,
|
|
276
|
+
buildHotspotsPrompt,
|
|
277
|
+
buildMasterPrompt,
|
|
278
|
+
formatPromptBox,
|
|
279
|
+
formatGroupedPromptViolations,
|
|
280
|
+
createPatternRegistry,
|
|
281
|
+
buildRemediationRoadmap,
|
|
282
|
+
formatRoadmapSection,
|
|
283
|
+
formatRoadmapMarkdown,
|
|
284
|
+
buildSelfHealingRoadmapPrompt
|
|
237
285
|
};
|
|
238
286
|
|
|
287
|
+
export * from './audit/rules-predicates.js';
|
|
288
|
+
|
|
239
289
|
export default {
|
|
240
290
|
auditFile,
|
|
241
291
|
scanDirectory,
|
|
@@ -269,6 +319,25 @@ export default {
|
|
|
269
319
|
formatAsciiGrade,
|
|
270
320
|
getReportCardAsciiLines,
|
|
271
321
|
REPORT_CARD_ASCII,
|
|
322
|
+
formatPillarReactionBadgesTerminal,
|
|
323
|
+
formatPillarReactionBadgesMarkdown,
|
|
324
|
+
formatPillarShieldBadges,
|
|
325
|
+
resolveBadgeColor,
|
|
326
|
+
PILLAR_SHORT_NAMES,
|
|
272
327
|
PILLARS,
|
|
273
|
-
RULE_REGISTRY
|
|
328
|
+
RULE_REGISTRY,
|
|
329
|
+
buildGradeFPrompt,
|
|
330
|
+
buildGradeDPrompt,
|
|
331
|
+
buildGradeCPrompt,
|
|
332
|
+
buildGradeBPrompt,
|
|
333
|
+
buildAiSlopPrompt,
|
|
334
|
+
buildHotspotsPrompt,
|
|
335
|
+
buildMasterPrompt,
|
|
336
|
+
formatPromptBox,
|
|
337
|
+
formatGroupedPromptViolations,
|
|
338
|
+
createPatternRegistry,
|
|
339
|
+
buildRemediationRoadmap,
|
|
340
|
+
formatRoadmapSection,
|
|
341
|
+
formatRoadmapMarkdown,
|
|
342
|
+
buildSelfHealingRoadmapPrompt
|
|
274
343
|
};
|
package/cli/badge.js
CHANGED
|
@@ -53,7 +53,7 @@ export const generateHtmlBadgeSnippet = (label, grade, discussionUrl = null) =>
|
|
|
53
53
|
const targetHref = discussionUrl || 'https://chemicalx.xophz.com';
|
|
54
54
|
const targetTitle = discussionUrl ? 'Verified Chemical X Audit Report on GitHub Discussions' : 'Verified by Chemical X Protocol';
|
|
55
55
|
|
|
56
|
-
return `<a href="${targetHref}" target="_blank" rel="noopener noreferrer" style="display:inline-flex;align-items:center;gap:8px;padding:4px 12px;border-radius:9999px;font-family:monospace;font-size:11px;text-decoration:none;border:1px solid rgba(
|
|
56
|
+
return `<a href="${targetHref}" target="_blank" rel="noopener noreferrer" style="display:inline-flex;align-items:center;gap:8px;padding:4px 12px;border-radius:9999px;font-family:monospace;font-size:11px;text-decoration:none;border:1px solid rgba(56,189,248,0.35);background:rgba(9,13,22,0.85);backdrop-filter:blur(8px);color:#e2e8f0;transition:all 0.2s ease;" title="${targetTitle}">
|
|
57
57
|
<span style="width:8px;height:8px;border-radius:50%;background:#a3e635;box-shadow:0 0 6px rgba(163,230,53,0.6);"></span>
|
|
58
58
|
<span>${label}</span>
|
|
59
59
|
<span style="padding:2px 7px;border-radius:9999px;font-weight:bold;font-size:10px;background:rgba(163,230,53,0.15);color:#a3e635;border:1px solid rgba(163,230,53,0.3);">${grade}</span>
|
|
@@ -155,7 +155,14 @@ export default ChemicalXBadge;
|
|
|
155
155
|
export const generateSvgBadgeSnippet = (label, grade) => {
|
|
156
156
|
const width = Math.max(280, label.length * 8 + 60);
|
|
157
157
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="28" viewBox="0 0 ${width} 28" fill="none">
|
|
158
|
-
<
|
|
158
|
+
<defs>
|
|
159
|
+
<linearGradient id="chemx-comic" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
160
|
+
<stop offset="0%" stop-color="#f43f85"/>
|
|
161
|
+
<stop offset="50%" stop-color="#38bdf8"/>
|
|
162
|
+
<stop offset="100%" stop-color="#a3e635"/>
|
|
163
|
+
</linearGradient>
|
|
164
|
+
</defs>
|
|
165
|
+
<rect width="${width}" height="28" rx="14" fill="#090d16" stroke="url(#chemx-comic)" stroke-width="1.2"/>
|
|
159
166
|
<circle cx="16" cy="14" r="4" fill="#a3e635"/>
|
|
160
167
|
<text x="28" y="17" fill="#cbd5e1" font-family="monospace" font-size="11" font-weight="500">${label}</text>
|
|
161
168
|
<rect x="${width - 44}" y="6" width="34" height="16" rx="8" fill="#a3e635" fill-opacity="0.18" stroke="#a3e635" stroke-opacity="0.4"/>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const findFileUpward = (fileName, startDir) => {
|
|
5
|
+
let currentDir = path.resolve(startDir);
|
|
6
|
+
while (true) {
|
|
7
|
+
const candidate = path.join(currentDir, fileName);
|
|
8
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
9
|
+
const parent = path.dirname(currentDir);
|
|
10
|
+
if (parent === currentDir) break;
|
|
11
|
+
currentDir = parent;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const resolvePackageManager = (cwd) => {
|
|
17
|
+
const hasPnpmLock = Boolean(findFileUpward('pnpm-lock.yaml', cwd));
|
|
18
|
+
const hasYarnLock = Boolean(findFileUpward('yarn.lock', cwd));
|
|
19
|
+
const hasBunLock = Boolean(findFileUpward('bun.lockb', cwd) || findFileUpward('bun.lock', cwd));
|
|
20
|
+
|
|
21
|
+
if (hasPnpmLock) return 'pnpm';
|
|
22
|
+
if (hasYarnLock) return 'yarn';
|
|
23
|
+
if (hasBunLock) return 'bun';
|
|
24
|
+
return 'npm';
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const loadLocalPackageJson = (cwd) => {
|
|
28
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
29
|
+
if (!fs.existsSync(pkgPath)) return null;
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const detectProjectBuildCommand = (customCmd, cwd = process.cwd()) => {
|
|
38
|
+
const hasCustomCommand = Boolean(customCmd && customCmd.trim().length > 0);
|
|
39
|
+
if (hasCustomCommand) return customCmd.trim();
|
|
40
|
+
|
|
41
|
+
const pm = resolvePackageManager(cwd);
|
|
42
|
+
const pkg = loadLocalPackageJson(cwd);
|
|
43
|
+
const scripts = (pkg && pkg.scripts) || {};
|
|
44
|
+
|
|
45
|
+
const hasBuildScript = Boolean(scripts.build);
|
|
46
|
+
const hasBuildPublicScript = Boolean(scripts['build:public']);
|
|
47
|
+
const hasBuildProdScript = Boolean(scripts['build:prod']);
|
|
48
|
+
|
|
49
|
+
if (hasBuildScript) {
|
|
50
|
+
return pm === 'yarn' ? 'yarn build' : `${pm} run build`;
|
|
51
|
+
}
|
|
52
|
+
if (hasBuildPublicScript) {
|
|
53
|
+
return pm === 'yarn' ? 'yarn build:public' : `${pm} run build:public`;
|
|
54
|
+
}
|
|
55
|
+
if (hasBuildProdScript) {
|
|
56
|
+
return pm === 'yarn' ? 'yarn build:prod' : `${pm} run build:prod`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const hasViteConfig = fs.existsSync(path.join(cwd, 'vite.config.js')) ||
|
|
60
|
+
fs.existsSync(path.join(cwd, 'vite.config.ts')) ||
|
|
61
|
+
fs.existsSync(path.join(cwd, 'vite.config.mjs'));
|
|
62
|
+
if (hasViteConfig) return 'npx vite build';
|
|
63
|
+
|
|
64
|
+
const hasTsConfig = fs.existsSync(path.join(cwd, 'tsconfig.json'));
|
|
65
|
+
if (hasTsConfig) return 'npx tsc --noEmit';
|
|
66
|
+
|
|
67
|
+
return `${pm} run build`;
|
|
68
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export const executeBuild = (command, cwd = process.cwd(), options = {}) => {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
const startTime = Date.now();
|
|
6
|
+
let stdoutBuffer = '';
|
|
7
|
+
let stderrBuffer = '';
|
|
8
|
+
|
|
9
|
+
const isRawStream = Boolean(options.raw);
|
|
10
|
+
|
|
11
|
+
const child = spawn(command, {
|
|
12
|
+
shell: true,
|
|
13
|
+
cwd,
|
|
14
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
15
|
+
env: { ...process.env, FORCE_COLOR: '1' }
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
child.stdout.on('data', (chunk) => {
|
|
19
|
+
const text = chunk.toString();
|
|
20
|
+
stdoutBuffer += text;
|
|
21
|
+
if (isRawStream) {
|
|
22
|
+
process.stdout.write(text);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
child.stderr.on('data', (chunk) => {
|
|
27
|
+
const text = chunk.toString();
|
|
28
|
+
stderrBuffer += text;
|
|
29
|
+
if (isRawStream) {
|
|
30
|
+
process.stderr.write(text);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
child.on('error', (err) => {
|
|
35
|
+
const durationMs = Date.now() - startTime;
|
|
36
|
+
stderrBuffer += `\nProcess error: ${err.message}\n`;
|
|
37
|
+
resolve({
|
|
38
|
+
exitCode: 1,
|
|
39
|
+
stdout: stdoutBuffer,
|
|
40
|
+
stderr: stderrBuffer,
|
|
41
|
+
durationMs,
|
|
42
|
+
command
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
child.on('close', (code) => {
|
|
47
|
+
const durationMs = Date.now() - startTime;
|
|
48
|
+
const normalizedCode = code === null ? 1 : code;
|
|
49
|
+
resolve({
|
|
50
|
+
exitCode: normalizedCode,
|
|
51
|
+
stdout: stdoutBuffer,
|
|
52
|
+
stderr: stderrBuffer,
|
|
53
|
+
durationMs,
|
|
54
|
+
command
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { stripAnsi } from '../terminal.js';
|
|
2
|
+
|
|
3
|
+
const buildDiagnosticKey = (d) => {
|
|
4
|
+
return `${d.category}|${d.file || ''}|${d.line || 0}|${d.column || 0}|${d.code || ''}|${d.message}`;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const deduplicateDiagnostics = (diagnostics) => {
|
|
8
|
+
const seen = new Set();
|
|
9
|
+
const unique = [];
|
|
10
|
+
|
|
11
|
+
for (const item of diagnostics) {
|
|
12
|
+
const key = buildDiagnosticKey(item);
|
|
13
|
+
if (!seen.has(key)) {
|
|
14
|
+
seen.add(key);
|
|
15
|
+
unique.push(item);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return unique;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const computeSummaryCounts = (diagnostics) => {
|
|
23
|
+
let errorCount = 0;
|
|
24
|
+
let warningCount = 0;
|
|
25
|
+
const uniqueFiles = new Set();
|
|
26
|
+
|
|
27
|
+
for (const item of diagnostics) {
|
|
28
|
+
if (item.severity === 'ERROR') errorCount += 1;
|
|
29
|
+
if (item.severity === 'WARNING') warningCount += 1;
|
|
30
|
+
if (item.file) uniqueFiles.add(item.file);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
total: diagnostics.length,
|
|
35
|
+
errors: errorCount,
|
|
36
|
+
warnings: warningCount,
|
|
37
|
+
files: uniqueFiles.size
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const computeCategoryBreakdown = (diagnostics) => {
|
|
42
|
+
const categories = {};
|
|
43
|
+
for (const item of diagnostics) {
|
|
44
|
+
categories[item.category] = (categories[item.category] || 0) + 1;
|
|
45
|
+
}
|
|
46
|
+
return categories;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const extractRawTailLines = (stdout, stderr, maxLines = 15) => {
|
|
50
|
+
const combined = `${stdout}\n${stderr}`;
|
|
51
|
+
const lines = combined
|
|
52
|
+
.split(/\r?\n/)
|
|
53
|
+
.map((l) => stripAnsi(l).trim())
|
|
54
|
+
.filter((l) => l.length > 0);
|
|
55
|
+
return lines.slice(-maxLines);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const groupBuildDiagnostics = (rawDiagnostics, executionResult) => {
|
|
59
|
+
const unique = deduplicateDiagnostics(rawDiagnostics);
|
|
60
|
+
const counts = computeSummaryCounts(unique);
|
|
61
|
+
const categories = computeCategoryBreakdown(unique);
|
|
62
|
+
|
|
63
|
+
const shouldCaptureTail = executionResult.exitCode !== 0 && unique.length === 0;
|
|
64
|
+
const rawTail = shouldCaptureTail
|
|
65
|
+
? extractRawTailLines(executionResult.stdout, executionResult.stderr)
|
|
66
|
+
: [];
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
success: executionResult.exitCode === 0,
|
|
70
|
+
exitCode: executionResult.exitCode,
|
|
71
|
+
durationMs: executionResult.durationMs,
|
|
72
|
+
command: executionResult.command,
|
|
73
|
+
counts,
|
|
74
|
+
categories,
|
|
75
|
+
diagnostics: unique,
|
|
76
|
+
rawTail
|
|
77
|
+
};
|
|
78
|
+
};
|