aislop 0.8.2 → 0.9.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/dist/cli.js +558 -128
- package/dist/index.js +419 -77
- package/dist/{json-DxLkV8n2.js → json-DZfGz2xa.js} +1 -1
- package/dist/{json-BbMwrgyd.js → json-OIzja7OM.js} +1 -1
- package/dist/mcp.js +273 -10
- package/dist/{typecheck-B1MXNAy-.js → typecheck-wVSohmOX.js} +1 -1
- package/dist/{version-G3ekYjY1.js → version-D_rqBdyj.js} +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,14 +4,14 @@ import { Command } from "commander";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import crypto, { randomUUID } from "node:crypto";
|
|
8
|
+
import { performance } from "node:perf_hooks";
|
|
7
9
|
import YAML from "yaml";
|
|
8
10
|
import { z } from "zod/v4";
|
|
9
|
-
import { performance } from "node:perf_hooks";
|
|
10
11
|
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
11
12
|
import micromatch from "micromatch";
|
|
12
13
|
import { fileURLToPath } from "node:url";
|
|
13
14
|
import ts from "typescript";
|
|
14
|
-
import crypto from "node:crypto";
|
|
15
15
|
import { isCancel, multiselect, select, text } from "@clack/prompts";
|
|
16
16
|
import pc from "picocolors";
|
|
17
17
|
import wcwidth from "wcwidth";
|
|
@@ -32,6 +32,366 @@ var __exportAll = (all, no_symbols) => {
|
|
|
32
32
|
return target;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/version.ts
|
|
37
|
+
const APP_VERSION = "0.9.0";
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/telemetry/env.ts
|
|
41
|
+
const detectPackageManager$1 = (env = process.env) => {
|
|
42
|
+
const execPath = env.npm_execpath ?? "";
|
|
43
|
+
if (execPath.includes("npx")) return "npx";
|
|
44
|
+
const userAgent = env.npm_config_user_agent ?? "";
|
|
45
|
+
if (userAgent.startsWith("pnpm/")) return "pnpm";
|
|
46
|
+
if (userAgent.startsWith("yarn/")) return "yarn";
|
|
47
|
+
if (userAgent.startsWith("bun/")) return "bun";
|
|
48
|
+
if (userAgent.startsWith("npm/")) return "npm";
|
|
49
|
+
if (execPath.includes("pnpm")) return "pnpm";
|
|
50
|
+
if (execPath.includes("yarn")) return "yarn";
|
|
51
|
+
if (execPath.includes("bun")) return "bun";
|
|
52
|
+
if (execPath.includes("npm")) return "npm";
|
|
53
|
+
return "unknown";
|
|
54
|
+
};
|
|
55
|
+
const CI_ENV_KEYS = [
|
|
56
|
+
"CI",
|
|
57
|
+
"GITHUB_ACTIONS",
|
|
58
|
+
"GITLAB_CI",
|
|
59
|
+
"CIRCLECI",
|
|
60
|
+
"TRAVIS",
|
|
61
|
+
"BUILDKITE",
|
|
62
|
+
"DRONE",
|
|
63
|
+
"TEAMCITY_VERSION",
|
|
64
|
+
"TF_BUILD"
|
|
65
|
+
];
|
|
66
|
+
const isCiEnv = (env = process.env) => CI_ENV_KEYS.some((k) => {
|
|
67
|
+
const v = env[k];
|
|
68
|
+
return v === "true" || v === "1" || v != null && v.length > 0 && k !== "CI";
|
|
69
|
+
}) || env.CI === "true" || env.CI === "1";
|
|
70
|
+
const fileCountBucket = (count) => {
|
|
71
|
+
if (count < 10) return "0-10";
|
|
72
|
+
if (count < 50) return "10-50";
|
|
73
|
+
if (count < 100) return "50-100";
|
|
74
|
+
if (count < 500) return "100-500";
|
|
75
|
+
if (count < 1e3) return "500-1000";
|
|
76
|
+
return "1000+";
|
|
77
|
+
};
|
|
78
|
+
const scoreBucket = (score) => {
|
|
79
|
+
if (score >= 75) return "75-100";
|
|
80
|
+
if (score >= 50) return "50-75";
|
|
81
|
+
if (score >= 25) return "25-50";
|
|
82
|
+
return "0-25";
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/telemetry/identity.ts
|
|
87
|
+
const FILE_BASENAME = "install_id";
|
|
88
|
+
const resolveInstallIdPath = (homedir = os.homedir(), env = process.env) => {
|
|
89
|
+
if (process.platform === "linux" && env.XDG_STATE_HOME) return path.join(env.XDG_STATE_HOME, "aislop", FILE_BASENAME);
|
|
90
|
+
return path.join(homedir, ".aislop", FILE_BASENAME);
|
|
91
|
+
};
|
|
92
|
+
const ensureInstallId = (idPath = resolveInstallIdPath()) => {
|
|
93
|
+
if (fs.existsSync(idPath)) {
|
|
94
|
+
const existing = fs.readFileSync(idPath, "utf-8").trim();
|
|
95
|
+
if (existing.length > 0) return {
|
|
96
|
+
installId: existing,
|
|
97
|
+
created: false
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const dir = path.dirname(idPath);
|
|
101
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
102
|
+
const installId = randomUUID();
|
|
103
|
+
const tmpPath = `${idPath}.${process.pid}.tmp`;
|
|
104
|
+
fs.writeFileSync(tmpPath, `${installId}\n`, { mode: 384 });
|
|
105
|
+
try {
|
|
106
|
+
fs.renameSync(tmpPath, idPath);
|
|
107
|
+
return {
|
|
108
|
+
installId,
|
|
109
|
+
created: true
|
|
110
|
+
};
|
|
111
|
+
} catch {
|
|
112
|
+
fs.rmSync(tmpPath, { force: true });
|
|
113
|
+
return {
|
|
114
|
+
installId: fs.readFileSync(idPath, "utf-8").trim(),
|
|
115
|
+
created: false
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/telemetry/redaction.ts
|
|
122
|
+
const SAFE_PROPERTY_NAMES = new Set([
|
|
123
|
+
"aislop_version",
|
|
124
|
+
"node_version",
|
|
125
|
+
"os",
|
|
126
|
+
"arch",
|
|
127
|
+
"schema_version",
|
|
128
|
+
"anonymous_install_id",
|
|
129
|
+
"package_manager",
|
|
130
|
+
"is_ci",
|
|
131
|
+
"command",
|
|
132
|
+
"language_summary",
|
|
133
|
+
"lang_typescript",
|
|
134
|
+
"lang_javascript",
|
|
135
|
+
"lang_python",
|
|
136
|
+
"lang_java",
|
|
137
|
+
"file_count_bucket",
|
|
138
|
+
"exit_code",
|
|
139
|
+
"duration_ms",
|
|
140
|
+
"error_kind",
|
|
141
|
+
"score",
|
|
142
|
+
"score_bucket",
|
|
143
|
+
"finding_count",
|
|
144
|
+
"error_count",
|
|
145
|
+
"warning_count",
|
|
146
|
+
"fixable_count",
|
|
147
|
+
"fix_steps",
|
|
148
|
+
"fix_resolved",
|
|
149
|
+
"fix_score_delta",
|
|
150
|
+
"engine_format_issues",
|
|
151
|
+
"engine_format_ms",
|
|
152
|
+
"engine_lint_issues",
|
|
153
|
+
"engine_lint_ms",
|
|
154
|
+
"engine_code_quality_issues",
|
|
155
|
+
"engine_code_quality_ms",
|
|
156
|
+
"engine_ai_slop_issues",
|
|
157
|
+
"engine_ai_slop_ms",
|
|
158
|
+
"engine_architecture_issues",
|
|
159
|
+
"engine_architecture_ms",
|
|
160
|
+
"engine_security_issues",
|
|
161
|
+
"engine_security_ms",
|
|
162
|
+
"tool",
|
|
163
|
+
"ok",
|
|
164
|
+
"agent",
|
|
165
|
+
"score_delta"
|
|
166
|
+
]);
|
|
167
|
+
const redactProperties = (props) => {
|
|
168
|
+
const clean = {};
|
|
169
|
+
const dropped = [];
|
|
170
|
+
for (const [key, value] of Object.entries(props)) {
|
|
171
|
+
if (value === void 0) continue;
|
|
172
|
+
if (SAFE_PROPERTY_NAMES.has(key)) clean[key] = value;
|
|
173
|
+
else dropped.push(key);
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
clean,
|
|
177
|
+
dropped
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/telemetry/client.ts
|
|
183
|
+
const POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
184
|
+
const POSTHOG_KEY = "phc_eY2cOMFva9q24GrWeOuvuVIOhCIdjOALxeAR3ItrqbJ";
|
|
185
|
+
const SCHEMA_VERSION = "v2";
|
|
186
|
+
const REQUEST_TIMEOUT_MS = 3e3;
|
|
187
|
+
const isTelemetryDisabled = (config) => {
|
|
188
|
+
const env = process.env;
|
|
189
|
+
if (env.AISLOP_NO_TELEMETRY === "1" || env.DO_NOT_TRACK === "1") return true;
|
|
190
|
+
if (config?.enabled === false) return true;
|
|
191
|
+
if (config?.enabled === true) return false;
|
|
192
|
+
if (env.CI === "true" || env.CI === "1") return true;
|
|
193
|
+
return false;
|
|
194
|
+
};
|
|
195
|
+
const isDebug = () => process.env.AISLOP_TELEMETRY_DEBUG === "1";
|
|
196
|
+
const pendingRequests = /* @__PURE__ */ new Set();
|
|
197
|
+
let cachedInstallId = null;
|
|
198
|
+
let installCreated = false;
|
|
199
|
+
const baseProperties = (installId) => ({
|
|
200
|
+
aislop_version: APP_VERSION,
|
|
201
|
+
node_version: process.version,
|
|
202
|
+
os: os.platform(),
|
|
203
|
+
arch: os.arch(),
|
|
204
|
+
schema_version: SCHEMA_VERSION,
|
|
205
|
+
anonymous_install_id: installId,
|
|
206
|
+
package_manager: detectPackageManager$1(),
|
|
207
|
+
is_ci: isCiEnv()
|
|
208
|
+
});
|
|
209
|
+
const track = (input) => {
|
|
210
|
+
if (isTelemetryDisabled(input.config)) return { installCreated: false };
|
|
211
|
+
if (cachedInstallId == null) {
|
|
212
|
+
const ensured = ensureInstallId(resolveInstallIdPath());
|
|
213
|
+
cachedInstallId = ensured.installId;
|
|
214
|
+
installCreated = ensured.created;
|
|
215
|
+
}
|
|
216
|
+
const { clean, dropped } = redactProperties({
|
|
217
|
+
...baseProperties(cachedInstallId),
|
|
218
|
+
...input.properties
|
|
219
|
+
});
|
|
220
|
+
if (isDebug()) {
|
|
221
|
+
const compact = JSON.stringify({
|
|
222
|
+
event: input.event,
|
|
223
|
+
properties: clean
|
|
224
|
+
});
|
|
225
|
+
process.stderr.write(`[telemetry] ${compact}\n`);
|
|
226
|
+
if (dropped.length > 0) for (const key of dropped) process.stderr.write(`[telemetry] dropped non-allowlisted property: ${key}\n`);
|
|
227
|
+
}
|
|
228
|
+
if (process.env.AISLOP_TELEMETRY_DRY_RUN === "1") return { installCreated };
|
|
229
|
+
const payload = {
|
|
230
|
+
api_key: POSTHOG_KEY,
|
|
231
|
+
event: input.event,
|
|
232
|
+
distinct_id: cachedInstallId,
|
|
233
|
+
properties: clean,
|
|
234
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
235
|
+
};
|
|
236
|
+
const request = fetch(`${POSTHOG_HOST}/capture/`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: { "Content-Type": "application/json" },
|
|
239
|
+
body: JSON.stringify(payload),
|
|
240
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
241
|
+
}).then(() => {}).catch(() => {}).finally(() => {
|
|
242
|
+
pendingRequests.delete(request);
|
|
243
|
+
});
|
|
244
|
+
pendingRequests.add(request);
|
|
245
|
+
return { installCreated };
|
|
246
|
+
};
|
|
247
|
+
const flushTelemetry = async () => {
|
|
248
|
+
if (pendingRequests.size === 0) return;
|
|
249
|
+
await Promise.all(pendingRequests);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
//#endregion
|
|
253
|
+
//#region src/telemetry/language.ts
|
|
254
|
+
const ALL_LANGUAGES = [
|
|
255
|
+
"typescript",
|
|
256
|
+
"javascript",
|
|
257
|
+
"python",
|
|
258
|
+
"java"
|
|
259
|
+
];
|
|
260
|
+
const buildLanguageProperties = (detected) => {
|
|
261
|
+
const present = new Set(detected);
|
|
262
|
+
const summary = [...present].filter((l) => ALL_LANGUAGES.includes(l));
|
|
263
|
+
summary.sort();
|
|
264
|
+
return {
|
|
265
|
+
language_summary: summary.join(","),
|
|
266
|
+
lang_typescript: present.has("typescript"),
|
|
267
|
+
lang_javascript: present.has("javascript"),
|
|
268
|
+
lang_python: present.has("python"),
|
|
269
|
+
lang_java: present.has("java")
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/telemetry/events.ts
|
|
275
|
+
const buildCommandStartedProps = (input) => {
|
|
276
|
+
const props = { command: input.command };
|
|
277
|
+
if (input.languages) Object.assign(props, buildLanguageProperties(input.languages));
|
|
278
|
+
if (typeof input.fileCount === "number") props.file_count_bucket = fileCountBucket(input.fileCount);
|
|
279
|
+
return props;
|
|
280
|
+
};
|
|
281
|
+
const ENGINE_KEY_MAP = {
|
|
282
|
+
format: "engine_format",
|
|
283
|
+
lint: "engine_lint",
|
|
284
|
+
"code-quality": "engine_code_quality",
|
|
285
|
+
"ai-slop": "engine_ai_slop",
|
|
286
|
+
architecture: "engine_architecture",
|
|
287
|
+
security: "engine_security"
|
|
288
|
+
};
|
|
289
|
+
const flattenEngineStats = (issues, timings) => {
|
|
290
|
+
const out = {};
|
|
291
|
+
for (const [engine, count] of Object.entries(issues)) {
|
|
292
|
+
const key = ENGINE_KEY_MAP[engine];
|
|
293
|
+
if (key != null && typeof count === "number") out[`${key}_issues`] = count;
|
|
294
|
+
}
|
|
295
|
+
for (const [engine, ms] of Object.entries(timings)) {
|
|
296
|
+
const key = ENGINE_KEY_MAP[engine];
|
|
297
|
+
if (key != null && typeof ms === "number") out[`${key}_ms`] = Math.round(ms);
|
|
298
|
+
}
|
|
299
|
+
return out;
|
|
300
|
+
};
|
|
301
|
+
const buildCommandCompletedProps = (input) => {
|
|
302
|
+
const props = {
|
|
303
|
+
...input.startProps,
|
|
304
|
+
exit_code: input.exitCode,
|
|
305
|
+
duration_ms: Math.round(input.durationMs)
|
|
306
|
+
};
|
|
307
|
+
if (input.errorKind) props.error_kind = input.errorKind;
|
|
308
|
+
if (typeof input.score === "number") {
|
|
309
|
+
props.score = input.score;
|
|
310
|
+
props.score_bucket = scoreBucket(input.score);
|
|
311
|
+
}
|
|
312
|
+
if (typeof input.findingCount === "number") props.finding_count = input.findingCount;
|
|
313
|
+
if (typeof input.errorCount === "number") props.error_count = input.errorCount;
|
|
314
|
+
if (typeof input.warningCount === "number") props.warning_count = input.warningCount;
|
|
315
|
+
if (typeof input.fixableCount === "number") props.fixable_count = input.fixableCount;
|
|
316
|
+
if (input.engineIssues && input.engineTimings) Object.assign(props, flattenEngineStats(input.engineIssues, input.engineTimings));
|
|
317
|
+
if (typeof input.fixSteps === "number") props.fix_steps = input.fixSteps;
|
|
318
|
+
if (typeof input.fixResolved === "number") props.fix_resolved = input.fixResolved;
|
|
319
|
+
if (typeof input.fixScoreDelta === "number") props.fix_score_delta = input.fixScoreDelta;
|
|
320
|
+
return props;
|
|
321
|
+
};
|
|
322
|
+
const buildHookScanCompletedProps = (input) => {
|
|
323
|
+
const props = {
|
|
324
|
+
agent: input.agent,
|
|
325
|
+
score: input.score,
|
|
326
|
+
score_bucket: scoreBucket(input.score),
|
|
327
|
+
finding_count: input.findingCount,
|
|
328
|
+
file_count_bucket: fileCountBucket(input.fileCount)
|
|
329
|
+
};
|
|
330
|
+
if (typeof input.scoreDelta === "number") props.score_delta = input.scoreDelta;
|
|
331
|
+
return props;
|
|
332
|
+
};
|
|
333
|
+
const errorKindFromException = (error) => {
|
|
334
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
335
|
+
if (message.includes("timeout") || message.includes("timed out")) return "timeout";
|
|
336
|
+
if (message.includes("invalid config") || message.includes("config_invalid")) return "config_invalid";
|
|
337
|
+
if (message.includes("engine") && message.includes("crash")) return "engine_crash";
|
|
338
|
+
return "unknown";
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region src/telemetry/lifecycle.ts
|
|
343
|
+
const withCommandLifecycle = async (start, run) => {
|
|
344
|
+
const startProps = buildCommandStartedProps({
|
|
345
|
+
command: start.command,
|
|
346
|
+
languages: start.languages,
|
|
347
|
+
fileCount: start.fileCount
|
|
348
|
+
});
|
|
349
|
+
track({
|
|
350
|
+
event: "cli_command_started",
|
|
351
|
+
properties: startProps,
|
|
352
|
+
config: start.config
|
|
353
|
+
});
|
|
354
|
+
const startedAt = performance.now();
|
|
355
|
+
try {
|
|
356
|
+
const result = await run();
|
|
357
|
+
const durationMs = performance.now() - startedAt;
|
|
358
|
+
track({
|
|
359
|
+
event: "cli_command_completed",
|
|
360
|
+
properties: buildCommandCompletedProps({
|
|
361
|
+
startProps,
|
|
362
|
+
exitCode: result.exitCode,
|
|
363
|
+
durationMs,
|
|
364
|
+
score: result.score,
|
|
365
|
+
findingCount: result.findingCount,
|
|
366
|
+
errorCount: result.errorCount,
|
|
367
|
+
warningCount: result.warningCount,
|
|
368
|
+
fixableCount: result.fixableCount,
|
|
369
|
+
engineIssues: result.engineIssues,
|
|
370
|
+
engineTimings: result.engineTimings,
|
|
371
|
+
fixSteps: result.fixSteps,
|
|
372
|
+
fixResolved: result.fixResolved,
|
|
373
|
+
fixScoreDelta: result.fixScoreDelta
|
|
374
|
+
}),
|
|
375
|
+
config: start.config
|
|
376
|
+
});
|
|
377
|
+
await flushTelemetry();
|
|
378
|
+
return result;
|
|
379
|
+
} catch (error) {
|
|
380
|
+
track({
|
|
381
|
+
event: "cli_command_completed",
|
|
382
|
+
properties: buildCommandCompletedProps({
|
|
383
|
+
startProps,
|
|
384
|
+
exitCode: 1,
|
|
385
|
+
durationMs: performance.now() - startedAt,
|
|
386
|
+
errorKind: errorKindFromException(error)
|
|
387
|
+
}),
|
|
388
|
+
config: start.config
|
|
389
|
+
});
|
|
390
|
+
await flushTelemetry();
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
35
395
|
//#endregion
|
|
36
396
|
//#region src/hooks/feedback.ts
|
|
37
397
|
const fingerprintFinding = (f) => `${f.file}:${f.line}:${f.ruleId}`;
|
|
@@ -1452,6 +1812,27 @@ const collectJsDeps = (rootDir, jsDeps) => {
|
|
|
1452
1812
|
collectNestedManifests(rootDir, jsDeps);
|
|
1453
1813
|
return true;
|
|
1454
1814
|
};
|
|
1815
|
+
const TS_CONFIG_FILES = ["tsconfig.json", "jsconfig.json"];
|
|
1816
|
+
const buildAliasMatcher = (key) => {
|
|
1817
|
+
const starIdx = key.indexOf("*");
|
|
1818
|
+
if (starIdx === -1) return (spec) => spec === key;
|
|
1819
|
+
const before = key.slice(0, starIdx);
|
|
1820
|
+
const after = key.slice(starIdx + 1);
|
|
1821
|
+
return (spec) => spec.length >= before.length + after.length && spec.startsWith(before) && spec.endsWith(after);
|
|
1822
|
+
};
|
|
1823
|
+
const collectAliasMatchersFromConfig = (configPath, matchers) => {
|
|
1824
|
+
const opts = readJson(configPath)?.compilerOptions;
|
|
1825
|
+
if (!opts || typeof opts !== "object") return;
|
|
1826
|
+
const paths = opts.paths;
|
|
1827
|
+
if (!paths || typeof paths !== "object") return;
|
|
1828
|
+
for (const key of Object.keys(paths)) matchers.push(buildAliasMatcher(key));
|
|
1829
|
+
};
|
|
1830
|
+
const collectTsPathAliases = (rootDir) => {
|
|
1831
|
+
const matchers = [];
|
|
1832
|
+
const dirs = [rootDir, ...expandWorkspaceDirs(rootDir, readWorkspaceGlobs(rootDir, readJson(path.join(rootDir, "package.json"))))];
|
|
1833
|
+
for (const dir of dirs) for (const fname of TS_CONFIG_FILES) collectAliasMatchersFromConfig(path.join(dir, fname), matchers);
|
|
1834
|
+
return matchers;
|
|
1835
|
+
};
|
|
1455
1836
|
const addPyDep = (pyDeps, name) => {
|
|
1456
1837
|
const normalized = name.toLowerCase().replace(/_/g, "-");
|
|
1457
1838
|
pyDeps.add(normalized);
|
|
@@ -1609,10 +1990,11 @@ const extractPyImports = (content) => {
|
|
|
1609
1990
|
}
|
|
1610
1991
|
return results;
|
|
1611
1992
|
};
|
|
1612
|
-
const checkJsImport = (spec, manifest) => {
|
|
1993
|
+
const checkJsImport = (spec, manifest, tsAliasMatchers) => {
|
|
1613
1994
|
if (isJsRelativeOrAbsolute(spec)) return null;
|
|
1614
1995
|
if (isJsBuiltin(spec)) return null;
|
|
1615
1996
|
if (isJsVirtualModule(spec)) return null;
|
|
1997
|
+
if (tsAliasMatchers.some((m) => m(spec))) return null;
|
|
1616
1998
|
const pkg = packageNameFromImport(spec);
|
|
1617
1999
|
if (manifest.jsDeps.has(pkg)) return null;
|
|
1618
2000
|
if (pkg.startsWith("@types/")) {
|
|
@@ -1633,6 +2015,7 @@ const checkPyImport = (spec, manifest) => {
|
|
|
1633
2015
|
const detectHallucinatedImports = async (context) => {
|
|
1634
2016
|
const manifest = loadManifest(context.rootDirectory);
|
|
1635
2017
|
if (!manifest.hasJsManifest && !manifest.hasPyManifest) return [];
|
|
2018
|
+
const tsAliasMatchers = manifest.hasJsManifest ? collectTsPathAliases(context.rootDirectory) : [];
|
|
1636
2019
|
const diagnostics = [];
|
|
1637
2020
|
const files = getSourceFiles(context);
|
|
1638
2021
|
for (const filePath of files) {
|
|
@@ -1652,7 +2035,7 @@ const detectHallucinatedImports = async (context) => {
|
|
|
1652
2035
|
const relPath = path.relative(context.rootDirectory, filePath);
|
|
1653
2036
|
const imports = isJs ? extractJsImports(content) : extractPyImports(content);
|
|
1654
2037
|
for (const { spec, line } of imports) {
|
|
1655
|
-
const hallucinated = isJs ? checkJsImport(spec, manifest) : checkPyImport(spec, manifest);
|
|
2038
|
+
const hallucinated = isJs ? checkJsImport(spec, manifest, tsAliasMatchers) : checkPyImport(spec, manifest);
|
|
1656
2039
|
if (!hallucinated) continue;
|
|
1657
2040
|
const manifestLabel = isJs ? "package.json" : "requirements.txt / pyproject.toml / Pipfile";
|
|
1658
2041
|
diagnostics.push({
|
|
@@ -4613,7 +4996,7 @@ const lintEngine = {
|
|
|
4613
4996
|
const promises = [];
|
|
4614
4997
|
if (languages.includes("typescript") || languages.includes("javascript")) {
|
|
4615
4998
|
promises.push(runOxlint(context));
|
|
4616
|
-
if (context.config.lint.typecheck) promises.push(import("./typecheck-
|
|
4999
|
+
if (context.config.lint.typecheck) promises.push(import("./typecheck-wVSohmOX.js").then((mod) => mod.runTypecheck(context)));
|
|
4617
5000
|
}
|
|
4618
5001
|
if (context.frameworks.includes("expo")) promises.push(Promise.resolve().then(() => expo_doctor_exports).then((mod) => mod.runExpoDoctor(context)));
|
|
4619
5002
|
if (languages.includes("python") && installedTools["ruff"]) promises.push(runRuffLint(context));
|
|
@@ -5832,6 +6215,16 @@ const runClaudeHook = async (deps = {}) => {
|
|
|
5832
6215
|
score: baseline.score,
|
|
5833
6216
|
findingFingerprints: baseline.findingFingerprints
|
|
5834
6217
|
} : void 0);
|
|
6218
|
+
track({
|
|
6219
|
+
event: "hook_scan_completed",
|
|
6220
|
+
properties: buildHookScanCompletedProps({
|
|
6221
|
+
agent: "claude",
|
|
6222
|
+
score,
|
|
6223
|
+
scoreDelta: baseline ? score - baseline.score : null,
|
|
6224
|
+
findingCount: diagnostics.length,
|
|
6225
|
+
fileCount: files.length
|
|
6226
|
+
})
|
|
6227
|
+
});
|
|
5835
6228
|
const envelope = renderClaudeOutput(JSON.stringify(feedback));
|
|
5836
6229
|
write(JSON.stringify(envelope));
|
|
5837
6230
|
return 0;
|
|
@@ -5963,6 +6356,15 @@ const runCursorHook = async (deps = {}) => {
|
|
|
5963
6356
|
try {
|
|
5964
6357
|
const { diagnostics, score, rootDirectory } = await runScopedScan(cwd, files);
|
|
5965
6358
|
const feedback = buildFeedback(diagnostics, score, rootDirectory);
|
|
6359
|
+
track({
|
|
6360
|
+
event: "hook_scan_completed",
|
|
6361
|
+
properties: buildHookScanCompletedProps({
|
|
6362
|
+
agent: "cursor",
|
|
6363
|
+
score,
|
|
6364
|
+
findingCount: diagnostics.length,
|
|
6365
|
+
fileCount: files.length
|
|
6366
|
+
})
|
|
6367
|
+
});
|
|
5966
6368
|
const serialized = JSON.stringify(feedback);
|
|
5967
6369
|
write(JSON.stringify(renderCursorOutput(serialized)));
|
|
5968
6370
|
writeErr(`${serialized}\n`);
|
|
@@ -6013,6 +6415,15 @@ const runGeminiHook = async (deps = {}) => {
|
|
|
6013
6415
|
try {
|
|
6014
6416
|
const { diagnostics, score, rootDirectory } = await runScopedScan(cwd, files);
|
|
6015
6417
|
const feedback = buildFeedback(diagnostics, score, rootDirectory);
|
|
6418
|
+
track({
|
|
6419
|
+
event: "hook_scan_completed",
|
|
6420
|
+
properties: buildHookScanCompletedProps({
|
|
6421
|
+
agent: "gemini",
|
|
6422
|
+
score,
|
|
6423
|
+
findingCount: diagnostics.length,
|
|
6424
|
+
fileCount: files.length
|
|
6425
|
+
})
|
|
6426
|
+
});
|
|
6016
6427
|
write(JSON.stringify(renderGeminiOutput(JSON.stringify(feedback))));
|
|
6017
6428
|
return 0;
|
|
6018
6429
|
} catch {
|
|
@@ -7027,12 +7438,18 @@ const registerInstall = (hook) => {
|
|
|
7027
7438
|
install.action(async (positional, opts) => {
|
|
7028
7439
|
const agents = await pickAgents("install", opts, positional);
|
|
7029
7440
|
if (agents === null || agents.length === 0) return;
|
|
7030
|
-
await
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7441
|
+
await withCommandLifecycle({
|
|
7442
|
+
command: "hook_install",
|
|
7443
|
+
config: loadConfig(process.cwd()).telemetry
|
|
7444
|
+
}, async () => {
|
|
7445
|
+
await hookInstall({
|
|
7446
|
+
agents,
|
|
7447
|
+
scope: resolveScope(opts),
|
|
7448
|
+
dryRun: Boolean(opts.dryRun),
|
|
7449
|
+
yes: Boolean(opts.yes),
|
|
7450
|
+
qualityGate: Boolean(opts.qualityGate)
|
|
7451
|
+
});
|
|
7452
|
+
return { exitCode: 0 };
|
|
7036
7453
|
});
|
|
7037
7454
|
});
|
|
7038
7455
|
};
|
|
@@ -7042,21 +7459,39 @@ const registerUninstall = (hook) => {
|
|
|
7042
7459
|
uninstall.action(async (positional, opts) => {
|
|
7043
7460
|
const agents = await pickAgents("uninstall", opts, positional);
|
|
7044
7461
|
if (agents === null || agents.length === 0) return;
|
|
7045
|
-
await
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7462
|
+
await withCommandLifecycle({
|
|
7463
|
+
command: "hook_uninstall",
|
|
7464
|
+
config: loadConfig(process.cwd()).telemetry
|
|
7465
|
+
}, async () => {
|
|
7466
|
+
await hookUninstall({
|
|
7467
|
+
agents,
|
|
7468
|
+
scope: resolveScope(opts),
|
|
7469
|
+
dryRun: Boolean(opts.dryRun),
|
|
7470
|
+
yes: true,
|
|
7471
|
+
qualityGate: false
|
|
7472
|
+
});
|
|
7473
|
+
return { exitCode: 0 };
|
|
7051
7474
|
});
|
|
7052
7475
|
});
|
|
7053
7476
|
};
|
|
7054
7477
|
const registerCallbacks = (hook) => {
|
|
7055
7478
|
hook.command("status").description("Show which agent hooks are installed").action(async () => {
|
|
7056
|
-
await
|
|
7479
|
+
await withCommandLifecycle({
|
|
7480
|
+
command: "hook_status",
|
|
7481
|
+
config: loadConfig(process.cwd()).telemetry
|
|
7482
|
+
}, async () => {
|
|
7483
|
+
await hookStatus();
|
|
7484
|
+
return { exitCode: 0 };
|
|
7485
|
+
});
|
|
7057
7486
|
});
|
|
7058
7487
|
hook.command("baseline").description("Capture the current project score as the quality-gate baseline").action(async () => {
|
|
7059
|
-
await
|
|
7488
|
+
await withCommandLifecycle({
|
|
7489
|
+
command: "hook_baseline",
|
|
7490
|
+
config: loadConfig(process.cwd()).telemetry
|
|
7491
|
+
}, async () => {
|
|
7492
|
+
await hookBaseline();
|
|
7493
|
+
return { exitCode: 0 };
|
|
7494
|
+
});
|
|
7060
7495
|
});
|
|
7061
7496
|
hook.command("claude").description("Internal: Claude Code PostToolUse / Stop / FileChanged callback (reads stdin)").option("--stop", "run in Stop-hook mode for the quality gate").option("--on-file-changed", "run in FileChanged mode (refresh baseline on watched file change)").action(async (opts) => {
|
|
7062
7497
|
await hookRun("claude", {
|
|
@@ -7569,73 +8004,6 @@ const renderCleanRun = (input, deps = {}) => {
|
|
|
7569
8004
|
return `\n ${parts.join(` ${sep} `)}\n`;
|
|
7570
8005
|
};
|
|
7571
8006
|
|
|
7572
|
-
//#endregion
|
|
7573
|
-
//#region src/version.ts
|
|
7574
|
-
const APP_VERSION = "0.8.2";
|
|
7575
|
-
|
|
7576
|
-
//#endregion
|
|
7577
|
-
//#region src/utils/telemetry.ts
|
|
7578
|
-
const POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
7579
|
-
const POSTHOG_KEY = "phc_eY2cOMFva9q24GrWeOuvuVIOhCIdjOALxeAR3ItrqbJ";
|
|
7580
|
-
/**
|
|
7581
|
-
* Returns true if telemetry should be disabled.
|
|
7582
|
-
* Telemetry is opt-out: it runs unless explicitly disabled.
|
|
7583
|
-
*/
|
|
7584
|
-
const isTelemetryDisabled = (configEnabled) => {
|
|
7585
|
-
if (process.env.AISLOP_NO_TELEMETRY === "1" || process.env.DO_NOT_TRACK === "1") return true;
|
|
7586
|
-
if (process.env.CI === "true" || process.env.CI === "1") return true;
|
|
7587
|
-
if (configEnabled === false) return true;
|
|
7588
|
-
return false;
|
|
7589
|
-
};
|
|
7590
|
-
const getScoreBucket = (score) => {
|
|
7591
|
-
if (score >= 75) return "75-100";
|
|
7592
|
-
if (score >= 50) return "50-75";
|
|
7593
|
-
if (score >= 25) return "25-50";
|
|
7594
|
-
return "0-25";
|
|
7595
|
-
};
|
|
7596
|
-
const getAnonymousId = () => {
|
|
7597
|
-
const raw = `${os.hostname()}-${os.platform()}-${os.arch()}`;
|
|
7598
|
-
let hash = 5381;
|
|
7599
|
-
for (let i = 0; i < raw.length; i++) hash = hash * 33 ^ raw.charCodeAt(i);
|
|
7600
|
-
return `aislop_${(hash >>> 0).toString(36)}`;
|
|
7601
|
-
};
|
|
7602
|
-
/** Pending telemetry request — kept alive so Node doesn't exit before it completes. */
|
|
7603
|
-
let pendingRequest = null;
|
|
7604
|
-
const trackEvent = (event) => {
|
|
7605
|
-
const payload = {
|
|
7606
|
-
api_key: POSTHOG_KEY,
|
|
7607
|
-
event: `cli_${event.command}`,
|
|
7608
|
-
distinct_id: getAnonymousId(),
|
|
7609
|
-
properties: {
|
|
7610
|
-
version: APP_VERSION,
|
|
7611
|
-
node_version: process.version,
|
|
7612
|
-
os: os.platform(),
|
|
7613
|
-
arch: os.arch(),
|
|
7614
|
-
languages: event.languages,
|
|
7615
|
-
score_bucket: event.scoreBucket,
|
|
7616
|
-
engine_issues: event.engineIssues,
|
|
7617
|
-
engine_timings: event.engineTimings,
|
|
7618
|
-
elapsed_ms: event.elapsedMs,
|
|
7619
|
-
file_count: event.fileCount,
|
|
7620
|
-
fix_steps: event.fixSteps,
|
|
7621
|
-
fix_resolved: event.fixResolved
|
|
7622
|
-
},
|
|
7623
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7624
|
-
};
|
|
7625
|
-
pendingRequest = fetch(`${POSTHOG_HOST}/capture/`, {
|
|
7626
|
-
method: "POST",
|
|
7627
|
-
headers: { "Content-Type": "application/json" },
|
|
7628
|
-
body: JSON.stringify(payload),
|
|
7629
|
-
signal: AbortSignal.timeout(3e3)
|
|
7630
|
-
}).then(() => {}).catch(() => {});
|
|
7631
|
-
};
|
|
7632
|
-
const flushTelemetry = async () => {
|
|
7633
|
-
if (pendingRequest) {
|
|
7634
|
-
await pendingRequest;
|
|
7635
|
-
pendingRequest = null;
|
|
7636
|
-
}
|
|
7637
|
-
};
|
|
7638
|
-
|
|
7639
8007
|
//#endregion
|
|
7640
8008
|
//#region src/commands/scan.ts
|
|
7641
8009
|
const shouldUseSpinner = () => Boolean(process.stderr.isTTY) && process.env.CI !== "true" && process.env.CI !== "1";
|
|
@@ -7693,7 +8061,6 @@ const buildScanRender = (input) => {
|
|
|
7693
8061
|
}, deps)}`;
|
|
7694
8062
|
};
|
|
7695
8063
|
const scanCommand = async (directory, config, options) => {
|
|
7696
|
-
const startTime = performance.now();
|
|
7697
8064
|
const resolvedDir = path.resolve(directory);
|
|
7698
8065
|
if (!fs.existsSync(resolvedDir)) {
|
|
7699
8066
|
const msg = `Path does not exist: ${resolvedDir}`;
|
|
@@ -7707,9 +8074,18 @@ const scanCommand = async (directory, config, options) => {
|
|
|
7707
8074
|
else log.error(msg);
|
|
7708
8075
|
return { exitCode: 1 };
|
|
7709
8076
|
}
|
|
8077
|
+
const projectInfo = await discoverProject(resolvedDir);
|
|
8078
|
+
return withCommandLifecycle({
|
|
8079
|
+
command: options.command ?? "scan",
|
|
8080
|
+
config: config.telemetry,
|
|
8081
|
+
languages: projectInfo.languages,
|
|
8082
|
+
fileCount: projectInfo.sourceFileCount
|
|
8083
|
+
}, () => runScanBody(resolvedDir, config, options, projectInfo));
|
|
8084
|
+
};
|
|
8085
|
+
const runScanBody = async (resolvedDir, config, options, projectInfo) => {
|
|
8086
|
+
const startTime = performance.now();
|
|
7710
8087
|
const showHeader = options.showHeader !== false;
|
|
7711
8088
|
const useLiveProgress = !options.json && shouldUseSpinner();
|
|
7712
|
-
const projectInfo = await discoverProject(resolvedDir);
|
|
7713
8089
|
let files;
|
|
7714
8090
|
if (options.staged) {
|
|
7715
8091
|
files = filterProjectFiles(resolvedDir, getStagedFiles(resolvedDir), [], config.exclude);
|
|
@@ -7776,28 +8152,27 @@ const scanCommand = async (directory, config, options) => {
|
|
|
7776
8152
|
const elapsedMs = performance.now() - startTime;
|
|
7777
8153
|
const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds, projectInfo.sourceFileCount, config.scoring.smoothing);
|
|
7778
8154
|
const exitCode = allDiagnostics.some((d) => d.severity === "error") || scoreResult.score < config.ci.failBelow ? 1 : 0;
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
}
|
|
8155
|
+
const engineIssues = {};
|
|
8156
|
+
const engineTimings = {};
|
|
8157
|
+
for (const r of results) {
|
|
8158
|
+
engineIssues[r.engine] = r.diagnostics.length;
|
|
8159
|
+
engineTimings[r.engine] = Math.round(r.elapsed);
|
|
8160
|
+
}
|
|
8161
|
+
const completion = {
|
|
8162
|
+
exitCode,
|
|
8163
|
+
score: scoreResult.score,
|
|
8164
|
+
findingCount: allDiagnostics.length,
|
|
8165
|
+
errorCount: allDiagnostics.filter((d) => d.severity === "error").length,
|
|
8166
|
+
warningCount: allDiagnostics.filter((d) => d.severity === "warning").length,
|
|
8167
|
+
fixableCount: allDiagnostics.filter((d) => d.fixable).length,
|
|
8168
|
+
engineIssues,
|
|
8169
|
+
engineTimings
|
|
8170
|
+
};
|
|
7796
8171
|
if (options.json) {
|
|
7797
|
-
const { buildJsonOutput } = await import("./json-
|
|
8172
|
+
const { buildJsonOutput } = await import("./json-OIzja7OM.js");
|
|
7798
8173
|
const jsonOut = buildJsonOutput(results, scoreResult, projectInfo.sourceFileCount, elapsedMs);
|
|
7799
8174
|
console.log(JSON.stringify(jsonOut, null, 2));
|
|
7800
|
-
return
|
|
8175
|
+
return completion;
|
|
7801
8176
|
}
|
|
7802
8177
|
const projectName = projectInfo.projectName ?? "project";
|
|
7803
8178
|
const language = projectInfo.languages[0] ?? "unknown";
|
|
@@ -7814,7 +8189,7 @@ const scanCommand = async (directory, config, options) => {
|
|
|
7814
8189
|
includeHeader: showHeader,
|
|
7815
8190
|
printBrand: options.printBrand
|
|
7816
8191
|
}));
|
|
7817
|
-
return
|
|
8192
|
+
return completion;
|
|
7818
8193
|
};
|
|
7819
8194
|
|
|
7820
8195
|
//#endregion
|
|
@@ -9762,15 +10137,23 @@ const fixCommand = async (directory, config, options = {
|
|
|
9762
10137
|
verbose: false,
|
|
9763
10138
|
showHeader: true
|
|
9764
10139
|
}) => {
|
|
9765
|
-
const startTime = performance.now();
|
|
9766
10140
|
const resolvedDir = path.resolve(directory);
|
|
9767
10141
|
if (!fs.existsSync(resolvedDir) || !fs.statSync(resolvedDir).isDirectory()) {
|
|
9768
10142
|
const msg = !fs.existsSync(resolvedDir) ? `Path does not exist: ${resolvedDir}` : `Not a directory: ${resolvedDir}`;
|
|
9769
10143
|
log.error(msg);
|
|
9770
10144
|
return;
|
|
9771
10145
|
}
|
|
9772
|
-
const showHeader = options.showHeader !== false;
|
|
9773
10146
|
const projectInfo = await discoverProject(resolvedDir);
|
|
10147
|
+
await withCommandLifecycle({
|
|
10148
|
+
command: "fix",
|
|
10149
|
+
config: config.telemetry,
|
|
10150
|
+
languages: projectInfo.languages,
|
|
10151
|
+
fileCount: projectInfo.sourceFileCount
|
|
10152
|
+
}, () => runFixBody(resolvedDir, config, options, projectInfo));
|
|
10153
|
+
};
|
|
10154
|
+
const runFixBody = async (resolvedDir, config, options, projectInfo) => {
|
|
10155
|
+
const startTime = performance.now();
|
|
10156
|
+
const showHeader = options.showHeader !== false;
|
|
9774
10157
|
const projectName = projectInfo.projectName ?? "project";
|
|
9775
10158
|
if (showHeader) process.stdout.write(renderHeader({
|
|
9776
10159
|
version: APP_VERSION,
|
|
@@ -9807,12 +10190,6 @@ const fixCommand = async (directory, config, options = {
|
|
|
9807
10190
|
await runFormattingStep(pipelineDeps);
|
|
9808
10191
|
await runForceSteps(pipelineDeps);
|
|
9809
10192
|
const totalResolved = steps.reduce((sum, s) => sum + s.resolvedIssues, 0);
|
|
9810
|
-
if (!isTelemetryDisabled(config.telemetry?.enabled)) trackEvent({
|
|
9811
|
-
command: "fix",
|
|
9812
|
-
languages: projectInfo.languages,
|
|
9813
|
-
fixSteps: steps.length,
|
|
9814
|
-
fixResolved: totalResolved
|
|
9815
|
-
});
|
|
9816
10193
|
const configDir = findConfigDir(resolvedDir);
|
|
9817
10194
|
const rulesPath = configDir ? path.join(configDir, RULES_FILE) : void 0;
|
|
9818
10195
|
const engineConfig = {
|
|
@@ -9835,7 +10212,9 @@ const fixCommand = async (directory, config, options = {
|
|
|
9835
10212
|
});
|
|
9836
10213
|
const allDiagnostics = scanResults.flatMap((r) => r.diagnostics);
|
|
9837
10214
|
const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds, projectInfo.sourceFileCount, config.scoring.smoothing);
|
|
9838
|
-
const
|
|
10215
|
+
const errors = allDiagnostics.filter((d) => d.severity === "error").length;
|
|
10216
|
+
const warnings = allDiagnostics.filter((d) => d.severity === "warning").length;
|
|
10217
|
+
const remaining = errors + warnings;
|
|
9839
10218
|
if (steps.length === 0) rail.complete({
|
|
9840
10219
|
status: "skipped",
|
|
9841
10220
|
label: "No applicable auto-fixers found"
|
|
@@ -9864,12 +10243,31 @@ const fixCommand = async (directory, config, options = {
|
|
|
9864
10243
|
}
|
|
9865
10244
|
if (options.agent) {
|
|
9866
10245
|
launchAgent(options.agent, resolvedDir, allDiagnostics, scoreResult.score);
|
|
9867
|
-
return
|
|
10246
|
+
return {
|
|
10247
|
+
exitCode: 0,
|
|
10248
|
+
score: scoreResult.score,
|
|
10249
|
+
fixSteps: steps.length,
|
|
10250
|
+
fixResolved: totalResolved
|
|
10251
|
+
};
|
|
9868
10252
|
}
|
|
9869
10253
|
if (options.prompt) {
|
|
9870
10254
|
printPrompt(resolvedDir, allDiagnostics, scoreResult.score);
|
|
9871
|
-
return
|
|
10255
|
+
return {
|
|
10256
|
+
exitCode: 0,
|
|
10257
|
+
score: scoreResult.score,
|
|
10258
|
+
fixSteps: steps.length,
|
|
10259
|
+
fixResolved: totalResolved
|
|
10260
|
+
};
|
|
9872
10261
|
}
|
|
10262
|
+
return {
|
|
10263
|
+
exitCode: 0,
|
|
10264
|
+
score: scoreResult.score,
|
|
10265
|
+
findingCount: allDiagnostics.length,
|
|
10266
|
+
errorCount: errors,
|
|
10267
|
+
warningCount: warnings,
|
|
10268
|
+
fixSteps: steps.length,
|
|
10269
|
+
fixResolved: totalResolved
|
|
10270
|
+
};
|
|
9873
10271
|
};
|
|
9874
10272
|
|
|
9875
10273
|
//#endregion
|
|
@@ -10352,6 +10750,13 @@ const interactiveCommand = async (directory, config) => {
|
|
|
10352
10750
|
//#region src/cli.ts
|
|
10353
10751
|
process.on("SIGINT", () => process.exit(0));
|
|
10354
10752
|
process.on("SIGTERM", () => process.exit(0));
|
|
10753
|
+
const fireInstalledOnce = () => {
|
|
10754
|
+
if (isTelemetryDisabled(loadConfig(process.cwd()).telemetry)) return;
|
|
10755
|
+
if (ensureInstallId(resolveInstallIdPath()).created) track({
|
|
10756
|
+
event: "cli_installed",
|
|
10757
|
+
config: loadConfig(process.cwd()).telemetry
|
|
10758
|
+
});
|
|
10759
|
+
};
|
|
10355
10760
|
const excludeParser = (value, previous = []) => {
|
|
10356
10761
|
const parts = value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
10357
10762
|
return [...previous, ...parts];
|
|
@@ -10500,10 +10905,22 @@ fixProgram.action(async (directory = ".", _flags, command) => {
|
|
|
10500
10905
|
});
|
|
10501
10906
|
});
|
|
10502
10907
|
program.command("init [directory]").description("Initialize aislop config in project").action(async (directory = ".") => {
|
|
10503
|
-
await
|
|
10908
|
+
await withCommandLifecycle({
|
|
10909
|
+
command: "init",
|
|
10910
|
+
config: loadConfig(directory).telemetry
|
|
10911
|
+
}, async () => {
|
|
10912
|
+
await initCommand(directory);
|
|
10913
|
+
return { exitCode: 0 };
|
|
10914
|
+
});
|
|
10504
10915
|
});
|
|
10505
10916
|
program.command("doctor [directory]").description("Check installed tools and environment").action(async (directory = ".") => {
|
|
10506
|
-
await
|
|
10917
|
+
await withCommandLifecycle({
|
|
10918
|
+
command: "doctor",
|
|
10919
|
+
config: loadConfig(directory).telemetry
|
|
10920
|
+
}, async () => {
|
|
10921
|
+
await doctorCommand(directory);
|
|
10922
|
+
return { exitCode: 0 };
|
|
10923
|
+
});
|
|
10507
10924
|
});
|
|
10508
10925
|
program.command("ci [directory]").description("CI-friendly JSON output with exit codes").option("--human", "render the human-friendly scan design instead of JSON").action(async (directory = ".", _flags, command) => {
|
|
10509
10926
|
const flags = command.optsWithGlobals();
|
|
@@ -10514,16 +10931,28 @@ program.command("ci [directory]").description("CI-friendly JSON output with exit
|
|
|
10514
10931
|
}
|
|
10515
10932
|
});
|
|
10516
10933
|
program.command("rules [directory]").description("List all available rules").action(async (directory = ".") => {
|
|
10517
|
-
await
|
|
10934
|
+
await withCommandLifecycle({
|
|
10935
|
+
command: "rules",
|
|
10936
|
+
config: loadConfig(directory).telemetry
|
|
10937
|
+
}, async () => {
|
|
10938
|
+
await rulesCommand(directory);
|
|
10939
|
+
return { exitCode: 0 };
|
|
10940
|
+
});
|
|
10518
10941
|
});
|
|
10519
10942
|
program.command("badge [directory]").description("Print the public score badge URL + README markdown for this repo").option("--owner <owner>", "GitHub owner (auto-detected from git remote if omitted)").option("--repo <repo>", "GitHub repo name (auto-detected from git remote if omitted)").option("--json", "emit machine-readable JSON instead of the rendered output").action(async (directory = ".", _flags, command) => {
|
|
10520
10943
|
const flags = command.optsWithGlobals();
|
|
10521
10944
|
try {
|
|
10522
|
-
await
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10945
|
+
await withCommandLifecycle({
|
|
10946
|
+
command: "badge",
|
|
10947
|
+
config: loadConfig(directory).telemetry
|
|
10948
|
+
}, async () => {
|
|
10949
|
+
await badgeCommand({
|
|
10950
|
+
directory,
|
|
10951
|
+
owner: flags.owner,
|
|
10952
|
+
repo: flags.repo,
|
|
10953
|
+
json: Boolean(flags.json)
|
|
10954
|
+
});
|
|
10955
|
+
return { exitCode: 0 };
|
|
10527
10956
|
});
|
|
10528
10957
|
} catch (err) {
|
|
10529
10958
|
process.stderr.write(`${err?.message ?? "Failed to print badge"}\n`);
|
|
@@ -10532,10 +10961,11 @@ program.command("badge [directory]").description("Print the public score badge U
|
|
|
10532
10961
|
});
|
|
10533
10962
|
registerHookCommand(program);
|
|
10534
10963
|
const main = async () => {
|
|
10964
|
+
fireInstalledOnce();
|
|
10535
10965
|
await program.parseAsync();
|
|
10536
10966
|
await flushTelemetry();
|
|
10537
10967
|
};
|
|
10538
10968
|
main();
|
|
10539
10969
|
|
|
10540
10970
|
//#endregion
|
|
10541
|
-
export {
|
|
10971
|
+
export { runSubprocess as n, APP_VERSION as r, ENGINE_INFO as t };
|