@chemx/starter-kit 26.9.9-720 → 26.9.11-362
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/ast-visitors.js +243 -0
- package/cli/audit/discussion-store.js +39 -0
- package/cli/audit/history.d.ts +63 -0
- package/cli/audit/history.js +352 -0
- package/cli/audit/metrics.js +179 -0
- package/cli/audit/prompts.js +174 -0
- package/cli/audit/reporter-grades.js +259 -0
- package/cli/audit/reporter-markdown.js +136 -0
- package/cli/audit/reporter-utils.js +107 -0
- package/cli/audit/reporter.js +459 -0
- package/cli/audit/rules-helpers.js +109 -0
- package/cli/audit/rules-registry.js +97 -0
- package/cli/audit/rules.js +123 -0
- package/cli/audit/social-constants.js +4 -0
- package/cli/audit/social-gh.js +126 -0
- package/cli/audit/social-git.js +214 -0
- package/cli/audit/social-http.js +222 -0
- package/cli/audit/social-publisher.js +193 -0
- package/cli/audit/social.d.ts +66 -0
- package/cli/audit/social.js +254 -0
- package/cli/audit/types.d.ts +131 -0
- package/cli/audit.d.ts +3 -24
- package/cli/audit.js +195 -210
- package/cli/help.d.ts +1 -0
- package/cli/help.js +136 -0
- package/cli/index.js +109 -438
- package/cli/installer-templates.js +80 -0
- package/cli/installer.d.ts +20 -0
- package/cli/installer.js +56 -0
- package/cli/license.js +320 -0
- package/cli/navigator-conversion.js +100 -0
- package/cli/navigator-grades.js +70 -0
- package/cli/navigator-paged.js +86 -0
- package/cli/navigator-share.js +129 -0
- package/cli/navigator.d.ts +24 -0
- package/cli/navigator.js +400 -0
- package/cli/scaffold.js +148 -0
- package/cli/terminal.js +126 -0
- package/docs/CHANGELOG.md +74 -1
- package/package.json +11 -10
package/cli/index.js
CHANGED
|
@@ -2,481 +2,145 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import {
|
|
6
|
+
runAudit as executeAstAudit,
|
|
7
|
+
auditFile,
|
|
8
|
+
formatTerminalReport,
|
|
9
|
+
generateMarkdownReport,
|
|
10
|
+
saveAuditSnapshot,
|
|
11
|
+
copyToClipboard,
|
|
12
|
+
buildMasterPrompt
|
|
13
|
+
} from './audit.js';
|
|
14
|
+
import {
|
|
15
|
+
runScaffold,
|
|
16
|
+
runInit,
|
|
17
|
+
runGenerateCapsule,
|
|
18
|
+
printHelp
|
|
19
|
+
} from './scaffold.js';
|
|
20
|
+
import {
|
|
21
|
+
runInteractiveAuditNavigator,
|
|
22
|
+
showConversionMenu,
|
|
23
|
+
handleShareToDiscussions
|
|
24
|
+
} from './navigator.js';
|
|
25
|
+
import { runInstallWizard } from './installer.js';
|
|
9
26
|
|
|
10
27
|
const rawArgs = process.argv.slice(2);
|
|
11
28
|
const invokedBin = path.basename(process.argv[1] || '');
|
|
12
|
-
const isCreateInvoked = invokedBin.includes('create-chemx') || (rawArgs[0] && rawArgs[0] === 'create');
|
|
13
29
|
|
|
14
|
-
const
|
|
15
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
16
|
-
const DEVICE_FILE = path.join(CONFIG_DIR, 'device_id');
|
|
17
|
-
|
|
18
|
-
const API_BASE = process.env.CHEMICAL_X_API_URL || 'https://chemicalx.xophz.com';
|
|
19
|
-
const URL_STANDARD = 'https://mycompassconsulting.com/buy/chemical-x/standard';
|
|
20
|
-
const URL_MASTER = 'https://mycompassconsulting.com/buy/chemical-x/master';
|
|
21
|
-
|
|
22
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
23
|
-
try { fs.mkdirSync(CONFIG_DIR, { recursive: true }); } catch {}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const openBrowser = (url) => {
|
|
27
|
-
const platform = process.platform;
|
|
28
|
-
try {
|
|
29
|
-
if (platform === 'darwin') spawnSync('open', [url], { stdio: 'ignore' });
|
|
30
|
-
else if (platform === 'win32') spawnSync('cmd.exe', ['/c', 'start', '""', url], { stdio: 'ignore' });
|
|
31
|
-
else spawnSync('xdg-open', [url], { stdio: 'ignore' });
|
|
32
|
-
} catch {}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const hasGum = () => {
|
|
30
|
+
const loadProjectConfig = () => {
|
|
36
31
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const gumChoose = (options, header = '') => {
|
|
44
|
-
const args = ['choose'];
|
|
45
|
-
if (header) {
|
|
46
|
-
args.push(`--header=${header}`, '--header.foreground=81');
|
|
47
|
-
}
|
|
48
|
-
args.push('--cursor.foreground=81', ...options);
|
|
49
|
-
const res = spawnSync('gum', args, { encoding: 'utf-8', stdio: ['inherit', 'pipe', 'inherit'] });
|
|
50
|
-
return (res.stdout || '').trim();
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const gumInput = (promptText, placeholder = '', isPassword = false) => {
|
|
54
|
-
const args = ['input', `--prompt=${promptText} `, `--placeholder=${placeholder}`];
|
|
55
|
-
if (isPassword) args.push('--password');
|
|
56
|
-
const res = spawnSync('gum', args, { encoding: 'utf-8', stdio: ['inherit', 'pipe', 'inherit'] });
|
|
57
|
-
return (res.stdout || '').trim();
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const promptQuestion = (query) => {
|
|
61
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
62
|
-
return new Promise((resolve) => {
|
|
63
|
-
rl.question(query, (answer) => {
|
|
64
|
-
rl.close();
|
|
65
|
-
resolve(answer.trim());
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const getOrCreateDeviceId = () => {
|
|
71
|
-
if (fs.existsSync(DEVICE_FILE)) {
|
|
72
|
-
try {
|
|
73
|
-
const id = fs.readFileSync(DEVICE_FILE, 'utf-8').trim();
|
|
74
|
-
if (id) return id;
|
|
75
|
-
} catch {}
|
|
76
|
-
}
|
|
77
|
-
const newId = `cli_${Math.random().toString(36).substring(2, 12)}_${Date.now()}`;
|
|
78
|
-
try { fs.writeFileSync(DEVICE_FILE, newId, 'utf-8'); } catch {}
|
|
79
|
-
return newId;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const getCachedLicenseKey = () => {
|
|
83
|
-
if (fs.existsSync(CONFIG_FILE)) {
|
|
84
|
-
try {
|
|
85
|
-
const data = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
86
|
-
return data.licenseKey || null;
|
|
87
|
-
} catch {
|
|
88
|
-
return null;
|
|
32
|
+
const cfgPath = path.resolve(process.cwd(), '.chemx', 'config.json');
|
|
33
|
+
if (fs.existsSync(cfgPath)) {
|
|
34
|
+
return JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
89
35
|
}
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const saveLicenseKey = (licenseKey) => {
|
|
95
|
-
try {
|
|
96
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify({ licenseKey, updatedAt: new Date().toISOString() }, null, 2), 'utf-8');
|
|
97
36
|
} catch {}
|
|
37
|
+
return {};
|
|
98
38
|
};
|
|
39
|
+
const isCreateInvoked =
|
|
40
|
+
invokedBin.includes('create-chemx') || (rawArgs[0] && rawArgs[0] === 'create');
|
|
99
41
|
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
'--margin=1',
|
|
106
|
-
'--padding=1 2',
|
|
107
|
-
'--border-foreground=45',
|
|
108
|
-
'--foreground=81',
|
|
109
|
-
'--bold',
|
|
110
|
-
` ${title}\n Zero-Context-Rot Scaffolding & Engineering Directives`
|
|
111
|
-
], { stdio: 'inherit' });
|
|
112
|
-
} else {
|
|
113
|
-
process.stdout.write('\n\x1b[38;2;98;201;255m=====================================================\x1b[0m\n');
|
|
114
|
-
process.stdout.write(`\x1b[1m\x1b[38;2;98;201;255m ${title}\x1b[0m\n`);
|
|
115
|
-
process.stdout.write(' Zero-Context-Rot Scaffolding & Engineering Directives\n');
|
|
116
|
-
process.stdout.write('\x1b[38;2;98;201;255m=====================================================\x1b[0m\n\n');
|
|
117
|
-
}
|
|
42
|
+
const resolveTargetDir = (custom, flag) => {
|
|
43
|
+
if (custom) return custom;
|
|
44
|
+
if (flag) return flag.split('=')[1];
|
|
45
|
+
if (fs.existsSync('src')) return 'src';
|
|
46
|
+
return '.';
|
|
118
47
|
};
|
|
119
48
|
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const useGum = hasGum();
|
|
132
|
-
|
|
133
|
-
if (useGum) {
|
|
134
|
-
const choice = gumChoose([
|
|
135
|
-
'1. Buy Standard Edition ($49) -> Launch Checkout',
|
|
136
|
-
'2. Buy Master Bundle ($99) -> Launch Checkout',
|
|
137
|
-
'3. Enter License Key (CX-XXXX-XXXX-XXXX)',
|
|
138
|
-
'4. Run Free Public Audit (npx chemx audit)',
|
|
139
|
-
'5. Exit'
|
|
140
|
-
], 'Chemical X Scaffolding Requires a Paid License:');
|
|
141
|
-
|
|
142
|
-
if (choice.startsWith('1.')) {
|
|
143
|
-
process.stdout.write(`\x1b[36mOpening checkout in default browser:\x1b[0m ${URL_STANDARD}\n`);
|
|
144
|
-
openBrowser(URL_STANDARD);
|
|
145
|
-
process.stdout.write('\nOnce completed, paste your Sponsor / VIP License Key below.\n');
|
|
146
|
-
return gumInput('License Key (CX-XXXX-XXXX-XXXX):', 'CX-XXXX-XXXX-XXXX');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (choice.startsWith('2.')) {
|
|
150
|
-
process.stdout.write(`\x1b[36mOpening checkout in default browser:\x1b[0m ${URL_MASTER}\n`);
|
|
151
|
-
openBrowser(URL_MASTER);
|
|
152
|
-
process.stdout.write('\nOnce completed, paste your Sponsor / VIP License Key below.\n');
|
|
153
|
-
return gumInput('License Key (CX-XXXX-XXXX-XXXX):', 'CX-XXXX-XXXX-XXXX');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (choice.startsWith('3.')) {
|
|
157
|
-
return gumInput('License Key (CX-XXXX-XXXX-XXXX):', 'CX-XXXX-XXXX-XXXX');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (choice.startsWith('4.')) {
|
|
161
|
-
await runAudit(null, true);
|
|
162
|
-
}
|
|
49
|
+
export const runAudit = async (customDir = null, isCli = false) => {
|
|
50
|
+
const projectConfig = loadProjectConfig();
|
|
51
|
+
const isJson = rawArgs.includes('--json');
|
|
52
|
+
const isMarkdown = rawArgs.includes('--markdown') || rawArgs.includes('--md');
|
|
53
|
+
const isUnroll = rawArgs.includes('--unroll') || rawArgs.includes('--all');
|
|
54
|
+
const isShare = rawArgs.includes('--share') || rawArgs.includes('--post');
|
|
55
|
+
const isStrict = rawArgs.includes('--strict');
|
|
56
|
+
const isPromptOnFail = rawArgs.includes('--prompt-on-fail');
|
|
57
|
+
const isCopyPrompt = rawArgs.includes('--copy-prompt');
|
|
163
58
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
59
|
+
const dirFlag = rawArgs.find((arg) => arg.startsWith('--dir='));
|
|
60
|
+
const outputFlag = rawArgs.find((arg) => arg.startsWith('--output=') || arg.startsWith('-o='));
|
|
61
|
+
const outputFile = outputFlag ? outputFlag.split('=')[1] : null;
|
|
167
62
|
|
|
168
|
-
|
|
169
|
-
|
|
63
|
+
const minGradeFlag = rawArgs.find((arg) => arg.startsWith('--min-grade='));
|
|
64
|
+
const minGrade = (minGradeFlag ? minGradeFlag.split('=')[1] : (process.env.CHEMX_MIN_GRADE || projectConfig.minGrade || '')).toUpperCase();
|
|
170
65
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
process.stdout.write(' [3] Enter License Key\n');
|
|
175
|
-
process.stdout.write(' [4] Run Free Public Audit (npx chemx audit)\n');
|
|
176
|
-
process.stdout.write(' [5] Exit\n\n');
|
|
66
|
+
const minScoreFlag = rawArgs.find((arg) => arg.startsWith('--min-score='));
|
|
67
|
+
const minScoreRaw = minScoreFlag ? minScoreFlag.split('=')[1] : (process.env.CHEMX_MIN_SCORE || projectConfig.minScore || null);
|
|
68
|
+
const minScore = minScoreRaw !== null && minScoreRaw !== undefined ? parseInt(String(minScoreRaw), 10) : null;
|
|
177
69
|
|
|
178
|
-
const
|
|
70
|
+
const modelFlag = rawArgs.find((arg) => arg.startsWith('--model='));
|
|
71
|
+
const model = modelFlag ? modelFlag.split('=')[1] : (projectConfig.model || 'blended');
|
|
72
|
+
const costFlag = rawArgs.find((arg) => arg.startsWith('--cost-per-million='));
|
|
73
|
+
const costPerMillion = costFlag ? parseFloat(costFlag.split('=')[1]) : null;
|
|
179
74
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return promptQuestion('Enter License Key after purchase: ');
|
|
184
|
-
}
|
|
75
|
+
const targetDir = resolveTargetDir(customDir, dirFlag);
|
|
76
|
+
const report = executeAstAudit(targetDir, { outputFile, model, costPerMillion });
|
|
77
|
+
saveAuditSnapshot(report);
|
|
185
78
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
79
|
+
const GRADE_RANKS = { 'A+': 5, 'A': 4, 'B': 3, 'C': 2, 'D': 1, 'F': 0 };
|
|
80
|
+
const hasCriticalOrHigh = report.violations.some((v) => v.severity === 'CRITICAL' || v.severity === 'HIGH');
|
|
81
|
+
const isStrictFail = isStrict && report.violations.length > 0;
|
|
82
|
+
const isGradeFail = Boolean(minGrade && GRADE_RANKS[report.health.grade] !== undefined && GRADE_RANKS[minGrade] !== undefined && GRADE_RANKS[report.health.grade] < GRADE_RANKS[minGrade]);
|
|
83
|
+
const isScoreFail = minScore !== null && !isNaN(minScore) && report.health.score < minScore;
|
|
84
|
+
const hasFailingViolations = isStrictFail || hasCriticalOrHigh || isGradeFail || isScoreFail;
|
|
191
85
|
|
|
192
|
-
if (
|
|
193
|
-
|
|
86
|
+
if (isJson) {
|
|
87
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
88
|
+
if (isCli) process.exit(hasFailingViolations ? 1 : 0);
|
|
89
|
+
return report;
|
|
194
90
|
}
|
|
195
91
|
|
|
196
|
-
if (
|
|
197
|
-
|
|
92
|
+
if (isMarkdown) {
|
|
93
|
+
const md = generateMarkdownReport(report);
|
|
94
|
+
process.stdout.write(md + '\n');
|
|
95
|
+
if (isCli) process.exit(hasFailingViolations ? 1 : 0);
|
|
96
|
+
return report;
|
|
198
97
|
}
|
|
199
98
|
|
|
200
|
-
if (
|
|
201
|
-
process.
|
|
99
|
+
if (outputFile) {
|
|
100
|
+
process.stdout.write(`\x1b[32m✔ Exported markdown audit report to:\x1b[0m ${outputFile}\n\n`);
|
|
202
101
|
}
|
|
203
102
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const normalizedKey = licenseKey.trim().toUpperCase();
|
|
209
|
-
const deviceId = getOrCreateDeviceId();
|
|
210
|
-
|
|
211
|
-
process.stdout.write(`\nVerifying license via edge: ${API_BASE}...\n`);
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
const res = await fetch(`${API_BASE}/api/starter-kit/download`, {
|
|
215
|
-
method: 'POST',
|
|
216
|
-
headers: { 'Content-Type': 'application/json' },
|
|
217
|
-
body: JSON.stringify({ licenseKey: normalizedKey, deviceId })
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
const responseData = await res.json();
|
|
221
|
-
|
|
222
|
-
if (!res.ok || !responseData.valid) {
|
|
223
|
-
process.stderr.write(`\x1b[31m✕ License Verification Failed: ${responseData.error || 'Invalid key.'}\x1b[0m\n`);
|
|
224
|
-
process.stderr.write(`Purchase key at: ${URL_STANDARD}\n\n`);
|
|
225
|
-
process.exit(1);
|
|
103
|
+
if (isCli) {
|
|
104
|
+
if (isShare) {
|
|
105
|
+
await handleShareToDiscussions(report);
|
|
106
|
+
process.exit(0);
|
|
226
107
|
}
|
|
227
108
|
|
|
228
|
-
|
|
229
|
-
process.stdout.write(`\x1b[32m✔ Verified License for @${responseData.githubUser || 'sponsor'}\x1b[0m\n\n`);
|
|
230
|
-
return responseData.files || {};
|
|
231
|
-
} catch (err) {
|
|
232
|
-
process.stderr.write(`\x1b[31m✕ Network Error: Failed to reach edge server (${err.message}).\x1b[0m\n`);
|
|
233
|
-
process.exit(1);
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
const runScaffold = async (projectName) => {
|
|
238
|
-
renderBanner('Chemical X: Quantum Scaffolder (npm create chemx)');
|
|
109
|
+
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
239
110
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
process.stderr.write('\x1b[31m✕ Valid license key is required to scaffold blueprints.\x1b[0m\n');
|
|
243
|
-
process.exit(1);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
let targetName = projectName;
|
|
247
|
-
if (!targetName) {
|
|
248
|
-
if (hasGum()) {
|
|
249
|
-
targetName = gumInput('Project directory name:', 'my-quantum-app');
|
|
111
|
+
if (isInteractive && !isUnroll) {
|
|
112
|
+
await runInteractiveAuditNavigator(report, () => runScaffold(undefined, rawArgs, runAudit));
|
|
250
113
|
} else {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const finalDirName = targetName.trim() || 'my-quantum-app';
|
|
256
|
-
const targetDir = path.resolve(process.cwd(), finalDirName);
|
|
257
|
-
|
|
258
|
-
if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
|
|
259
|
-
process.stderr.write(`\x1b[31m✕ Error: Directory '${finalDirName}' already exists and is not empty.\x1b[0m\n`);
|
|
260
|
-
process.exit(1);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const files = await fetchStarterKitFiles(licenseKey);
|
|
264
|
-
|
|
265
|
-
process.stdout.write(`Scaffolding Quantum Architecture into: \x1b[36m${finalDirName}/\x1b[0m\n`);
|
|
266
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
267
|
-
|
|
268
|
-
for (const [relPath, content] of Object.entries(files)) {
|
|
269
|
-
const fullPath = path.join(targetDir, relPath);
|
|
270
|
-
const dirName = path.dirname(fullPath);
|
|
271
|
-
if (!fs.existsSync(dirName)) {
|
|
272
|
-
fs.mkdirSync(dirName, { recursive: true });
|
|
114
|
+
process.stdout.write(formatTerminalReport(report));
|
|
115
|
+
if (isInteractive && isUnroll) {
|
|
116
|
+
await showConversionMenu(() => runScaffold(undefined, rawArgs, runAudit));
|
|
117
|
+
}
|
|
273
118
|
}
|
|
274
|
-
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
275
|
-
process.stdout.write(` \x1b[32m✔\x1b[0m ${relPath}\n`);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const cursorRulesPath = path.join(targetDir, '.cursorrules');
|
|
279
|
-
if (!fs.existsSync(cursorRulesPath)) {
|
|
280
|
-
const rules = `# Chemical X Quantum Architecture Directives\nStrictly follow AGENTS.md rules. Never exceed 500 lines per file. All molecule capsules must stay under 100 lines.\n`;
|
|
281
|
-
fs.writeFileSync(cursorRulesPath, rules, 'utf-8');
|
|
282
|
-
process.stdout.write(` \x1b[32m✔\x1b[0m .cursorrules\n`);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
process.stdout.write(`\n\x1b[1m\x1b[32m✔ Quantum project created successfully at ${finalDirName}!\x1b[0m\n\n`);
|
|
286
|
-
process.stdout.write('Next Steps:\n');
|
|
287
|
-
process.stdout.write(` 1. cd ${finalDirName}\n`);
|
|
288
|
-
process.stdout.write(' 2. Review AGENTS.md for line budgets and architecture standards\n');
|
|
289
|
-
process.stdout.write(' 3. Run npx chemx generate m-<feature> to create capsules\n');
|
|
290
|
-
process.stdout.write(' 4. Run npx chemx audit to scan for line budget compliance\n\n');
|
|
291
|
-
};
|
|
292
119
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
process.exit(1);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const files = await fetchStarterKitFiles(licenseKey);
|
|
304
|
-
|
|
305
|
-
process.stdout.write(`Unpacking blueprints and hooks into: \x1b[36m${targetSubDir}/\x1b[0m\n`);
|
|
306
|
-
|
|
307
|
-
let count = 0;
|
|
308
|
-
for (const [relPath, content] of Object.entries(files)) {
|
|
309
|
-
const fullPath = path.join(targetDir, relPath);
|
|
310
|
-
const dirName = path.dirname(fullPath);
|
|
311
|
-
if (!fs.existsSync(dirName)) {
|
|
312
|
-
fs.mkdirSync(dirName, { recursive: true });
|
|
120
|
+
if (hasFailingViolations && (isPromptOnFail || isCopyPrompt)) {
|
|
121
|
+
const prompt = buildMasterPrompt(report);
|
|
122
|
+
const copied = copyToClipboard(prompt);
|
|
123
|
+
if (copied) {
|
|
124
|
+
process.stdout.write('\n\x1b[1m\x1b[32m✔ AI Agent refactoring prompt copied to clipboard!\x1b[0m\n');
|
|
125
|
+
process.stdout.write('\x1b[36mPaste directly into Cursor, Claude, or Windsurf to resolve architectural hazards.\x1b[0m\n\n');
|
|
126
|
+
}
|
|
313
127
|
}
|
|
314
|
-
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
315
|
-
process.stdout.write(` \x1b[32m✔\x1b[0m ${relPath}\n`);
|
|
316
|
-
count++;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
process.stdout.write(`\n\x1b[1m\x1b[32m✔ Successfully installed ${count} Chemical X assets into ${targetSubDir}!\x1b[0m\n\n`);
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
const runGenerateCapsule = (capsuleName) => {
|
|
323
|
-
const normalizedName = capsuleName.startsWith('m-') ? capsuleName : `m-${capsuleName}`;
|
|
324
|
-
const targetDir = path.resolve(process.cwd(), normalizedName);
|
|
325
128
|
|
|
326
|
-
|
|
327
|
-
process.stderr.write(`\x1b[31m✕ Error: Directory ${normalizedName} already exists.\x1b[0m\n`);
|
|
328
|
-
process.exit(1);
|
|
129
|
+
process.exit(hasFailingViolations ? 1 : 0);
|
|
329
130
|
}
|
|
330
131
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const pascalName = normalizedName
|
|
334
|
-
.split('-')
|
|
335
|
-
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
|
336
|
-
.join('');
|
|
337
|
-
|
|
338
|
-
const componentCode = `import React from 'react';
|
|
339
|
-
import type { ${pascalName}Props } from './types';
|
|
340
|
-
|
|
341
|
-
export const ${pascalName}: React.FC<${pascalName}Props> = ({ label }) => {
|
|
342
|
-
return (
|
|
343
|
-
<div className="${normalizedName}">
|
|
344
|
-
<span>{label}</span>
|
|
345
|
-
</div>
|
|
346
|
-
);
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
export default ${pascalName};
|
|
350
|
-
`;
|
|
351
|
-
|
|
352
|
-
const typesCode = `export interface ${pascalName}Props {
|
|
353
|
-
readonly label: string;
|
|
354
|
-
}
|
|
355
|
-
`;
|
|
356
|
-
|
|
357
|
-
const indexCode = `export { ${pascalName} } from './${normalizedName}';
|
|
358
|
-
export type { ${pascalName}Props } from './types';
|
|
359
|
-
`;
|
|
360
|
-
|
|
361
|
-
fs.writeFileSync(path.join(targetDir, `${normalizedName}.tsx`), componentCode, 'utf-8');
|
|
362
|
-
fs.writeFileSync(path.join(targetDir, 'types.d.ts'), typesCode, 'utf-8');
|
|
363
|
-
fs.writeFileSync(path.join(targetDir, 'index.ts'), indexCode, 'utf-8');
|
|
364
|
-
|
|
365
|
-
process.stdout.write(`\x1b[32m✔ Successfully generated crystalline capsule:\x1b[0m ${normalizedName}/\n`);
|
|
366
|
-
process.stdout.write(` - ${normalizedName}/${normalizedName}.tsx (< 50 lines)\n`);
|
|
367
|
-
process.stdout.write(` - ${normalizedName}/types.d.ts\n`);
|
|
368
|
-
process.stdout.write(` - ${normalizedName}/index.ts\n\n`);
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
export const runAudit = async (customDir = null, isCli = false) => {
|
|
372
|
-
const isJson = rawArgs.includes('--json');
|
|
373
|
-
const dirFlag = rawArgs.find((arg) => arg.startsWith('--dir='));
|
|
374
|
-
const targetDir = customDir || (dirFlag ? dirFlag.split('=')[1] : (fs.existsSync('src') ? 'src' : '.'));
|
|
375
|
-
|
|
376
|
-
const report = executeAstAudit(targetDir);
|
|
377
|
-
|
|
378
|
-
if (isJson) {
|
|
379
|
-
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
380
|
-
if (isCli) process.exit(report.violations.length > 0 ? 1 : 0);
|
|
381
|
-
return report;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
process.stdout.write('\n\x1b[38;2;98;201;255m[Chemical X Context Hazard Audit]\x1b[0m Scanning codebase for AST architectural hazards...\n');
|
|
385
|
-
process.stdout.write(`Target Directory: ${targetDir}\n`);
|
|
386
|
-
process.stdout.write(`Scanned ${report.scannedFiles} source files.\n\n`);
|
|
387
|
-
|
|
388
|
-
if (report.violations.length === 0) {
|
|
389
|
-
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');
|
|
390
|
-
} else {
|
|
391
|
-
process.stdout.write(`\x1b[31m✕ FAILED: ${report.totalViolations} context hazard violations detected:\x1b[0m\n\n`);
|
|
392
|
-
for (const v of report.violations) {
|
|
393
|
-
process.stdout.write(` \x1b[31m[${v.rule}]\x1b[0m \x1b[33m${v.filePath}:${v.line}\x1b[0m\n`);
|
|
394
|
-
process.stdout.write(` Hazard: ${v.hazard}\n`);
|
|
395
|
-
process.stdout.write(` Directive: ${v.directive}\n\n`);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (isCli) {
|
|
400
|
-
while (true) {
|
|
401
|
-
if (hasGum()) {
|
|
402
|
-
spawnSync('gum', [
|
|
403
|
-
'style',
|
|
404
|
-
'--border=rounded',
|
|
405
|
-
'--border-foreground=81',
|
|
406
|
-
'--padding=0 1',
|
|
407
|
-
'--bold',
|
|
408
|
-
'Eliminate AI Context Rot with Chemical X Architecture'
|
|
409
|
-
], { stdio: 'inherit' });
|
|
410
|
-
|
|
411
|
-
const choice = gumChoose([
|
|
412
|
-
'1. Buy Standard Edition ($49) -> Launch Checkout',
|
|
413
|
-
'2. Buy Master Bundle ($99) -> Launch Checkout',
|
|
414
|
-
'3. Enter License Key to Scaffold (Paid License Holders)',
|
|
415
|
-
'4. Exit'
|
|
416
|
-
], 'Select a CTA action:');
|
|
417
|
-
|
|
418
|
-
if (choice.startsWith('1.')) {
|
|
419
|
-
process.stdout.write(`\n\x1b[36mOpening Standard Edition checkout in browser:\x1b[0m ${URL_STANDARD}\n\n`);
|
|
420
|
-
openBrowser(URL_STANDARD);
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
if (choice.startsWith('2.')) {
|
|
424
|
-
process.stdout.write(`\n\x1b[36mOpening Master Bundle checkout in browser:\x1b[0m ${URL_MASTER}\n\n`);
|
|
425
|
-
openBrowser(URL_MASTER);
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
|
-
if (choice.startsWith('3.')) {
|
|
429
|
-
await runScaffold();
|
|
430
|
-
break;
|
|
431
|
-
}
|
|
432
|
-
break;
|
|
433
|
-
} else {
|
|
434
|
-
process.stdout.write('\x1b[1m\x1b[38;2;98;201;255mEliminate AI Context Rot with Chemical X Architecture:\x1b[0m\n');
|
|
435
|
-
process.stdout.write(` [1] Buy Standard Edition ($49) - ${URL_STANDARD}\n`);
|
|
436
|
-
process.stdout.write(` [2] Buy Master Bundle ($99) - ${URL_MASTER}\n`);
|
|
437
|
-
process.stdout.write(' [3] Enter License Key to Scaffold (Paid License Holders)\n');
|
|
438
|
-
process.stdout.write(' [4] Exit\n\n');
|
|
439
|
-
|
|
440
|
-
const selection = await promptQuestion('Select option [1-4]: ');
|
|
441
|
-
if (selection === '1') {
|
|
442
|
-
process.stdout.write(`\nOpening: ${URL_STANDARD}\n\n`);
|
|
443
|
-
openBrowser(URL_STANDARD);
|
|
444
|
-
continue;
|
|
445
|
-
}
|
|
446
|
-
if (selection === '2') {
|
|
447
|
-
process.stdout.write(`\nOpening: ${URL_MASTER}\n\n`);
|
|
448
|
-
openBrowser(URL_MASTER);
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
if (selection === '3') {
|
|
452
|
-
await runScaffold();
|
|
453
|
-
break;
|
|
454
|
-
}
|
|
455
|
-
break;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
process.exit(report.violations.length > 0 ? 1 : 0);
|
|
459
|
-
}
|
|
132
|
+
process.stdout.write(formatTerminalReport(report));
|
|
460
133
|
return report;
|
|
461
134
|
};
|
|
462
135
|
|
|
463
136
|
export { auditFile };
|
|
464
137
|
|
|
465
|
-
const printHelp = () => {
|
|
466
|
-
renderBanner();
|
|
467
|
-
process.stdout.write('\x1b[1mAvailable Commands:\x1b[0m\n');
|
|
468
|
-
process.stdout.write(' \x1b[36mnpm create chemx [dir]\x1b[0m [PAID] Scaffold complete Quantum Architecture project\n');
|
|
469
|
-
process.stdout.write(' \x1b[36mnpx @chemx/starter-kit init [dir]\x1b[0m [PAID] Drop blueprints & hooks into existing project\n');
|
|
470
|
-
process.stdout.write(' \x1b[36mnpx chemx generate <m-name>\x1b[0m Generate isolated molecule capsule (< 100 lines)\n');
|
|
471
|
-
process.stdout.write(' \x1b[36mnpx chemx audit [--json] [--dir=src]\x1b[0m[FREE] Scan codebase for AST architectural hazards\n\n');
|
|
472
|
-
};
|
|
473
|
-
|
|
474
138
|
const main = async () => {
|
|
475
139
|
const firstArg = rawArgs[0];
|
|
476
140
|
|
|
477
141
|
if (isCreateInvoked) {
|
|
478
142
|
const dirArg = firstArg === 'create' ? rawArgs[1] : firstArg;
|
|
479
|
-
await runScaffold(dirArg);
|
|
143
|
+
await runScaffold(dirArg, rawArgs, runAudit);
|
|
480
144
|
return;
|
|
481
145
|
}
|
|
482
146
|
|
|
@@ -485,19 +149,27 @@ const main = async () => {
|
|
|
485
149
|
await runAudit(null, true);
|
|
486
150
|
break;
|
|
487
151
|
case 'init':
|
|
488
|
-
await runInit(rawArgs[1] || 'src/chemical-x');
|
|
152
|
+
await runInit(rawArgs[1] || 'src/chemical-x', rawArgs, runAudit);
|
|
489
153
|
break;
|
|
490
154
|
case 'create':
|
|
491
|
-
await runScaffold(rawArgs[1]);
|
|
155
|
+
await runScaffold(rawArgs[1], rawArgs, runAudit);
|
|
156
|
+
break;
|
|
157
|
+
case 'hook':
|
|
158
|
+
case 'hooks':
|
|
159
|
+
case 'install-hooks':
|
|
160
|
+
case 'setup-ci':
|
|
161
|
+
await runInstallWizard(rawArgs[1] || process.cwd());
|
|
492
162
|
break;
|
|
493
163
|
case 'generate':
|
|
494
164
|
case 'capsule':
|
|
495
165
|
case 'add':
|
|
496
166
|
if (!rawArgs[1]) {
|
|
497
|
-
process.stderr.write(
|
|
167
|
+
process.stderr.write(
|
|
168
|
+
'Usage: npx chemx generate <capsule-name>\nExample: npx chemx generate m-user-avatar\n'
|
|
169
|
+
);
|
|
498
170
|
process.exit(1);
|
|
499
171
|
}
|
|
500
|
-
runGenerateCapsule(rawArgs[1]);
|
|
172
|
+
await runGenerateCapsule(rawArgs[1]);
|
|
501
173
|
break;
|
|
502
174
|
case 'help':
|
|
503
175
|
case '--help':
|
|
@@ -506,9 +178,9 @@ const main = async () => {
|
|
|
506
178
|
break;
|
|
507
179
|
default:
|
|
508
180
|
if (firstArg && firstArg.startsWith('m-')) {
|
|
509
|
-
runGenerateCapsule(firstArg);
|
|
181
|
+
await runGenerateCapsule(firstArg);
|
|
510
182
|
} else if (firstArg && !firstArg.startsWith('-')) {
|
|
511
|
-
await runScaffold(firstArg);
|
|
183
|
+
await runScaffold(firstArg, rawArgs, runAudit);
|
|
512
184
|
} else {
|
|
513
185
|
printHelp();
|
|
514
186
|
}
|
|
@@ -520,4 +192,3 @@ main().catch((err) => {
|
|
|
520
192
|
process.stderr.write(`\x1b[31m✕ Unexpected Error: ${err.message}\x1b[0m\n`);
|
|
521
193
|
process.exit(1);
|
|
522
194
|
});
|
|
523
|
-
|