@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.
Files changed (45) hide show
  1. package/README.md +63 -0
  2. package/blueprints/molecule-capsule/m-tab-button/m-tab-button.controller.ts +8 -11
  3. package/cli/audit/ast-visitors.js +73 -0
  4. package/cli/audit/extended-visitors.js +239 -0
  5. package/cli/audit/reporter-utils.js +13 -22
  6. package/cli/audit/rules-registry.js +25 -0
  7. package/cli/audit/rules.spec.js +205 -0
  8. package/cli/build.d.ts +2 -1
  9. package/cli/build.js +8 -5
  10. package/cli/columnar.d.ts +14 -0
  11. package/cli/columnar.js +52 -0
  12. package/cli/columnar.spec.js +49 -0
  13. package/cli/help.js +29 -4
  14. package/cli/index.js +51 -7
  15. package/cli/installer.js +23 -6
  16. package/cli/mcp/antigravity.js +59 -0
  17. package/cli/mcp/index.js +2 -1
  18. package/cli/mcp/installer.js +26 -4
  19. package/cli/mcp/installer.spec.js +23 -1
  20. package/cli/mcp/manifests.js +251 -0
  21. package/cli/mcp/prompts.js +28 -0
  22. package/cli/mcp/resources.js +94 -90
  23. package/cli/mcp/server.js +13 -0
  24. package/cli/mcp/server.spec.js +213 -1
  25. package/cli/mcp/tools.js +252 -189
  26. package/cli/mcp/types.d.ts +37 -0
  27. package/cli/navigator-actions.js +23 -10
  28. package/cli/navigator-banner.js +2 -3
  29. package/cli/navigator-guide.js +73 -0
  30. package/cli/navigator-menu.js +20 -0
  31. package/cli/navigator.js +1 -0
  32. package/cli/patcher.d.ts +73 -0
  33. package/cli/patcher.js +249 -4
  34. package/cli/patcher.spec.js +148 -0
  35. package/cli/scaffold.js +7 -1
  36. package/cli/search-commands.js +10 -8
  37. package/cli/search-queries.js +8 -3
  38. package/cli/search-schema.js +1 -0
  39. package/cli/search.d.ts +16 -0
  40. package/cli/search.js +103 -11
  41. package/cli/silence-warnings.js +17 -0
  42. package/cli/verify.js +377 -0
  43. package/cli/verify.spec.js +98 -0
  44. package/package.json +3 -1
  45. 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
- process.stdout.write(formatJsonBuildReport(report) + '\n');
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[];
@@ -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 6 core tools (chemx_query_patterns, chemx_autofix, chemx_audit,`,
101
- ` chemx_generate_capsule, chemx_get_refactor_prompt, chemx_audit_build),`,
102
- ` living resources (chemx://directives, chemx://scorecard), and prompts.`,
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 tools.`,
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 hasCriticalOrHigh = report.violations.some((v) => v.severity === 'CRITICAL' || v.severity === 'HIGH');
135
- const isStrictFail = isStrict && report.violations.length > 0;
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 isScoreFail = minScore !== null && !isNaN(minScore) && report.health.score < minScore;
138
- const hasThreshold = Boolean(minGrade) || (minScore !== null && !isNaN(minScore));
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 "search" scripts in package.json (run via pnpm q <query>)\n');
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 searchDirective = `
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 catting entire source files.
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
- if (!content.includes('Chemical X Codebase Query Machine Protocol')) {
129
- fs.appendFileSync(agentsPath, `\n${searchDirective}\n`, 'utf-8');
130
- process.stdout.write(' \x1b[32m✔\x1b[0m Injected search-first directive into AGENTS.md\n');
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,