@chemx/starter-kit 26.9.9-786 → 26.9.11-481

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 (45) hide show
  1. package/cli/audit/ast-visitors.js +243 -0
  2. package/cli/audit/discussion-store.js +39 -0
  3. package/cli/audit/history.d.ts +69 -0
  4. package/cli/audit/history.js +397 -0
  5. package/cli/audit/metrics.js +179 -0
  6. package/cli/audit/prompts.js +174 -0
  7. package/cli/audit/reporter-ascii.d.ts +4 -0
  8. package/cli/audit/reporter-ascii.js +99 -0
  9. package/cli/audit/reporter-grades.js +275 -0
  10. package/cli/audit/reporter-markdown.js +137 -0
  11. package/cli/audit/reporter-utils.js +125 -0
  12. package/cli/audit/reporter.js +497 -0
  13. package/cli/audit/rules-helpers.js +109 -0
  14. package/cli/audit/rules-registry.js +97 -0
  15. package/cli/audit/rules.js +123 -0
  16. package/cli/audit/social-constants.js +4 -0
  17. package/cli/audit/social-gh.js +126 -0
  18. package/cli/audit/social-git.js +214 -0
  19. package/cli/audit/social-http.js +222 -0
  20. package/cli/audit/social-publisher.js +193 -0
  21. package/cli/audit/social.d.ts +69 -0
  22. package/cli/audit/social.js +303 -0
  23. package/cli/audit/types.d.ts +140 -0
  24. package/cli/audit.d.ts +3 -24
  25. package/cli/audit.js +207 -210
  26. package/cli/help.d.ts +1 -0
  27. package/cli/help.js +136 -0
  28. package/cli/index.js +128 -566
  29. package/cli/installer-templates.js +91 -0
  30. package/cli/installer.d.ts +21 -0
  31. package/cli/installer.js +104 -0
  32. package/cli/license.js +320 -0
  33. package/cli/navigator-actions.js +274 -0
  34. package/cli/navigator-banner.js +98 -0
  35. package/cli/navigator-conversion.js +100 -0
  36. package/cli/navigator-grades.js +87 -0
  37. package/cli/navigator-menu.js +127 -0
  38. package/cli/navigator-paged.js +86 -0
  39. package/cli/navigator-share.js +129 -0
  40. package/cli/navigator.d.ts +43 -0
  41. package/cli/navigator.js +126 -0
  42. package/cli/scaffold.js +148 -0
  43. package/cli/terminal.js +126 -0
  44. package/docs/CHANGELOG.md +101 -0
  45. package/package.json +11 -10
