aislop 0.6.2 → 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.
- package/README.md +76 -6
- package/dist/cli.js +1581 -110
- package/dist/expo-doctor-T4DswmX5.js +136 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1308 -67
- package/dist/{json-ZItDVIZL.js → json-BJGLCIK-.js} +1 -1
- package/dist/mcp.js +5115 -0
- package/dist/subprocess-CCnnN_oQ.js +60 -0
- package/dist/typecheck-B1MXNAy-.js +102 -0
- package/dist/typecheck-By967nny.js +102 -0
- package/dist/typecheck-XJMuCczG.js +101 -0
- package/dist/{version-AmNwcw_U.js → version-B9ZchFMv.js} +1 -1
- package/package.json +10 -3
- /package/dist/{json-B51etWTw.js → json-BbMwrgyd.js} +0 -0
|
@@ -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.
|
|
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.
|
|
4
|
-
"description": "
|
|
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",
|
|
@@ -81,5 +83,10 @@
|
|
|
81
83
|
"@types/node": "^25.6.0",
|
|
82
84
|
"tsdown": "^0.20.3",
|
|
83
85
|
"vitest": "^4.0.18"
|
|
86
|
+
},
|
|
87
|
+
"pnpm": {
|
|
88
|
+
"overrides": {
|
|
89
|
+
"postcss@<8.5.10": "^8.5.10"
|
|
90
|
+
}
|
|
84
91
|
}
|
|
85
92
|
}
|
|
File without changes
|