@chemx/create-chemx 26.9.13-272 → 26.9.13-383

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.
@@ -222,12 +222,13 @@ export const createAiSlopVisitors = ({ relativePath, violations }) => {
222
222
  VariableDeclarator(astPath) {
223
223
  if (!isComponentFile) return;
224
224
  const idName = astPath.node.id?.name?.toLowerCase();
225
- if (
226
- idName &&
227
- REINVENTED_UTILS.has(idName) &&
228
- (t.isArrowFunctionExpression(astPath.node.init) || t.isFunctionExpression(astPath.node.init))
229
- ) {
230
- const line = astPath.node.loc?.start.line || 1;
225
+ if (!idName || !REINVENTED_UTILS.has(idName)) return;
226
+
227
+ const initNode = astPath.node.init;
228
+ const isFunction = t.isArrowFunctionExpression(initNode) || t.isFunctionExpression(initNode);
229
+ if (!isFunction) return;
230
+
231
+ const line = astPath.node.loc?.start.line || 1;
231
232
  const meta = RULE_REGISTRY.AI_SLOP_UTILITY_REINVENTION;
232
233
  violations.push({
233
234
  filePath: relativePath,
@@ -240,7 +241,6 @@ export const createAiSlopVisitors = ({ relativePath, violations }) => {
240
241
  directive: meta.directive,
241
242
  isAiSlop: true
242
243
  });
243
- }
244
244
  }
245
245
  };
246
246
  };
@@ -3,6 +3,11 @@ import path from 'node:path';
3
3
  import * as t from '@babel/types';
4
4
  import { RULE_REGISTRY } from './rules-registry.js';
5
5
  import { toResultSync } from './rules-helpers.js';
6
+ import {
7
+ isComponentExtension,
8
+ isTemplateExtension,
9
+ isCommentLine
10
+ } from './rules-predicates.js';
6
11
 
