aislop 0.7.0 → 0.8.0

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.
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+
4
+ //#region src/utils/subprocess.ts
5
+ const runSubprocess = (command, args, options = {}) => {
6
+ return new Promise((resolve, reject) => {
7
+ const child = spawn(command, args, {
8
+ cwd: options.cwd,
9
+ env: {
10
+ ...process.env,
11
+ ...options.env
12
+ },
13
+ stdio: [
14
+ "ignore",
15
+ "pipe",
16
+ "pipe"
17
+ ],
18
+ windowsHide: true
19
+ });
20
+ const stdoutBuffers = [];
21
+ const stderrBuffers = [];
22
+ child.stdout?.on("data", (buffer) => stdoutBuffers.push(buffer));
23
+ child.stderr?.on("data", (buffer) => stderrBuffers.push(buffer));
24
+ let settled = false;
25
+ let timer;
26
+ const finalize = (callback) => {
27
+ if (settled) return;
28
+ settled = true;
29
+ if (timer) clearTimeout(timer);
30
+ callback();
31
+ };
32
+ if (options.timeout && options.timeout > 0) {
33
+ timer = setTimeout(() => {
34
+ child.kill("SIGTERM");
35
+ setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
36
+ finalize(() => reject(/* @__PURE__ */ new Error(`Command timed out after ${options.timeout}ms: ${command}`)));
37
+ }, options.timeout);
38
+ timer.unref();
39
+ }
40
+ child.once("error", (error) => finalize(() => reject(/* @__PURE__ */ new Error(`Failed to run ${command}: ${error.message}`))));
41
+ child.once("close", (code) => {
42
+ finalize(() => resolve({
43
+ stdout: Buffer.concat(stdoutBuffers).toString("utf-8").trim(),
44
+ stderr: Buffer.concat(stderrBuffers).toString("utf-8").trim(),
45
+ exitCode: code
46
+ }));
47
+ });
48
+ });
49
+ };
50
+ const isToolInstalled = async (tool) => {
51
+ try {
52
+ const result = await runSubprocess("which", [tool]);
53
+ return result.exitCode === 0 && result.stdout.length > 0;
54
+ } catch {
55
+ return false;
56
+ }
57
+ };
58
+
59
+ //#endregion
60
+ export { runSubprocess as n, isToolInstalled as t };
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ import { r as runSubprocess } from "./cli.js";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ //#region src/engines/lint/typecheck.ts
7
+ const MAX_DEPTH = 3;
8
+ const TSC_TIMEOUT_MS = 12e4;
9
+ const TSC_LINE_RE = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS(\d+):\s+(.+)$/;
10
+ const findTsconfigs = (root) => {
11
+ const results = [];
12
+ const walk = (dir, depth) => {
13
+ if (depth > MAX_DEPTH) return;
14
+ let entries;
15
+ try {
16
+ entries = fs.readdirSync(dir, { withFileTypes: true });
17
+ } catch {
18
+ return;
19
+ }
20
+ for (const entry of entries) {
21
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
22
+ const full = path.join(dir, entry.name);
23
+ if (entry.isDirectory()) walk(full, depth + 1);
24
+ else if (entry.name === "tsconfig.json") results.push(full);
25
+ }
26
+ };
27
+ walk(root, 0);
28
+ return results;
29
+ };
30
+ const findTscBinary = (fromDir) => {
31
+ let dir = fromDir;
32
+ while (dir !== path.dirname(dir)) {
33
+ const candidate = path.join(dir, "node_modules", ".bin", "tsc");
34
+ if (fs.existsSync(candidate)) return candidate;
35
+ dir = path.dirname(dir);
36
+ }
37
+ return null;
38
+ };
39
+ const isReferenceOnlyConfig = (tsconfigPath) => {
40
+ try {
41
+ const stripped = fs.readFileSync(tsconfigPath, "utf-8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
42
+ const parsed = JSON.parse(stripped);
43
+ return Array.isArray(parsed.references) && !parsed.files && !parsed.include && !parsed.extends;
44
+ } catch {
45
+ return false;
46
+ }
47
+ };
48
+ const runTypecheck = async (context) => {
49
+ const tsconfigs = findTsconfigs(context.rootDirectory).filter((p) => !isReferenceOnlyConfig(p));
50
+ if (tsconfigs.length === 0) return [];
51
+ const diagnostics = [];
52
+ const seen = /* @__PURE__ */ new Set();
53
+ for (const tsconfig of tsconfigs) {
54
+ const projectDir = path.dirname(tsconfig);
55
+ const tscBinary = findTscBinary(projectDir);
56
+ if (!tscBinary) continue;
57
+ let output = "";
58
+ try {
59
+ const result = await runSubprocess(tscBinary, [
60
+ "--noEmit",
61
+ "--pretty",
62
+ "false",
63
+ "-p",
64
+ tsconfig
65
+ ], {
66
+ cwd: projectDir,
67
+ timeout: TSC_TIMEOUT_MS
68
+ });
69
+ output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
70
+ } catch {
71
+ continue;
72
+ }
73
+ for (const rawLine of output.split("\n")) {
74
+ const line = rawLine.trim();
75
+ if (!line) continue;
76
+ const match = TSC_LINE_RE.exec(line);
77
+ if (!match) continue;
78
+ const [, filePath, lineStr, colStr, severity, code, message] = match;
79
+ const absolute = path.resolve(projectDir, filePath);
80
+ const relative = path.relative(context.rootDirectory, absolute);
81
+ const key = `${relative}:${lineStr}:${colStr}:TS${code}`;
82
+ if (seen.has(key)) continue;
83
+ seen.add(key);
84
+ diagnostics.push({
85
+ filePath: relative,
86
+ engine: "lint",
87
+ rule: `typescript/TS${code}`,
88
+ severity: severity === "error" ? "error" : "warning",
89
+ message,
90
+ help: `Fix the underlying type — TS${code} is a hard contract violation, not a style nit.`,
91
+ line: Number.parseInt(lineStr, 10),
92
+ column: Number.parseInt(colStr, 10),
93
+ category: "TypeScript",
94
+ fixable: false
95
+ });
96
+ }
97
+ }
98
+ return diagnostics;
99
+ };
100
+
101
+ //#endregion
102
+ export { runTypecheck };
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ import { n as runSubprocess } from "./subprocess-CCnnN_oQ.js";
3
+ import path from "node:path";
4
+ import fs from "node:fs";
5
+
6
+ //#region src/engines/lint/typecheck.ts
7
+ const MAX_DEPTH = 3;
8
+ const TSC_TIMEOUT_MS = 12e4;
9
+ const TSC_LINE_RE = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS(\d+):\s+(.+)$/;
10
+ const findTsconfigs = (root) => {
11
+ const results = [];
12
+ const walk = (dir, depth) => {
13
+ if (depth > MAX_DEPTH) return;
14
+ let entries;
15
+ try {
16
+ entries = fs.readdirSync(dir, { withFileTypes: true });
17
+ } catch {
18
+ return;
19
+ }
20
+ for (const entry of entries) {
21
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
22
+ const full = path.join(dir, entry.name);
23
+ if (entry.isDirectory()) walk(full, depth + 1);
24
+ else if (entry.name === "tsconfig.json") results.push(full);
25
+ }
26
+ };
27
+ walk(root, 0);
28
+ return results;
29
+ };
30
+ const findTscBinary = (fromDir) => {
31
+ let dir = fromDir;
32
+ while (dir !== path.dirname(dir)) {
33
+ const candidate = path.join(dir, "node_modules", ".bin", "tsc");
34
+ if (fs.existsSync(candidate)) return candidate;
35
+ dir = path.dirname(dir);
36
+ }
37
+ return null;
38
+ };
39
+ const isReferenceOnlyConfig = (tsconfigPath) => {
40
+ try {
41
+ const stripped = fs.readFileSync(tsconfigPath, "utf-8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
42
+ const parsed = JSON.parse(stripped);
43
+ return Array.isArray(parsed.references) && !parsed.files && !parsed.include && !parsed.extends;
44
+ } catch {
45
+ return false;
46
+ }
47
+ };
48
+ const runTypecheck = async (context) => {
49
+ const tsconfigs = findTsconfigs(context.rootDirectory).filter((p) => !isReferenceOnlyConfig(p));
50
+ if (tsconfigs.length === 0) return [];
51
+ const diagnostics = [];
52
+ const seen = /* @__PURE__ */ new Set();
53
+ for (const tsconfig of tsconfigs) {
54
+ const projectDir = path.dirname(tsconfig);
55
+ const tscBinary = findTscBinary(projectDir);
56
+ if (!tscBinary) continue;
57
+ let output = "";
58
+ try {
59
+ const result = await runSubprocess(tscBinary, [
60
+ "--noEmit",
61
+ "--pretty",
62
+ "false",
63
+ "-p",
64
+ tsconfig
65
+ ], {
66
+ cwd: projectDir,
67
+ timeout: TSC_TIMEOUT_MS
68
+ });
69
+ output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
70
+ } catch {
71
+ continue;
72
+ }
73
+ for (const rawLine of output.split("\n")) {
74
+ const line = rawLine.trim();
75
+ if (!line) continue;
76
+ const match = TSC_LINE_RE.exec(line);
77
+ if (!match) continue;
78
+ const [, filePath, lineStr, colStr, severity, code, message] = match;
79
+ const absolute = path.resolve(projectDir, filePath);
80
+ const relative = path.relative(context.rootDirectory, absolute);
81
+ const key = `${relative}:${lineStr}:${colStr}:TS${code}`;
82
+ if (seen.has(key)) continue;
83
+ seen.add(key);
84
+ diagnostics.push({
85
+ filePath: relative,
86
+ engine: "lint",
87
+ rule: `typescript/TS${code}`,
88
+ severity: severity === "error" ? "error" : "warning",
89
+ message,
90
+ help: `Fix the underlying type — TS${code} is a hard contract violation, not a style nit.`,
91
+ line: Number.parseInt(lineStr, 10),
92
+ column: Number.parseInt(colStr, 10),
93
+ category: "TypeScript",
94
+ fixable: false
95
+ });
96
+ }
97
+ }
98
+ return diagnostics;
99
+ };
100
+
101
+ //#endregion
102
+ export { runTypecheck };
@@ -0,0 +1,101 @@
1
+ import { n as runSubprocess } from "./subprocess-CQUJDGgn.js";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ //#region src/engines/lint/typecheck.ts
6
+ const MAX_DEPTH = 3;
7
+ const TSC_TIMEOUT_MS = 12e4;
8
+ const TSC_LINE_RE = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS(\d+):\s+(.+)$/;
9
+ const findTsconfigs = (root) => {
10
+ const results = [];
11
+ const walk = (dir, depth) => {
12
+ if (depth > MAX_DEPTH) return;
13
+ let entries;
14
+ try {
15
+ entries = fs.readdirSync(dir, { withFileTypes: true });
16
+ } catch {
17
+ return;
18
+ }
19
+ for (const entry of entries) {
20
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
21
+ const full = path.join(dir, entry.name);
22
+ if (entry.isDirectory()) walk(full, depth + 1);
23
+ else if (entry.name === "tsconfig.json") results.push(full);
24
+ }
25
+ };
26
+ walk(root, 0);
27
+ return results;
28
+ };
29
+ const findTscBinary = (fromDir) => {
30
+ let dir = fromDir;
31
+ while (dir !== path.dirname(dir)) {
32
+ const candidate = path.join(dir, "node_modules", ".bin", "tsc");
33
+ if (fs.existsSync(candidate)) return candidate;
34
+ dir = path.dirname(dir);
35
+ }
36
+ return null;
37
+ };
38
+ const isReferenceOnlyConfig = (tsconfigPath) => {
39
+ try {
40
+ const stripped = fs.readFileSync(tsconfigPath, "utf-8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
41
+ const parsed = JSON.parse(stripped);
42
+ return Array.isArray(parsed.references) && !parsed.files && !parsed.include && !parsed.extends;
43
+ } catch {
44
+ return false;
45
+ }
46
+ };
47
+ const runTypecheck = async (context) => {
48
+ const tsconfigs = findTsconfigs(context.rootDirectory).filter((p) => !isReferenceOnlyConfig(p));
49
+ if (tsconfigs.length === 0) return [];
50
+ const diagnostics = [];
51
+ const seen = /* @__PURE__ */ new Set();
52
+ for (const tsconfig of tsconfigs) {
53
+ const projectDir = path.dirname(tsconfig);
54
+ const tscBinary = findTscBinary(projectDir);
55
+ if (!tscBinary) continue;
56
+ let output = "";
57
+ try {
58
+ const result = await runSubprocess(tscBinary, [
59
+ "--noEmit",
60
+ "--pretty",
61
+ "false",
62
+ "-p",
63
+ tsconfig
64
+ ], {
65
+ cwd: projectDir,
66
+ timeout: TSC_TIMEOUT_MS
67
+ });
68
+ output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
69
+ } catch {
70
+ continue;
71
+ }
72
+ for (const rawLine of output.split("\n")) {
73
+ const line = rawLine.trim();
74
+ if (!line) continue;
75
+ const match = TSC_LINE_RE.exec(line);
76
+ if (!match) continue;
77
+ const [, filePath, lineStr, colStr, severity, code, message] = match;
78
+ const absolute = path.resolve(projectDir, filePath);
79
+ const relative = path.relative(context.rootDirectory, absolute);
80
+ const key = `${relative}:${lineStr}:${colStr}:TS${code}`;
81
+ if (seen.has(key)) continue;
82
+ seen.add(key);
83
+ diagnostics.push({
84
+ filePath: relative,
85
+ engine: "lint",
86
+ rule: `typescript/TS${code}`,
87
+ severity: severity === "error" ? "error" : "warning",
88
+ message,
89
+ help: `Fix the underlying type — TS${code} is a hard contract violation, not a style nit.`,
90
+ line: Number.parseInt(lineStr, 10),
91
+ column: Number.parseInt(colStr, 10),
92
+ category: "TypeScript",
93
+ fixable: false
94
+ });
95
+ }
96
+ }
97
+ return diagnostics;
98
+ };
99
+
100
+ //#endregion
101
+ export { runTypecheck };
@@ -29,7 +29,7 @@ const getEngineLabel = (engine) => ENGINE_INFO[engine].label;
29
29
 
30
30
  //#endregion
31
31
  //#region src/version.ts
32
- const APP_VERSION = "0.7.0";
32
+ const APP_VERSION = "0.8.0";
33
33
 
34
34
  //#endregion
35
35
  export { ENGINE_INFO as n, getEngineLabel as r, APP_VERSION as t };
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "aislop",
3
- "version": "0.7.0",
4
- "description": "Stop AI slop from shipping. A unified code quality CLI that catches the lazy patterns AI coding tools leave behind.",
3
+ "version": "0.8.0",
4
+ "description": "The engineering standards layer and quality gate for AI-written code. Define your standard once. Every agent Claude Code, Cursor, Codex is held to it automatically, on every edit and every PR. Catches the slop they leave behind, enforces the rules your team sets. 8+ languages. Deterministic.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "aislop": "./dist/cli.js"
7
+ "aislop": "./dist/cli.js",
8
+ "aislop-mcp": "./dist/mcp.js"
8
9
  },
9
10
  "files": [
10
11
  "dist",
@@ -63,6 +64,7 @@
63
64
  "dependencies": {
64
65
  "@biomejs/biome": "^2.4.5",
65
66
  "@clack/prompts": "^1.2.0",
67
+ "@modelcontextprotocol/sdk": "^1.29.0",
66
68
  "adm-zip": "^0.5.16",
67
69
  "commander": "^14.0.3",
68
70
  "expo-doctor": "^1.18.10",
File without changes