@chemx/starter-kit 26.9.12-107 → 26.9.12-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/prompts.js +22 -38
- package/cli/audit/reporter-banner.js +4 -16
- package/cli/audit/reporter-grouping.d.ts +6 -0
- package/cli/audit/reporter-grouping.js +48 -5
- package/cli/audit/reporter-utils.js +13 -1
- package/cli/audit/reporter.js +1 -0
- package/cli/badge.js +9 -2
- package/cli/index.js +7 -1
- package/cli/installer-templates.js +1 -1
- package/cli/terminal.js +6 -5
- package/cli/theme.d.ts +41 -0
- package/cli/theme.js +84 -0
- package/docs/CHANGELOG.md +7 -1
- package/package.json +1 -1
package/cli/audit/prompts.js
CHANGED
|
@@ -1,36 +1,19 @@
|
|
|
1
|
-
import { groupViolationsByRule } from './reporter-grouping.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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;
|
|
1
|
+
import { groupViolationsByRule, buildPathTree } from './reporter-grouping.js';
|
|
2
|
+
import {
|
|
3
|
+
CYAN,
|
|
4
|
+
YELLOW,
|
|
5
|
+
RED,
|
|
6
|
+
ORANGE,
|
|
7
|
+
DIM,
|
|
8
|
+
BOLD,
|
|
9
|
+
RESET
|
|
10
|
+
} from './reporter-utils.js';
|
|
11
|
+
|
|
12
|
+
const SEVERITY_COLORS = {
|
|
13
|
+
CRITICAL: RED,
|
|
14
|
+
HIGH: ORANGE,
|
|
15
|
+
MEDIUM: YELLOW,
|
|
16
|
+
LOW: DIM
|
|
34
17
|
};
|
|
35
18
|
|
|
36
19
|
const renderTreeLines = (node, depth = 0) => {
|
|
@@ -39,7 +22,7 @@ const renderTreeLines = (node, depth = 0) => {
|
|
|
39
22
|
|
|
40
23
|
const sortedDirs = Array.from(node.dirs.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
41
24
|
for (const [dirName, childNode] of sortedDirs) {
|
|
42
|
-
lines.push(`${indent}
|
|
25
|
+
lines.push(`${indent}📁 ${BOLD}${dirName}${RESET}`);
|
|
43
26
|
const childLines = renderTreeLines(childNode, depth + 1);
|
|
44
27
|
lines.push(...childLines);
|
|
45
28
|
}
|
|
@@ -47,7 +30,7 @@ const renderTreeLines = (node, depth = 0) => {
|
|
|
47
30
|
const sortedFiles = Array.from(node.files.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
48
31
|
for (const [fileName, locs] of sortedFiles) {
|
|
49
32
|
const uniqueLocs = Array.from(new Set(locs)).join(', ');
|
|
50
|
-
lines.push(`${indent}- \`${fileName}:${uniqueLocs}\``);
|
|
33
|
+
lines.push(`${indent}- \`${YELLOW}${fileName}:${uniqueLocs}${RESET}\``);
|
|
51
34
|
}
|
|
52
35
|
|
|
53
36
|
return lines;
|
|
@@ -61,10 +44,11 @@ export const formatGroupedPromptViolations = (violations = []) => {
|
|
|
61
44
|
const lines = [];
|
|
62
45
|
|
|
63
46
|
ruleGroups.forEach((rg, idx) => {
|
|
47
|
+
const sevColor = SEVERITY_COLORS[rg.severity] || YELLOW;
|
|
64
48
|
const countLabel = rg.total === 1 ? '1 item' : `${rg.total} items`;
|
|
65
|
-
lines.push(`${idx + 1}. [${rg.rule}] (${countLabel})`);
|
|
66
|
-
lines.push(` Hazard:
|
|
67
|
-
lines.push(` Directive: ${rg.directive}`);
|
|
49
|
+
lines.push(`${idx + 1}. ${sevColor}[${rg.rule}]${RESET} ${BOLD}(${countLabel})${RESET}`);
|
|
50
|
+
lines.push(` Hazard: ${rg.hazard}`);
|
|
51
|
+
lines.push(` Directive: ${CYAN}${rg.directive}${RESET}`);
|
|
68
52
|
lines.push(' Locations:');
|
|
69
53
|
|
|
70
54
|
const tree = buildPathTree(rg.violations);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveGradeColor } from './reporter-utils.js';
|
|
2
2
|
import { getAsciiGradeLines } from './reporter-ascii.js';
|
|
3
|
+
import { getChemicalXGradientColor, ANSI } from '../theme.js';
|
|
3
4
|
|
|
4
5
|
const BANNER_ART = [
|
|
5
6
|
' ██████╗██╗ ██╗███████╗███╗ ███╗██╗ ██████╗ █████╗ ██╗ ██╗ ██╗',
|
|
@@ -18,7 +19,7 @@ export const getChemicalXAsciiBanner = (gradeOrReport = null) => {
|
|
|
18
19
|
: gradeOrReport;
|
|
19
20
|
|
|
20
21
|
const lines = [];
|
|
21
|
-
lines.push(
|
|
22
|
+
lines.push(` ${ANSI.BOLD}${ANSI.GOLD}The Secret Sauce to Vibe Coding!${ANSI.RESET}`);
|
|
22
23
|
|
|
23
24
|
const gColor = grade ? resolveGradeColor(grade) : '';
|
|
24
25
|
const gradeLines = grade ? getAsciiGradeLines(grade, gColor, true) : [];
|
|
@@ -35,21 +36,8 @@ export const getChemicalXAsciiBanner = (gradeOrReport = null) => {
|
|
|
35
36
|
continue;
|
|
36
37
|
}
|
|
37
38
|
const t = i / (MAX_BANNER_LEN - 1);
|
|
38
|
-
|
|
39
|
-
|
|
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`;
|
|
39
|
+
const { ansi } = getChemicalXGradientColor(t);
|
|
40
|
+
out += `${ansi}${ANSI.BOLD}${ch}${ANSI.RESET}`;
|
|
53
41
|
}
|
|
54
42
|
|
|
55
43
|
if (isSideBySide && gradeLines.length > lineIdx) {
|
|
@@ -29,9 +29,15 @@ export interface RuleHazardSummary {
|
|
|
29
29
|
readonly directories: readonly RuleDirectoryOccurrence[];
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export interface PathTreeNode {
|
|
33
|
+
readonly dirs: Map<string, PathTreeNode>;
|
|
34
|
+
readonly files: Map<string, string[]>;
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
export declare function resolveDirectory(filePath?: string): string;
|
|
33
38
|
export declare function groupViolationsByDirectory(violations?: readonly HazardViolation[]): readonly DirectoryHazardSummary[];
|
|
34
39
|
export declare function groupViolationsByRule(violations?: readonly HazardViolation[]): readonly RuleHazardSummary[];
|
|
40
|
+
export declare function buildPathTree(violations?: readonly HazardViolation[]): PathTreeNode;
|
|
35
41
|
export declare function formatCompactLocations(violations: readonly HazardViolation[], maxShown?: number): string;
|
|
36
42
|
export declare function renderGroupedViolationsTerminal(violations: readonly HazardViolation[]): string;
|
|
37
43
|
export declare function formatDirectoryDistributionSection(report: AuditReport, themeColor?: string | null): string;
|
|
@@ -157,6 +157,34 @@ const resolveSeverityColor = (severity) => {
|
|
|
157
157
|
return DIM;
|
|
158
158
|
};
|
|
159
159
|
|
|
160
|
+
export const buildPathTree = (violations = []) => {
|
|
161
|
+
const root = { dirs: new Map(), files: new Map() };
|
|
162
|
+
|
|
163
|
+
for (const v of violations) {
|
|
164
|
+
const rawPath = v.filePath || '';
|
|
165
|
+
const normalized = rawPath.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
166
|
+
const segments = normalized.split('/');
|
|
167
|
+
const fileName = segments.pop();
|
|
168
|
+
|
|
169
|
+
let current = root;
|
|
170
|
+
for (const seg of segments) {
|
|
171
|
+
const segName = seg.endsWith('/') ? seg : `${seg}/`;
|
|
172
|
+
if (!current.dirs.has(segName)) {
|
|
173
|
+
current.dirs.set(segName, { dirs: new Map(), files: new Map() });
|
|
174
|
+
}
|
|
175
|
+
current = current.dirs.get(segName);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!current.files.has(fileName)) {
|
|
179
|
+
current.files.set(fileName, []);
|
|
180
|
+
}
|
|
181
|
+
const loc = v.column ? `${v.line}:${v.column}` : `${v.line}`;
|
|
182
|
+
current.files.get(fileName).push(loc);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return root;
|
|
186
|
+
};
|
|
187
|
+
|
|
160
188
|
export const formatCompactLocations = (violations, maxShown = 4) => {
|
|
161
189
|
const formatLocationItem = (v) => {
|
|
162
190
|
const base = path.basename(v.filePath);
|
|
@@ -186,7 +214,8 @@ export const renderGroupedViolationsTerminal = (violations) => {
|
|
|
186
214
|
|
|
187
215
|
if (isSingleOccurrence) {
|
|
188
216
|
const v = rg.violations[0];
|
|
189
|
-
|
|
217
|
+
const colStr = v.column ? `:${v.column}` : '';
|
|
218
|
+
lines.push(` ${sevColor}[#${idx + 1} ${rg.severity}]${RESET} [${rg.rule}] ${YELLOW}${v.filePath}:${v.line}${colStr}${RESET}`);
|
|
190
219
|
lines.push(` Hazard: ${rg.hazard}`);
|
|
191
220
|
lines.push(` Directive: ${CYAN}${rg.directive}${RESET}\n`);
|
|
192
221
|
return;
|
|
@@ -199,10 +228,24 @@ export const renderGroupedViolationsTerminal = (violations) => {
|
|
|
199
228
|
lines.push(` Directive: ${CYAN}${rg.directive}${RESET}`);
|
|
200
229
|
lines.push(` Locations:`);
|
|
201
230
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
231
|
+
const tree = buildPathTree(rg.violations);
|
|
232
|
+
const renderTerminalTree = (node, depth = 0) => {
|
|
233
|
+
const indent = ' ' + ' '.repeat(depth);
|
|
234
|
+
|
|
235
|
+
const sortedDirs = Array.from(node.dirs.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
236
|
+
for (const [dirName, childNode] of sortedDirs) {
|
|
237
|
+
lines.push(`${indent}📁 ${BOLD}${dirName}${RESET}`);
|
|
238
|
+
renderTerminalTree(childNode, depth + 1);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const sortedFiles = Array.from(node.files.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
242
|
+
for (const [fileName, locs] of sortedFiles) {
|
|
243
|
+
const uniqueLocs = Array.from(new Set(locs)).join(', ');
|
|
244
|
+
lines.push(`${indent}- ${YELLOW}${fileName}:${uniqueLocs}${RESET}`);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
renderTerminalTree(tree, 0);
|
|
206
249
|
lines.push('');
|
|
207
250
|
});
|
|
208
251
|
|
|
@@ -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';
|
package/cli/audit/reporter.js
CHANGED
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"/>
|
package/cli/index.js
CHANGED
|
@@ -109,7 +109,13 @@ export const runAudit = async (customDir = null, isCli = false) => {
|
|
|
109
109
|
process.exit(0);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
const
|
|
112
|
+
const isNonInteractive =
|
|
113
|
+
rawArgs.includes('--non-interactive') ||
|
|
114
|
+
rawArgs.includes('--no-interactive') ||
|
|
115
|
+
rawArgs.includes('--ci') ||
|
|
116
|
+
Boolean(process.env.CI) ||
|
|
117
|
+
Boolean(process.env.GIT_DIR);
|
|
118
|
+
const isInteractive = !isNonInteractive && Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
113
119
|
|
|
114
120
|
if (isInteractive && !isUnroll) {
|
|
115
121
|
const handleReAudit = () => {
|
|
@@ -44,7 +44,7 @@ fi
|
|
|
44
44
|
|
|
45
45
|
if [ -n "\$AUDIT_BIN" ]; then
|
|
46
46
|
printf "\\033[38;2;98;201;255m[Chemical X] Verifying architectural health (Min Grade: %s, Min Score: %s)...\\033[0m\\n" "\$MIN_GRADE" "\$MIN_SCORE"
|
|
47
|
-
if ! \$AUDIT_BIN audit --min-grade="\$MIN_GRADE" --min-score="\$MIN_SCORE" --
|
|
47
|
+
if ! \$AUDIT_BIN audit --min-grade="\$MIN_GRADE" --min-score="\$MIN_SCORE" --non-interactive < /dev/null; then
|
|
48
48
|
printf "\\n\\033[1m\\033[31m[Chemical X] Commit Blocked: Codebase falls below required Grade %s (Score %s)\\033[0m\\n" "\$MIN_GRADE" "\$MIN_SCORE"
|
|
49
49
|
printf "\\033[36m💡 Tip: Want crystalline drop-in templates? Run 'npm create chemx' or sponsor at https://github.com/sponsors/Chemical-X-Protocol\\033[0m\\n\\n"
|
|
50
50
|
exit 1
|
package/cli/terminal.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import readline from "node:readline";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { formatChemicalXGradient, ANSI } from "./theme.js";
|
|
3
4
|
|
|
4
5
|
export const openBrowser = (url) => {
|
|
5
6
|
const platform = process.platform;
|
|
@@ -70,18 +71,18 @@ export const renderBanner = (title = "Chemical X Protocol: Molecular Architectur
|
|
|
70
71
|
"--border-foreground=45",
|
|
71
72
|
"--foreground=81",
|
|
72
73
|
"--bold",
|
|
73
|
-
` ${title}\n Zero-Context-Rot
|
|
74
|
+
` ${title}\n The Secret Sauce to Vibe Coding | Zero-Context-Rot Directives`
|
|
74
75
|
],
|
|
75
76
|
{ stdio: "inherit" }
|
|
76
77
|
);
|
|
77
78
|
} else {
|
|
78
79
|
process.stdout.write(
|
|
79
|
-
"\n
|
|
80
|
+
`\n${formatChemicalXGradient("=====================================================")}\n`
|
|
80
81
|
);
|
|
81
|
-
process.stdout.write(
|
|
82
|
-
process.stdout.write(
|
|
82
|
+
process.stdout.write(` ${formatChemicalXGradient(title)}\n`);
|
|
83
|
+
process.stdout.write(` ${ANSI.BOLD}${ANSI.GOLD}The Secret Sauce to Vibe Coding!${ANSI.RESET} ${ANSI.DIM}| Zero-Context-Rot Directives${ANSI.RESET}\n`);
|
|
83
84
|
process.stdout.write(
|
|
84
|
-
"\
|
|
85
|
+
`${formatChemicalXGradient("=====================================================")}\n\n`
|
|
85
86
|
);
|
|
86
87
|
}
|
|
87
88
|
};
|
package/cli/theme.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface ChemxGradientColor {
|
|
2
|
+
readonly r: number;
|
|
3
|
+
readonly g: number;
|
|
4
|
+
readonly b: number;
|
|
5
|
+
readonly ansi: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export declare const CHEMX_COLORS: {
|
|
9
|
+
readonly blossomPink: string;
|
|
10
|
+
readonly powerPurple: string;
|
|
11
|
+
readonly bubblesCyan: string;
|
|
12
|
+
readonly chemicalMint: string;
|
|
13
|
+
readonly buttercupLime: string;
|
|
14
|
+
readonly vibeGold: string;
|
|
15
|
+
readonly goldenRibbon: string;
|
|
16
|
+
readonly midnightViolet: string;
|
|
17
|
+
readonly obsidian: string;
|
|
18
|
+
readonly slateBorder: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export declare const CHEMX_RGB: {
|
|
22
|
+
readonly blossomPink: readonly [number, number, number];
|
|
23
|
+
readonly bubblesCyan: readonly [number, number, number];
|
|
24
|
+
readonly buttercupLime: readonly [number, number, number];
|
|
25
|
+
readonly vibeGold: readonly [number, number, number];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export declare const ANSI: {
|
|
29
|
+
readonly PINK: string;
|
|
30
|
+
readonly PURPLE: string;
|
|
31
|
+
readonly CYAN: string;
|
|
32
|
+
readonly MINT: string;
|
|
33
|
+
readonly LIME: string;
|
|
34
|
+
readonly GOLD: string;
|
|
35
|
+
readonly BOLD: string;
|
|
36
|
+
readonly DIM: string;
|
|
37
|
+
readonly RESET: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export declare function getChemicalXGradientColor(t: number): ChemxGradientColor;
|
|
41
|
+
export declare function formatChemicalXGradient(text: string): string;
|
package/cli/theme.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chemical X Protocol: Official Comic Book Color Palette
|
|
3
|
+
* Codified from Hall of the Gods Chemical X Vol. 1 Comic Book Edition
|
|
4
|
+
*
|
|
5
|
+
* Blossom Pink (#f43f85) -> Bubbles Cyan (#38bdf8) -> Buttercup Lime (#a3e635)
|
|
6
|
+
* Golden Ribbon: (#fbbf24) | Midnight Violet Armor: (#181126)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const CHEMX_COLORS = Object.freeze({
|
|
10
|
+
blossomPink: '#f43f85',
|
|
11
|
+
powerPurple: '#a855f7',
|
|
12
|
+
bubblesCyan: '#38bdf8',
|
|
13
|
+
chemicalMint: '#2dd4bf',
|
|
14
|
+
buttercupLime: '#a3e635',
|
|
15
|
+
vibeGold: '#fbbf24',
|
|
16
|
+
goldenRibbon: '#f59e0b',
|
|
17
|
+
midnightViolet: '#181126',
|
|
18
|
+
obsidian: '#090d16',
|
|
19
|
+
slateBorder: '#26354a'
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const CHEMX_RGB = Object.freeze({
|
|
23
|
+
blossomPink: [244, 63, 133],
|
|
24
|
+
bubblesCyan: [56, 189, 248],
|
|
25
|
+
buttercupLime: [163, 230, 53],
|
|
26
|
+
vibeGold: [251, 191, 36]
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const ANSI = Object.freeze({
|
|
30
|
+
PINK: '\x1b[38;2;244;63;133m',
|
|
31
|
+
PURPLE: '\x1b[38;2;168;85;247m',
|
|
32
|
+
CYAN: '\x1b[38;2;56;189;248m',
|
|
33
|
+
MINT: '\x1b[38;2;45;212;191m',
|
|
34
|
+
LIME: '\x1b[38;2;163;230;53m',
|
|
35
|
+
GOLD: '\x1b[38;2;251;191;36m',
|
|
36
|
+
BOLD: '\x1b[1m',
|
|
37
|
+
DIM: '\x1b[2m',
|
|
38
|
+
RESET: '\x1b[0m'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const getChemicalXGradientColor = (t) => {
|
|
42
|
+
const clamped = Math.max(0, Math.min(1, t));
|
|
43
|
+
let r = 0;
|
|
44
|
+
let g = 0;
|
|
45
|
+
let b = 0;
|
|
46
|
+
|
|
47
|
+
if (clamped < 0.45) {
|
|
48
|
+
const factor = clamped / 0.45;
|
|
49
|
+
r = Math.round(244 * (1 - factor) + 56 * factor);
|
|
50
|
+
g = Math.round(63 * (1 - factor) + 189 * factor);
|
|
51
|
+
b = Math.round(133 * (1 - factor) + 248 * factor);
|
|
52
|
+
} else {
|
|
53
|
+
const factor = (clamped - 0.45) / 0.55;
|
|
54
|
+
r = Math.round(56 * (1 - factor) + 163 * factor);
|
|
55
|
+
g = Math.round(189 * (1 - factor) + 230 * factor);
|
|
56
|
+
b = Math.round(248 * (1 - factor) + 53 * factor);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
r,
|
|
61
|
+
g,
|
|
62
|
+
b,
|
|
63
|
+
ansi: `\x1b[38;2;${r};${g};${b}m`
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const formatChemicalXGradient = (text) => {
|
|
68
|
+
if (!text) return '';
|
|
69
|
+
const len = text.length;
|
|
70
|
+
if (len === 1) return `${ANSI.PINK}${ANSI.BOLD}${text}${ANSI.RESET}`;
|
|
71
|
+
|
|
72
|
+
let out = '';
|
|
73
|
+
for (let i = 0; i < len; i++) {
|
|
74
|
+
const ch = text[i];
|
|
75
|
+
if (ch === ' ') {
|
|
76
|
+
out += ' ';
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const t = i / (len - 1);
|
|
80
|
+
const { ansi } = getChemicalXGradientColor(t);
|
|
81
|
+
out += `${ansi}${ANSI.BOLD}${ch}${ANSI.RESET}`;
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
};
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -19,8 +19,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
19
19
|
## [2026-09-11]
|
|
20
20
|
|
|
21
21
|
### Added
|
|
22
|
+
- Standardized the Chemical X color palette across the starter kit to match the official Hall of the Gods Chemical X Vol. 1 Comic Book cover:
|
|
23
|
+
- Created centralized palette module (`cli/theme.js`, `cli/theme.d.ts`) codifying Blossom Pink (`#f43f85`), Power Purple (`#a855f7`), Bubbles Cyan (`#38bdf8`), Chemical Mint (`#2dd4bf`), Buttercup Lime (`#a3e635`), Vibe Gold (`#fbbf24`), Golden Ribbon (`#f59e0b`), and Midnight Violet armor (`#181126`).
|
|
24
|
+
- Updated 24-bit TrueColor ASCII art banner (`getChemicalXAsciiBanner` in `cli/audit/reporter-banner.js`) with 3-phase gradient (Blossom Pink -> Bubbles Cyan -> Buttercup Lime) and golden ribbon motto styling (`The Secret Sauce to Vibe Coding!`).
|
|
25
|
+
- Upgraded terminal banner renderer (`renderBanner` in `cli/terminal.js`) with TrueColor gradient border and title formatting.
|
|
26
|
+
- Enhanced Verified Chemical X Footer Badge SVG asset generator (`generateSvgBadgeSnippet` in `cli/badge.js`) with embedded `<linearGradient id="chemx-comic">` border and updated HTML snippet.
|
|
27
|
+
- Aligned `m-chemx-badge` blueprint capsule styles (`_m-chemx-badge.scss`) with the official comic palette for hover glow and grade badges.
|
|
22
28
|
- Expanded AST static analysis engine with Pillars 8 to 11 (`cli/audit/extended-visitors.js`): added automated rules and visitors for Accessibility & Semantic Integrity (`A11Y_CLICKABLE_NON_SEMANTIC`, `A11Y_IMAGE_MISSING_ALT`), Security & Content Safety (`SECURITY_RAW_HTML_INJECTION`, `SECURITY_HARDCODED_SECRET`), Testing Discipline (`TEST_FAKE_GREEN`, `TEST_MISSING_COLOCATED`), and Naming Conventions (`NAMING_BARE_BOOLEAN`, `NAMING_HANDLER_PREFIX`).
|
|
23
|
-
- Implemented nested bullet tree path-chain formatter (`formatGroupedPromptViolations` in `cli/audit/prompts.js`), clustering violations by rule and rendering folder hierarchy steps as indented bullet chains (
|
|
29
|
+
- Implemented nested bullet tree path-chain formatter (`formatGroupedPromptViolations` in `cli/audit/prompts.js` and `renderGroupedViolationsTerminal` in `cli/audit/reporter-grouping.js`), clustering violations by rule and rendering folder hierarchy steps as indented bullet chains with `📁` folder emojis and ANSI severity colors (`📁 app/` -> `📁 components/` -> `📁 molecules/` -> leaf files with line hits) to eliminate boilerplate repetitions and slash AI prompt token consumption by up to 85%.
|
|
24
30
|
- Consolidated monolith refactoring action directives across `buildHotspotsPrompt`, `buildGradeFPrompt`, `buildGradeDPrompt`, and `buildGradeCPrompt`, stating action requirements once per section.
|
|
25
31
|
- Added `{ excludeAiSlop }` filter options to grade prompt builders to prevent duplicate slop violation listings in `buildMasterPrompt`.
|
|
26
32
|
- Added `{ includePrompt: false }` flag to `formatHotspotsSection` and `formatAiSlopSection` when rendered within full terminal report (`cli/audit/reporter.js`), preventing mid-report prompt box duplication before the master prompt.
|