7
12
  const BARE_BOOLEANS = new Set([
8
13
  'loading', 'valid', 'active', 'visible', 'open', 'disabled',
@@ -37,11 +42,12 @@ const checkMoleculeCoLocatedTest = (filePath, relativePath, violations) => {
37
42
 
38
43
  // Stage 1: Atomic Concept Declarations
39
44
  const isMoleculePath = relativePath.includes('molecules') || baseName.startsWith('m-');
40
- const isComponentExt = ext === '.vue' || ext === '.tsx' || ext === '.jsx';
45
+ const isComponentExt = isComponentExtension(ext);
41
46
  const isTestOrSpecFile = baseName.includes('.spec.') || baseName.includes('.test.');
42
47
 
43
48
  // Stage 2: Unified Decision Variable & Early Guard Clause
44
- const isMoleculeComponent = isMoleculePath && isComponentExt && !isTestOrSpecFile;
49
+ const isCandidateComponent = isMoleculePath && isComponentExt;
50
+ const isMoleculeComponent = isCandidateComponent && !isTestOrSpecFile;
45
51
  if (!isMoleculeComponent) return;
46
52
 
47
53
  const dir = path.dirname(filePath);
@@ -75,12 +81,12 @@ export const checkExtendedTextPatterns = (content, lines, relativePath, filePath
75
81
 
76
82
  const isTestFile = /\.(test|spec)\.[jt]sx?$/.test(filePath);
77
83
  const ext = path.extname(filePath);
78
- const isTemplateFile = ext === '.vue' || ext === '.html' || ext === '.svelte';
84
+ const isTemplateFile = isTemplateExtension(ext);
79
85
 
80
86
  lines.forEach((lineText, idx) => {
81
87
  const lineNum = idx + 1;
82
88
  const trimmed = lineText.trim();
83
- const isComment = trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*');
89
+ const isComment = isCommentLine(trimmed);
84
90
 
85
91
  // Pillar 9: Hardcoded Secret Detection
86
92
  const hasEnvReference = trimmed.includes('process.env') || trimmed.includes('import.meta.env');
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { groupViolationsBySeverity } from './reporter.js';
4
+ import { hasMatchingAuditMetrics } from './rules-predicates.js';
4
5
 
5
6
  const RESET = '\x1b[0m';
6
7
  const BOLD = '\x1b[1m';
@@ -177,10 +178,7 @@ export const saveAuditSnapshot = (report, cwd = process.cwd()) => {
177
178
  if (history.length > 0) {
178
179
  const last = history[history.length - 1];
179
180
  const timeDiff = Math.abs(Date.now() - new Date(last.timestamp).getTime());
180
- const isSameMetrics =
181
- last.health?.score === snapshot.health?.score &&
182
- last.metrics?.totalLoc === snapshot.metrics?.totalLoc &&
183
- last.violations?.total === snapshot.violations?.total;
181
+ const isSameMetrics = hasMatchingAuditMetrics(last, snapshot);
184
182
 
185
183
  if (timeDiff < 5000 && isSameMetrics) {
186
184
  return {
@@ -252,6 +252,43 @@ export const buildHotspotsPrompt = (report) => {
252
252
  return lines.join('\n');
253
253
  };
254
254
 
255
+ export const buildPillarPrompt = (report, pillarName) => {
256
+ const { violations = [], hotspots = [] } = report;
257
+ const pillarViolations = violations.filter((v) => v.pillar === pillarName);
258
+ const isPillar1 = pillarName === 'Line Budgets & Monolith Decomposition';
259
+ const relevantHotspots = isPillar1 ? hotspots.filter((h) => h.isMonolith || h.lineCount > 500) : [];
260
+
261
+ const hasNoViolations = pillarViolations.length === 0;
262
+ const hasNoHotspots = relevantHotspots.length === 0;
263
+ if (hasNoViolations && hasNoHotspots) return '';
264
+
265
+ const lines = [];
266
+ lines.push(`Act as a Principal Systems Architect. Surgically refactor the following ${pillarName} violations according to Chemical X Molecular Architecture Standards:\n`);
267
+
268
+ if (relevantHotspots.length > 0) {
269
+ lines.push('### MONOLITHIC REFACTORING HOTSPOTS');
270
+ lines.push('Action: Decompose into crystalline molecule capsules (< 100 lines) and declarative Table-of-Contents views.\n');
271
+ relevantHotspots.forEach((h, i) => {
272
+ lines.push(`${i + 1}. File: \`${h.filePath}\` (${h.lineCount} lines, ${h.violationCount} hazards)`);
273
+ });
274
+ lines.push('');
275
+ }
276
+
277
+ if (pillarViolations.length > 0) {
278
+ lines.push(`### ${pillarName.toUpperCase()} HAZARD VIOLATIONS`);
279
+ lines.push(...formatGroupedPromptViolations(pillarViolations));
280
+ }
281
+
282
+ lines.push('### STRICT EXECUTION RULES:');
283
+ lines.push('1. Branch First: Change to a new branch prefixed with `chem-x/NAMEOFIMPROVEMENT` before making changes.');
284
+ lines.push('2. Pre-Split Pattern Discovery: Survey cross-file patterns before slicing; extract canonical shared capsules first.');
285
+ lines.push('3. Molecular Capsule Limit: Maximum 100 lines per molecule capsule file.');
286
+ lines.push('4. Table-of-Contents Views: Top-level page views must be 10 to 20 line declarative templates assembling components via named slots.');
287
+ lines.push('5. Zero breaking changes to external component APIs, route exports, or existing props.');
288
+
289
+ return lines.join('\n');
290
+ };
291
+
255
292
  export const buildMasterPrompt = (report) => {
256
293
  const sections = [
257
294
  buildGradeFPrompt(report, { excludeAiSlop: true }),
@@ -5,6 +5,7 @@ export declare function formatCriticalSection(report: AuditReport): string;
5
5
  export declare function formatHighMediumSection(report: AuditReport): string;
6
6
  export declare function formatLowSection(report: AuditReport): string;
7
7
  export declare function formatPillarsSection(report: AuditReport, themeColor?: string | null): string;
8
+ export declare function formatSinglePillarSection(report: AuditReport, pillarName: string, themeColor?: string | null): string;
8
9
  export declare function formatHotspotsSection(report: AuditReport, themeColor?: string | null): string;
9
10
  export declare function formatContextAnalysisSection(report: AuditReport, themeColor?: string | null): string;
10
11
  export declare function formatAiSlopSection(report: AuditReport, themeColor?: string | null): string;
@@ -14,6 +14,7 @@ import {
14
14
  resolveRiskColor,
15
15
  resolveHotspotBadge,
16
16
  resolveTopSectionColor,
17
+ resolveIndividualPillarGrade,
17
18
  PILLAR_EMOJIS,
18
19
  formatPillarReactionBadgesTerminal
19
20
  } from './reporter-utils.js';
@@ -24,6 +25,7 @@ import {
24
25
  buildHotspotsPrompt,
25
26
  formatPromptBox
26
27
  } from './prompts.js';
28
+ import { resolveFirstDefined } from './rules-predicates.js';
27
29
 
28
30
  const isSlopViolation = (v) => Boolean(v.isAiSlop);
29
31
 
@@ -35,7 +37,8 @@ const formatHotspotItem = (h, idx) => {
35
37
  export const formatScorecardSection = (report, themeColor = null) => {
36
38
  const { metrics, health } = report;
37
39
  const sectionColor = themeColor || resolveTopSectionColor(report);
38
- const gradeColor = resolveGradeColor(health?.score ?? health?.grade ?? 'A');
40
+ const gradeTarget = resolveFirstDefined(health?.score, health?.grade, 'A');
41
+ const gradeColor = resolveGradeColor(gradeTarget);
39
42
  const lines = [];
40
43
 
41
44
  lines.push('');
@@ -176,6 +179,57 @@ export const formatPillarsSection = (report, themeColor = null) => {
176
179
  return lines.join('\n');
177
180
  };
178
181
 
182
+ export const formatSinglePillarSection = (report, pillarName, themeColor = null) => {
183
+ const pillarData = report?.pillars?.[pillarName] || {
184
+ violations: 0,
185
+ critical: 0,
186
+ high: 0,
187
+ medium: 0,
188
+ low: 0,
189
+ status: 'PASSED'
190
+ };
191
+ const grade = resolveIndividualPillarGrade(pillarData);
192
+ const gradeColor = resolveGradeColor(grade);
193
+ const sectionColor = themeColor || gradeColor;
194
+ const icon = PILLAR_EMOJIS[pillarName] || '🏛️';
195
+ const lines = [];
196
+
197
+ lines.push('');
198
+ lines.push(`${sectionColor}======================================================================${RESET}`);
199
+ lines.push(`${BOLD}${sectionColor} [ ${icon} ] PILLAR ARCHITECTURAL AUDIT : ${pillarName.toUpperCase()}${RESET}`);
200
+ lines.push(`${sectionColor}======================================================================${RESET}`);
201
+ lines.push(` Pillar Grade: ${gradeColor}${BOLD}Grade: ${grade}${RESET}`);
202
+ lines.push(` Compliance Status: ${getStatusBadge(pillarData.status)}`);
203
+ lines.push(` Violations: ${pillarData.violations} total (${pillarData.critical} Critical, ${pillarData.high} High, ${pillarData.medium} Medium, ${pillarData.low} Low)`);
204
+ lines.push(`${sectionColor}----------------------------------------------------------------------${RESET}`);
205
+
206
+ if (pillarData.violations === 0) {
207
+ lines.push(`\n ${GREEN}✔ Outstanding! Zero violations detected for this pillar.${RESET}`);
208
+ lines.push(` ${DIM}All inspected modules adhere strictly to Chemical X standards for ${pillarName}.${RESET}\n`);
209
+ } else {
210
+ const pillarViolations = (report.violations || []).filter((v) => v.pillar === pillarName);
211
+ if (pillarViolations.length > 0) {
212
+ lines.push(`\n ${BOLD}${sectionColor}🚨 DETECTED VIOLATIONS (${pillarViolations.length}):${RESET}\n`);
213
+ lines.push(renderGroupedViolationsTerminal(pillarViolations));
214
+ }
215
+ }
216
+
217
+ const isPillar1 = pillarName === 'Line Budgets & Monolith Decomposition';
218
+ if (isPillar1) {
219
+ const monolithHotspots = (report?.hotspots || []).filter((h) => h.isMonolith || h.lineCount > 500);
220
+ if (monolithHotspots.length > 0) {
221
+ lines.push(` ${BOLD}${sectionColor}🔥 MONOLITHIC HOTSPOTS (${monolithHotspots.length}):${RESET}`);
222
+ monolithHotspots.forEach((h, idx) => {
223
+ lines.push(formatHotspotItem(h, idx));
224
+ });
225
+ lines.push('');
226
+ }
227
+ }
228
+
229
+ lines.push(`${sectionColor}======================================================================${RESET}\n`);
230
+ return lines.join('\n');
231
+ };
232
+
179
233
  export const formatHotspotsSection = (report, themeColor = null, options = {}) => {
180
234
  const { includePrompt = true } = typeof options === 'boolean' ? { includePrompt: options } : options;
181
235
  const { hotspots } = report;
@@ -63,6 +63,34 @@ export const getStatusBadge = (status) => {
63
63
  }
64
64
  };
65
65
 
66
+ export const resolveIndividualPillarGrade = (pillarData) => {
67
+ const hasNoViolations = !pillarData || pillarData.violations === 0;
68
+ if (hasNoViolations) return 'A+';
69
+
70
+ const hasCritical = (pillarData.critical || 0) > 0;
71
+ if (hasCritical) return 'F';
72
+
73
+ const hasHigh = (pillarData.high || 0) > 0;
74
+ if (hasHigh) return 'D';
75
+
76
+ const hasMedium = (pillarData.medium || 0) > 0;
77
+ if (hasMedium) return 'C';
78
+
79
+ const hasLow = (pillarData.low || 0) > 0;
80
+ if (hasLow) return 'B';
81
+
82
+ return 'A+';
83
+ };
84
+
85
+ export const resolvePillarRiskWeight = (pillarData) => {
86
+ if (!pillarData) return 0;
87
+ const critical = pillarData.critical || 0;
88
+ const high = pillarData.high || 0;
89
+ const medium = pillarData.medium || 0;
90
+ const low = pillarData.low || 0;
91
+ return critical * 8 + high * 4 + medium * 2 + low * 1;
92
+ };
93
+
66
94
  export const resolveGradeColor = (scoreOrGrade) => {
67
95
  if (typeof scoreOrGrade === 'string') {
68
96
  const g = scoreOrGrade.toUpperCase();
@@ -16,6 +16,7 @@ import {
16
16
  formatHighMediumSection,
17
17
  formatLowSection,
18
18
  formatPillarsSection,
19
+ formatSinglePillarSection,
19
20
  formatHotspotsSection,
20
21
  formatContextAnalysisSection,
21
22
  formatAiSlopSection
@@ -108,6 +109,7 @@ export {
108
109
  formatHighMediumSection,
109
110
  formatLowSection,
110
111
  formatPillarsSection,
112
+ formatSinglePillarSection,
111
113
  formatHotspotsSection,
112
114
  formatContextAnalysisSection,
113
115
  formatAiSlopSection
@@ -1,6 +1,9 @@
1
1
  import type * as babelTypes from '@babel/types';
2
2
 
3
3
  export declare function isComponentPath(path: string): boolean;
4
+ export declare function isComponentExtension(ext: string): boolean;
5
+ export declare function isTemplateExtension(ext: string): boolean;
6
+ export declare function isCommentLine(line: string): boolean;
4
7
  export declare function isCodeLine(line: string): boolean;
5
8
  export declare function isConsoleCallStatement(stmt: unknown, t: typeof babelTypes): boolean;
6
9
  export declare function isShallowCatchBody(body: readonly unknown[], t: typeof babelTypes): boolean;
@@ -9,6 +12,8 @@ export declare function isRedundantPassthroughReturn(curr: unknown, next: unknow
9
12
  export declare function isHookIdentifier(idNode: unknown): boolean;
10
13
  export declare function isCustomHookFunction(astPath: unknown): boolean;
11
14
  export declare function resolveStartLine(primaryNode?: unknown, fallbackNode?: unknown, defaultLine?: number): number;
15
+ export declare function resolveFirstDefined<T>(...candidates: readonly (T | null | undefined)[]): T | undefined;
16
+ export declare function hasMatchingAuditMetrics(a: Record<string, any>, b: Record<string, any>): boolean;
12
17
  export declare function isZeroDelayTimeout(callee: unknown, delayArg: unknown, t: typeof babelTypes): boolean;
13
18
  export declare function isUnguardedConsoleCall(callee: unknown, t: typeof babelTypes): boolean;
14
19
  export declare function matchesDiscussionTitle(titleLower: string, targetLower: string): boolean;
@@ -4,14 +4,27 @@
4
4
  */
5
5
 
6
6
  const COMPONENT_PATH_SEGMENTS = ['molecules', 'components', '/m-', '/views/', '/pages/'];
7
+ const COMPONENT_EXTENSIONS = new Set(['.vue', '.tsx', '.jsx']);
8
+ const TEMPLATE_EXTENSIONS = new Set(['.vue', '.html', '.svelte']);
9
+ const COMMENT_PREFIXES = ['//', '*', '/*'];
7
10
  const CONSOLE_LOG_METHODS = ['log', 'info', 'warn'];
8
11
  const NON_INTERACTIVE_FLAGS = ['--non-interactive', '--no-interactive', '--ci'];
12
+ const FORMAT_FLAGS = ['--markdown', '--md', '--json'];
9
13
  const GRADE_RANKS = { 'A+': 5, 'A': 4, 'B': 3, 'C': 2, 'D': 1, 'F': 0 };
10
14
 
11
15
  export const isComponentPath = (path) => {
12
16
  return COMPONENT_PATH_SEGMENTS.some((segment) => path.includes(segment));
13
17
  };
14
18
 
19
+ export const isComponentExtension = (ext) => COMPONENT_EXTENSIONS.has(ext);
20
+
21
+ export const isTemplateExtension = (ext) => TEMPLATE_EXTENSIONS.has(ext);
22
+
23
+ export const isCommentLine = (line) => {
24
+ if (!line) return false;
25
+ return COMMENT_PREFIXES.some((prefix) => line.startsWith(prefix));
26
+ };
27
+
15
28
  export const isCodeLine = (line) => {
16
29
  if (!line) return false;
17
30
  const isLineComment = line.startsWith('//');
@@ -60,7 +73,26 @@ export const isCustomHookFunction = (astPath) => {
60
73
  };
61
74
 
62
75
  export const resolveStartLine = (primaryNode, fallbackNode, defaultLine = 1) => {
63
- return primaryNode?.loc?.start?.line ?? fallbackNode?.loc?.start?.line ?? defaultLine;
76
+ const primaryLine = primaryNode?.loc?.start?.line;
77
+ if (primaryLine !== undefined) return primaryLine;
78
+ const fallbackLine = fallbackNode?.loc?.start?.line;
79
+ if (fallbackLine !== undefined) return fallbackLine;
80
+ return defaultLine;
81
+ };
82
+
83
+ export const resolveFirstDefined = (...candidates) => {
84
+ for (const val of candidates) {
85
+ if (val !== null && val !== undefined) return val;
86
+ }
87
+ return undefined;
88
+ };
89
+
90
+ export const hasMatchingAuditMetrics = (a, b) => {
91
+ const isScoreMatch = a.health?.score === b.health?.score;
92
+ if (!isScoreMatch) return false;
93
+ const isLocMatch = a.metrics?.totalLoc === b.metrics?.totalLoc;
94
+ if (!isLocMatch) return false;
95
+ return a.violations?.total === b.violations?.total;
64
96
  };
65
97
 
66
98
  export const isZeroDelayTimeout = (callee, delayArg, t) => {
@@ -101,7 +133,7 @@ export const evaluateAuditFailure = (conditions = []) => {
101
133
  export const isNonInteractiveSession = (rawArgs, env = process.env) => {
102
134
  const hasCliFlag = NON_INTERACTIVE_FLAGS.some((flag) => rawArgs.includes(flag));
103
135
  const hasOutputFlag = rawArgs.some((arg) => arg.startsWith('--output=') || arg.startsWith('-o='));
104
- const hasFormatFlag = rawArgs.includes('--markdown') || rawArgs.includes('--md') || rawArgs.includes('--json');
136
+ const hasFormatFlag = FORMAT_FLAGS.some((flag) => rawArgs.includes(flag));
105
137
  const hasCiEnv = Boolean(env.CI || env.GIT_DIR);
106
- return hasCliFlag || hasOutputFlag || hasFormatFlag || hasCiEnv;
138
+ return [hasCliFlag, hasOutputFlag, hasFormatFlag, hasCiEnv].some(Boolean);
107
139
  };
package/cli/audit.js CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  formatHighMediumSection,
33
33
  formatLowSection,
34
34
  formatPillarsSection,
35
+ formatSinglePillarSection,
35
36
  formatHotspotsSection,
36
37
  formatContextAnalysisSection,
37
38
  formatFailuresSection,
@@ -50,6 +51,7 @@ import {
50
51
  formatPillarReactionBadgesMarkdown,
51
52
  formatPillarShieldBadges,
52
53
  resolveBadgeColor,
54
+ PILLAR_EMOJIS,
53
55
  PILLAR_SHORT_NAMES
54
56
  } from './audit/reporter.js';
55
57
  import {
@@ -59,6 +61,7 @@ import {
59
61
  buildGradeBPrompt,
60
62
  buildAiSlopPrompt,
61
63
  buildHotspotsPrompt,
64
+ buildPillarPrompt,
62
65
  buildMasterPrompt,
63
66
  formatPromptBox,
64
67
  formatGroupedPromptViolations
@@ -78,14 +81,14 @@ const IGNORED_DIRS = new Set([
78
81
  'out'
79
82
  ]);
80
83
 
84
+ const EXCLUDED_NAME_PATTERNS = ['.test.', '.spec.', '.min.'];
85
+
81
86
  const isSourceFile = (name) => {
82
- return (
83
- /\.(tsx|ts|jsx|js|vue)$/.test(name) &&
84
- !name.endsWith('.d.ts') &&
85
- !name.includes('.test.') &&
86
- !name.includes('.spec.') &&
87
- !name.includes('.min.')
88
- );
87
+ const isExtensionValid = /\.(tsx|ts|jsx|js|vue)$/.test(name);
88
+ if (!isExtensionValid) return false;
89
+ if (name.endsWith('.d.ts')) return false;
90
+ const isExcluded = EXCLUDED_NAME_PATTERNS.some((pat) => name.includes(pat));
91
+ return !isExcluded;
89
92
  };
90
93
 
91
94
  export const auditFile = (filePath, relativePath) => {
@@ -249,6 +252,7 @@ export {
249
252
  formatHighMediumSection,
250
253
  formatLowSection,
251
254
  formatPillarsSection,
255
+ formatSinglePillarSection,
252
256
  formatHotspotsSection,
253
257
  formatContextAnalysisSection,
254
258
  formatFailuresSection,
@@ -267,6 +271,7 @@ export {
267
271
  formatPillarReactionBadgesMarkdown,
268
272
  formatPillarShieldBadges,
269
273
  resolveBadgeColor,
274
+ PILLAR_EMOJIS,
270
275
  PILLAR_SHORT_NAMES,
271
276
  buildGradeFPrompt,
272
277
  buildGradeDPrompt,
@@ -274,6 +279,7 @@ export {
274
279
  buildGradeBPrompt,
275
280
  buildAiSlopPrompt,
276
281
  buildHotspotsPrompt,
282
+ buildPillarPrompt,
277
283
  buildMasterPrompt,
278
284
  formatPromptBox,
279
285
  formatGroupedPromptViolations,
package/cli/build.js CHANGED
@@ -5,6 +5,10 @@ import { groupBuildDiagnostics } from './build/grouper.js';
5
5
  import { formatTerminalBuildReport, formatJsonBuildReport } from './build/reporter.js';
6
6
  import { ANSI } from './theme.js';
7
7
 
8
+ const IGNORED_COMMAND_TOKENS = new Set(['build', 'run', 'wrap']);
9
+
10
+ const isCommandCandidate = (arg) => !arg.startsWith('-') && !IGNORED_COMMAND_TOKENS.has(arg);
11
+
8
12
  const parseCommandFromArgs = (args) => {
9
13
  const dashDashIndex = args.indexOf('--');
10
14
  if (dashDashIndex !== -1) {
@@ -12,7 +16,7 @@ const parseCommandFromArgs = (args) => {
12
16
  if (afterDash.length > 0) return afterDash;
13
17
  }
14
18
 
15
- const candidate = args.find((a) => !a.startsWith('-') && a !== 'build' && a !== 'run' && a !== 'wrap');
19
+ const candidate = args.find(isCommandCandidate);
16
20
  return candidate ? candidate.trim() : null;
17
21
  };
18
22
 
package/cli/generator.js CHANGED
@@ -34,6 +34,9 @@ const detectBaseDir = () => {
34
34
  return '.';
35
35
  };
36
36
 
37
+ const IGNORED_NAME_TOKENS = new Set(['generate', 'capsule', 'add']);
38
+ const isCapsuleNameArg = (arg) => !arg.startsWith('-') && !IGNORED_NAME_TOKENS.has(arg);
39
+
37
40
  export const runGenerateWizard = async (rawArgs = []) => {
38
41
  renderBanner('Chemical X: Molecular Capsule Wizard');
39
42
  await checkOrPromptEvaluation('generate capsule');
@@ -41,7 +44,7 @@ export const runGenerateWizard = async (rawArgs = []) => {
41
44
  const useGum = hasGum();
42
45
  const isYes = rawArgs.includes('-y') || rawArgs.includes('--yes');
43
46
 
44
- const nameArg = rawArgs.find((a) => !a.startsWith('-') && a !== 'generate' && a !== 'capsule' && a !== 'add');
47
+ const nameArg = rawArgs.find(isCapsuleNameArg);
45
48
  const frameworkArg = (rawArgs.find((a) => a.startsWith('--framework=')) || '').split('=')[1]
46
49
  || (rawArgs.includes('-f') ? rawArgs[rawArgs.indexOf('-f') + 1] : null);
47
50
  const tierArg = (rawArgs.find((a) => a.startsWith('--tier=')) || '').split('=')[1];
package/cli/index.js CHANGED
@@ -36,6 +36,9 @@ import { runBuildAudit } from './build.js';
36
36
 
37
37
  const rawArgs = process.argv.slice(2);
38
38
  const invokedBin = path.basename(process.argv[1] || '');
39
+ const CAPSULE_PREFIXES = ['m-', 'a-', 'o-', 't-'];
40
+
41
+ const isCapsulePrefix = (arg) => CAPSULE_PREFIXES.some((prefix) => arg.startsWith(prefix));
39
42
 
40
43
  const loadProjectConfig = () => {
41
44
  const cfgPath = path.resolve(process.cwd(), '.chemx', 'config.json');
@@ -212,7 +215,7 @@ const main = async () => {
212
215
  printHelp();
213
216
  break;
214
217
  default:
215
- if (firstArg && (firstArg.startsWith('m-') || firstArg.startsWith('a-') || firstArg.startsWith('o-') || firstArg.startsWith('t-'))) {
218
+ if (firstArg && isCapsulePrefix(firstArg)) {
216
219
  await runGenerateWizard(rawArgs);
217
220
  } else if (firstArg && !firstArg.startsWith('-')) {
218
221
  await runScaffold(firstArg, rawArgs, runAudit);
package/cli/license.js CHANGED
@@ -16,11 +16,28 @@ export const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
16
16
  export const DEVICE_FILE = path.join(CONFIG_DIR, 'device_id');
17
17
 
18
18
  export const API_BASE = process.env.CHEMICAL_X_API_URL || 'https://chemicalx.xophz.com';
19
+ export const GATEKEEPER_API_URL = process.env.COMPASS_GATEKEEPER_URL || 'https://mycompassconsulting.com/wp-json/compass/v1/gatekeeper';
19
20
  export const URL_LEARN = 'https://chemicalx.xophz.com';
20
21
  export const URL_SPONSOR = 'https://github.com/sponsors/Chemical-X-Protocol';
21
22
  export const URL_STANDARD = 'https://mycompassconsulting.com/buy/chemical-x/standard';
22
23
  export const URL_MASTER = 'https://mycompassconsulting.com/buy/chemical-x/master';
23
24
 
25
+ export const verifyWithGatekeeper = async (keyOrUser, deviceId = null) => {
26
+ const effectiveDeviceId = deviceId || getOrCreateDeviceId();
27
+ try {
28
+ const res = await fetch(`${GATEKEEPER_API_URL}/licenses/validate`, {
29
+ method: 'POST',
30
+ headers: { 'Content-Type': 'application/json' },
31
+ body: JSON.stringify({ key: keyOrUser, deviceId: effectiveDeviceId })
32
+ });
33
+ if (!res.ok) return { valid: false };
34
+ return await res.json();
35
+ } catch {
36
+ return { valid: false };
37
+ }
38
+ };
39
+
40
+
24
41
  export const ensureConfigDir = () => {
25
42
  if (fs.existsSync(CONFIG_DIR)) return true;
26
43
  const [, err] = toResultSync(() => fs.mkdirSync(CONFIG_DIR, { recursive: true }));
@@ -328,9 +345,42 @@ export const fetchStarterKitFiles = async (licenseKey) => {
328
345
  );
329
346
  return responseData.files || {};
330
347
  } catch (err) {
348
+ // Fallback: Verify with Gatekeeper or local blueprints
349
+ const gatekeeperCheck = await verifyWithGatekeeper(normalizedKey, deviceId);
350
+ if (gatekeeperCheck.valid) {
351
+ saveLicenseKey(normalizedKey);
352
+ process.stdout.write(
353
+ `\x1b[32m✔ Verified via Gatekeeper for @${gatekeeperCheck.github_user || 'sponsor'} [Tier: ${gatekeeperCheck.tier}]\x1b[0m\n\n`
354
+ );
355
+ const localFiles = loadLocalBlueprintFiles();
356
+ if (Object.keys(localFiles).length > 0) return localFiles;
357
+ }
358
+
331
359
  process.stderr.write(
332
360
  `\x1b[31m✕ Network Error: Failed to reach edge server (${err.message}).\x1b[0m\n`
333
361
  );
334
362
  process.exit(1);
335
363
  }
336
364
  };
365
+
366
+ export const loadLocalBlueprintFiles = () => {
367
+ const blueprintsDir = path.resolve(path.dirname(new URL(import.meta.url).pathname), '../blueprints');
368
+ const files = {};
369
+ if (!fs.existsSync(blueprintsDir)) return files;
370
+
371
+ const walk = (dir, base = '') => {
372
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
373
+ for (const entry of entries) {
374
+ const rel = base ? `${base}/${entry.name}` : entry.name;
375
+ const full = path.join(dir, entry.name);
376
+ if (entry.isDirectory()) {
377
+ walk(full, rel);
378
+ } else {
379
+ files[rel] = fs.readFileSync(full, 'utf-8');
380
+ }
381
+ }
382
+ };
383
+ walk(blueprintsDir);
384
+ return files;
385
+ };
386
+
@@ -27,13 +27,21 @@ import {
27
27
  buildGradeCPrompt,
28
28
  buildGradeBPrompt,
29
29
  buildAiSlopPrompt,
30
+ formatSinglePillarSection,
31
+ buildPillarPrompt,
32
+ PILLAR_EMOJIS,
33
+ PILLAR_SHORT_NAMES,
30
34
  buildHotspotsPrompt,
31
35
  buildMasterPrompt,
32
36
  formatRoadmapSection,
33
37
  buildSelfHealingRoadmapPrompt,
34
38
  copyToClipboard
35
39
  } from "./audit.js";
36
- import { resolveGradeBadge } from "./navigator-grades.js";
40
+ import {
41
+ resolveGradeBadge,
42
+ resolveIndividualPillarGrade,
43
+ resolvePillarRiskWeight
44
+ } from "./navigator-grades.js";
37
45
  import { showPagedContent } from "./navigator-paged.js";
38
46
  import { showConversionMenu } from "./navigator-conversion.js";
39
47
  import { handleShareToDiscussions } from "./navigator-share.js";
@@ -41,111 +49,99 @@ import { runInstallWizard } from "./installer.js";
41
49
  import { formatButtonTag } from "./navigator-menu.js";
42
50
  import { runBadgeCommand } from "./badge.js";
43
51
 
44
- const isExtremeMonolith = (h) => h.lineCount >= 2000;
45
- const isSevereMonolith = (h) => h.lineCount >= 1000 && h.lineCount < 2000;
46
- const isWarningMonolith = (h) => h.lineCount >= 500 && h.lineCount < 1000;
47
- const isPassedPillar = (p) => p.status === "PASSED";
48
- const isFailedPillar = (p) => p.status === "FAILED";
49
- const isWarnPillar = (p) => p.status === "WARN";
52
+ const GRADE_RISK_RANKS = {
53
+ F: 5,
54
+ D: 4,
55
+ C: 3,
56
+ B: 2,
57
+ A: 1,
58
+ 'A+': 0
59
+ };
50
60
 
51
- export const buildActiveGrades = (report) => {
52
- const { hotspots, violations, pillars } = report;
53
- const { critical, high, medium, low } = groupViolationsBySeverity(violations);
61
+ const comparePillarsByRisk = (a, b) => {
62
+ const rankA = GRADE_RISK_RANKS[a.grade] ?? 0;
63
+ const rankB = GRADE_RISK_RANKS[b.grade] ?? 0;
64
+ if (rankA !== rankB) {
65
+ return rankB - rankA;
66
+ }
54
67
 
55
- const passedPillarsCount = Object.values(pillars || {}).filter(isPassedPillar).length;
56
- const failedPillarsCount = Object.values(pillars || {}).filter(isFailedPillar).length;
57
- const warnPillarsCount = Object.values(pillars || {}).filter(isWarnPillar).length;
68
+ if (b.riskWeight !== a.riskWeight) {
69
+ return b.riskWeight - a.riskWeight;
70
+ }
58
71
 
59
- const extremeMonoliths = hotspots.filter(isExtremeMonolith);
60
- const severeMonoliths = hotspots.filter(isSevereMonolith);
61
- const warningMonoliths = hotspots.filter(isWarningMonolith);
72
+ if (b.violations !== a.violations) {
73
+ return b.violations - a.violations;
74
+ }
62
75
 
63
- const gradeFCount = critical.length + extremeMonoliths.length + failedPillarsCount;
64
- const gradeDCount = high.length + severeMonoliths.length + warnPillarsCount;
65
- const gradeCCount = medium.length + warningMonoliths.length;
66
- const gradeBCount = low.length;
67
- const gradeACount = passedPillarsCount;
76
+ return a.originalIndex - b.originalIndex;
77
+ };
68
78
 
69
- const handleGradeAAction = async () => {
70
- await showPagedContent(formatGradeASection(report));
71
- };
72
- const handleGradeBAction = async () => {
73
- await showPagedContent(formatGradeBSection(report), buildGradeBPrompt(report));
74
- };
75
- const handleGradeCAction = async () => {
76
- await showPagedContent(formatGradeCSection(report), buildGradeCPrompt(report));
77
- };
78
- const handleGradeDAction = async () => {
79
- await showPagedContent(formatGradeDSection(report), buildGradeDPrompt(report));
80
- };
81
- const handleGradeFAction = async () => {
82
- await showPagedContent(formatGradeFSection(report), buildGradeFPrompt(report));
83
- };
84
- const slopViolations = (violations || []).filter((v) => v.isAiSlop);
85
- const handleSlopAction = async () => {
86
- await showPagedContent(formatAiSlopSection(report), buildAiSlopPrompt(report));
87
- };
79
+ export const buildActiveGrades = (report) => {
80
+ const { pillars = {}, violations = [], aiSlop } = report;
81
+ const pillarEntries = Object.entries(pillars);
82
+ const items = [];
83
+
84
+ pillarEntries.forEach(([pillarName, data], index) => {
85
+ const grade = resolveIndividualPillarGrade(data);
86
+ const riskWeight = resolvePillarRiskWeight(data);
87
+ const icon = PILLAR_EMOJIS[pillarName] || '🏛️';
88
+ const shortName = PILLAR_SHORT_NAMES[pillarName] || pillarName;
89
+ const isClean = data.violations === 0;
90
+ const countLabel = isClean
91
+ ? '0 items'
92
+ : `${data.violations} ${data.violations === 1 ? 'item' : 'items'}`;
93
+
94
+ const handlePillarAction = async () => {
95
+ await showPagedContent(
96
+ formatSinglePillarSection(report, pillarName),
97
+ buildPillarPrompt(report, pillarName)
98
+ );
99
+ };
100
+
101
+ items.push({
102
+ key: `pillar_${data.pillarKey || index + 1}`,
103
+ name: pillarName,
104
+ shortName,
105
+ grade,
106
+ riskWeight,
107
+ violations: data.violations || 0,
108
+ originalIndex: index,
109
+ tag: resolveGradeBadge(grade, 11),
110
+ label: `${icon} ${pillarName} (${countLabel})`,
111
+ action: handlePillarAction
112
+ });
113
+ });
88
114
 
89
- const gradeTiers = [
90
- {
91
- key: "grade_a",
92
- grade: "A",
93
- count: gradeACount,
94
- icon: "✅",
95
- description: "Compliant Checks & Passing Pillars",
96
- action: handleGradeAAction
97
- },
98
- {
99
- key: "grade_slop",
100
- grade: report.aiSlop?.grade || (slopViolations.length > 0 ? "D" : "A"),
101
- count: slopViolations.length,
102
- icon: "🤖",
103
- description: `AI Slop & Authenticity (${report.aiSlop?.score ?? 100}/100 - ${report.aiSlop?.label ?? "Pure Artisanal"})`,
115
+ if (aiSlop) {
116
+ const slopViolations = violations.filter((v) => v.isAiSlop);
117
+ const slopGrade = aiSlop.grade || (slopViolations.length > 0 ? 'D' : 'A+');
118
+ const slopRiskWeight = resolvePillarRiskWeight(aiSlop.breakdown);
119
+ const slopCount = slopViolations.length;
120
+ const isSlopClean = slopCount === 0;
121
+ const slopCountLabel = isSlopClean
122
+ ? '0 items'
123
+ : `${slopCount} ${slopCount === 1 ? 'item' : 'items'}`;
124
+
125
+ const handleSlopAction = async () => {
126
+ await showPagedContent(formatAiSlopSection(report), buildAiSlopPrompt(report));
127
+ };
128
+
129
+ items.push({
130
+ key: 'grade_slop',
131
+ name: 'AI Slop & Code Authenticity',
132
+ shortName: 'Slop',
133
+ grade: slopGrade,
134
+ riskWeight: slopRiskWeight,
135
+ violations: slopCount,
136
+ originalIndex: pillarEntries.length,
137
+ tag: resolveGradeBadge(slopGrade, 11),
138
+ label: `🤖 AI Slop & Authenticity (${aiSlop.score ?? 100}/100 - ${aiSlop.label ?? 'Pure Artisanal'}) (${slopCountLabel})`,
104
139
  action: handleSlopAction
105
- },
106
- {
107
- key: "grade_b",
108
- grade: "B",
109
- count: gradeBCount,
110
- icon: "💣",
111
- description: "Low Hygiene Issues & Minor Debts",
112
- action: handleGradeBAction
113
- },
114
- {
115
- key: "grade_c",
116
- grade: "C",
117
- count: gradeCCount,
118
- icon: "⚡",
119
- description: "Medium Severity Debts & Monolith Drift",
120
- action: handleGradeCAction
121
- },
122
- {
123
- key: "grade_d",
124
- grade: "D",
125
- count: gradeDCount,
126
- icon: "🔥",
127
- description: "High Severity Debts & Severe Monoliths",
128
- action: handleGradeDAction
129
- },
130
- {
131
- key: "grade_f",
132
- grade: "F",
133
- count: gradeFCount,
134
- icon: "💥",
135
- description: "Critical Hazards & Extreme Monoliths",
136
- action: handleGradeFAction
137
- }
138
- ];
139
-
140
- const createActiveGradeItem = (g) => ({
141
- key: g.key || `grade_${g.grade.toLowerCase()}`,
142
- grade: g.grade.toLowerCase(),
143
- tag: resolveGradeBadge(g.grade, 11),
144
- label: `${g.icon} Grade ${g.grade}: ${g.description} (${g.count} ${g.grade === "A" ? "clean" : "items"})`,
145
- action: g.action
146
- });
140
+ });
141
+ }
147
142
 
148
- return gradeTiers.map(createActiveGradeItem);
143
+ items.sort(comparePillarsByRisk);
144
+ return items;
149
145
  };
150
146
 
151
147
  export const buildDashboardActionGroups = ({ report, onScaffold = null, onRerun = null }) => {
@@ -53,6 +53,34 @@ export const resolveLowGrade = (count) => {
53
53
  const isFailedPillar = (p) => p.status === 'FAILED';
54
54
  const isWarnPillar = (p) => p.status === 'WARN';
55
55
 
56
+ export const resolveIndividualPillarGrade = (pillarData) => {
57
+ const hasNoViolations = !pillarData || pillarData.violations === 0;
58
+ if (hasNoViolations) return 'A+';
59
+
60
+ const hasCritical = (pillarData.critical || 0) > 0;
61
+ if (hasCritical) return 'F';
62
+
63
+ const hasHigh = (pillarData.high || 0) > 0;
64
+ if (hasHigh) return 'D';
65
+
66
+ const hasMedium = (pillarData.medium || 0) > 0;
67
+ if (hasMedium) return 'C';
68
+
69
+ const hasLow = (pillarData.low || 0) > 0;
70
+ if (hasLow) return 'B';
71
+
72
+ return 'A+';
73
+ };
74
+
75
+ export const resolvePillarRiskWeight = (pillarData) => {
76
+ if (!pillarData) return 0;
77
+ const critical = pillarData.critical || 0;
78
+ const high = pillarData.high || 0;
79
+ const medium = pillarData.medium || 0;
80
+ const low = pillarData.low || 0;
81
+ return critical * 8 + high * 4 + medium * 2 + low * 1;
82
+ };
83
+
56
84
  export const resolvePillarGrade = (pillars) => {
57
85
  const values = Object.values(pillars || {});
58
86
  const failed = values.filter(isFailedPillar).length;
@@ -30,6 +30,12 @@ export const buildNavigatorMenu = (topActions, activeGrades, midActions, bottomA
30
30
  return { menuItems, menuOptions };
31
31
  };
32
32
 
33
+ const ROADMAP_KEYWORDS = ['Roadmap', 'Healing', 'Self-Healing'];
34
+ const RERUN_KEYWORDS = ['Re-Run', 'Rerun', 'Re-run', 'Re-Audit'];
35
+ const SHARE_KEYWORDS = ['Share', 'Discussions', 'Plug your'];
36
+
37
+ const isAnyKeywordPresent = (text, keywords) => keywords.some((kw) => text.includes(kw));
38
+
33
39
  const isChoiceMatchingItem = (cleanChoice) => (item) => {
34
40
  const prefix = `${item.index}.`;
35
41
  if (cleanChoice.startsWith(prefix) || cleanChoice === String(item.index)) return true;
@@ -51,19 +57,10 @@ const isChoiceMatchingItem = (cleanChoice) => (item) => {
51
57
  ) {
52
58
  return true;
53
59
  }
54
- if (
55
- item.key === "roadmap" &&
56
- (cleanChoice.includes("Roadmap") || cleanChoice.includes("Healing") || cleanChoice.includes("Self-Healing"))
57
- ) {
60
+ if (item.key === "roadmap" && isAnyKeywordPresent(cleanChoice, ROADMAP_KEYWORDS)) {
58
61
  return true;
59
62
  }
60
- if (
61
- item.key === "rerun" &&
62
- (cleanChoice.includes("Re-Run") ||
63
- cleanChoice.includes("Rerun") ||
64
- cleanChoice.includes("Re-run") ||
65
- cleanChoice.includes("Re-Audit"))
66
- ) {
63
+ if (item.key === "rerun" && isAnyKeywordPresent(cleanChoice, RERUN_KEYWORDS)) {
67
64
  return true;
68
65
  }
69
66
  if (
@@ -73,12 +70,7 @@ const isChoiceMatchingItem = (cleanChoice) => (item) => {
73
70
  return true;
74
71
  }
75
72
  if (item.key === "progress" && cleanChoice.includes("Progress")) return true;
76
- if (
77
- item.key === "share" &&
78
- (cleanChoice.includes("Share") ||
79
- cleanChoice.includes("Discussions") ||
80
- cleanChoice.includes("Plug your"))
81
- ) {
73
+ if (item.key === "share" && isAnyKeywordPresent(cleanChoice, SHARE_KEYWORDS)) {
82
74
  return true;
83
75
  }
84
76
  if (item.key === "export" && cleanChoice.includes("Export")) return true;
@@ -111,6 +103,12 @@ const isChoiceMatchingItem = (cleanChoice) => (item) => {
111
103
  ) {
112
104
  return true;
113
105
  }
106
+ if (item.name && cleanChoice.toLowerCase().includes(item.name.toLowerCase())) {
107
+ return true;
108
+ }
109
+ if (item.shortName && cleanChoice.toLowerCase().includes(item.shortName.toLowerCase())) {
110
+ return true;
111
+ }
114
112
  if (
115
113
  item.grade &&
116
114
  (cleanChoice.toLowerCase().includes(`grade ${item.grade}`) ||
@@ -168,6 +166,9 @@ const isEffectiveMatchingItem = (effective, exitIndex) => (item) => {
168
166
  ) {
169
167
  return true;
170
168
  }
169
+ if (item.name && effective === item.name.toLowerCase()) return true;
170
+ if (item.shortName && effective === item.shortName.toLowerCase()) return true;
171
+ if (item.key && (effective === item.key.toLowerCase() || effective === item.key.replace(/^pillar_/, '').toLowerCase())) return true;
171
172
  if (item.key === effective) return true;
172
173
  if (item.grade && (effective === item.grade || effective === `grade ${item.grade}`)) {
173
174
  return true;
@@ -7,6 +7,8 @@ export declare function resolveCriticalGrade(count: number): string;
7
7
  export declare function resolveHighMedGrade(count: number): string;
8
8
  export declare function resolveLowGrade(count: number): string;
9
9
  export declare function resolvePillarGrade(pillars: Record<string, unknown>): string;
10
+ export declare function resolveIndividualPillarGrade(pillarData: unknown): string;
11
+ export declare function resolvePillarRiskWeight(pillarData: unknown): number;
10
12
  export declare function resolveHotspotGrade(hotspots: readonly { readonly lineCount: number }[]): string;
11
13
  export declare function resolveContextGrade(riskTier: string): string;
12
14
 
package/cli/navigator.js CHANGED
@@ -29,6 +29,8 @@ export {
29
29
  resolveHighMedGrade,
30
30
  resolveLowGrade,
31
31
  resolvePillarGrade,
32
+ resolveIndividualPillarGrade,
33
+ resolvePillarRiskWeight,
32
34
  resolveHotspotGrade,
33
35
  resolveContextGrade,
34
36
  resolveAiSlopGrade
package/cli/scaffold.js CHANGED
@@ -1,15 +1,27 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { hasGum, gumInput, promptQuestion, renderBanner } from './terminal.js';
4
- import { obtainLicenseKey, fetchStarterKitFiles } from './license.js';
4
+ import { obtainLicenseKey, fetchStarterKitFiles, loadLocalBlueprintFiles } from './license.js';
5
5
 
6
6
  export const runScaffold = async (projectName, rawArgs = [], onRunAudit = null) => {
7
7
  renderBanner('Chemical X: Molecular Architecture Scaffolder (npm create chemx)');
8
8
 
9
9
  const licenseKey = await obtainLicenseKey(rawArgs, onRunAudit);
10
+ let isCommunity = false;
11
+ let files = {};
12
+
10
13
  if (!licenseKey) {
11
- process.stderr.write('\x1b[31m✕ Valid license key is required to scaffold blueprints.\x1b[0m\n');
12
- process.exit(1);
14
+ process.stdout.write(
15
+ '\n\x1b[38;2;98;201;255m⚡ Chemical X: Community Edition (Free)\x1b[0m\n' +
16
+ 'The Community version is free because you tell us about your project\n' +
17
+ 'and the app will automatically post your scorecard to our GitHub Discussions.\n' +
18
+ 'You get a free plug, we get data to prove Chemical X works and brings real value.\n' +
19
+ 'Everybody wins! Thank you for supporting our Community version.\n\n'
20
+ );
21
+ files = loadLocalBlueprintFiles();
22
+ isCommunity = true;
23
+ } else {
24
+ files = await fetchStarterKitFiles(licenseKey);
13
25
  }
14
26
 
15
27
  let targetName = projectName;
@@ -29,8 +41,6 @@ export const runScaffold = async (projectName, rawArgs = [], onRunAudit = null)
29
41
  process.exit(1);
30
42
  }
31
43
 
32
- const files = await fetchStarterKitFiles(licenseKey);
33
-
34
44
  process.stdout.write(`Scaffolding Molecular Architecture into: \x1b[36m${finalDirName}/\x1b[0m\n`);
35
45
  fs.mkdirSync(targetDir, { recursive: true });
36
46
 
@@ -66,13 +76,18 @@ export const runInit = async (targetSubDir = 'src/chemical-x', rawArgs = [], onR
66
76
 
67
77
  const targetDir = path.resolve(process.cwd(), targetSubDir);
68
78
  const licenseKey = await obtainLicenseKey(rawArgs, onRunAudit);
79
+ let files = {};
80
+
69
81
  if (!licenseKey) {
70
- process.stderr.write('\x1b[31m✕ Valid license key is required.\x1b[0m\n');
71
- process.exit(1);
82
+ process.stdout.write(
83
+ '\n\x1b[38;2;98;201;255m⚡ Chemical X: Community Drop-in (Free)\x1b[0m\n' +
84
+ 'Unpacking standard blueprints for your project. Support community standards: chemicalx.xophz.com\n\n'
85
+ );
86
+ files = loadLocalBlueprintFiles();
87
+ } else {
88
+ files = await fetchStarterKitFiles(licenseKey);
72
89
  }
73
90
 
74
- const files = await fetchStarterKitFiles(licenseKey);
75
-
76
91
  process.stdout.write(`Unpacking blueprints and hooks into: \x1b[36m${targetSubDir}/\x1b[0m\n`);
77
92
 
78
93
  let count = 0;
package/docs/CHANGELOG.md CHANGED
@@ -4,9 +4,20 @@ All notable changes to the Chemical X Starter Kit repository will be documented
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
 
7
+ ## [2026-09-13]
8
+
9
+ ### Added
10
+ - **Gatekeeper SaaS & Sponsor Verification**: Integrated client license validation in `cli/license.js` with `Xophz Gatekeeper` (`/wp-json/compass/v1/gatekeeper/licenses/validate`), allowing verification of both license keys (`CX-ST-`, `CX-SP-`) and GitHub Sponsor handles.
11
+ - **Community Edition Flow**: Added reciprocal free Community Edition scaffolding in `cli/scaffold.js` allowing developers to unpack blueprints locally while sharing scorecards with community GitHub Discussions.
12
+ - **Local Blueprint Fallback**: Added `loadLocalBlueprintFiles` in `cli/license.js` enabling offline and community blueprint scaffolding without remote edge download dependencies.
13
+
7
14
  ## [2026-09-12]
8
15
 
9
16
  ### Added
17
+ - Added individual pillar grading and risk-ordered navigation menu (`cli/navigator-actions.js`, `cli/navigator-menu.js`, `cli/navigator-grades.js`):
18
+ - Replaced generic letter-grade buckets with individual architectural pillar categories, dynamically displaying each pillar's specific grade tag (`A+` to `F`) and item counts.
19
+ - Sorted pillar categories dynamically from highest to lowest risk based on severity weight (`critical * 8 + high * 4 + medium * 2 + low * 1`), while preserving canonical 1 to 11 order when all pillars pass with Grade A+.
20
+ - Added dedicated individual pillar report view (`formatSinglePillarSection` in `cli/audit/reporter-sections.js`) and pillar AI refactoring prompt generator (`buildPillarPrompt` in `cli/audit/prompts.js`).
10
21
  - Added Build Auditor wrapper command (`chemx build`, aliases `run`, `wrap`) with token-optimized silent execution (`cli/build.js`, `cli/build/`):
11
22
  - Wraps arbitrary or auto-detected build scripts (`pnpm run build`, `yarn build`, `bun run build`, `npm run build`, `vite build`, `tsc --noEmit`).
12
23
  - Automatically parses and catalogs TypeScript (`TS*`), Rollup/Vite resolution, Sass/PostCSS syntax, and bundle budget errors into structured diagnostic categories with 1-line remediation hints.
@@ -20,6 +31,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
20
31
  - Preserved clean stdout in workflows by writing markdown reports to `--output` files without dumping raw markdown to the terminal.
21
32
  - Replaced top-level synchronous config directory creation in `cli/license.js` with lazy `ensureConfigDir` and `toResultSync` Result Tuple pattern.
22
33
 
34
+ ### Refactored
35
+ - Harmonized multi-clause boolean predicate topologies across CLI and audit modules:
36
+ - Extracted canonical extension helpers (`isComponentExtension`, `isTemplateExtension`, `isCommentLine`), metric comparison (`hasMatchingAuditMetrics`), and fallback resolution (`resolveFirstDefined`) in `cli/audit/rules-predicates.js` and `cli/audit/rules-predicates.d.ts`.
37
+ - Refactored `checkMoleculeCoLocatedTest` and `checkExtendedTextPatterns` in `cli/audit/extended-visitors.js` to use canonical predicate helpers and 2-stage atomic booleans.
38
+ - Refactored `VariableDeclarator` in `cli/audit/ai-slop-detector.js` to use sequential guard clauses for reinvented utility detection.
39
+ - Refactored `saveAuditSnapshot` in `cli/audit/history.js` using `hasMatchingAuditMetrics`.
40
+ - Refactored capsule prefix detection in `cli/index.js` and CLI build/generate argument parsing in `cli/build.js` and `cli/generator.js` using predicate helpers.
41
+ - Refactored menu choice matching in `cli/navigator-menu.js` using `isAnyKeywordPresent` and keyword catalogs.
42
+ - Refactored scorecard grade target resolution in `cli/audit/reporter-sections.js` using `resolveFirstDefined`.
43
+ - Refactored source file detection in `cli/audit.js` using early return guard clauses and exclusion arrays.
44
+
23
45
  ## [2026-09-08]
24
46
 
25
47
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chemx/create-chemx",
3
- "version": "26.9.13-272",
3
+ "version": "26.9.13-383",
4
4
  "description": "Chemical X Protocol: Private drop-in architecture starter kit and capsule generator",
5
5
  "type": "module",
6
6
  "bin": {