@@ -0,0 +1,243 @@
1
+ import * as t from '@babel/types';
2
+ import { RULE_REGISTRY } from './rules-registry.js';
3
+ import { countLogicalOperators } from './rules-helpers.js';
4
+
5
+ export const createAstVisitors = ({ relativePath, violations }) => {
6
+ return {
7
+ 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
+ );
12
+
13
+ // Pillar 3: Hook Saturation
14
+ let hookCount = 0;
15
+ astPath.traverse({
16
+ CallExpression(callPath) {
17
+ const callee = callPath.node.callee;
18
+ if (t.isIdentifier(callee) && /^use[A-Z0-9]/.test(callee.name)) {
19
+ if (callPath.getFunctionParent() === astPath) {
20
+ hookCount += 1;
21
+ }
22
+ }
23
+ }
24
+ });
25
+
26
+ if (hookCount > 5) {
27
+ const line = astPath.node.loc?.start.line || 1;
28
+ const meta = RULE_REGISTRY.HOOK_SATURATION;
29
+ violations.push({
30
+ filePath: relativePath,
31
+ line,
32
+ column: astPath.node.loc?.start.column || 1,
33
+ hazard: `Hook saturation detected (${hookCount} hooks > 5 limit)`,
34
+ rule: 'HOOK_SATURATION',
35
+ severity: meta.severity,
36
+ pillar: meta.pillar,
37
+ directive: meta.directive
38
+ });
39
+ }
40
+
41
+ // Pillar 3: Hook Return Overload (3 to 5 limit)
42
+ if (isCustomHook) {
43
+ astPath.traverse({
44
+ ReturnStatement(retPath) {
45
+ if (retPath.getFunctionParent() === astPath && t.isObjectExpression(retPath.node.argument)) {
46
+ const propCount = retPath.node.argument.properties.length;
47
+ if (propCount > 5) {
48
+ const line = retPath.node.loc?.start.line || 1;
49
+ const meta = RULE_REGISTRY.HOOK_RETURN_OVERLOAD;
50
+ violations.push({
51
+ filePath: relativePath,
52
+ line,
53
+ column: retPath.node.loc?.start.column || 1,
54
+ hazard: `Hook return saturation (${propCount} properties > 5 limit)`,
55
+ rule: 'HOOK_RETURN_OVERLOAD',
56
+ severity: meta.severity,
57
+ pillar: meta.pillar,
58
+ directive: meta.directive
59
+ });
60
+ }
61
+ }
62
+ }
63
+ });
64
+ }
65
+ },
66
+
67
+ // Pillar 2: Control Flow Complexity
68
+ JSXExpressionContainer(astPath) {
69
+ const expr = astPath.node.expression;
70
+ if (t.isLogicalExpression(expr) || t.isUnaryExpression(expr)) {
71
+ const opCount = countLogicalOperators(expr);
72
+ if (opCount > 2) {
73
+ const line = expr.loc?.start.line || astPath.node.loc?.start.line || 1;
74
+ const meta = RULE_REGISTRY.CONTROL_FLOW_INLINE_BOOLEAN;
75
+ violations.push({
76
+ filePath: relativePath,
77
+ line,
78
+ column: expr.loc?.start.column || 1,
79
+ hazard: `Inline boolean complexity (${opCount} logical operators > 2 limit)`,
80
+ rule: 'CONTROL_FLOW_INLINE_BOOLEAN',
81
+ severity: meta.severity,
82
+ pillar: meta.pillar,
83
+ directive: meta.directive
84
+ });
85
+ }
86
+ }
87
+ },
88
+
89
+ ConditionalExpression(astPath) {
90
+ if (t.isConditionalExpression(astPath.node.consequent) || t.isConditionalExpression(astPath.node.alternate)) {
91
+ const line = astPath.node.loc?.start.line || 1;
92
+ const meta = RULE_REGISTRY.CONTROL_FLOW_NESTED_TERNARY;
93
+ violations.push({
94
+ filePath: relativePath,
95
+ line,
96
+ column: astPath.node.loc?.start.column || 1,
97
+ hazard: 'Nested ternary operator detected',
98
+ rule: 'CONTROL_FLOW_NESTED_TERNARY',
99
+ severity: meta.severity,
100
+ pillar: meta.pillar,
101
+ directive: meta.directive
102
+ });
103
+ }
104
+ },
105
+
106
+ // Pillar 5: Design System & Inline Styles
107
+ JSXAttribute(astPath) {
108
+ const attrName = astPath.node.name?.name;
109
+ if (attrName === 'style') {
110
+ const value = astPath.node.value;
111
+ if (t.isJSXExpressionContainer(value) && t.isObjectExpression(value.expression)) {
112
+ const line = astPath.node.loc?.start.line || 1;
113
+ const meta = RULE_REGISTRY.RAW_INLINE_STYLE;
114
+ violations.push({
115
+ filePath: relativePath,
116
+ line,
117
+ column: astPath.node.loc?.start.column || 1,
118
+ hazard: 'Raw inline style attribute detected in JSX',
119
+ rule: 'RAW_INLINE_STYLE',
120
+ severity: meta.severity,
121
+ pillar: meta.pillar,
122
+ directive: meta.directive
123
+ });
124
+ }
125
+ } else if (attrName === 'className' || attrName === 'class') {
126
+ const value = astPath.node.value;
127
+ const strVal = t.isStringLiteral(value) ? value.value : '';
128
+ if (strVal && (strVal.includes('fa-') || strVal.includes('fa '))) {
129
+ if (/\btext-(primary|secondary|danger|warning|success|info|light|dark|\w+)\b/.test(strVal)) {
130
+ const line = astPath.node.loc?.start.line || 1;
131
+ const meta = RULE_REGISTRY.ICON_SVG_STYLE_LEAK;
132
+ violations.push({
133
+ filePath: relativePath,
134
+ line,
135
+ column: astPath.node.loc?.start.column || 1,
136
+ hazard: 'FontAwesome icon with text-* class breaks SVG fill',
137
+ rule: 'ICON_SVG_STYLE_LEAK',
138
+ severity: meta.severity,
139
+ pillar: meta.pillar,
140
+ directive: meta.directive
141
+ });
142
+ }
143
+ }
144
+ }
145
+ },
146
+
147
+ // Pillar 6 & Pillar 7: Call Expressions (Timers & Logging)
148
+ CallExpression(astPath) {
149
+ const callee = astPath.node.callee;
150
+
151
+ // Timer Discipline
152
+ if (t.isIdentifier(callee) && (callee.name === 'setInterval' || callee.name === 'setTimeout')) {
153
+ const args = astPath.node.arguments;
154
+ const delayArg = args[1];
155
+
156
+ // Render-hack check: setTimeout(fn, 0)
157
+ if (callee.name === 'setTimeout' && delayArg && t.isNumericLiteral(delayArg) && delayArg.value === 0) {
158
+ const line = astPath.node.loc?.start.line || 1;
159
+ const meta = RULE_REGISTRY.RENDER_HACK_TIMEOUT;
160
+ violations.push({
161
+ filePath: relativePath,
162
+ line,
163
+ column: astPath.node.loc?.start.column || 1,
164
+ hazard: 'Zero-delay render hack setTimeout(..., 0) detected',
165
+ rule: 'RENDER_HACK_TIMEOUT',
166
+ severity: meta.severity,
167
+ pillar: meta.pillar,
168
+ directive: meta.directive
169
+ });
170
+ }
171
+
172
+ const fnParent = astPath.getFunctionParent();
173
+ let hasCleanup = false;
174
+ if (fnParent) {
175
+ fnParent.traverse({
176
+ ReturnStatement(retPath) {
177
+ if (retPath.node.argument) {
178
+ hasCleanup = true;
179
+ }
180
+ }
181
+ });
182
+ }
183
+
184
+ if (!hasCleanup) {
185
+ const line = astPath.node.loc?.start.line || 1;
186
+ const meta = RULE_REGISTRY.TIMER_DISCIPLINE;
187
+ violations.push({
188
+ filePath: relativePath,
189
+ line,
190
+ column: astPath.node.loc?.start.column || 1,
191
+ hazard: `Raw ${callee.name} lacking lifecycle scope disposal`,
192
+ rule: 'TIMER_DISCIPLINE',
193
+ severity: meta.severity,
194
+ pillar: meta.pillar,
195
+ directive: meta.directive
196
+ });
197
+ }
198
+ }
199
+
200
+ // 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
+ ) {
208
+ const line = astPath.node.loc?.start.line || 1;
209
+ const meta = RULE_REGISTRY.UNGUARDED_LOGGING;
210
+ violations.push({
211
+ filePath: relativePath,
212
+ line,
213
+ column: astPath.node.loc?.start.column || 1,
214
+ hazard: `Unguarded console.${callee.property.name} statement`,
215
+ rule: 'UNGUARDED_LOGGING',
216
+ severity: meta.severity,
217
+ pillar: meta.pillar,
218
+ directive: meta.directive
219
+ });
220
+ }
221
+ },
222
+
223
+ // Pillar 4: Type Co-location
224
+ TSTypeLiteral(astPath) {
225
+ if (astPath.node.members.length > 3) {
226
+ if (!astPath.findParent((p) => p.isTSTypeAliasDeclaration() || p.isTSInterfaceDeclaration())) {
227
+ const line = astPath.node.loc?.start.line || 1;
228
+ const meta = RULE_REGISTRY.TYPE_COLOCATION;
229
+ violations.push({
230
+ filePath: relativePath,
231
+ line,
232
+ column: astPath.node.loc?.start.column || 1,
233
+ hazard: `Inlined anonymous complex type (${astPath.node.members.length} members)`,
234
+ rule: 'TYPE_COLOCATION',
235
+ severity: meta.severity,
236
+ pillar: meta.pillar,
237
+ directive: meta.directive
238
+ });
239
+ }
240
+ }
241
+ }
242
+ };
243
+ };
@@ -0,0 +1,39 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { ensureChemxDir } from './history.js';
4
+
5
+ const DISCUSSION_FILE = 'discussion.json';
6
+
7
+ export const getStoredDiscussion = (cwd = process.cwd()) => {
8
+ try {
9
+ const filePath = path.resolve(cwd, '.chemx', DISCUSSION_FILE);
10
+ if (!fs.existsSync(filePath)) return null;
11
+ const content = fs.readFileSync(filePath, 'utf-8');
12
+ return JSON.parse(content);
13
+ } catch {
14
+ return null;
15
+ }
16
+ };
17
+
18
+ export const saveStoredDiscussion = (data, cwd = process.cwd()) => {
19
+ try {
20
+ const dir = ensureChemxDir(cwd);
21
+ const filePath = path.resolve(dir, DISCUSSION_FILE);
22
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ };
28
+
29
+ export const clearStoredDiscussion = (cwd = process.cwd()) => {
30
+ try {
31
+ const filePath = path.resolve(cwd, '.chemx', DISCUSSION_FILE);
32
+ if (fs.existsSync(filePath)) {
33
+ fs.unlinkSync(filePath);
34
+ }
35
+ return true;
36
+ } catch {
37
+ return false;
38
+ }
39
+ };
@@ -0,0 +1,69 @@
1
+ import type { MolecularHealthScore, CodebaseMetrics, PillarStatus, ContextTokenAnalysis, AuditReport } from './types';
2
+
3
+ export interface AuditSnapshot {
4
+ readonly id: string;
5
+ readonly timestamp: string;
6
+ readonly health: MolecularHealthScore;
7
+ readonly metrics: CodebaseMetrics;
8
+ readonly violations: {
9
+ readonly total: number;
10
+ readonly critical: number;
11
+ readonly high: number;
12
+ readonly medium: number;
13
+ readonly low: number;
14
+ };
15
+ readonly monoliths: {
16
+ readonly total: number;
17
+ readonly extreme: number;
18
+ readonly severe: number;
19
+ readonly warning: number;
20
+ };
21
+ readonly tokens: ContextTokenAnalysis;
22
+ readonly pillars: Record<string, {
23
+ readonly status: PillarStatus;
24
+ readonly violations: number;
25
+ readonly critical: number;
26
+ }>;
27
+ }
28
+
29
+ export interface PillarDelta {
30
+ readonly beforeStatus: PillarStatus;
31
+ readonly afterStatus: PillarStatus;
32
+ readonly beforeViolations: number;
33
+ readonly afterViolations: number;
34
+ readonly improved: boolean;
35
+ }
36
+
37
+ export interface TransformationDelta {
38
+ readonly scoreDelta: number;
39
+ readonly critDelta: number;
40
+ readonly totalDelta: number;
41
+ readonly monolithDelta: number;
42
+ readonly tokensDelta: number;
43
+ readonly costPassBefore?: number;
44
+ readonly costPassAfter?: number;
45
+ readonly costPassDelta?: number;
46
+ readonly monthlyTaxBefore?: number;
47
+ readonly monthlyTaxAfter?: number;
48
+ readonly monthlyTaxDelta?: number;
49
+ readonly pillarDeltas: Record<string, PillarDelta>;
50
+ readonly isImproved: boolean;
51
+ }
52
+
53
+ export interface SaveSnapshotResult {
54
+ readonly snapshot: AuditSnapshot;
55
+ readonly history: readonly AuditSnapshot[];
56
+ readonly baseline: AuditSnapshot | null;
57
+ readonly isNewBaseline: boolean;
58
+ readonly totalAudits: number;
59
+ }
60
+
61
+ export declare function ensureChemxDir(cwd?: string): string;
62
+ export declare function createSnapshotFromReport(report: AuditReport): AuditSnapshot;
63
+ export declare function getAuditHistory(cwd?: string): AuditSnapshot[];
64
+ export declare function getAuditBaseline(cwd?: string): AuditSnapshot | null;
65
+ export declare function setAuditBaseline(snapshot: AuditSnapshot, cwd?: string): AuditSnapshot;
66
+ export declare function saveAuditSnapshot(report: AuditReport, cwd?: string): SaveSnapshotResult;
67
+ export declare function calculateTransformationDelta(beforeSnapshot: AuditSnapshot, afterSnapshot: AuditSnapshot): TransformationDelta;
68
+ export declare function formatTransformationTerminal(beforeSnapshot: AuditSnapshot, afterSnapshot: AuditSnapshot): string;
69
+ export declare function formatHistoryTimelineTerminal(history: readonly AuditSnapshot[]): string;