@chemx/starter-kit 26.9.17-309 → 26.9.17-606
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/README.md +63 -0
- package/blueprints/molecule-capsule/m-tab-button/m-tab-button.controller.ts +8 -11
- package/cli/audit/ast-visitors.js +73 -0
- package/cli/audit/extended-visitors.js +239 -0
- package/cli/audit/reporter-utils.js +13 -22
- package/cli/audit/rules-registry.js +25 -0
- package/cli/audit/rules.spec.js +205 -0
- package/cli/build.d.ts +2 -1
- package/cli/build.js +8 -5
- package/cli/columnar.d.ts +14 -0
- package/cli/columnar.js +52 -0
- package/cli/columnar.spec.js +49 -0
- package/cli/help.js +29 -4
- package/cli/index.js +51 -7
- package/cli/installer.js +23 -6
- package/cli/mcp/antigravity.js +59 -0
- package/cli/mcp/index.js +2 -1
- package/cli/mcp/installer.js +26 -4
- package/cli/mcp/installer.spec.js +23 -1
- package/cli/mcp/manifests.js +251 -0
- package/cli/mcp/prompts.js +28 -0
- package/cli/mcp/resources.js +94 -90
- package/cli/mcp/server.js +13 -0
- package/cli/mcp/server.spec.js +213 -1
- package/cli/mcp/tools.js +252 -189
- package/cli/mcp/types.d.ts +37 -0
- package/cli/navigator-actions.js +23 -10
- package/cli/navigator-banner.js +2 -3
- package/cli/navigator-guide.js +73 -0
- package/cli/navigator-menu.js +20 -0
- package/cli/navigator.js +1 -0
- package/cli/patcher.d.ts +73 -0
- package/cli/patcher.js +249 -4
- package/cli/patcher.spec.js +148 -0
- package/cli/scaffold.js +7 -1
- package/cli/search-commands.js +10 -8
- package/cli/search-queries.js +8 -3
- package/cli/search-schema.js +1 -0
- package/cli/search.d.ts +16 -0
- package/cli/search.js +103 -11
- package/cli/silence-warnings.js +17 -0
- package/cli/verify.js +377 -0
- package/cli/verify.spec.js +98 -0
- package/package.json +3 -1
- package/cli/mcp.js +0 -326
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { auditCode } from "./rules.js";
|
|
4
|
+
|
|
5
|
+
test("Audit Rules: flags repetitive dispatch switch statement as CONTROL_FLOW_DISPATCH_SWITCH", () => {
|
|
6
|
+
const code = `
|
|
7
|
+
export const resolveColor = (type) => {
|
|
8
|
+
switch (type) {
|
|
9
|
+
case "primary":
|
|
10
|
+
return "blue";
|
|
11
|
+
case "secondary":
|
|
12
|
+
return "gray";
|
|
13
|
+
case "danger":
|
|
14
|
+
return "red";
|
|
15
|
+
case "warning":
|
|
16
|
+
return "yellow";
|
|
17
|
+
default:
|
|
18
|
+
return "white";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const violations = auditCode(code, "src/utils/colors.js", "src/utils/colors.js");
|
|
24
|
+
const dispatchViolation = violations.find((v) => v.rule === "CONTROL_FLOW_DISPATCH_SWITCH");
|
|
25
|
+
assert.ok(dispatchViolation, "Expected CONTROL_FLOW_DISPATCH_SWITCH violation");
|
|
26
|
+
assert.strictEqual(dispatchViolation.severity, "MEDIUM");
|
|
27
|
+
assert.ok(dispatchViolation.hazard.includes("Dispatch switch smell detected"));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("Audit Rules: does NOT flag non-dispatch complex switch statements", () => {
|
|
31
|
+
const code = `
|
|
32
|
+
export const processOrder = (status, order) => {
|
|
33
|
+
switch (status) {
|
|
34
|
+
case "pending": {
|
|
35
|
+
validatePayment(order);
|
|
36
|
+
sendNotification(order.userId);
|
|
37
|
+
return queueForFulfillment(order);
|
|
38
|
+
}
|
|
39
|
+
case "completed": {
|
|
40
|
+
archiveOrder(order);
|
|
41
|
+
emitAnalytics("order_completed", order);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
default:
|
|
45
|
+
throw new Error("Invalid status");
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
const violations = auditCode(code, "src/order.js", "src/order.js");
|
|
51
|
+
const dispatchViolation = violations.find((v) => v.rule === "CONTROL_FLOW_DISPATCH_SWITCH");
|
|
52
|
+
assert.strictEqual(dispatchViolation, undefined, "Complex switch should not be flagged as dispatch smell");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("Audit Rules: clean keyed map implementation produces 0 violations", () => {
|
|
56
|
+
const code = `
|
|
57
|
+
const COLOR_MAP = {
|
|
58
|
+
primary: "blue",
|
|
59
|
+
secondary: "gray",
|
|
60
|
+
danger: "red",
|
|
61
|
+
warning: "yellow"
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const resolveColor = (type) => {
|
|
65
|
+
return Object.prototype.hasOwnProperty.call(COLOR_MAP, type) ? COLOR_MAP[type] : "white";
|
|
66
|
+
};
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
const violations = auditCode(code, "src/utils/colors.js", "src/utils/colors.js");
|
|
70
|
+
const dispatchViolation = violations.find((v) => v.rule === "CONTROL_FLOW_DISPATCH_SWITCH");
|
|
71
|
+
assert.strictEqual(dispatchViolation, undefined);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("Audit Security: flags external link target=_blank missing rel=noopener noreferrer in JSX", () => {
|
|
75
|
+
const code = `
|
|
76
|
+
export const ExternalLink = () => (
|
|
77
|
+
<a href="https://example.com" target="_blank">External</a>
|
|
78
|
+
);
|
|
79
|
+
`;
|
|
80
|
+
const violations = auditCode(code, "src/Link.tsx", "src/Link.tsx");
|
|
81
|
+
const violation = violations.find((v) => v.rule === "SECURITY_REVERSE_TABNABBING");
|
|
82
|
+
assert.ok(violation, "Expected SECURITY_REVERSE_TABNABBING violation in JSX");
|
|
83
|
+
assert.strictEqual(violation.severity, "MEDIUM");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("Audit Security: passes external link with rel=noopener noreferrer in JSX", () => {
|
|
87
|
+
const code = `
|
|
88
|
+
export const ExternalLink = () => (
|
|
89
|
+
<a href="https://example.com" target="_blank" rel="noopener noreferrer">External</a>
|
|
90
|
+
);
|
|
91
|
+
`;
|
|
92
|
+
const violations = auditCode(code, "src/Link.tsx", "src/Link.tsx");
|
|
93
|
+
const violation = violations.find((v) => v.rule === "SECURITY_REVERSE_TABNABBING");
|
|
94
|
+
assert.strictEqual(violation, undefined);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("Audit Security: flags target=_blank missing rel in Vue template", () => {
|
|
98
|
+
const code = `
|
|
99
|
+
<template>
|
|
100
|
+
<a href="https://example.com" target="_blank">External</a>
|
|
101
|
+
</template>
|
|
102
|
+
`;
|
|
103
|
+
const violations = auditCode(code, "src/Link.vue", "src/Link.vue");
|
|
104
|
+
const violation = violations.find((v) => v.rule === "SECURITY_REVERSE_TABNABBING");
|
|
105
|
+
assert.ok(violation, "Expected SECURITY_REVERSE_TABNABBING in Vue template");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("Audit Security: flags javascript: pseudo-protocol in JSX href", () => {
|
|
109
|
+
const code = `
|
|
110
|
+
export const BadLink = () => (
|
|
111
|
+
<a href="javascript:alert(1)">Click Me</a>
|
|
112
|
+
);
|
|
113
|
+
`;
|
|
114
|
+
const violations = auditCode(code, "src/BadLink.tsx", "src/BadLink.tsx");
|
|
115
|
+
const violation = violations.find((v) => v.rule === "SECURITY_JAVASCRIPT_URL");
|
|
116
|
+
assert.ok(violation, "Expected SECURITY_JAVASCRIPT_URL violation");
|
|
117
|
+
assert.strictEqual(violation.severity, "CRITICAL");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("Audit Security: flags javascript: in template attributes", () => {
|
|
121
|
+
const code = `
|
|
122
|
+
<template>
|
|
123
|
+
<form action="javascript:void(0)">
|
|
124
|
+
<button type="submit">Submit</button>
|
|
125
|
+
</form>
|
|
126
|
+
</template>
|
|
127
|
+
`;
|
|
128
|
+
const violations = auditCode(code, "src/Form.vue", "src/Form.vue");
|
|
129
|
+
const violation = violations.find((v) => v.rule === "SECURITY_JAVASCRIPT_URL");
|
|
130
|
+
assert.ok(violation, "Expected SECURITY_JAVASCRIPT_URL in template");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("Audit Security: flags dynamic code execution via eval()", () => {
|
|
134
|
+
const code = `
|
|
135
|
+
export const runDynamic = (str: string) => {
|
|
136
|
+
return eval(str);
|
|
137
|
+
};
|
|
138
|
+
`;
|
|
139
|
+
const violations = auditCode(code, "src/eval.ts", "src/eval.ts");
|
|
140
|
+
const violation = violations.find((v) => v.rule === "SECURITY_DYNAMIC_CODE_EXECUTION");
|
|
141
|
+
assert.ok(violation, "Expected SECURITY_DYNAMIC_CODE_EXECUTION for eval()");
|
|
142
|
+
assert.strictEqual(violation.severity, "CRITICAL");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("Audit Security: flags dynamic code execution via new Function()", () => {
|
|
146
|
+
const code = `
|
|
147
|
+
export const buildFn = (code: string) => {
|
|
148
|
+
return new Function("return " + code);
|
|
149
|
+
};
|
|
150
|
+
`;
|
|
151
|
+
const violations = auditCode(code, "src/fn.ts", "src/fn.ts");
|
|
152
|
+
const violation = violations.find((v) => v.rule === "SECURITY_DYNAMIC_CODE_EXECUTION");
|
|
153
|
+
assert.ok(violation, "Expected SECURITY_DYNAMIC_CODE_EXECUTION for new Function()");
|
|
154
|
+
assert.strictEqual(violation.severity, "CRITICAL");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("Audit Security: flags string-based execution in setTimeout", () => {
|
|
158
|
+
const code = `
|
|
159
|
+
export const scheduleCode = () => {
|
|
160
|
+
setTimeout("alert('hello')", 1000);
|
|
161
|
+
};
|
|
162
|
+
`;
|
|
163
|
+
const violations = auditCode(code, "src/timer.ts", "src/timer.ts");
|
|
164
|
+
const violation = violations.find((v) => v.rule === "SECURITY_DYNAMIC_CODE_EXECUTION");
|
|
165
|
+
assert.ok(violation, "Expected SECURITY_DYNAMIC_CODE_EXECUTION for string setTimeout");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("Audit Security: does NOT flag function callback setTimeout", () => {
|
|
169
|
+
const code = `
|
|
170
|
+
export const scheduleCode = () => {
|
|
171
|
+
const timer = setTimeout(() => {
|
|
172
|
+
// legitimate callback
|
|
173
|
+
}, 1000);
|
|
174
|
+
return () => clearTimeout(timer);
|
|
175
|
+
};
|
|
176
|
+
`;
|
|
177
|
+
const violations = auditCode(code, "src/timer.ts", "src/timer.ts");
|
|
178
|
+
const violation = violations.find((v) => v.rule === "SECURITY_DYNAMIC_CODE_EXECUTION");
|
|
179
|
+
assert.strictEqual(violation, undefined);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("Audit Security: flags sensitive variable logging in console", () => {
|
|
183
|
+
const code = `
|
|
184
|
+
export const debugAuth = (authToken: string, user: any) => {
|
|
185
|
+
console.log("Authenticated with token:", authToken);
|
|
186
|
+
console.info("User details:", { password: user.password });
|
|
187
|
+
};
|
|
188
|
+
`;
|
|
189
|
+
const violations = auditCode(code, "src/auth.ts", "src/auth.ts");
|
|
190
|
+
const loggingViolations = violations.filter((v) => v.rule === "SECURITY_SENSITIVE_LOGGING");
|
|
191
|
+
assert.strictEqual(loggingViolations.length >= 1, true, "Expected SECURITY_SENSITIVE_LOGGING violation");
|
|
192
|
+
assert.strictEqual(loggingViolations[0].severity, "HIGH");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("Audit Security: does NOT flag normal non-sensitive console logging", () => {
|
|
196
|
+
const code = `
|
|
197
|
+
export const debugCount = (itemCount: number) => {
|
|
198
|
+
console.log("Items rendered:", itemCount);
|
|
199
|
+
};
|
|
200
|
+
`;
|
|
201
|
+
const violations = auditCode(code, "src/items.ts", "src/items.ts");
|
|
202
|
+
const violation = violations.find((v) => v.rule === "SECURITY_SENSITIVE_LOGGING");
|
|
203
|
+
assert.strictEqual(violation, undefined);
|
|
204
|
+
});
|
|
205
|
+
|
package/cli/build.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import type { BuildAuditReport, BuildOptions } from './build/types.js';
|
|
|
2
2
|
|
|
3
3
|
export function runBuildAudit(
|
|
4
4
|
rawArgs?: readonly string[],
|
|
5
|
-
isCli?: boolean
|
|
5
|
+
isCli?: boolean,
|
|
6
|
+
options?: { readonly print?: boolean; readonly silent?: boolean }
|
|
6
7
|
): Promise<BuildAuditReport>;
|
|
7
8
|
|
|
8
9
|
export {
|
package/cli/build.js
CHANGED
|
@@ -20,16 +20,17 @@ const parseCommandFromArgs = (args) => {
|
|
|
20
20
|
return candidate ? candidate.trim() : null;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export const runBuildAudit = async (rawArgs = [], isCli = false) => {
|
|
23
|
+
export const runBuildAudit = async (rawArgs = [], isCli = false, options = {}) => {
|
|
24
24
|
const isJson = rawArgs.includes('--json');
|
|
25
|
-
const isSilent = rawArgs.includes('--silent');
|
|
25
|
+
const isSilent = rawArgs.includes('--silent') || options.silent === true;
|
|
26
26
|
const isRaw = rawArgs.includes('--raw');
|
|
27
27
|
const isSummary = rawArgs.includes('--summary');
|
|
28
|
+
const shouldPrint = options.print !== false;
|
|
28
29
|
|
|
29
30
|
const customCommand = parseCommandFromArgs(rawArgs);
|
|
30
31
|
const command = detectProjectBuildCommand(customCommand, process.cwd());
|
|
31
32
|
|
|
32
|
-
const shouldPrintStart = !isJson && !isSilent && !isRaw;
|
|
33
|
+
const shouldPrintStart = shouldPrint && !isJson && !isSilent && !isRaw;
|
|
33
34
|
if (shouldPrintStart) {
|
|
34
35
|
process.stdout.write(`${ANSI.CYAN}Auditing build:${ANSI.RESET} ${ANSI.DIM}${command}${ANSI.RESET}\n`);
|
|
35
36
|
}
|
|
@@ -39,13 +40,15 @@ export const runBuildAudit = async (rawArgs = [], isCli = false) => {
|
|
|
39
40
|
const report = groupBuildDiagnostics(rawDiagnostics, executionResult);
|
|
40
41
|
|
|
41
42
|
if (isJson) {
|
|
42
|
-
|
|
43
|
+
if (shouldPrint) {
|
|
44
|
+
process.stdout.write(formatJsonBuildReport(report) + '\n');
|
|
45
|
+
}
|
|
43
46
|
if (isCli) process.exit(report.exitCode);
|
|
44
47
|
return report;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
const terminalOutput = formatTerminalBuildReport(report, { silent: isSilent, summary: isSummary });
|
|
48
|
-
if (terminalOutput) {
|
|
51
|
+
if (terminalOutput && shouldPrint) {
|
|
49
52
|
process.stdout.write(terminalOutput);
|
|
50
53
|
}
|
|
51
54
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ColumnarData<T = any> {
|
|
2
|
+
readonly cols: readonly string[];
|
|
3
|
+
readonly rows: readonly T[][];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export declare function toColumnar<T extends Record<string, any>>(
|
|
7
|
+
items?: readonly T[],
|
|
8
|
+
cols?: readonly string[],
|
|
9
|
+
accessors?: Record<string, (item: T) => any>
|
|
10
|
+
): ColumnarData;
|
|
11
|
+
|
|
12
|
+
export declare function fromColumnar<T extends Record<string, any>>(
|
|
13
|
+
columnar?: ColumnarData
|
|
14
|
+
): readonly T[];
|
package/cli/columnar.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chemical X Protocol: Columnar Serialization Utility
|
|
3
|
+
* Converts object collections into compact single-schema matrices
|
|
4
|
+
* to eliminate repeated dictionary keys and slash token consumption.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Transforms an array of objects into a single-schema columnar structure.
|
|
9
|
+
*
|
|
10
|
+
* @param {readonly object[]} items Array of record objects.
|
|
11
|
+
* @param {readonly string[]} cols Column names to extract.
|
|
12
|
+
* @param {Record<string, (item: any) => any>} [accessors={}] Optional field transformers.
|
|
13
|
+
* @returns {{ cols: readonly string[], rows: readonly any[][] }}
|
|
14
|
+
*/
|
|
15
|
+
export const toColumnar = (items = [], cols = [], accessors = {}) => {
|
|
16
|
+
if (!items || items.length === 0) {
|
|
17
|
+
return { cols, rows: [] };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const rows = items.map((item) => {
|
|
21
|
+
return cols.map((col) => {
|
|
22
|
+
const accessor = accessors[col];
|
|
23
|
+
if (typeof accessor === 'function') {
|
|
24
|
+
return accessor(item);
|
|
25
|
+
}
|
|
26
|
+
return item[col];
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return { cols, rows };
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Reconstructs an array of objects from a columnar structure.
|
|
35
|
+
*
|
|
36
|
+
* @param {{ cols: readonly string[], rows: readonly any[][] }} columnar
|
|
37
|
+
* @returns {readonly Record<string, any>[]}
|
|
38
|
+
*/
|
|
39
|
+
export const fromColumnar = (columnar) => {
|
|
40
|
+
if (!columnar || !columnar.cols || !columnar.rows) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { cols, rows } = columnar;
|
|
45
|
+
return rows.map((row) => {
|
|
46
|
+
const obj = {};
|
|
47
|
+
cols.forEach((col, idx) => {
|
|
48
|
+
obj[col] = row[idx];
|
|
49
|
+
});
|
|
50
|
+
return obj;
|
|
51
|
+
});
|
|
52
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { toColumnar, fromColumnar } from './columnar.js';
|
|
4
|
+
|
|
5
|
+
test('toColumnar: converts array of objects into cols and rows', () => {
|
|
6
|
+
const items = [
|
|
7
|
+
{ file: 'src/a.ts', tier: 'atom', lines: 40 },
|
|
8
|
+
{ file: 'src/b.ts', tier: 'molecule', lines: 75 }
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const result = toColumnar(items, ['file', 'tier', 'lines']);
|
|
12
|
+
|
|
13
|
+
assert.deepStrictEqual(result.cols, ['file', 'tier', 'lines']);
|
|
14
|
+
assert.strictEqual(result.rows.length, 2);
|
|
15
|
+
assert.deepStrictEqual(result.rows[0], ['src/a.ts', 'atom', 40]);
|
|
16
|
+
assert.deepStrictEqual(result.rows[1], ['src/b.ts', 'molecule', 75]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('toColumnar: handles empty or missing arrays cleanly', () => {
|
|
20
|
+
assert.deepStrictEqual(toColumnar([], ['file', 'tier']), { cols: ['file', 'tier'], rows: [] });
|
|
21
|
+
assert.deepStrictEqual(toColumnar(null, ['file']), { cols: ['file'], rows: [] });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('toColumnar: supports custom accessor functions for nested fields', () => {
|
|
25
|
+
const items = [
|
|
26
|
+
{ file: 'src/card.vue', symbols: [{ name: 'UserCard' }, { name: 'CardBadge' }] }
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const result = toColumnar(items, ['file', 'symbols'], {
|
|
30
|
+
symbols: (item) => item.symbols.map((s) => s.name)
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
assert.deepStrictEqual(result.rows[0], ['src/card.vue', ['UserCard', 'CardBadge']]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('fromColumnar: reconstructs array of objects from columnar data', () => {
|
|
37
|
+
const columnar = {
|
|
38
|
+
cols: ['file', 'tier', 'lines'],
|
|
39
|
+
rows: [
|
|
40
|
+
['src/a.ts', 'atom', 40],
|
|
41
|
+
['src/b.ts', 'molecule', 75]
|
|
42
|
+
]
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const items = fromColumnar(columnar);
|
|
46
|
+
assert.strictEqual(items.length, 2);
|
|
47
|
+
assert.deepStrictEqual(items[0], { file: 'src/a.ts', tier: 'atom', lines: 40 });
|
|
48
|
+
assert.deepStrictEqual(items[1], { file: 'src/b.ts', tier: 'molecule', lines: 75 });
|
|
49
|
+
});
|
package/cli/help.js
CHANGED
|
@@ -51,6 +51,25 @@ export const printHelp = () => {
|
|
|
51
51
|
` Flags: ${CYAN}--json${RESET}, ${CYAN}--silent${RESET}, ${CYAN}--raw${RESET}, ${CYAN}--summary${RESET}`,
|
|
52
52
|
` Examples: ${CYAN}npx chemx build${RESET} or ${CYAN}npx chemx build -- pnpm build${RESET}`,
|
|
53
53
|
'',
|
|
54
|
+
` ${CYAN}verify${RESET} [options] ${DIM}(alias: check:all)${RESET}`,
|
|
55
|
+
` Zero-token-burn project verification pipeline (AST Audit + Typecheck + Tests).`,
|
|
56
|
+
` Runs all verification layers silently and emits a single summary card (~45 tokens if green).`,
|
|
57
|
+
` AI agents should always run this instead of separate raw shell commands.`,
|
|
58
|
+
` Flags: ${CYAN}--dir=<path>${RESET}, ${CYAN}--build${RESET}, ${CYAN}--json${RESET}`,
|
|
59
|
+
` Examples: ${CYAN}npx chemx verify${RESET} or ${CYAN}npx chemx verify --json${RESET}`,
|
|
60
|
+
'',
|
|
61
|
+
` ${CYAN}typecheck${RESET} [options] [-- <command>] ${DIM}(aliases: check:types, tsc)${RESET}`,
|
|
62
|
+
` Silent TypeScript typecheck audit. Drops compiler noise and returns structured`,
|
|
63
|
+
` file, line, code, and suggestion metadata only if errors exist. Zero noise when clean.`,
|
|
64
|
+
` Flags: ${CYAN}--json${RESET}, ${CYAN}--raw${RESET}`,
|
|
65
|
+
` Examples: ${CYAN}npx chemx typecheck${RESET} or ${CYAN}npx chemx typecheck --json${RESET}`,
|
|
66
|
+
'',
|
|
67
|
+
` ${CYAN}test${RESET} [options] [-- <command>] ${DIM}(aliases: tests, check:test)${RESET}`,
|
|
68
|
+
` Silent, failure-only project test runner. Suppresses hundreds of passing checkmarks`,
|
|
69
|
+
` and runtime banners; emits ONLY failing test assertions and stack diffs.`,
|
|
70
|
+
` Flags: ${CYAN}--json${RESET}, ${CYAN}--raw${RESET}`,
|
|
71
|
+
` Examples: ${CYAN}npx chemx test${RESET} or ${CYAN}npx chemx test --json${RESET}`,
|
|
72
|
+
'',
|
|
54
73
|
` ${CYAN}audit${RESET} [options]`,
|
|
55
74
|
` Run the 7-Pillar Molecular Architecture static analysis audit.`,
|
|
56
75
|
` Scans AST for line budgets, control flow booleans, nested ternaries,`,
|
|
@@ -97,16 +116,22 @@ export const printHelp = () => {
|
|
|
97
116
|
` ${CYAN}mcp${RESET} ${DIM}(aliases: mcp-server)${RESET}`,
|
|
98
117
|
` ${GREEN}[FREE]${RESET} Launch the Chemical X Model Context Protocol (MCP) server.`,
|
|
99
118
|
` Communicates via JSON-RPC 2.0 stdio with Cursor, Claude Desktop, Windsurf, and Antigravity.`,
|
|
100
|
-
` Exposes
|
|
101
|
-
`
|
|
102
|
-
`
|
|
119
|
+
` Exposes 10 AST-aware tools:`,
|
|
120
|
+
` • Discovery: chemx_query_patterns, chemx_q`,
|
|
121
|
+
` • Reading & Patching: chemx_read, chemx_patch`,
|
|
122
|
+
` • Verification: chemx_verify, chemx_typecheck, chemx_test, chemx_audit, chemx_check, chemx_audit_build`,
|
|
123
|
+
` • Remediation: chemx_autofix, chemx_generate_capsule, chemx_get_refactor_prompt`,
|
|
124
|
+
` Plus living resources (chemx://directives, chemx://scorecard) and prompts.`,
|
|
103
125
|
` Example: ${CYAN}npx chemx mcp${RESET}`,
|
|
104
126
|
'',
|
|
105
127
|
` ${CYAN}install-mcp${RESET} [directory] ${DIM}(aliases: setup-mcp)${RESET}`,
|
|
106
|
-
` ${GREEN}[FREE]${RESET} Auto-configure Chemical X MCP server for your project and
|
|
128
|
+
` ${GREEN}[FREE]${RESET} Auto-configure Chemical X MCP server for your project and editors.`,
|
|
107
129
|
` Registers in .cursor/mcp.json, .vscode/mcp.json, and ~/.gemini/config/mcp_config.json.`,
|
|
108
130
|
` Example: ${CYAN}npx chemx install-mcp${RESET}`,
|
|
109
131
|
'',
|
|
132
|
+
` ${CYAN}--version${RESET}, ${CYAN}-v${RESET}, ${CYAN}version${RESET}`,
|
|
133
|
+
` Display current Chemical X package version string.`,
|
|
134
|
+
'',
|
|
110
135
|
` ${CYAN}help${RESET}, ${CYAN}--help${RESET}, ${CYAN}-h${RESET}`,
|
|
111
136
|
` Display this comprehensive CLI documentation and options guide.`,
|
|
112
137
|
'',
|
package/cli/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import './silence-warnings.js';
|
|
3
4
|
import fs from 'node:fs';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import {
|
|
@@ -37,12 +38,24 @@ import { runBuildAudit } from './build.js';
|
|
|
37
38
|
import { runSearch, syncSearchIndex, resolveTargetDir, syncViolationsIndex, recordAuditSnapshot, handleCheckCommand } from './search.js';
|
|
38
39
|
import { runMutatorCli } from './mutators.js';
|
|
39
40
|
import { runReaderCli, readTokenOptimized } from './reader.js';
|
|
40
|
-
import { runPatcherCli, patchFile } from './patcher.js';
|
|
41
|
+
import { runPatcherCli, patchFile, runWriterCli, writeFile } from './patcher.js';
|
|
41
42
|
import { runMcpServer, runMcpInstaller } from './mcp/index.js';
|
|
43
|
+
import { runTypecheckAudit, runTestAudit, runProjectVerify } from './verify.js';
|
|
42
44
|
import { sanitizeOutputStreams } from './terminal.js';
|
|
43
45
|
|
|
44
46
|
sanitizeOutputStreams();
|
|
45
47
|
|
|
48
|
+
const getPackageVersion = () => {
|
|
49
|
+
try {
|
|
50
|
+
const pkgPath = new URL('../package.json', import.meta.url);
|
|
51
|
+
const pkgContent = fs.readFileSync(pkgPath, 'utf-8');
|
|
52
|
+
const pkg = JSON.parse(pkgContent);
|
|
53
|
+
return pkg.version || 'unknown';
|
|
54
|
+
} catch {
|
|
55
|
+
return 'unknown';
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
46
59
|
const rawArgs = process.argv.slice(2);
|
|
47
60
|
const invokedBin = path.basename(process.argv[1] || '');
|
|
48
61
|
const CAPSULE_PREFIXES = ['m-', 'a-', 'o-', 't-', 'use-', 'v-'];
|
|
@@ -131,11 +144,19 @@ export const runAudit = async (customDir = null, isCli = false) => {
|
|
|
131
144
|
recordAuditSnapshot(syncRes.db, report);
|
|
132
145
|
}
|
|
133
146
|
|
|
134
|
-
const
|
|
135
|
-
|
|
147
|
+
const isSevereViolation = (v) => {
|
|
148
|
+
const isCritical = v.severity === 'CRITICAL';
|
|
149
|
+
const isHigh = v.severity === 'HIGH';
|
|
150
|
+
return isCritical || isHigh;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const hasCriticalOrHigh = report.violations.some(isSevereViolation);
|
|
154
|
+
const hasViolations = report.violations.length > 0;
|
|
155
|
+
const isStrictFail = isStrict && hasViolations;
|
|
136
156
|
const isGradeFail = isGradeBelowMinimum(report.health.grade, minGrade);
|
|
137
|
-
const
|
|
138
|
-
const
|
|
157
|
+
const hasMinScore = minScore !== null && !isNaN(minScore);
|
|
158
|
+
const isScoreFail = hasMinScore && report.health.score < minScore;
|
|
159
|
+
const hasThreshold = Boolean(minGrade) || hasMinScore;
|
|
139
160
|
const isDefaultFail = !hasThreshold && !isStrict && hasCriticalOrHigh;
|
|
140
161
|
const hasFailingViolations = evaluateAuditFailure([isStrictFail, isDefaultFail, isGradeFail, isScoreFail]);
|
|
141
162
|
|
|
@@ -239,7 +260,9 @@ export {
|
|
|
239
260
|
runReaderCli,
|
|
240
261
|
readTokenOptimized,
|
|
241
262
|
runPatcherCli,
|
|
242
|
-
patchFile
|
|
263
|
+
patchFile,
|
|
264
|
+
runWriterCli,
|
|
265
|
+
writeFile
|
|
243
266
|
};
|
|
244
267
|
|
|
245
268
|
const main = async () => {
|
|
@@ -265,6 +288,9 @@ const main = async () => {
|
|
|
265
288
|
case 'edit':
|
|
266
289
|
runPatcherCli(rawArgs.slice(1), true);
|
|
267
290
|
break;
|
|
291
|
+
case 'write':
|
|
292
|
+
runWriterCli(rawArgs.slice(1), true);
|
|
293
|
+
break;
|
|
268
294
|
case 'search':
|
|
269
295
|
case 'q':
|
|
270
296
|
case 'query':
|
|
@@ -294,7 +320,6 @@ const main = async () => {
|
|
|
294
320
|
await runInstallWizard(rawArgs[1] || process.cwd());
|
|
295
321
|
break;
|
|
296
322
|
case 'check':
|
|
297
|
-
case 'verify':
|
|
298
323
|
handleCheckCommand(rawArgs[1], {
|
|
299
324
|
isJson: rawArgs.includes('--json'),
|
|
300
325
|
isCli: true
|
|
@@ -327,6 +352,25 @@ const main = async () => {
|
|
|
327
352
|
case 'setup-mcp':
|
|
328
353
|
await runMcpInstaller(rawArgs.slice(1));
|
|
329
354
|
break;
|
|
355
|
+
case '-v':
|
|
356
|
+
case '--version':
|
|
357
|
+
case 'version':
|
|
358
|
+
process.stdout.write(`create-chemx v${getPackageVersion()}\n`);
|
|
359
|
+
break;
|
|
360
|
+
case 'verify':
|
|
361
|
+
case 'check:all':
|
|
362
|
+
await runProjectVerify(rawArgs.slice(1), true);
|
|
363
|
+
break;
|
|
364
|
+
case 'typecheck':
|
|
365
|
+
case 'check:types':
|
|
366
|
+
case 'tsc':
|
|
367
|
+
await runTypecheckAudit(rawArgs.slice(1), true);
|
|
368
|
+
break;
|
|
369
|
+
case 'test':
|
|
370
|
+
case 'tests':
|
|
371
|
+
case 'check:test':
|
|
372
|
+
await runTestAudit(rawArgs.slice(1), true);
|
|
373
|
+
break;
|
|
330
374
|
case 'help':
|
|
331
375
|
case '--help':
|
|
332
376
|
case '-h':
|
package/cli/installer.js
CHANGED
|
@@ -105,8 +105,10 @@ export const installAgentSearchConfig = async (targetDir = '.') => {
|
|
|
105
105
|
pkg.scripts = pkg.scripts || {};
|
|
106
106
|
pkg.scripts.q = 'chemx search';
|
|
107
107
|
pkg.scripts.search = 'chemx search';
|
|
108
|
+
pkg.scripts.verify = 'chemx verify';
|
|
109
|
+
pkg.scripts.check = 'chemx verify';
|
|
108
110
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
109
|
-
process.stdout.write(' \x1b[32m✔\x1b[0m Configured "q" and "
|
|
111
|
+
process.stdout.write(' \x1b[32m✔\x1b[0m Configured "q", "search", and "verify" scripts in package.json\n');
|
|
110
112
|
} catch {
|
|
111
113
|
process.stdout.write(' \x1b[33m⚠\x1b[0m Could not update package.json scripts\n');
|
|
112
114
|
}
|
|
@@ -114,20 +116,35 @@ export const installAgentSearchConfig = async (targetDir = '.') => {
|
|
|
114
116
|
|
|
115
117
|
// 2. Inject rule into AGENTS.md
|
|
116
118
|
const agentsPath = path.join(resolvedTarget, 'AGENTS.md');
|
|
117
|
-
const
|
|
119
|
+
const agentDirectives = `
|
|
118
120
|
## Chemical X Codebase Query Machine Protocol
|
|
119
121
|
- Search First Rule: AI agents MUST invoke 'pnpm q "<query>"' (or 'npx chemx search "<query>"') before running broad ripgrep, find, or file dumping.
|
|
120
122
|
- AST Architecture Intelligence: Always leverage 'pnpm q' to inspect component tiers, exported symbols, props, and hooks with minimal token burn.
|
|
121
|
-
- Inspect Mode: Use 'pnpm q "<capsule-name>" --inspect' to examine props and hooks without
|
|
123
|
+
- Inspect Mode: Use 'pnpm q "<capsule-name>" --inspect' to examine props and hooks without reading entire source files.
|
|
122
124
|
- JSON Mode: Use 'pnpm q "<query>" --json' for zero-overhead, machine-readable agent lookups.
|
|
125
|
+
|
|
126
|
+
## Chemical X Verification-First Protocol & Zero-Token-Burn Pipeline
|
|
127
|
+
- Verification First Rule: AI agents MUST NEVER run raw, unthrottled "npm test", "pnpm test", "vitest", "tsc --noEmit", or "npm run build" directly in a bash subshell.
|
|
128
|
+
- MCP Verification Tools First: AI agents MUST invoke dedicated Chemical X MCP tools or CLI wrappers ('npx chemx verify', 'npx chemx test', 'npx chemx typecheck', 'npx chemx build') before running terminal commands.
|
|
123
129
|
`;
|
|
124
130
|
|
|
125
131
|
if (fs.existsSync(agentsPath)) {
|
|
126
132
|
try {
|
|
127
133
|
const content = fs.readFileSync(agentsPath, 'utf-8');
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
const hasQueryProtocol = content.includes('Chemical X Codebase Query Machine Protocol');
|
|
135
|
+
const hasVerifyProtocol = content.includes('Chemical X Verification-First Protocol');
|
|
136
|
+
|
|
137
|
+
if (!hasQueryProtocol && !hasVerifyProtocol) {
|
|
138
|
+
fs.appendFileSync(agentsPath, `\n${agentDirectives}\n`, 'utf-8');
|
|
139
|
+
process.stdout.write(' \x1b[32m✔\x1b[0m Injected query and verification directives into AGENTS.md\n');
|
|
140
|
+
} else if (!hasVerifyProtocol) {
|
|
141
|
+
const verifyOnly = `
|
|
142
|
+
## Chemical X Verification-First Protocol & Zero-Token-Burn Pipeline
|
|
143
|
+
- Verification First Rule: AI agents MUST NEVER run raw, unthrottled "npm test", "pnpm test", "vitest", "tsc --noEmit", or "npm run build" directly in a bash subshell.
|
|
144
|
+
- MCP Verification Tools First: AI agents MUST invoke dedicated Chemical X MCP tools or CLI wrappers ('npx chemx verify', 'npx chemx test', 'npx chemx typecheck', 'npx chemx build') before running terminal commands.
|
|
145
|
+
`;
|
|
146
|
+
fs.appendFileSync(agentsPath, `\n${verifyOnly}\n`, 'utf-8');
|
|
147
|
+
process.stdout.write(' \x1b[32m✔\x1b[0m Injected verification-first directive into AGENTS.md\n');
|
|
131
148
|
}
|
|
132
149
|
} catch (err) {
|
|
133
150
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { MCP_TOOLS } from './manifests.js';
|
|
5
|
+
|
|
6
|
+
export const buildMcpInstructions = () => {
|
|
7
|
+
return [
|
|
8
|
+
'# Chemical X Protocol: Mandatory AI Agent Directives',
|
|
9
|
+
'',
|
|
10
|
+
'MANDATORY VERIFICATION & TOKEN CONSERVATION PROTOCOL:',
|
|
11
|
+
'1. NEVER execute raw, unthrottled "npm test", "pnpm test", "vitest", "tsc --noEmit", or "npm run build" in a bash shell. Raw execution floods context with hundreds of lines of passing checkmarks and compiler spam.',
|
|
12
|
+
'2. ALWAYS invoke Chemical X MCP tools or CLI wrappers first:',
|
|
13
|
+
' - chemx_verify (or "npx chemx verify --json"): Complete AST Audit + Typecheck + Tests (~45 tokens if green).',
|
|
14
|
+
' - chemx_typecheck (or "npx chemx typecheck --json"): Silent TypeScript audit; returns structured errors only.',
|
|
15
|
+
' - chemx_test (or "npx chemx test --json"): Silent test runner; returns ONLY failing assertions and diffs.',
|
|
16
|
+
' - chemx_audit_build (or "npx chemx build --json"): Silent build audit; categorizes errors by subsystem.',
|
|
17
|
+
'',
|
|
18
|
+
'AST SEARCH & SURGICAL EDITING PROTOCOL:',
|
|
19
|
+
'3. ALWAYS invoke chemx_q (or "pnpm q <query>") for AST symbol search before running broad ripgrep or find.',
|
|
20
|
+
'4. ALWAYS use chemx_read (with outline: true or symbol: "name") to inspect code structure without dumping entire files into context.',
|
|
21
|
+
'5. ALWAYS use chemx_patch for surgical file edits and chemx_check to verify molecular boundary compliance.',
|
|
22
|
+
'',
|
|
23
|
+
'MOLECULAR ARCHITECTURE GUARDRAILS:',
|
|
24
|
+
'6. 100-line outer bound per molecule capsule file. Never exceed.',
|
|
25
|
+
'7. Zero raw DOM elements in molecules or organisms: raw HTML tags are restricted exclusively to foundational atoms.',
|
|
26
|
+
'8. Two-stage atomic boolean composition: break multi-clause logic into named booleans before decision computeds.',
|
|
27
|
+
''
|
|
28
|
+
].join('\n');
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const syncAntigravityMcpSchemas = (customTargetDir = null, options = {}) => {
|
|
32
|
+
const isSilent = Boolean(options.silent);
|
|
33
|
+
const targetDir = customTargetDir || path.join(os.homedir(), '.gemini', 'antigravity', 'mcp', 'chemical-x');
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(targetDir)) {
|
|
36
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let toolCount = 0;
|
|
40
|
+
for (const tool of MCP_TOOLS) {
|
|
41
|
+
const filePath = path.join(targetDir, `${tool.name}.json`);
|
|
42
|
+
const schema = JSON.stringify({
|
|
43
|
+
name: tool.name,
|
|
44
|
+
description: tool.description,
|
|
45
|
+
parameters: tool.inputSchema
|
|
46
|
+
});
|
|
47
|
+
fs.writeFileSync(filePath, schema, 'utf-8');
|
|
48
|
+
toolCount += 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const instructionsPath = path.join(targetDir, 'instructions.md');
|
|
52
|
+
fs.writeFileSync(instructionsPath, buildMcpInstructions(), 'utf-8');
|
|
53
|
+
|
|
54
|
+
if (!isSilent) {
|
|
55
|
+
process.stdout.write(` \x1b[32m✔\x1b[0m Synchronized ${toolCount} MCP tool schemas and instructions.md in: ${targetDir}\n`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { targetDir, toolCount, instructions: true };
|
|
59
|
+
};
|
package/cli/mcp/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { startStdioServer, createMcpHandler } from './server.js';
|
|
2
|
-
import { MCP_TOOLS, executeMcpTool } from './tools.js';
|
|
2
|
+
import { MCP_TOOLS, executeMcpTool, Tools } from './tools.js';
|
|
3
3
|
import { MCP_RESOURCES, readMcpResource } from './resources.js';
|
|
4
4
|
import { MCP_PROMPTS, getMcpPrompt } from './prompts.js';
|
|
5
5
|
import {
|
|
@@ -27,6 +27,7 @@ export {
|
|
|
27
27
|
startStdioServer,
|
|
28
28
|
MCP_TOOLS,
|
|
29
29
|
executeMcpTool,
|
|
30
|
+
Tools,
|
|
30
31
|
MCP_RESOURCES,
|
|
31
32
|
readMcpResource,
|
|
32
33
|
MCP_PROMPTS,
|