@chemx/starter-kit 26.9.9-632 → 26.9.9-700
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.d.ts +25 -0
- package/cli/audit.js +241 -0
- package/cli/index.js +120 -84
- package/docs/CHANGELOG.md +6 -0
- package/package.json +9 -1
package/cli/audit.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface HazardViolation {
|
|
2
|
+
filePath: string;
|
|
3
|
+
line: number;
|
|
4
|
+
hazard: string;
|
|
5
|
+
rule: string;
|
|
6
|
+
directive: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AuditReport {
|
|
10
|
+
scannedFiles: number;
|
|
11
|
+
totalViolations: number;
|
|
12
|
+
violations: HazardViolation[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export declare function auditFile(filePath: string, relativePath: string): HazardViolation[];
|
|
16
|
+
export declare function scanDirectory(targetDir: string, baseDir: string): HazardViolation[];
|
|
17
|
+
export declare function runAudit(targetDir?: string): AuditReport;
|
|
18
|
+
|
|
19
|
+
declare const _default: {
|
|
20
|
+
auditFile: typeof auditFile;
|
|
21
|
+
scanDirectory: typeof scanDirectory;
|
|
22
|
+
runAudit: typeof runAudit;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default _default;
|
package/cli/audit.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parse } from '@babel/parser';
|
|
4
|
+
import traverse from '@babel/traverse';
|
|
5
|
+
import * as t from '@babel/types';
|
|
6
|
+
|
|
7
|
+
const countLogicalOperators = (node) => {
|
|
8
|
+
let count = 0;
|
|
9
|
+
if (t.isLogicalExpression(node)) {
|
|
10
|
+
count += 1;
|
|
11
|
+
count += countLogicalOperators(node.left);
|
|
12
|
+
count += countLogicalOperators(node.right);
|
|
13
|
+
} else if (t.isUnaryExpression(node) && node.operator === '!') {
|
|
14
|
+
count += 1;
|
|
15
|
+
count += countLogicalOperators(node.argument);
|
|
16
|
+
}
|
|
17
|
+
return count;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const extractParseableCode = (content, ext) => {
|
|
21
|
+
if (ext === '.vue') {
|
|
22
|
+
const scriptMatch = content.match(/<script\b[^>]*>([\s\S]*?)<\/script>/i);
|
|
23
|
+
return scriptMatch ? scriptMatch[1] : '';
|
|
24
|
+
}
|
|
25
|
+
return content;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const auditFile = (filePath, relativePath) => {
|
|
29
|
+
const violations = [];
|
|
30
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
31
|
+
const lines = content.split('\n');
|
|
32
|
+
const lineCount = lines.length;
|
|
33
|
+
const ext = path.extname(filePath);
|
|
34
|
+
|
|
35
|
+
// 1. Line Budget Checks
|
|
36
|
+
const isMolecule = relativePath.includes('molecules') || relativePath.includes('/m-');
|
|
37
|
+
if (lineCount > 500) {
|
|
38
|
+
violations.push({
|
|
39
|
+
filePath: relativePath,
|
|
40
|
+
line: 1,
|
|
41
|
+
hazard: `File line budget exceeded (${lineCount} > 500 lines)`,
|
|
42
|
+
rule: 'LINE_BUDGET_FILE',
|
|
43
|
+
directive: 'Decompose monolith into domain capsules and molecules'
|
|
44
|
+
});
|
|
45
|
+
} else if (isMolecule && lineCount > 100) {
|
|
46
|
+
violations.push({
|
|
47
|
+
filePath: relativePath,
|
|
48
|
+
line: 1,
|
|
49
|
+
hazard: `Molecule capsule budget exceeded (${lineCount} > 100 lines)`,
|
|
50
|
+
rule: 'LINE_BUDGET_MOLECULE',
|
|
51
|
+
directive: 'Split molecule into focused sub-molecules or extract state to hook'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const codeToParse = extractParseableCode(content, ext);
|
|
56
|
+
if (!codeToParse.trim()) {
|
|
57
|
+
return violations;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let ast;
|
|
61
|
+
try {
|
|
62
|
+
ast = parse(codeToParse, {
|
|
63
|
+
sourceType: 'module',
|
|
64
|
+
plugins: ['typescript', 'jsx']
|
|
65
|
+
});
|
|
66
|
+
} catch (err) {
|
|
67
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
68
|
+
violations.push({
|
|
69
|
+
filePath: relativePath,
|
|
70
|
+
line: 1,
|
|
71
|
+
hazard: `Parse error: ${errorMsg}`,
|
|
72
|
+
rule: 'SYNTAX_PARSE_ERROR',
|
|
73
|
+
directive: 'Fix syntax errors before static analysis'
|
|
74
|
+
});
|
|
75
|
+
return violations;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const traverseFn = traverse.default || traverse;
|
|
79
|
+
|
|
80
|
+
traverseFn(ast, {
|
|
81
|
+
// 2. Hook Saturation Check
|
|
82
|
+
Function(astPath) {
|
|
83
|
+
let hookCount = 0;
|
|
84
|
+
astPath.traverse({
|
|
85
|
+
CallExpression(callPath) {
|
|
86
|
+
if (t.isIdentifier(callPath.node.callee) && /^use[A-Z0-9]/.test(callPath.node.callee.name)) {
|
|
87
|
+
if (callPath.getFunctionParent() === astPath) {
|
|
88
|
+
hookCount += 1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (hookCount > 5) {
|
|
95
|
+
const line = astPath.node.loc?.start.line || 1;
|
|
96
|
+
violations.push({
|
|
97
|
+
filePath: relativePath,
|
|
98
|
+
line,
|
|
99
|
+
hazard: `Hook saturation detected (${hookCount} hooks > 5 limit)`,
|
|
100
|
+
rule: 'HOOK_SATURATION',
|
|
101
|
+
directive: 'Extract related state and effects into dedicated domain hooks'
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// 3. Control Flow Complexity
|
|
107
|
+
JSXExpressionContainer(astPath) {
|
|
108
|
+
const expr = astPath.node.expression;
|
|
109
|
+
if (t.isLogicalExpression(expr) || t.isUnaryExpression(expr)) {
|
|
110
|
+
const opCount = countLogicalOperators(expr);
|
|
111
|
+
if (opCount > 2) {
|
|
112
|
+
const line = expr.loc?.start.line || astPath.node.loc?.start.line || 1;
|
|
113
|
+
violations.push({
|
|
114
|
+
filePath: relativePath,
|
|
115
|
+
line,
|
|
116
|
+
hazard: `Inline boolean complexity (${opCount} logical operators > 2 limit)`,
|
|
117
|
+
rule: 'CONTROL_FLOW_INLINE_BOOLEAN',
|
|
118
|
+
directive: 'Compose booleans into Stage 1 concepts and Stage 2 decision variables'
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
ConditionalExpression(astPath) {
|
|
125
|
+
if (t.isConditionalExpression(astPath.node.consequent) || t.isConditionalExpression(astPath.node.alternate)) {
|
|
126
|
+
const line = astPath.node.loc?.start.line || 1;
|
|
127
|
+
violations.push({
|
|
128
|
+
filePath: relativePath,
|
|
129
|
+
line,
|
|
130
|
+
hazard: 'Nested ternary operator detected',
|
|
131
|
+
rule: 'CONTROL_FLOW_NESTED_TERNARY',
|
|
132
|
+
directive: 'Extract display states into computed descriptor objects or early returns'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
// 4. Timer Discipline
|
|
138
|
+
CallExpression(astPath) {
|
|
139
|
+
const callee = astPath.node.callee;
|
|
140
|
+
if (t.isIdentifier(callee) && (callee.name === 'setInterval' || callee.name === 'setTimeout')) {
|
|
141
|
+
const fnParent = astPath.getFunctionParent();
|
|
142
|
+
let hasCleanup = false;
|
|
143
|
+
if (fnParent) {
|
|
144
|
+
fnParent.traverse({
|
|
145
|
+
ReturnStatement(retPath) {
|
|
146
|
+
if (retPath.node.argument) {
|
|
147
|
+
hasCleanup = true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!hasCleanup) {
|
|
154
|
+
const line = astPath.node.loc?.start.line || 1;
|
|
155
|
+
violations.push({
|
|
156
|
+
filePath: relativePath,
|
|
157
|
+
line,
|
|
158
|
+
hazard: `Raw ${callee.name} lacking lifecycle scope disposal`,
|
|
159
|
+
rule: 'TIMER_DISCIPLINE',
|
|
160
|
+
directive: 'Wrap timers in self-cleaning hooks returning cleanup disposers'
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// 5. Type Co-location
|
|
167
|
+
TSTypeLiteral(astPath) {
|
|
168
|
+
if (astPath.node.members.length > 3) {
|
|
169
|
+
if (!astPath.findParent((p) => p.isTSTypeAliasDeclaration() || p.isTSInterfaceDeclaration())) {
|
|
170
|
+
const line = astPath.node.loc?.start.line || 1;
|
|
171
|
+
violations.push({
|
|
172
|
+
filePath: relativePath,
|
|
173
|
+
line,
|
|
174
|
+
hazard: `Inlined anonymous complex type (${astPath.node.members.length} members)`,
|
|
175
|
+
rule: 'TYPE_COLOCATION',
|
|
176
|
+
directive: 'Define co-located domain interfaces in types/*.d.ts'
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return violations;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const scanDirectory = (targetDir, baseDir) => {
|
|
187
|
+
let results = [];
|
|
188
|
+
if (!fs.existsSync(targetDir)) return results;
|
|
189
|
+
|
|
190
|
+
const entries = fs.readdirSync(targetDir, { withFileTypes: true });
|
|
191
|
+
for (const entry of entries) {
|
|
192
|
+
const fullPath = path.join(targetDir, entry.name);
|
|
193
|
+
const relPath = path.relative(baseDir, fullPath);
|
|
194
|
+
|
|
195
|
+
if (entry.isDirectory()) {
|
|
196
|
+
if (!['node_modules', 'dist', '.git', '.next', 'out'].includes(entry.name)) {
|
|
197
|
+
results = results.concat(scanDirectory(fullPath, baseDir));
|
|
198
|
+
}
|
|
199
|
+
} else if (/\.(tsx|ts|jsx|js|vue)$/.test(entry.name) && !entry.name.endsWith('.d.ts') && !entry.name.includes('.test.')) {
|
|
200
|
+
results = results.concat(auditFile(fullPath, relPath));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return results;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const countTotalScannedFiles = (targetDir) => {
|
|
207
|
+
let count = 0;
|
|
208
|
+
if (!fs.existsSync(targetDir)) return count;
|
|
209
|
+
|
|
210
|
+
const entries = fs.readdirSync(targetDir, { withFileTypes: true });
|
|
211
|
+
for (const entry of entries) {
|
|
212
|
+
const fullPath = path.join(targetDir, entry.name);
|
|
213
|
+
if (entry.isDirectory()) {
|
|
214
|
+
if (!['node_modules', 'dist', '.git', '.next', 'out'].includes(entry.name)) {
|
|
215
|
+
count += countTotalScannedFiles(fullPath);
|
|
216
|
+
}
|
|
217
|
+
} else if (/\.(tsx|ts|jsx|js|vue)$/.test(entry.name) && !entry.name.endsWith('.d.ts') && !entry.name.includes('.test.')) {
|
|
218
|
+
count += 1;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return count;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const runAudit = (targetDir = 'src') => {
|
|
225
|
+
const cwd = process.cwd();
|
|
226
|
+
const absoluteTarget = path.resolve(cwd, targetDir);
|
|
227
|
+
const violations = scanDirectory(absoluteTarget, cwd);
|
|
228
|
+
const scannedFiles = countTotalScannedFiles(absoluteTarget);
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
scannedFiles,
|
|
232
|
+
totalViolations: violations.length,
|
|
233
|
+
violations
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export default {
|
|
238
|
+
auditFile,
|
|
239
|
+
scanDirectory,
|
|
240
|
+
runAudit
|
|
241
|
+
};
|
package/cli/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import readline from 'node:readline';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { runAudit as executeAstAudit, auditFile } from './audit.js';
|
|
8
9
|
|
|
9
10
|
const rawArgs = process.argv.slice(2);
|
|
10
11
|
const invokedBin = path.basename(process.argv[1] || '');
|
|
@@ -40,8 +41,10 @@ const hasGum = () => {
|
|
|
40
41
|
};
|
|
41
42
|
|
|
42
43
|
const gumChoose = (options, header = '') => {
|
|
43
|
-
const args = ['choose', ...options];
|
|
44
|
-
if (header)
|
|
44
|
+
const args = ['choose', ...options, '--cursor.foreground=81'];
|
|
45
|
+
if (header) {
|
|
46
|
+
args.unshift(`--header=${header}`, '--header.foreground=81');
|
|
47
|
+
}
|
|
45
48
|
const res = spawnSync('gum', args, { encoding: 'utf-8', stdio: ['inherit', 'pipe', 'inherit'] });
|
|
46
49
|
return (res.stdout || '').trim();
|
|
47
50
|
};
|
|
@@ -128,30 +131,33 @@ const obtainLicenseKey = async () => {
|
|
|
128
131
|
|
|
129
132
|
if (useGum) {
|
|
130
133
|
const choice = gumChoose([
|
|
131
|
-
'1.
|
|
132
|
-
'2. Buy
|
|
133
|
-
'3.
|
|
134
|
+
'1. Buy Standard Edition ($49) -> Launch Checkout',
|
|
135
|
+
'2. Buy Master Bundle ($99) -> Launch Checkout',
|
|
136
|
+
'3. Enter License Key (CX-XXXX-XXXX-XXXX)',
|
|
134
137
|
'4. Run Free Public Audit (npx chemx audit)',
|
|
135
138
|
'5. Exit'
|
|
136
|
-
], '
|
|
139
|
+
], 'Chemical X Scaffolding Requires a Paid License:');
|
|
137
140
|
|
|
138
|
-
if (choice.startsWith('
|
|
141
|
+
if (choice.startsWith('1.')) {
|
|
139
142
|
process.stdout.write(`\x1b[36mOpening checkout in default browser:\x1b[0m ${URL_STANDARD}\n`);
|
|
140
143
|
openBrowser(URL_STANDARD);
|
|
141
144
|
process.stdout.write('\nOnce completed, paste your Sponsor / VIP License Key below.\n');
|
|
142
145
|
return gumInput('License Key (CX-XXXX-XXXX-XXXX):', 'CX-XXXX-XXXX-XXXX');
|
|
143
146
|
}
|
|
144
147
|
|
|
145
|
-
if (choice.startsWith('
|
|
148
|
+
if (choice.startsWith('2.')) {
|
|
146
149
|
process.stdout.write(`\x1b[36mOpening checkout in default browser:\x1b[0m ${URL_MASTER}\n`);
|
|
147
150
|
openBrowser(URL_MASTER);
|
|
148
151
|
process.stdout.write('\nOnce completed, paste your Sponsor / VIP License Key below.\n');
|
|
149
152
|
return gumInput('License Key (CX-XXXX-XXXX-XXXX):', 'CX-XXXX-XXXX-XXXX');
|
|
150
153
|
}
|
|
151
154
|
|
|
155
|
+
if (choice.startsWith('3.')) {
|
|
156
|
+
return gumInput('License Key (CX-XXXX-XXXX-XXXX):', 'CX-XXXX-XXXX-XXXX');
|
|
157
|
+
}
|
|
158
|
+
|
|
152
159
|
if (choice.startsWith('4.')) {
|
|
153
|
-
runAudit();
|
|
154
|
-
process.exit(0);
|
|
160
|
+
await runAudit(null, true);
|
|
155
161
|
}
|
|
156
162
|
|
|
157
163
|
if (choice.startsWith('5.') || !choice) {
|
|
@@ -161,30 +167,33 @@ const obtainLicenseKey = async () => {
|
|
|
161
167
|
return gumInput('License Key (CX-XXXX-XXXX-XXXX):', 'CX-XXXX-XXXX-XXXX');
|
|
162
168
|
}
|
|
163
169
|
|
|
164
|
-
process.stdout.write('\x1b[
|
|
165
|
-
process.stdout.write(' [1]
|
|
166
|
-
process.stdout.write(' [2] Buy
|
|
167
|
-
process.stdout.write(' [3]
|
|
170
|
+
process.stdout.write('\x1b[1mChemical X Scaffolding Requires a Paid License:\x1b[0m\n');
|
|
171
|
+
process.stdout.write(' [1] Buy Standard Edition ($49) - Opens browser\n');
|
|
172
|
+
process.stdout.write(' [2] Buy Master Bundle ($99) - Opens browser\n');
|
|
173
|
+
process.stdout.write(' [3] Enter License Key\n');
|
|
168
174
|
process.stdout.write(' [4] Run Free Public Audit (npx chemx audit)\n');
|
|
169
175
|
process.stdout.write(' [5] Exit\n\n');
|
|
170
176
|
|
|
171
177
|
const selection = await promptQuestion('Select option [1-5]: ');
|
|
172
178
|
|
|
173
|
-
if (selection === '
|
|
179
|
+
if (selection === '1') {
|
|
174
180
|
process.stdout.write(`Opening: ${URL_STANDARD}\n`);
|
|
175
181
|
openBrowser(URL_STANDARD);
|
|
176
182
|
return promptQuestion('Enter License Key after purchase: ');
|
|
177
183
|
}
|
|
178
184
|
|
|
179
|
-
if (selection === '
|
|
185
|
+
if (selection === '2') {
|
|
180
186
|
process.stdout.write(`Opening: ${URL_MASTER}\n`);
|
|
181
187
|
openBrowser(URL_MASTER);
|
|
182
188
|
return promptQuestion('Enter License Key after purchase: ');
|
|
183
189
|
}
|
|
184
190
|
|
|
191
|
+
if (selection === '3') {
|
|
192
|
+
return promptQuestion('Enter License Key (CX-XXXX-XXXX-XXXX): ');
|
|
193
|
+
}
|
|
194
|
+
|
|
185
195
|
if (selection === '4') {
|
|
186
|
-
runAudit();
|
|
187
|
-
process.exit(0);
|
|
196
|
+
await runAudit(null, true);
|
|
188
197
|
}
|
|
189
198
|
|
|
190
199
|
if (selection === '5') {
|
|
@@ -227,6 +236,12 @@ const fetchStarterKitFiles = async (licenseKey) => {
|
|
|
227
236
|
const runScaffold = async (projectName) => {
|
|
228
237
|
renderBanner('Chemical X: Quantum Scaffolder (npm create chemx)');
|
|
229
238
|
|
|
239
|
+
const licenseKey = await obtainLicenseKey();
|
|
240
|
+
if (!licenseKey) {
|
|
241
|
+
process.stderr.write('\x1b[31m✕ Valid license key is required to scaffold blueprints.\x1b[0m\n');
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
|
|
230
245
|
let targetName = projectName;
|
|
231
246
|
if (!targetName) {
|
|
232
247
|
if (hasGum()) {
|
|
@@ -244,12 +259,6 @@ const runScaffold = async (projectName) => {
|
|
|
244
259
|
process.exit(1);
|
|
245
260
|
}
|
|
246
261
|
|
|
247
|
-
const licenseKey = await obtainLicenseKey();
|
|
248
|
-
if (!licenseKey) {
|
|
249
|
-
process.stderr.write('\x1b[31m✕ Valid license key is required to scaffold blueprints.\x1b[0m\n');
|
|
250
|
-
process.exit(1);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
262
|
const files = await fetchStarterKitFiles(licenseKey);
|
|
254
263
|
|
|
255
264
|
process.stdout.write(`Scaffolding Quantum Architecture into: \x1b[36m${finalDirName}/\x1b[0m\n`);
|
|
@@ -358,80 +367,107 @@ export type { ${pascalName}Props } from './types';
|
|
|
358
367
|
process.stdout.write(` - ${normalizedName}/index.ts\n\n`);
|
|
359
368
|
};
|
|
360
369
|
|
|
361
|
-
export const runAudit = () => {
|
|
362
|
-
|
|
363
|
-
const
|
|
370
|
+
export const runAudit = async (customDir = null, isCli = false) => {
|
|
371
|
+
const isJson = rawArgs.includes('--json');
|
|
372
|
+
const dirFlag = rawArgs.find((arg) => arg.startsWith('--dir='));
|
|
373
|
+
const targetDir = customDir || (dirFlag ? dirFlag.split('=')[1] : (fs.existsSync('src') ? 'src' : '.'));
|
|
364
374
|
|
|
365
|
-
|
|
366
|
-
let violations = 0;
|
|
367
|
-
const offendingFiles = [];
|
|
375
|
+
const report = executeAstAudit(targetDir);
|
|
368
376
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (
|
|
372
|
-
|
|
377
|
+
if (isJson) {
|
|
378
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
379
|
+
if (isCli) process.exit(report.violations.length > 0 ? 1 : 0);
|
|
380
|
+
return report;
|
|
381
|
+
}
|
|
373
382
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
383
|
+
process.stdout.write('\n\x1b[38;2;98;201;255m[Chemical X Context Hazard Audit]\x1b[0m Scanning codebase for AST architectural hazards...\n');
|
|
384
|
+
process.stdout.write(`Target Directory: ${targetDir}\n`);
|
|
385
|
+
process.stdout.write(`Scanned ${report.scannedFiles} source files.\n\n`);
|
|
386
|
+
|
|
387
|
+
if (report.violations.length === 0) {
|
|
388
|
+
process.stdout.write('\x1b[1m\x1b[32m✔ 100% Quantum Compliant: Zero context hazard violations detected across all line budgets, hooks, and AST rules.\x1b[0m\n\n');
|
|
389
|
+
} else {
|
|
390
|
+
process.stdout.write(`\x1b[31m✕ FAILED: ${report.totalViolations} context hazard violations detected:\x1b[0m\n\n`);
|
|
391
|
+
for (const v of report.violations) {
|
|
392
|
+
process.stdout.write(` \x1b[31m[${v.rule}]\x1b[0m \x1b[33m${v.filePath}:${v.line}\x1b[0m\n`);
|
|
393
|
+
process.stdout.write(` Hazard: ${v.hazard}\n`);
|
|
394
|
+
process.stdout.write(` Directive: ${v.directive}\n\n`);
|
|
386
395
|
}
|
|
387
|
-
}
|
|
396
|
+
}
|
|
388
397
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
398
|
+
if (isCli) {
|
|
399
|
+
while (true) {
|
|
400
|
+
if (hasGum()) {
|
|
401
|
+
spawnSync('gum', [
|
|
402
|
+
'style',
|
|
403
|
+
'--border=rounded',
|
|
404
|
+
'--border-foreground=81',
|
|
405
|
+
'--padding=0 1',
|
|
406
|
+
'--bold',
|
|
407
|
+
'Eliminate AI Context Rot with Chemical X Architecture'
|
|
408
|
+
], { stdio: 'inherit' });
|
|
409
|
+
|
|
410
|
+
const choice = gumChoose([
|
|
411
|
+
'1. Buy Standard Edition ($49) -> Launch Checkout',
|
|
412
|
+
'2. Buy Master Bundle ($99) -> Launch Checkout',
|
|
413
|
+
'3. Enter License Key to Scaffold (Paid License Holders)',
|
|
414
|
+
'4. Exit'
|
|
415
|
+
], 'Select a CTA action:');
|
|
416
|
+
|
|
417
|
+
if (choice.startsWith('1.')) {
|
|
418
|
+
process.stdout.write(`\n\x1b[36mOpening Standard Edition checkout in browser:\x1b[0m ${URL_STANDARD}\n\n`);
|
|
419
|
+
openBrowser(URL_STANDARD);
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (choice.startsWith('2.')) {
|
|
423
|
+
process.stdout.write(`\n\x1b[36mOpening Master Bundle checkout in browser:\x1b[0m ${URL_MASTER}\n\n`);
|
|
424
|
+
openBrowser(URL_MASTER);
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (choice.startsWith('3.')) {
|
|
428
|
+
await runScaffold();
|
|
429
|
+
break;
|
|
401
430
|
}
|
|
431
|
+
break;
|
|
432
|
+
} else {
|
|
433
|
+
process.stdout.write('\x1b[1m\x1b[38;2;98;201;255mEliminate AI Context Rot with Chemical X Architecture:\x1b[0m\n');
|
|
434
|
+
process.stdout.write(` [1] Buy Standard Edition ($49) - ${URL_STANDARD}\n`);
|
|
435
|
+
process.stdout.write(` [2] Buy Master Bundle ($99) - ${URL_MASTER}\n`);
|
|
436
|
+
process.stdout.write(' [3] Enter License Key to Scaffold (Paid License Holders)\n');
|
|
437
|
+
process.stdout.write(' [4] Exit\n\n');
|
|
438
|
+
|
|
439
|
+
const selection = await promptQuestion('Select option [1-4]: ');
|
|
440
|
+
if (selection === '1') {
|
|
441
|
+
process.stdout.write(`\nOpening: ${URL_STANDARD}\n\n`);
|
|
442
|
+
openBrowser(URL_STANDARD);
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (selection === '2') {
|
|
446
|
+
process.stdout.write(`\nOpening: ${URL_MASTER}\n\n`);
|
|
447
|
+
openBrowser(URL_MASTER);
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (selection === '3') {
|
|
451
|
+
await runScaffold();
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
break;
|
|
402
455
|
}
|
|
403
|
-
} catch {
|
|
404
|
-
// Fallback
|
|
405
|
-
}
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
walk(targetDir);
|
|
409
|
-
|
|
410
|
-
process.stdout.write(`Scanned ${scanned} source files.\n\n`);
|
|
411
|
-
|
|
412
|
-
if (violations === 0) {
|
|
413
|
-
process.stdout.write('\x1b[1m\x1b[32m✔ 100% Quantum Compliant: All source files meet the 500-line budget ceiling.\x1b[0m\n\n');
|
|
414
|
-
} else {
|
|
415
|
-
process.stdout.write(`\x1b[31m✕ Found ${violations} Monolith Line-Budget Hazards (> 500 lines):\x1b[0m\n`);
|
|
416
|
-
for (const item of offendingFiles) {
|
|
417
|
-
process.stdout.write(` - \x1b[33m${item.file}\x1b[0m (${item.lines} lines)\n`);
|
|
418
456
|
}
|
|
419
|
-
|
|
420
|
-
process.stdout.write('\n\x1b[1m\x1b[38;2;98;201;255mEliminate AI Context Rot with Chemical X Architecture:\x1b[0m\n');
|
|
421
|
-
process.stdout.write(` * Book & Standards: ${URL_STANDARD}\n`);
|
|
422
|
-
process.stdout.write(` * Master Bundle: ${URL_MASTER}\n`);
|
|
423
|
-
process.stdout.write(' * Create Starter: npm create chemx\n');
|
|
424
|
-
process.stdout.write(' * Drop-in Capsules: npx @chemx/starter-kit init\n\n');
|
|
457
|
+
process.exit(report.violations.length > 0 ? 1 : 0);
|
|
425
458
|
}
|
|
459
|
+
return report;
|
|
426
460
|
};
|
|
427
461
|
|
|
462
|
+
export { auditFile };
|
|
463
|
+
|
|
428
464
|
const printHelp = () => {
|
|
429
465
|
renderBanner();
|
|
430
466
|
process.stdout.write('\x1b[1mAvailable Commands:\x1b[0m\n');
|
|
431
|
-
process.stdout.write(' \x1b[36mnpm create chemx [dir]\x1b[0m Scaffold complete Quantum Architecture project\n');
|
|
432
|
-
process.stdout.write(' \x1b[36mnpx @chemx/starter-kit init [dir]\x1b[0m Drop blueprints & hooks into existing project\n');
|
|
467
|
+
process.stdout.write(' \x1b[36mnpm create chemx [dir]\x1b[0m [PAID] Scaffold complete Quantum Architecture project\n');
|
|
468
|
+
process.stdout.write(' \x1b[36mnpx @chemx/starter-kit init [dir]\x1b[0m [PAID] Drop blueprints & hooks into existing project\n');
|
|
433
469
|
process.stdout.write(' \x1b[36mnpx chemx generate <m-name>\x1b[0m Generate isolated molecule capsule (< 100 lines)\n');
|
|
434
|
-
process.stdout.write(' \x1b[36mnpx chemx audit\x1b[0m
|
|
470
|
+
process.stdout.write(' \x1b[36mnpx chemx audit [--json] [--dir=src]\x1b[0m[FREE] Scan codebase for AST architectural hazards\n\n');
|
|
435
471
|
};
|
|
436
472
|
|
|
437
473
|
const main = async () => {
|
|
@@ -445,7 +481,7 @@ const main = async () => {
|
|
|
445
481
|
|
|
446
482
|
switch (firstArg) {
|
|
447
483
|
case 'audit':
|
|
448
|
-
runAudit();
|
|
484
|
+
await runAudit(null, true);
|
|
449
485
|
break;
|
|
450
486
|
case 'init':
|
|
451
487
|
await runInit(rawArgs[1] || 'src/chemical-x');
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -21,9 +21,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
21
21
|
### Added
|
|
22
22
|
- Edge-authenticated single-device project scaffolding command (`npx chemical-x init`) with machine fingerprinting and Cloudflare KV validation.
|
|
23
23
|
- AST hazard line budget audit command (`npx chemical-x audit`) scanning project trees for > 500 line monolith hazards.
|
|
24
|
+
- AST Context Hazard Audit engine module (`cli/audit.js`, `cli/audit.d.ts`) supporting 5 AST rules: line budget, hook saturation, control flow complexity, timer discipline, and type co-location.
|
|
25
|
+
- `./audit` subpath export in `package.json` for programmatic consumption by test suites and benchmarks.
|
|
26
|
+
- CLI flags `--json` and `--dir=<path>` for `npx chemx audit`.
|
|
24
27
|
- Automated NPM publish GitHub Actions workflow (`.github/workflows/publish.yml`) chained to `Auto Version` completion via `workflow_run`.
|
|
25
28
|
|
|
26
29
|
### Changed
|
|
30
|
+
- Upgraded `runAudit` in `cli/index.js` from basic line counting to full Babel AST static analysis engine.
|
|
27
31
|
- Updated default API endpoint in CLI (`cli/index.js`) to production custom domain `https://chemicalx.xophz.com`.
|
|
28
32
|
- Expanded `AGENTS.md` blueprint in CLI (`cli/index.js`) to include all 7 Quantum Engineering Architecture pillars.
|
|
29
33
|
- Configured npm package distribution for `@chemx/starter-kit` with `create-chemx`, `chemx`, `chem-x`, and `chemical-x` binary aliases.
|
|
@@ -32,6 +36,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
32
36
|
- Added cross-platform browser checkout launcher for `mycompassconsulting.com/buy/chemical-x/standard` and `master`.
|
|
33
37
|
- Unlocked `npx chemx audit` command as 100% free, unauthenticated, and ungated public utility with conversion CTAs.
|
|
34
38
|
- Added dual project scaffolder (`npm create chemx` / `create-chemx`) and in-repo capsule drop-in (`init`).
|
|
39
|
+
- Gated `runScaffold` (`npm create chemx`) with upfront license validation prior to project directory name prompt.
|
|
40
|
+
- Integrated interactive Gum CTA action buttons at the conclusion of public audit for instant checkout launch ($49 Standard / $99 Master) and key-gated scaffolding for license holders.
|
|
35
41
|
|
|
36
42
|
### Fixed
|
|
37
43
|
- Removed embedded offline blueprint fallbacks and preview bypass keys from CLI executable (`cli/index.js`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chemx/starter-kit",
|
|
3
|
-
"version": "26.9.9-
|
|
3
|
+
"version": "26.9.9-700",
|
|
4
4
|
"description": "Chemical X Protocol: Private drop-in architecture starter kit and capsule generator",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,16 +18,24 @@
|
|
|
18
18
|
"README.md",
|
|
19
19
|
"docs"
|
|
20
20
|
],
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./cli/index.js",
|
|
23
|
+
"./audit": "./cli/audit.js"
|
|
24
|
+
},
|
|
21
25
|
"scripts": {
|
|
22
26
|
"publish:both": "node scripts/publish-both.mjs",
|
|
23
27
|
"typecheck": "tsc --noEmit",
|
|
24
28
|
"create-capsule": "node cli/index.js"
|
|
25
29
|
},
|
|
26
30
|
"dependencies": {
|
|
31
|
+
"@babel/parser": "^7.26.9",
|
|
32
|
+
"@babel/traverse": "^7.26.9",
|
|
33
|
+
"@babel/types": "^7.26.9",
|
|
27
34
|
"react": "^18.3.1",
|
|
28
35
|
"react-dom": "^18.3.1"
|
|
29
36
|
},
|
|
30
37
|
"devDependencies": {
|
|
38
|
+
"@types/babel__traverse": "^7.20.6",
|
|
31
39
|
"@types/node": "^22.13.5",
|
|
32
40
|
"@types/react": "^18.3.18",
|
|
33
41
|
"@types/react-dom": "^18.3.5",
|