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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/cli/audit/ai-slop-detector.js +25 -41
  2. package/cli/audit/ast-visitors.js +10 -13
  3. package/cli/audit/history.js +45 -11
  4. package/cli/audit/pattern-detector.js +182 -0
  5. package/cli/audit/prompts.js +13 -10
  6. package/cli/audit/reporter-grades.js +28 -16
  7. package/cli/audit/reporter-markdown.js +31 -16
  8. package/cli/audit/reporter-sections.js +14 -4
  9. package/cli/audit/reporter-utils.js +75 -0
  10. package/cli/audit/reporter.d.ts +13 -4
  11. package/cli/audit/reporter.js +29 -3
  12. package/cli/audit/roadmap.js +236 -0
  13. package/cli/audit/rules-predicates.d.ts +17 -0
  14. package/cli/audit/rules-predicates.js +107 -0
  15. package/cli/audit/rules.js +6 -2
  16. package/cli/audit/social-gh.js +2 -6
  17. package/cli/audit/social-publisher.js +1 -1
  18. package/cli/audit/social.js +25 -9
  19. package/cli/audit/types.d.ts +43 -1
  20. package/cli/audit.js +47 -7
  21. package/cli/build/detector.js +68 -0
  22. package/cli/build/executor.js +58 -0
  23. package/cli/build/grouper.js +78 -0
  24. package/cli/build/parser-matchers.js +116 -0
  25. package/cli/build/parser.js +80 -0
  26. package/cli/build/reporter.js +66 -0
  27. package/cli/build/types.d.ts +58 -0
  28. package/cli/build/types.js +1 -0
  29. package/cli/build.d.ts +16 -0
  30. package/cli/build.js +55 -0
  31. package/cli/help.js +14 -0
  32. package/cli/index.js +38 -19
  33. package/cli/installer-templates.js +42 -3
  34. package/cli/license.js +10 -7
  35. package/cli/navigator-actions.js +34 -6
  36. package/cli/navigator-banner.js +4 -2
  37. package/cli/navigator-menu.js +18 -0
  38. package/cli/navigator-share.js +1 -1
  39. package/cli/navigator.d.ts +8 -1
  40. package/cli/navigator.js +3 -3
  41. package/docs/CHANGELOG.md +36 -0
  42. package/package.json +3 -2
@@ -1,5 +1,12 @@
1
1
  import * as t from '@babel/types';
2
2
  import { RULE_REGISTRY } from './rules-registry.js';
3
+ import {
4
+ isComponentPath,
5
+ isCodeLine,
6
+ isShallowCatchBody,
7
+ hasAnyTypeAnnotation,
8
+ isRedundantPassthroughReturn
9
+ } from './rules-predicates.js';
3
10
 
4
11
  const RESIDUE_PATTERNS = [
5
12
  ['hope this', 'helps'].join(' '),
@@ -95,7 +102,7 @@ export const checkSlopTextPatterns = (content, lines, relativePath, violations)
95
102
  let nextIdx = idx + 1;
96
103
  while (nextIdx < lines.length) {
97
104
  const nextTrim = lines[nextIdx].trim();
98
- if (nextTrim && !nextTrim.startsWith('//') && !nextTrim.startsWith('/*')) {
105
+ if (isCodeLine(nextTrim)) {
99
106
  const nextWords = new Set(normalizeWords(nextTrim));
100
107
  let matchCount = 0;
101
108
  for (const cw of commentWords) {
@@ -126,25 +133,12 @@ export const checkSlopTextPatterns = (content, lines, relativePath, violations)
126
133
  };
127
134
 
128
135
  export const createAiSlopVisitors = ({ relativePath, violations }) => {
129
- const isComponentFile = (
130
- relativePath.includes('molecules') ||
131
- relativePath.includes('components') ||
132
- relativePath.includes('/m-') ||
133
- relativePath.includes('/views/') ||
134
- relativePath.includes('/pages/')
135
- );
136
+ const isComponentFile = isComponentPath(relativePath);
136
137
 
137
138
  return {
138
139
  CatchClause(astPath) {
139
140
  const body = astPath.node.body?.body || [];
140
- const isShallowCatch = (
141
- body.length === 0 ||
142
- (body.length === 1 &&
143
- t.isExpressionStatement(body[0]) &&
144
- t.isCallExpression(body[0].expression) &&
145
- t.isMemberExpression(body[0].expression.callee) &&
146
- t.isIdentifier(body[0].expression.callee.object, { name: 'console' }))
147
- );
141
+ const isShallowCatch = isShallowCatchBody(body, t);
148
142
 
149
143
  if (isShallowCatch) {
150
144
  const line = astPath.node.loc?.start.line || 1;
@@ -162,23 +156,20 @@ export const createAiSlopVisitors = ({ relativePath, violations }) => {
162
156
  });
163
157
  }
164
158
 
165
- const param = astPath.node.param;
166
- if (param && t.isIdentifier(param) && param.typeAnnotation) {
167
- if (t.isTSTypeAnnotation(param.typeAnnotation) && t.isTSAnyKeyword(param.typeAnnotation.typeAnnotation)) {
168
- const line = astPath.node.loc?.start.line || 1;
169
- const meta = RULE_REGISTRY.AI_SLOP_LAZY_ANY;
170
- violations.push({
171
- filePath: relativePath,
172
- line,
173
- column: astPath.node.loc?.start.column || 1,
174
- hazard: 'Lazy any catch parameter widening detected',
175
- rule: 'AI_SLOP_LAZY_ANY',
176
- severity: meta.severity,
177
- pillar: meta.pillar,
178
- directive: meta.directive,
179
- isAiSlop: true
180
- });
181
- }
159
+ if (hasAnyTypeAnnotation(astPath.node.param, t)) {
160
+ const line = astPath.node.loc?.start.line || 1;
161
+ const meta = RULE_REGISTRY.AI_SLOP_LAZY_ANY;
162
+ violations.push({
163
+ filePath: relativePath,
164
+ line,
165
+ column: astPath.node.loc?.start.column || 1,
166
+ hazard: 'Lazy any catch parameter widening detected',
167
+ rule: 'AI_SLOP_LAZY_ANY',
168
+ severity: meta.severity,
169
+ pillar: meta.pillar,
170
+ directive: meta.directive,
171
+ isAiSlop: true
172
+ });
182
173
  }
183
174
  },
184
175
 
@@ -188,14 +179,7 @@ export const createAiSlopVisitors = ({ relativePath, violations }) => {
188
179
  for (let i = 0; i < stmts.length - 1; i++) {
189
180
  const curr = stmts[i];
190
181
  const next = stmts[i + 1];
191
- if (
192
- t.isVariableDeclaration(curr) &&
193
- curr.declarations.length === 1 &&
194
- t.isIdentifier(curr.declarations[0].id) &&
195
- t.isReturnStatement(next) &&
196
- t.isIdentifier(next.argument) &&
197
- curr.declarations[0].id.name === next.argument.name
198
- ) {
182
+ if (isRedundantPassthroughReturn(curr, next, t)) {
199
183
  const varName = curr.declarations[0].id.name;
200
184
  const line = curr.loc?.start.line || 1;
201
185
  const meta = RULE_REGISTRY.AI_SLOP_REDUNDANT_PASSTHROUGH;
@@ -1,14 +1,17 @@
1
1
  import * as t from '@babel/types';
2
2
  import { RULE_REGISTRY } from './rules-registry.js';
3
3
  import { countLogicalOperators } from './rules-helpers.js';
4
+ import {
5
+ isCustomHookFunction,
6
+ resolveStartLine,
7
+ isZeroDelayTimeout,
8
+ isUnguardedConsoleCall
9
+ } from './rules-predicates.js';
4
10
 
5
11
  export const createAstVisitors = ({ relativePath, violations }) => {
6
12
  return {
7
13
  Function(astPath) {
8
- const isCustomHook = (
9
- (astPath.node.id && /^use[A-Z0-9]/.test(astPath.node.id.name)) ||
10
- (astPath.parentPath?.node?.id && /^use[A-Z0-9]/.test(astPath.parentPath.node.id.name))
11
- );
14
+ const isCustomHook = isCustomHookFunction(astPath);
12
15
 
13
16
  // Pillar 3: Hook Saturation
14
17
  let hookCount = 0;
@@ -70,7 +73,7 @@ export const createAstVisitors = ({ relativePath, violations }) => {
70
73
  if (t.isLogicalExpression(expr) || t.isUnaryExpression(expr)) {
71
74
  const opCount = countLogicalOperators(expr);
72
75
  if (opCount > 2) {
73
- const line = expr.loc?.start.line || astPath.node.loc?.start.line || 1;
76
+ const line = resolveStartLine(expr, astPath.node, 1);
74
77
  const meta = RULE_REGISTRY.CONTROL_FLOW_INLINE_BOOLEAN;
75
78
  violations.push({
76
79
  filePath: relativePath,
@@ -154,7 +157,7 @@ export const createAstVisitors = ({ relativePath, violations }) => {
154
157
  const delayArg = args[1];
155
158
 
156
159
  // Render-hack check: setTimeout(fn, 0)
157
- if (callee.name === 'setTimeout' && delayArg && t.isNumericLiteral(delayArg) && delayArg.value === 0) {
160
+ if (isZeroDelayTimeout(callee, delayArg, t)) {
158
161
  const line = astPath.node.loc?.start.line || 1;
159
162
  const meta = RULE_REGISTRY.RENDER_HACK_TIMEOUT;
160
163
  violations.push({
@@ -198,13 +201,7 @@ export const createAstVisitors = ({ relativePath, violations }) => {
198
201
  }
199
202
 
200
203
  // Unguarded Logging
201
- if (
202
- t.isMemberExpression(callee) &&
203
- t.isIdentifier(callee.object) &&
204
- callee.object.name === 'console' &&
205
- t.isIdentifier(callee.property) &&
206
- ['log', 'info', 'warn'].includes(callee.property.name)
207
- ) {
204
+ if (isUnguardedConsoleCall(callee, t)) {
208
205
  const line = astPath.node.loc?.start.line || 1;
209
206
  const meta = RULE_REGISTRY.UNGUARDED_LOGGING;
210
207
  violations.push({
@@ -126,18 +126,39 @@ export const getAuditHistory = (cwd = process.cwd()) => {
126
126
  };
127
127
 
128
128
  export const getAuditBaseline = (cwd = process.cwd()) => {
129
+ const history = getAuditHistory(cwd);
129
130
  const baselinePath = path.resolve(cwd, '.chemx', 'baseline.json');
131
+ let explicitBaseline = null;
130
132
  if (fs.existsSync(baselinePath)) {
131
133
  try {
132
134
  const raw = fs.readFileSync(baselinePath, 'utf-8');
133
- return JSON.parse(raw);
135
+ explicitBaseline = JSON.parse(raw);
134
136
  } catch (err) {
135
137
  const error = err instanceof Error ? err : new Error(String(err));
136
- return null;
138
+ explicitBaseline = null;
137
139
  }
138
140
  }
139
- const history = getAuditHistory(cwd);
140
- return history.length > 0 ? history[0] : null;
141
+
142
+ if (history.length === 0) {
143
+ return explicitBaseline;
144
+ }
145
+
146
+ // Find the lowest scoring snapshot in history (the true debt floor)
147
+ let lowestSnapshot = history[0];
148
+ for (const s of history) {
149
+ const currentScore = s.health?.score ?? 100;
150
+ const lowestScore = lowestSnapshot.health?.score ?? 100;
151
+ if (currentScore < lowestScore) {
152
+ lowestSnapshot = s;
153
+ }
154
+ }
155
+
156
+ if (!explicitBaseline) return lowestSnapshot;
157
+
158
+ // Use the lowest score between explicit baseline and history floor
159
+ const explicitScore = explicitBaseline.health?.score ?? 100;
160
+ const minScore = lowestSnapshot.health?.score ?? 100;
161
+ return minScore < explicitScore ? lowestSnapshot : explicitBaseline;
141
162
  };
142
163
 
143
164
  export const setAuditBaseline = (snapshot, cwd = process.cwd()) => {
@@ -179,10 +200,14 @@ export const saveAuditSnapshot = (report, cwd = process.cwd()) => {
179
200
  const historyPath = path.resolve(cwd, '.chemx', 'history.json');
180
201
  fs.writeFileSync(historyPath, JSON.stringify(trimmed, null, 2), 'utf-8');
181
202
 
182
- // Auto-establish first run as baseline if none exists
203
+ // Auto-establish first run or lower score as baseline floor
183
204
  const baselinePath = path.resolve(cwd, '.chemx', 'baseline.json');
184
205
  let isNewBaseline = false;
185
- if (!fs.existsSync(baselinePath)) {
206
+ const currentBaseline = getAuditBaseline(cwd);
207
+ const currentScore = snapshot.health?.score ?? 100;
208
+ const baselineScore = currentBaseline?.health?.score ?? 101;
209
+
210
+ if (!currentBaseline || currentScore < baselineScore) {
186
211
  fs.writeFileSync(baselinePath, JSON.stringify(snapshot, null, 2), 'utf-8');
187
212
  isNewBaseline = true;
188
213
  }
@@ -275,7 +300,8 @@ export const calculateTransformationDelta = (beforeSnapshot, afterSnapshot) => {
275
300
  };
276
301
  };
277
302
 
278
- export const formatTransformationTerminal = (beforeSnapshot, afterSnapshot) => {
303
+ export const formatTransformationTerminal = (beforeSnapshot, afterSnapshot, options = {}) => {
304
+ const { isStepDelta = false } = options;
279
305
  const delta = calculateTransformationDelta(beforeSnapshot, afterSnapshot);
280
306
  const lines = [];
281
307
 
@@ -295,18 +321,26 @@ export const formatTransformationTerminal = (beforeSnapshot, afterSnapshot) => {
295
321
  return `${color}${BOLD}${sign}${perUnit}${RESET}`;
296
322
  };
297
323
 
324
+ const title = isStepDelta
325
+ ? 'STEP PROGRESSION : INCREMENTAL CHECKPOINT DELTA'
326
+ : 'ARCHITECTURAL TRANSFORMATION : BEFORE & AFTER PROGRESSION';
327
+ const subtitle = isStepDelta
328
+ ? 'Comparing immediately preceding audit vs. latest refactored audit'
329
+ : 'Comparing baseline floor snapshot vs. latest refactored audit';
330
+ const beforeLabel = isStepDelta ? 'Previous Audit:' : 'Baseline Floor:';
331
+
298
332
  lines.push('');
299
333
  lines.push(`${CYAN}======================================================================${RESET}`);
300
- lines.push(`${BOLD}${CYAN} ARCHITECTURAL TRANSFORMATION : BEFORE & AFTER PROGRESSION${RESET}`);
301
- lines.push(`${DIM} Comparing baseline snapshot vs. latest refactored audit${RESET}`);
334
+ lines.push(`${BOLD}${CYAN} ${title}${RESET}`);
335
+ lines.push(`${DIM} ${subtitle}${RESET}`);
302
336
  lines.push(`${CYAN}======================================================================${RESET}`);
303
337
  lines.push('');
304
338
 
305
339
  const dateBefore = new Date(beforeSnapshot.timestamp).toLocaleDateString();
306
340
  const dateAfter = new Date(afterSnapshot.timestamp).toLocaleDateString();
307
341
 
308
- lines.push(` ${BOLD}Baseline Audit:${RESET} ${DIM}${dateBefore} [${beforeSnapshot.id}]${RESET}`);
309
- lines.push(` ${BOLD}Latest Audit:${RESET} ${GREEN}${dateAfter} [${afterSnapshot.id}]${RESET}`);
342
+ lines.push(` ${BOLD}${beforeLabel.padEnd(18)}${RESET} ${DIM}${dateBefore} [${beforeSnapshot.id}]${RESET}`);
343
+ lines.push(` ${BOLD}${'Latest Audit:'.padEnd(18)}${RESET} ${GREEN}${dateAfter} [${afterSnapshot.id}]${RESET}`);
310
344
  lines.push('');
311
345
 
312
346
  lines.push(`${BOLD} METRIC COMPARISON TABLE${RESET}`);
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Chemical X Protocol: Structural Pattern Harmonization Detector
3
+ * Single-pass AST fingerprinting to discover cross-file clones and extraction candidates.
4
+ */
5
+
6
+ const MIN_UNION_MEMBERS = 3;
7
+ const MIN_CHILD_NODES = 2;
8
+
9
+ export const createPatternRegistry = () => {
10
+ const buckets = new Map();
11
+
12
+ const record = (type, signature, location) => {
13
+ if (!signature) return;
14
+ const key = `${type}::${signature}`;
15
+ let bucket = buckets.get(key);
16
+ if (!bucket) {
17
+ bucket = {
18
+ type,
19
+ signature,
20
+ detail: location.detail || signature,
21
+ occurrences: []
22
+ };
23
+ buckets.set(key, bucket);
24
+ }
25
+ bucket.occurrences.push({
26
+ filePath: location.filePath,
27
+ line: location.line || 1,
28
+ column: location.column || 1
29
+ });
30
+ };
31
+
32
+ const resolveHarmonizationCandidates = (hotspots = []) => {
33
+ const hotspotFiles = new Set(hotspots.map((h) => h.filePath));
34
+ const candidates = [];
35
+
36
+ for (const [key, bucket] of buckets.entries()) {
37
+ const uniqueFiles = Array.from(new Set(bucket.occurrences.map((o) => o.filePath)));
38
+ if (uniqueFiles.length < 2) continue;
39
+
40
+ const hasHotspot = uniqueFiles.some((f) => hotspotFiles.has(f));
41
+ const impactScore = uniqueFiles.length * (hasHotspot ? 3 : 1.5) + bucket.occurrences.length;
42
+
43
+ let label = 'Recurring Pattern';
44
+ let suggestedCapsule = 'm-shared-capsule';
45
+ let recommendation = 'Extract canonical capsule before decomposing consumers';
46
+
47
+ if (bucket.type === 'STATE_UNION') {
48
+ label = `Shared State Machine (${bucket.detail})`;
49
+ suggestedCapsule = 'types/state.d.ts';
50
+ recommendation = 'Extract shared discriminated union into domain types to eliminate duplicate definitions';
51
+ } else if (bucket.type === 'UI_STRUCTURE') {
52
+ label = `Shared UI Layout Structure (${bucket.detail})`;
53
+ suggestedCapsule = 'm-feature-card';
54
+ recommendation = 'Extract canonical molecule capsule before slicing consumer monoliths';
55
+ } else if (bucket.type === 'PREDICATE_LOGIC') {
56
+ label = `Duplicated Boolean Predicate Topology (${bucket.detail})`;
57
+ suggestedCapsule = 'usePredicateFilter.ts';
58
+ recommendation = 'Extract named predicate callback or higher-order filter function';
59
+ } else if (bucket.type === 'HOOK_SIGNATURE') {
60
+ label = `Parallel State Controller Return (${bucket.detail})`;
61
+ suggestedCapsule = 'useSharedController.ts';
62
+ recommendation = 'Unify ad-hoc local state returns into a canonical domain composable';
63
+ }
64
+
65
+ candidates.push({
66
+ id: key,
67
+ type: bucket.type,
68
+ label,
69
+ detail: bucket.detail,
70
+ suggestedCapsule,
71
+ recommendation,
72
+ fileCount: uniqueFiles.length,
73
+ totalHits: bucket.occurrences.length,
74
+ hasHotspot,
75
+ impactScore,
76
+ occurrences: bucket.occurrences
77
+ });
78
+ }
79
+
80
+ return candidates.sort((a, b) => b.impactScore - a.impactScore).slice(0, 12);
81
+ };
82
+
83
+ return { record, resolveHarmonizationCandidates };
84
+ };
85
+
86
+ export const createPatternVisitors = (registry, relativePath) => {
87
+ if (!registry) return {};
88
+
89
+ const getJsxTagName = (node) => {
90
+ if (!node || !node.openingElement) return null;
91
+ const nameNode = node.openingElement.name;
92
+ if (nameNode.type === 'JSXIdentifier') return nameNode.name;
93
+ if (nameNode.type === 'JSXMemberExpression') return `${nameNode.object.name}.${nameNode.property.name}`;
94
+ return null;
95
+ };
96
+
97
+ const getJsxHierarchy = (node, depth = 0) => {
98
+ if (depth > 2 || !node || node.type !== 'JSXElement') return '';
99
+ const tag = getJsxTagName(node);
100
+ if (!tag) return '';
101
+
102
+ const elementChildren = (node.children || [])
103
+ .filter((c) => c.type === 'JSXElement')
104
+ .map((c) => getJsxHierarchy(c, depth + 1))
105
+ .filter(Boolean);
106
+
107
+ if (elementChildren.length === 0) return tag;
108
+ return `${tag}>(${elementChildren.join('+')})`;
109
+ };
110
+
111
+ return {
112
+ TSTypeAliasDeclaration(path) {
113
+ const typeAnnotation = path.node.typeAnnotation;
114
+ if (typeAnnotation && typeAnnotation.type === 'TSUnionType') {
115
+ const literals = (typeAnnotation.types || [])
116
+ .filter((t) => t.type === 'TSLiteralType' && typeof t.literal?.value === 'string')
117
+ .map((t) => `'${t.literal.value}'`);
118
+
119
+ if (literals.length >= MIN_UNION_MEMBERS) {
120
+ const sorted = Array.from(new Set(literals)).sort().join(' | ');
121
+ registry.record('STATE_UNION', sorted, {
122
+ filePath: relativePath,
123
+ line: path.node.loc?.start?.line,
124
+ detail: sorted
125
+ });
126
+ }
127
+ }
128
+ },
129
+
130
+ JSXElement(path) {
131
+ const directElementChildren = (path.node.children || []).filter((c) => c.type === 'JSXElement');
132
+ if (directElementChildren.length >= MIN_CHILD_NODES) {
133
+ const hierarchy = getJsxHierarchy(path.node);
134
+ if (hierarchy && hierarchy.includes('>')) {
135
+ registry.record('UI_STRUCTURE', hierarchy, {
136
+ filePath: relativePath,
137
+ line: path.node.loc?.start?.line,
138
+ detail: hierarchy
139
+ });
140
+ }
141
+ }
142
+ },
143
+
144
+ LogicalExpression(path) {
145
+ let clauseCount = 1;
146
+ let curr = path.node;
147
+ while (curr && curr.type === 'LogicalExpression') {
148
+ clauseCount++;
149
+ curr = curr.left;
150
+ }
151
+ if (clauseCount >= 3) {
152
+ const sig = `CLAUSES_${clauseCount}_OP_${path.node.operator}`;
153
+ registry.record('PREDICATE_LOGIC', sig, {
154
+ filePath: relativePath,
155
+ line: path.node.loc?.start?.line,
156
+ detail: `${clauseCount}-clause ${path.node.operator} condition`
157
+ });
158
+ }
159
+ },
160
+
161
+ ReturnStatement(path) {
162
+ const functionParent = path.getFunctionParent();
163
+ const fnName = functionParent?.node?.id?.name;
164
+ const isHook = fnName && /^use[A-Z]/.test(fnName);
165
+
166
+ if (isHook && path.node.argument && path.node.argument.type === 'ObjectExpression') {
167
+ const props = (path.node.argument.properties || [])
168
+ .filter((p) => p.type === 'ObjectProperty' && p.key?.name)
169
+ .map((p) => p.key.name);
170
+
171
+ if (props.length >= 3) {
172
+ const sorted = props.sort().join(',');
173
+ registry.record('HOOK_SIGNATURE', sorted, {
174
+ filePath: relativePath,
175
+ line: path.node.loc?.start?.line,
176
+ detail: `{ ${sorted.replace(/,/g, ', ')} }`
177
+ });
178
+ }
179
+ }
180
+ }
181
+ };
182
+ };
@@ -90,11 +90,12 @@ export const buildGradeFPrompt = (report, options = {}) => {
90
90
  }
91
91
 
92
92
  lines.push('### STRICT EXECUTION RULES:');
93
- lines.push('1. Every new molecule component must stay under 100 lines.');
94
- lines.push('2. Top-level page views must be 10-20 line declarative Table-of-Contents templates assembling components via named slots.');
95
- lines.push('3. Zero synthetic or mock data: return live data or explicit empty states.');
96
- lines.push('4. Zero render-hack setTimeout: replace with await nextTick() or flush: post.');
97
- lines.push('5. Output surgical diffs or modular replacements. Preserve existing external exports.');
93
+ lines.push('1. Pre-Split Pattern Discovery: Survey cross-file patterns before slicing; extract canonical shared capsules first to avoid proliferating duplicate one-off patterns.');
94
+ lines.push('2. Every new molecule component must stay under 100 lines.');
95
+ lines.push('3. Top-level page views must be 10-20 line declarative Table-of-Contents templates assembling components via named slots.');
96
+ lines.push('4. Zero synthetic or mock data: return live data or explicit empty states.');
97
+ lines.push('5. Zero render-hack setTimeout: replace with await nextTick() or flush: post.');
98
+ lines.push('6. Output surgical diffs or modular replacements. Preserve existing external exports.');
98
99
 
99
100
  return lines.join('\n');
100
101
  };
@@ -240,11 +241,13 @@ export const buildHotspotsPrompt = (report) => {
240
241
  lines.push('');
241
242
 
242
243
  lines.push('### STRICT EXECUTION RULES:');
243
- lines.push('1. Molecular Limits: Molecule capsules must strictly remain under 100 lines per file.');
244
- lines.push('2. Table-of-Contents Views: Top-level page views must be 10 to 20 line declarative Table of Contents assembling molecules via named slots.');
245
- lines.push('3. Composable Return Contracts: Custom hooks/composables must strictly limit returns to 3 to 5 properties (State + Status + Actions).');
246
- lines.push('4. Type Co-location: Co-locate granular types/*.d.ts files inside each feature capsule (< 100 lines per type file) instead of creating type monoliths.');
247
- lines.push('5. Zero breaking changes to external component APIs, route exports, or existing props.');
244
+ lines.push('1. Branch First: Change to a new branch prefixed with `chem-x/NAMEOFIMPROVEMENT` before making changes, then commit the branch and create a PR to main.');
245
+ lines.push('2. Pre-Split Pattern Discovery: Audit recurring UI layouts, state machines, and predicates across monoliths first; extract canonical shared capsules before slicing to prevent bespoke pattern duplication.');
246
+ lines.push('3. Molecular Limits: Molecule capsules must strictly remain under 100 lines per file.');
247
+ lines.push('4. Table-of-Contents Views: Top-level page views must be 10 to 20 line declarative Table of Contents assembling molecules via named slots.');
248
+ lines.push('5. Composable Return Contracts: Custom hooks/composables must strictly limit returns to 3 to 5 properties (State + Status + Actions).');
249
+ lines.push('6. Type Co-location: Co-locate granular types/*.d.ts files inside each feature capsule (< 100 lines per type file) instead of creating type monoliths.');
250
+ lines.push('7. Zero breaking changes to external component APIs, route exports, or existing props.');
248
251
 
249
252
  return lines.join('\n');
250
253
  };
@@ -19,7 +19,8 @@ import {
19
19
  import { getReportCardAsciiLines } from './reporter-ascii.js';
20
20
  import { renderGroupedViolationsTerminal } from './reporter-grouping.js';
21
21
 
22
- export const formatGradeFSection = (report) => {
22
+ export const formatGradeFSection = (report, options = {}) => {
23
+ const { includePrompt = true } = options;
23
24
  const { violations, hotspots, pillars } = report;
24
25
  const { critical } = groupViolationsBySeverity(violations);
25
26
  const extremeMonoliths = hotspots.filter((h) => h.lineCount >= 2000);
@@ -67,16 +68,19 @@ export const formatGradeFSection = (report) => {
67
68
  }
68
69
  }
69
70
 
70
- const promptF = buildGradeFPrompt(report);
71
- if (promptF) {
72
- lines.push(formatPromptBox('🤖 AI AGENT REFACTORING PROMPT (GRADE F HAZARDS)', promptF));
71
+ if (includePrompt) {
72
+ const promptF = buildGradeFPrompt(report);
73
+ if (promptF) {
74
+ lines.push(formatPromptBox('🤖 AI AGENT REFACTORING PROMPT (GRADE F HAZARDS)', promptF));
75
+ }
73
76
  }
74
77
 
75
78
  lines.push(`${RED}======================================================================${RESET}\n`);
76
79
  return lines.join('\n');
77
80
  };
78
81
 
79
- export const formatGradeDSection = (report) => {
82
+ export const formatGradeDSection = (report, options = {}) => {
83
+ const { includePrompt = true } = options;
80
84
  const { violations, hotspots, pillars } = report;
81
85
  const { high } = groupViolationsBySeverity(violations);
82
86
  const severeMonoliths = hotspots.filter((h) => h.lineCount >= 1000 && h.lineCount < 2000);
@@ -123,16 +127,19 @@ export const formatGradeDSection = (report) => {
123
127
  }
124
128
  }
125
129
 
126
- const promptD = buildGradeDPrompt(report);
127
- if (promptD) {
128
- lines.push(formatPromptBox('🤖 AI AGENT REFACTORING PROMPT (GRADE D DEBTS)', promptD));
130
+ if (includePrompt) {
131
+ const promptD = buildGradeDPrompt(report);
132
+ if (promptD) {
133
+ lines.push(formatPromptBox('🤖 AI AGENT REFACTORING PROMPT (GRADE D DEBTS)', promptD));
134
+ }
129
135
  }
130
136
 
131
137
  lines.push(`${ORANGE}======================================================================${RESET}\n`);
132
138
  return lines.join('\n');
133
139
  };
134
140
 
135
- export const formatGradeCSection = (report) => {
141
+ export const formatGradeCSection = (report, options = {}) => {
142
+ const { includePrompt = true } = options;
136
143
  const { violations, hotspots } = report;
137
144
  const { medium } = groupViolationsBySeverity(violations);
138
145
  const warningMonoliths = hotspots.filter((h) => h.lineCount >= 500 && h.lineCount < 1000);
@@ -169,16 +176,19 @@ export const formatGradeCSection = (report) => {
169
176
  }
170
177
  }
171
178
 
172
- const promptC = buildGradeCPrompt(report);
173
- if (promptC) {
174
- lines.push(formatPromptBox('🤖 AI AGENT REFACTORING PROMPT (GRADE C DEBTS)', promptC));
179
+ if (includePrompt) {
180
+ const promptC = buildGradeCPrompt(report);
181
+ if (promptC) {
182
+ lines.push(formatPromptBox('🤖 AI AGENT REFACTORING PROMPT (GRADE C DEBTS)', promptC));
183
+ }
175
184
  }
176
185
 
177
186
  lines.push(`${CYAN}======================================================================${RESET}\n`);
178
187
  return lines.join('\n');
179
188
  };
180
189
 
181
- export const formatGradeBSection = (report) => {
190
+ export const formatGradeBSection = (report, options = {}) => {
191
+ const { includePrompt = true } = options;
182
192
  const { violations } = report;
183
193
  const { low } = groupViolationsBySeverity(violations);
184
194
  const lines = [];
@@ -202,9 +212,11 @@ export const formatGradeBSection = (report) => {
202
212
  lines.push(renderGroupedViolationsTerminal(low));
203
213
  }
204
214
 
205
- const promptB = buildGradeBPrompt(report);
206
- if (promptB) {
207
- lines.push(formatPromptBox('🤖 AI AGENT REFACTORING PROMPT (GRADE B HYGIENE)', promptB));
215
+ if (includePrompt) {
216
+ const promptB = buildGradeBPrompt(report);
217
+ if (promptB) {
218
+ lines.push(formatPromptBox('🤖 AI AGENT REFACTORING PROMPT (GRADE B HYGIENE)', promptB));
219
+ }
208
220
  }
209
221
 
210
222
  lines.push(`${YELLOW}======================================================================${RESET}\n`);