@methiu/raigal 0.2.2 → 0.2.3
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/{AgentApp-CIiRfOun.js → AgentApp-DJYdD1VH.js} +4 -2
- package/dist/adapters/vite.js +2 -1
- package/dist/cli.js +500 -178
- package/dist/{finding-assessment-M5fUTE-C.js → finding-assessment-CGpt5YUR.js} +3 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +388 -130
- package/dist/{json-DBoMe4xB.js → json-BflWt4dX.js} +2 -2
- package/dist/mcp.js +301 -105
- package/dist/{mount-DTc8WaIt.js → mount-CHGc3gB6.js} +1 -1
- package/dist/{sarif--DI_2p2r.js → sarif-CdtsAfJ1.js} +1 -1
- package/dist/{sarif-Ba5D8TPH.js → sarif-CpuYM3aK.js} +2 -2
- package/dist/{version-9Cnt1TiK.js → version-B4eYJn3o.js} +1 -1
- package/package.json +1 -1
- package/scripts/release-guard.mjs +2 -2
package/dist/cli.js
CHANGED
|
@@ -13,8 +13,7 @@ import { z } from "zod/v4";
|
|
|
13
13
|
import crypto, { createHash, createPublicKey, randomUUID, verify } from "node:crypto";
|
|
14
14
|
import YAML, { parse } from "yaml";
|
|
15
15
|
import { confirm, isCancel, multiselect, select, text } from "@clack/prompts";
|
|
16
|
-
import * as readline$1 from "node:readline";
|
|
17
|
-
import readline from "node:readline";
|
|
16
|
+
import readline, * as readline$1 from "node:readline";
|
|
18
17
|
import picomatch from "picomatch";
|
|
19
18
|
import ts from "typescript";
|
|
20
19
|
import { parse as parse$1 } from "smol-toml";
|
|
@@ -113,7 +112,8 @@ const commandExists = (bin) => {
|
|
|
113
112
|
}
|
|
114
113
|
}
|
|
115
114
|
}
|
|
116
|
-
const
|
|
115
|
+
const command = process.platform === "win32" ? "where.exe" : "which";
|
|
116
|
+
const result = spawnSync(command, [bin], {
|
|
117
117
|
encoding: "utf-8",
|
|
118
118
|
timeout: PROVIDER_PROBE_TIMEOUT_MS
|
|
119
119
|
});
|
|
@@ -711,7 +711,8 @@ const stringWidth = (s) => {
|
|
|
711
711
|
const bare = stripAnsi$1(s);
|
|
712
712
|
let total = 0;
|
|
713
713
|
for (const ch of bare) {
|
|
714
|
-
const
|
|
714
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
715
|
+
const w = wcwidth(cp);
|
|
715
716
|
total += w > 0 ? w : 1;
|
|
716
717
|
}
|
|
717
718
|
return total;
|
|
@@ -727,7 +728,8 @@ const truncate = (s, max, ellipsis = "…") => {
|
|
|
727
728
|
let out = "";
|
|
728
729
|
let w = 0;
|
|
729
730
|
for (const ch of s) {
|
|
730
|
-
const
|
|
731
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
732
|
+
const cw = wcwidth(cp);
|
|
731
733
|
if (w + cw > limit) break;
|
|
732
734
|
out += ch;
|
|
733
735
|
w += cw;
|
|
@@ -737,7 +739,8 @@ const truncate = (s, max, ellipsis = "…") => {
|
|
|
737
739
|
//#endregion
|
|
738
740
|
//#region src/ui/display.ts
|
|
739
741
|
const renderDisplaySection = (label, deps = {}) => {
|
|
740
|
-
|
|
742
|
+
const t = deps.theme ?? theme;
|
|
743
|
+
return style$1(t, "section", label);
|
|
741
744
|
};
|
|
742
745
|
const renderDisplayRows = (rows, options = {}) => {
|
|
743
746
|
if (rows.length === 0) return [];
|
|
@@ -898,7 +901,8 @@ const symbols = createSymbols({ plain: isPlain() });
|
|
|
898
901
|
*/
|
|
899
902
|
const renderHintLine = (hint, deps = {}) => {
|
|
900
903
|
const t = deps.theme ?? theme;
|
|
901
|
-
|
|
904
|
+
const s = deps.symbols ?? symbols;
|
|
905
|
+
return ` ${style$1(t, "accent", s.hint)} ${highlightAislop(maskSecrets(hint), t)}\n`;
|
|
902
906
|
};
|
|
903
907
|
const createLogger = (deps = {}) => {
|
|
904
908
|
const t = deps.theme ?? theme;
|
|
@@ -923,7 +927,7 @@ const createLogger = (deps = {}) => {
|
|
|
923
927
|
const log = createLogger();
|
|
924
928
|
//#endregion
|
|
925
929
|
//#region src/version.ts
|
|
926
|
-
const APP_VERSION = "0.2.
|
|
930
|
+
const APP_VERSION = "0.2.3";
|
|
927
931
|
//#endregion
|
|
928
932
|
//#region src/commands/agent-connect.ts
|
|
929
933
|
const commandText = (status) => [status.provider.loginCommand.command, ...status.provider.loginCommand.args].join(" ");
|
|
@@ -1410,7 +1414,9 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
|
|
|
1410
1414
|
if (!header.kid) throw new JwtVerificationError("JWT header missing 'kid'", "unknown_kid");
|
|
1411
1415
|
const publicKey = getPublicKey(header.kid);
|
|
1412
1416
|
if (!publicKey) throw new JwtVerificationError(`Unknown or untrusted signing key id (kid: ${header.kid})`, "unknown_kid");
|
|
1413
|
-
|
|
1417
|
+
const signedData = Buffer.from(`${headerB64}.${payloadB64}`, "utf-8");
|
|
1418
|
+
const signature = Buffer.from(signatureB64, "base64url");
|
|
1419
|
+
if (!verify(null, signedData, publicKey, signature)) throw new JwtVerificationError("Invalid JWT cryptographic signature", "invalid_signature");
|
|
1414
1420
|
let rawPayload;
|
|
1415
1421
|
try {
|
|
1416
1422
|
rawPayload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf-8"));
|
|
@@ -1422,7 +1428,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
|
|
|
1422
1428
|
const claims = parsed.data;
|
|
1423
1429
|
const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
|
|
1424
1430
|
if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
|
|
1425
|
-
if (!isVersionAtLeast("0.2.
|
|
1431
|
+
if (!isVersionAtLeast("0.2.3", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
|
|
1426
1432
|
const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
1427
1433
|
const grace = options.allowGraceSeconds ?? 0;
|
|
1428
1434
|
if (now > claims.exp + grace) {
|
|
@@ -1456,7 +1462,7 @@ const fetchGitHubOidcToken = async () => {
|
|
|
1456
1462
|
};
|
|
1457
1463
|
//#endregion
|
|
1458
1464
|
//#region src/cloud/entitlement.ts
|
|
1459
|
-
const GRACE_PERIOD_SECONDS =
|
|
1465
|
+
const GRACE_PERIOD_SECONDS = 259200;
|
|
1460
1466
|
var MissingCredentialError = class extends Error {
|
|
1461
1467
|
constructor(message = "No organization credential found") {
|
|
1462
1468
|
super(message);
|
|
@@ -1581,7 +1587,8 @@ const getEntitlement = async () => {
|
|
|
1581
1587
|
}
|
|
1582
1588
|
} else oidcToken = await fetchGitHubOidcToken();
|
|
1583
1589
|
try {
|
|
1584
|
-
const
|
|
1590
|
+
const tokenToPass = activeToken?.token ?? "";
|
|
1591
|
+
const response = await fetchEntitlementFromApi(tokenToPass, oidcToken);
|
|
1585
1592
|
const claims = verifyEntitlementJwt(response.entitlement, { nowSeconds: now });
|
|
1586
1593
|
saveCachedEntitlement(response.entitlement, response.refresh_after);
|
|
1587
1594
|
return {
|
|
@@ -1595,7 +1602,7 @@ const getEntitlement = async () => {
|
|
|
1595
1602
|
};
|
|
1596
1603
|
//#endregion
|
|
1597
1604
|
//#region src/cloud/owner.ts
|
|
1598
|
-
const GITHUB_REMOTE_RE$1 = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([
|
|
1605
|
+
const GITHUB_REMOTE_RE$1 = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
|
|
1599
1606
|
const parseGitRemote = (rawUrl) => {
|
|
1600
1607
|
if (!rawUrl) return {
|
|
1601
1608
|
owner: null,
|
|
@@ -2376,7 +2383,7 @@ var AgentTui = class {
|
|
|
2376
2383
|
}
|
|
2377
2384
|
ensureMounted() {
|
|
2378
2385
|
if (!this.tty || this.handle) return;
|
|
2379
|
-
this.handle = import("./mount-
|
|
2386
|
+
this.handle = import("./mount-CHGc3gB6.js").then((m) => m.mountAgentTui(this.store));
|
|
2380
2387
|
}
|
|
2381
2388
|
setActions(actions) {
|
|
2382
2389
|
this.store.update({ actions: actions.filter((action) => action.trim().length > 0) });
|
|
@@ -2471,7 +2478,7 @@ const runSelf = (args, cwd) => {
|
|
|
2471
2478
|
AISLOP_NO_UPDATE_NOTIFIER: "1",
|
|
2472
2479
|
NO_COLOR: "1"
|
|
2473
2480
|
},
|
|
2474
|
-
maxBuffer:
|
|
2481
|
+
maxBuffer: 52428800
|
|
2475
2482
|
});
|
|
2476
2483
|
return {
|
|
2477
2484
|
exitCode: result.status,
|
|
@@ -2510,7 +2517,7 @@ const applyDiff = async (root, patch) => {
|
|
|
2510
2517
|
cwd: root,
|
|
2511
2518
|
input: patch,
|
|
2512
2519
|
encoding: "utf-8",
|
|
2513
|
-
maxBuffer:
|
|
2520
|
+
maxBuffer: 52428800
|
|
2514
2521
|
});
|
|
2515
2522
|
if (child.status !== 0) throw new Error(child.stderr || child.stdout || "Failed to apply agent diff.");
|
|
2516
2523
|
};
|
|
@@ -2883,7 +2890,7 @@ const detectCiContext = () => {
|
|
|
2883
2890
|
};
|
|
2884
2891
|
//#endregion
|
|
2885
2892
|
//#region src/cloud/outbox.ts
|
|
2886
|
-
const MAX_OUTBOX_BYTES =
|
|
2893
|
+
const MAX_OUTBOX_BYTES = 5242880;
|
|
2887
2894
|
const getMaxOutboxBytes = () => {
|
|
2888
2895
|
const override = process.env.RAIGAL_MAX_OUTBOX_BYTES;
|
|
2889
2896
|
if (override) {
|
|
@@ -3078,7 +3085,8 @@ var CloudRunRecorder = class {
|
|
|
3078
3085
|
if (this.finished) return;
|
|
3079
3086
|
this.finished = true;
|
|
3080
3087
|
try {
|
|
3081
|
-
|
|
3088
|
+
const run = this.buildRunPayload(exitCode);
|
|
3089
|
+
enqueueRun(run);
|
|
3082
3090
|
const activeToken = getActiveToken();
|
|
3083
3091
|
let oidcToken;
|
|
3084
3092
|
if (!activeToken) oidcToken = await fetchGitHubOidcToken();
|
|
@@ -3164,7 +3172,7 @@ const recordAgentSessionTelemetry = ({ worktreePath, published, branchOption, pr
|
|
|
3164
3172
|
if (!recorder) return;
|
|
3165
3173
|
let patchDiff;
|
|
3166
3174
|
if (worktreePath) try {
|
|
3167
|
-
|
|
3175
|
+
const rawDiff = spawnSync("git", [
|
|
3168
3176
|
"diff",
|
|
3169
3177
|
"-U2",
|
|
3170
3178
|
"--no-color",
|
|
@@ -3172,7 +3180,8 @@ const recordAgentSessionTelemetry = ({ worktreePath, published, branchOption, pr
|
|
|
3172
3180
|
], {
|
|
3173
3181
|
cwd: worktreePath,
|
|
3174
3182
|
encoding: "utf-8"
|
|
3175
|
-
}).stdout || ""
|
|
3183
|
+
}).stdout || "";
|
|
3184
|
+
patchDiff = rawDiff ? maskSecrets(rawDiff).slice(0, 2e4) : void 0;
|
|
3176
3185
|
} catch {}
|
|
3177
3186
|
if (published) {
|
|
3178
3187
|
const prMatch = published.prUrl?.match(/\/pull\/(\d+)/);
|
|
@@ -3780,7 +3789,8 @@ const runSafeFixStep = (tui, cwd, options) => {
|
|
|
3780
3789
|
};
|
|
3781
3790
|
const selectFindings = (tui, cwd, limit, pass) => {
|
|
3782
3791
|
tui.start(`Pass ${pass}: selecting findings`);
|
|
3783
|
-
const
|
|
3792
|
+
const scan = scanJson(cwd);
|
|
3793
|
+
const findings = selectAgentFindings(scan.diagnostics, limit);
|
|
3784
3794
|
tui.setMetric("Selected", findings.length);
|
|
3785
3795
|
tui.complete({
|
|
3786
3796
|
status: findings.length > 0 ? "done" : "skipped",
|
|
@@ -4813,7 +4823,7 @@ const DEFAULT_RULES_YAML = `# Architecture rules (BYO)
|
|
|
4813
4823
|
//#endregion
|
|
4814
4824
|
//#region src/config/extends.ts
|
|
4815
4825
|
const MAX_DEPTH$1 = 5;
|
|
4816
|
-
const MAX_CONFIG_BYTES =
|
|
4826
|
+
const MAX_CONFIG_BYTES = 1048576;
|
|
4817
4827
|
const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4818
4828
|
const deepMerge = (...sources) => {
|
|
4819
4829
|
const result = {};
|
|
@@ -4994,6 +5004,9 @@ const CiSchema = z.object({
|
|
|
4994
5004
|
format: z.enum(["json"]).default("json")
|
|
4995
5005
|
}).strict();
|
|
4996
5006
|
const TelemetrySchema = z.object({ enabled: z.boolean().default(true) }).strict();
|
|
5007
|
+
const BaselineConfigSchema = z.object({ mode: z.enum(["strict", "relaxed"]).default("relaxed") }).strict();
|
|
5008
|
+
const LicensingConfigSchema = z.object({ onLapse: z.enum(["fail-open", "fail-closed"]).default("fail-open") }).strict();
|
|
5009
|
+
const WorkspaceConfigSchema = z.object({ minimumFloor: z.number().min(0).max(100).default(50) }).strict();
|
|
4997
5010
|
const RuleSeverityOverride = z.enum([
|
|
4998
5011
|
"error",
|
|
4999
5012
|
"warning",
|
|
@@ -5064,6 +5077,9 @@ const AislopConfigSchema = z.object({
|
|
|
5064
5077
|
format: "json"
|
|
5065
5078
|
})),
|
|
5066
5079
|
telemetry: TelemetrySchema.default(() => ({ enabled: true })),
|
|
5080
|
+
baseline: BaselineConfigSchema.optional(),
|
|
5081
|
+
licensing: LicensingConfigSchema.optional(),
|
|
5082
|
+
workspace: WorkspaceConfigSchema.optional(),
|
|
5067
5083
|
rules: RulesSchema,
|
|
5068
5084
|
overrides: z.array(FileOverrideSchema).default(() => []),
|
|
5069
5085
|
exclude: z.array(z.string()).default(() => [
|
|
@@ -5097,6 +5113,9 @@ const KNOWN_KEYS_BY_PATH = {
|
|
|
5097
5113
|
"scoring",
|
|
5098
5114
|
"ci",
|
|
5099
5115
|
"telemetry",
|
|
5116
|
+
"baseline",
|
|
5117
|
+
"licensing",
|
|
5118
|
+
"workspace",
|
|
5100
5119
|
"rules",
|
|
5101
5120
|
"exclude",
|
|
5102
5121
|
"include"
|
|
@@ -5147,7 +5166,10 @@ const KNOWN_KEYS_BY_PATH = {
|
|
|
5147
5166
|
],
|
|
5148
5167
|
"scoring.thresholds": ["good", "ok"],
|
|
5149
5168
|
ci: ["failBelow", "format"],
|
|
5150
|
-
telemetry: ["enabled"]
|
|
5169
|
+
telemetry: ["enabled"],
|
|
5170
|
+
baseline: ["mode"],
|
|
5171
|
+
licensing: ["onLapse"],
|
|
5172
|
+
workspace: ["minimumFloor"]
|
|
5151
5173
|
};
|
|
5152
5174
|
const levenshtein = (a, b) => {
|
|
5153
5175
|
const m = a.length;
|
|
@@ -5279,7 +5301,8 @@ const loadConfig = (directory) => {
|
|
|
5279
5301
|
const configPath = findConfigFile(directory);
|
|
5280
5302
|
if (!configPath) return DEFAULT_CONFIG;
|
|
5281
5303
|
try {
|
|
5282
|
-
|
|
5304
|
+
const merged = loadConfigChain(configPath);
|
|
5305
|
+
return validateConfig(merged, configPath);
|
|
5283
5306
|
} catch (error) {
|
|
5284
5307
|
if (error instanceof ConfigError) throw error;
|
|
5285
5308
|
throw new ConfigError(configPath, `Failed to parse ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -5554,10 +5577,11 @@ const track = (input) => {
|
|
|
5554
5577
|
cachedInstallId = ensured.installId;
|
|
5555
5578
|
installCreated = ensured.created;
|
|
5556
5579
|
}
|
|
5557
|
-
const
|
|
5580
|
+
const merged = {
|
|
5558
5581
|
...baseProperties(cachedInstallId),
|
|
5559
5582
|
...input.properties
|
|
5560
|
-
}
|
|
5583
|
+
};
|
|
5584
|
+
const { clean, dropped } = redactProperties(merged);
|
|
5561
5585
|
if (isDebug()) {
|
|
5562
5586
|
const compact = JSON.stringify({
|
|
5563
5587
|
event: input.event,
|
|
@@ -6397,14 +6421,17 @@ const renderRailStep = (step, deps = {}) => {
|
|
|
6397
6421
|
* between the last step and the footer.
|
|
6398
6422
|
*/
|
|
6399
6423
|
const renderRailConnector = (deps = {}) => {
|
|
6400
|
-
|
|
6424
|
+
const t = deps.theme ?? theme;
|
|
6425
|
+
const s = deps.symbols ?? symbols;
|
|
6426
|
+
return ` ${style$1(t, "accentDim", s.rail)}\n`;
|
|
6401
6427
|
};
|
|
6402
6428
|
/**
|
|
6403
6429
|
* Render the rail-end footer line ("└ <footer text>").
|
|
6404
6430
|
*/
|
|
6405
6431
|
const renderRailFooter = (footer, deps = {}) => {
|
|
6406
6432
|
const t = deps.theme ?? theme;
|
|
6407
|
-
|
|
6433
|
+
const s = deps.symbols ?? symbols;
|
|
6434
|
+
return ` ${style$1(t, "accentDim", s.railEnd)} ${highlightAislop(footer, t)}\n`;
|
|
6408
6435
|
};
|
|
6409
6436
|
const renderRail = (input, deps = {}) => {
|
|
6410
6437
|
const t = deps.theme ?? theme;
|
|
@@ -6561,7 +6588,7 @@ const launchMonitorInBackground = async (directory, options) => {
|
|
|
6561
6588
|
fs.mkdirSync(logDir, { recursive: true });
|
|
6562
6589
|
const logPath = path.join(logDir, `${monitorId}.log`);
|
|
6563
6590
|
const logFd = fs.openSync(logPath, "a");
|
|
6564
|
-
|
|
6591
|
+
const record = {
|
|
6565
6592
|
id: monitorId,
|
|
6566
6593
|
root,
|
|
6567
6594
|
requestedDirectory: path.resolve(directory),
|
|
@@ -6579,7 +6606,8 @@ const launchMonitorInBackground = async (directory, options) => {
|
|
|
6579
6606
|
noFix: options.noFix,
|
|
6580
6607
|
logPath,
|
|
6581
6608
|
recentCycles: []
|
|
6582
|
-
}
|
|
6609
|
+
};
|
|
6610
|
+
writeAgentMonitorRecord(root, record);
|
|
6583
6611
|
const child = spawn(process.execPath, [cliPath(), ...buildBackgroundMonitorArgs(directory, options)], {
|
|
6584
6612
|
cwd: root,
|
|
6585
6613
|
detached: true,
|
|
@@ -7828,7 +7856,8 @@ const registerPlanSubcommand = (agent) => {
|
|
|
7828
7856
|
const plan = agent.command("plan [directory]").description("Preview provider, worktree, findings, and publish actions without editing");
|
|
7829
7857
|
addAgentOptions(plan);
|
|
7830
7858
|
plan.action(async (directory = ".", _flags, command) => {
|
|
7831
|
-
const
|
|
7859
|
+
const flags = command.optsWithGlobals();
|
|
7860
|
+
const options = agentOptionsFromFlags(flags, command);
|
|
7832
7861
|
await withAgentLifecycle("agent_plan", directory, agentTelemetryProperties(options), async () => {
|
|
7833
7862
|
await agentPlanCommand(directory, options);
|
|
7834
7863
|
});
|
|
@@ -7838,7 +7867,8 @@ const registerAgentCommand = (program) => {
|
|
|
7838
7867
|
const agent = program.command("agent [directory]").description("Run a local AI slop repair session with Codex, Claude Code, or OpenCode");
|
|
7839
7868
|
addAgentOptions(agent);
|
|
7840
7869
|
agent.action(async (directory = ".", _flags, command) => {
|
|
7841
|
-
const
|
|
7870
|
+
const flags = command.optsWithGlobals();
|
|
7871
|
+
const options = agentOptionsFromFlags(flags, command);
|
|
7842
7872
|
await withAgentLifecycle("agent", directory, agentTelemetryProperties(options), async () => {
|
|
7843
7873
|
return await agentCommand(directory, options);
|
|
7844
7874
|
});
|
|
@@ -8130,9 +8160,9 @@ const buildSuggestedActions = (diagnostics, findings, regressed, delta) => {
|
|
|
8130
8160
|
if (fixableDiags.length > 0) {
|
|
8131
8161
|
const ruleIds = Array.from(new Set(fixableDiags.map((d) => d.rule)));
|
|
8132
8162
|
actions.push({
|
|
8133
|
-
id: "
|
|
8134
|
-
label: `Run
|
|
8135
|
-
command: "
|
|
8163
|
+
id: "run_raigal_fix",
|
|
8164
|
+
label: `Run raigal fix to clear ${fixableDiags.length} mechanical finding${fixableDiags.length === 1 ? "" : "s"}.`,
|
|
8165
|
+
command: "raigal fix",
|
|
8136
8166
|
rationale: "These findings have deterministic fixes (formatting, unused imports, trivial comments). Running this before any manual work avoids burning agent tokens on what the CLI handles for free.",
|
|
8137
8167
|
ruleIds
|
|
8138
8168
|
});
|
|
@@ -8193,8 +8223,40 @@ const buildFeedback = (diagnostics, score, rootDirectory, baseline, meta) => {
|
|
|
8193
8223
|
const regressed = typeof delta === "number" ? delta < 0 : false;
|
|
8194
8224
|
let newSinceBaseline;
|
|
8195
8225
|
if (baselineSnapshot && baselineSnapshot.findingFingerprints.length > 0) {
|
|
8196
|
-
const
|
|
8197
|
-
|
|
8226
|
+
const remainingExact = new Set(baselineSnapshot.findingFingerprints);
|
|
8227
|
+
const unmatchedFindings = [];
|
|
8228
|
+
for (const f of all) {
|
|
8229
|
+
const exactFp = fingerprintFinding(f);
|
|
8230
|
+
if (remainingExact.has(exactFp)) remainingExact.delete(exactFp);
|
|
8231
|
+
else unmatchedFindings.push(f);
|
|
8232
|
+
}
|
|
8233
|
+
const remainingPool = [];
|
|
8234
|
+
for (const fp of remainingExact) {
|
|
8235
|
+
const parts = fp.split(":");
|
|
8236
|
+
if (parts.length >= 3) {
|
|
8237
|
+
const file = parts[0];
|
|
8238
|
+
const line = Number.parseInt(parts[1], 10);
|
|
8239
|
+
const rule = parts.slice(2).join(":");
|
|
8240
|
+
remainingPool.push({
|
|
8241
|
+
file,
|
|
8242
|
+
line: Number.isNaN(line) ? 0 : line,
|
|
8243
|
+
rule
|
|
8244
|
+
});
|
|
8245
|
+
}
|
|
8246
|
+
}
|
|
8247
|
+
const stillUnmatched = [];
|
|
8248
|
+
for (const f of unmatchedFindings) {
|
|
8249
|
+
const candidateIdx = remainingPool.findIndex((b) => b.file === f.file && b.rule === f.ruleId && Math.abs(b.line - f.line) <= 5);
|
|
8250
|
+
if (candidateIdx >= 0) remainingPool.splice(candidateIdx, 1);
|
|
8251
|
+
else stillUnmatched.push(f);
|
|
8252
|
+
}
|
|
8253
|
+
const fresh = [];
|
|
8254
|
+
for (const f of stillUnmatched) {
|
|
8255
|
+
const candidateIdx = remainingPool.findIndex((b) => b.file === f.file && b.rule === f.ruleId);
|
|
8256
|
+
if (candidateIdx >= 0) remainingPool.splice(candidateIdx, 1);
|
|
8257
|
+
else fresh.push(f);
|
|
8258
|
+
}
|
|
8259
|
+
newSinceBaseline = fresh.slice(0, MAX_NEW_SINCE_BASELINE);
|
|
8198
8260
|
}
|
|
8199
8261
|
return {
|
|
8200
8262
|
schema: "aislop.hook.v2",
|
|
@@ -8406,7 +8468,7 @@ const filterExcludedDiagnostics = (diagnostics, rootDirectory, excludePatterns)
|
|
|
8406
8468
|
};
|
|
8407
8469
|
//#endregion
|
|
8408
8470
|
//#region src/utils/project-path-safety.ts
|
|
8409
|
-
const MAX_PROJECT_CONFIG_BYTES =
|
|
8471
|
+
const MAX_PROJECT_CONFIG_BYTES = 1048576;
|
|
8410
8472
|
const isWithinDirectory$3 = (rootDirectory, candidate) => {
|
|
8411
8473
|
const relative = path.relative(rootDirectory, candidate);
|
|
8412
8474
|
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
@@ -8506,7 +8568,7 @@ const isAutoGenerated = (filePath) => {
|
|
|
8506
8568
|
};
|
|
8507
8569
|
//#endregion
|
|
8508
8570
|
//#region src/utils/git-ignore.ts
|
|
8509
|
-
const MAX_BUFFER$2 =
|
|
8571
|
+
const MAX_BUFFER$2 = 52428800;
|
|
8510
8572
|
const NOT_IGNORED_ARGUMENTS = [
|
|
8511
8573
|
"ls-files",
|
|
8512
8574
|
"--cached",
|
|
@@ -8617,7 +8679,7 @@ const dropGitIgnoredPaths = (rootDirectory, absolutePaths) => {
|
|
|
8617
8679
|
};
|
|
8618
8680
|
//#endregion
|
|
8619
8681
|
//#region src/utils/project-file-list.ts
|
|
8620
|
-
const MAX_BUFFER$1 =
|
|
8682
|
+
const MAX_BUFFER$1 = 52428800;
|
|
8621
8683
|
const normalizePruneDirectories = (directories) => new Set([...directories].map((directory) => directory.toLowerCase()));
|
|
8622
8684
|
const isInPrunedDirectory = (filePath, pruneDirectories) => {
|
|
8623
8685
|
const pathSegments = filePath.split("/");
|
|
@@ -9549,7 +9611,7 @@ const tokenizeJs = (content, ext = ".ts") => {
|
|
|
9549
9611
|
};
|
|
9550
9612
|
//#endregion
|
|
9551
9613
|
//#region src/utils/source-masker.ts
|
|
9552
|
-
const JS_EXTS$
|
|
9614
|
+
const JS_EXTS$3 = /* @__PURE__ */ new Set([
|
|
9553
9615
|
".ts",
|
|
9554
9616
|
".tsx",
|
|
9555
9617
|
".js",
|
|
@@ -9557,7 +9619,7 @@ const JS_EXTS$2 = /* @__PURE__ */ new Set([
|
|
|
9557
9619
|
".mjs",
|
|
9558
9620
|
".cjs"
|
|
9559
9621
|
]);
|
|
9560
|
-
const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
|
|
9622
|
+
const PY_EXTS$1 = /* @__PURE__ */ new Set([".py"]);
|
|
9561
9623
|
const RB_EXTS = /* @__PURE__ */ new Set([".rb"]);
|
|
9562
9624
|
const PHP_EXTS = /* @__PURE__ */ new Set([".php"]);
|
|
9563
9625
|
const CSHARP_EXTS = /* @__PURE__ */ new Set([".cs"]);
|
|
@@ -9581,8 +9643,8 @@ const C_STYLE_COMMENT_EXTS = /* @__PURE__ */ new Set([
|
|
|
9581
9643
|
".zig"
|
|
9582
9644
|
]);
|
|
9583
9645
|
const familyForExt = (ext) => {
|
|
9584
|
-
if (JS_EXTS$
|
|
9585
|
-
if (PY_EXTS.has(ext)) return "py";
|
|
9646
|
+
if (JS_EXTS$3.has(ext)) return "js";
|
|
9647
|
+
if (PY_EXTS$1.has(ext)) return "py";
|
|
9586
9648
|
if (RB_EXTS.has(ext)) return "rb";
|
|
9587
9649
|
if (PHP_EXTS.has(ext)) return "php";
|
|
9588
9650
|
if (CSHARP_EXTS.has(ext)) return "csharp";
|
|
@@ -9852,7 +9914,7 @@ const areLiteralMultisetsEqual = (a, b) => {
|
|
|
9852
9914
|
};
|
|
9853
9915
|
//#endregion
|
|
9854
9916
|
//#region src/engines/ai-slop/abstractions.ts
|
|
9855
|
-
const JS_EXTS$
|
|
9917
|
+
const JS_EXTS$2 = /* @__PURE__ */ new Set([
|
|
9856
9918
|
".ts",
|
|
9857
9919
|
".tsx",
|
|
9858
9920
|
".js",
|
|
@@ -9863,11 +9925,11 @@ const JS_EXTS$1 = /* @__PURE__ */ new Set([
|
|
|
9863
9925
|
const THIN_WRAPPER_PATTERNS = [
|
|
9864
9926
|
{
|
|
9865
9927
|
pattern: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*(?::\s*\w[^{]*)?\{\s*\n?\s*return\s+\w+\([^)]*\);\s*\n?\s*\}/g,
|
|
9866
|
-
extensions: JS_EXTS$
|
|
9928
|
+
extensions: JS_EXTS$2
|
|
9867
9929
|
},
|
|
9868
9930
|
{
|
|
9869
9931
|
pattern: /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w[^=]*)?\s*=>\s*\w+\([^)]*\);/g,
|
|
9870
|
-
extensions: JS_EXTS$
|
|
9932
|
+
extensions: JS_EXTS$2
|
|
9871
9933
|
},
|
|
9872
9934
|
{
|
|
9873
9935
|
pattern: /def\s+(\w+)\s*\([^)]*\)(?:\s*->[^:]*)?:\s*\n\s+return\s+\w+\([^)]*\)\s*$/gm,
|
|
@@ -11115,7 +11177,7 @@ const scriptKindFor$1 = (ext) => {
|
|
|
11115
11177
|
}
|
|
11116
11178
|
};
|
|
11117
11179
|
const lineFor$1 = (sourceFile, node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
11118
|
-
const makeDiagnostic = (filePath, line, rule, message, help) => ({
|
|
11180
|
+
const makeDiagnostic$1 = (filePath, line, rule, message, help) => ({
|
|
11119
11181
|
filePath,
|
|
11120
11182
|
engine: "ai-slop",
|
|
11121
11183
|
rule,
|
|
@@ -11165,7 +11227,7 @@ const detectRedundantTryCatch = (sourceFile, relativePath) => {
|
|
|
11165
11227
|
if (ts.isTryStatement(node) && node.catchClause && !node.finallyBlock) {
|
|
11166
11228
|
const catchNameNode = node.catchClause.variableDeclaration?.name;
|
|
11167
11229
|
const [onlyStatement] = node.catchClause.block.statements;
|
|
11168
|
-
if (catchNameNode && ts.isIdentifier(catchNameNode) && node.catchClause.block.statements.length === 1 && onlyStatement && (isRethrowStatement(onlyStatement, catchNameNode.text) || isPromiseRejectRethrow(onlyStatement, catchNameNode.text))) diagnostics.push(makeDiagnostic(relativePath, lineFor$1(sourceFile, node.catchClause), "ai-slop/redundant-try-catch", "Catch block only rethrows the same error", "Remove the try/catch or add useful context, cleanup, or recovery. Rethrowing unchanged errors is usually defensive agent noise."));
|
|
11230
|
+
if (catchNameNode && ts.isIdentifier(catchNameNode) && node.catchClause.block.statements.length === 1 && onlyStatement && (isRethrowStatement(onlyStatement, catchNameNode.text) || isPromiseRejectRethrow(onlyStatement, catchNameNode.text))) diagnostics.push(makeDiagnostic$1(relativePath, lineFor$1(sourceFile, node.catchClause), "ai-slop/redundant-try-catch", "Catch block only rethrows the same error", "Remove the try/catch or add useful context, cleanup, or recovery. Rethrowing unchanged errors is usually defensive agent noise."));
|
|
11169
11231
|
}
|
|
11170
11232
|
ts.forEachChild(node, visit);
|
|
11171
11233
|
};
|
|
@@ -11183,7 +11245,7 @@ const detectPrimitiveCoercions = (sourceFile, relativePath) => {
|
|
|
11183
11245
|
const [arg] = child.arguments;
|
|
11184
11246
|
if (arg && ts.isIdentifier(arg)) {
|
|
11185
11247
|
const primitive = params.get(arg.text);
|
|
11186
|
-
if (primitive && child.expression.text === COERCION_CTORS[primitive]) diagnostics.push(makeDiagnostic(relativePath, lineFor$1(sourceFile, child), "ai-slop/redundant-type-coercion", `Parameter '${arg.text}' is already typed as ${primitive} but is coerced again`, "Trust the typed boundary or validate unknown input before this function. Re-coercing already typed parameters is usually defensive agent noise."));
|
|
11248
|
+
if (primitive && child.expression.text === COERCION_CTORS[primitive]) diagnostics.push(makeDiagnostic$1(relativePath, lineFor$1(sourceFile, child), "ai-slop/redundant-type-coercion", `Parameter '${arg.text}' is already typed as ${primitive} but is coerced again`, "Trust the typed boundary or validate unknown input before this function. Re-coercing already typed parameters is usually defensive agent noise."));
|
|
11187
11249
|
}
|
|
11188
11250
|
}
|
|
11189
11251
|
ts.forEachChild(child, visitBody);
|
|
@@ -11226,7 +11288,7 @@ const detectDuplicateExportedTypes = (parsedSources) => {
|
|
|
11226
11288
|
continue;
|
|
11227
11289
|
}
|
|
11228
11290
|
if (previous.filePath === declaration.filePath) continue;
|
|
11229
|
-
diagnostics.push(makeDiagnostic(declaration.filePath, declaration.line, "ai-slop/duplicate-type-declaration", `Exported type '${declaration.name}' duplicates an existing declaration`, `Reuse or import the existing type from ${previous.filePath} instead of re-declaring the same shape in another file.`));
|
|
11291
|
+
diagnostics.push(makeDiagnostic$1(declaration.filePath, declaration.line, "ai-slop/duplicate-type-declaration", `Exported type '${declaration.name}' duplicates an existing declaration`, `Reuse or import the existing type from ${previous.filePath} instead of re-declaring the same shape in another file.`));
|
|
11230
11292
|
}
|
|
11231
11293
|
}
|
|
11232
11294
|
return diagnostics;
|
|
@@ -11328,7 +11390,7 @@ const detectDuplicateImports = async (context) => {
|
|
|
11328
11390
|
rule: "ai-slop/duplicate-import",
|
|
11329
11391
|
severity: "warning",
|
|
11330
11392
|
message: `"${spec}" is also imported on line ${firstLine}. Merge into a single import statement.`,
|
|
11331
|
-
help: "Two imports from the same module split readers' attention and grow the import block. Run
|
|
11393
|
+
help: "Two imports from the same module split readers' attention and grow the import block. Run raigal fix to merge them automatically.",
|
|
11332
11394
|
line: dup.line,
|
|
11333
11395
|
column: 1,
|
|
11334
11396
|
category: "AI Slop",
|
|
@@ -13036,7 +13098,8 @@ const isJsRelativeOrAbsolute = (spec) => spec.startsWith(".") || spec.startsWith
|
|
|
13036
13098
|
const RUNTIME_BUILTINS = /* @__PURE__ */ new Set(["bun"]);
|
|
13037
13099
|
const isJsBuiltin = (spec) => {
|
|
13038
13100
|
if (RUNTIME_BUILTINS.has(spec)) return true;
|
|
13039
|
-
|
|
13101
|
+
const stripped = spec.startsWith("node:") ? spec.slice(5) : spec;
|
|
13102
|
+
return isBuiltin(stripped) || isBuiltin(spec);
|
|
13040
13103
|
};
|
|
13041
13104
|
const VIRTUAL_MODULE_PREFIXES = [
|
|
13042
13105
|
"astro:",
|
|
@@ -13248,7 +13311,8 @@ const isLocalAliasReplacement = (rawValue, configDirectory, rootDirectory) => {
|
|
|
13248
13311
|
if (!target) return false;
|
|
13249
13312
|
if (!target.startsWith(".") && !target.startsWith("/") && !target.startsWith("~/") && !target.startsWith("@/")) return false;
|
|
13250
13313
|
const normalized = target.startsWith("~/") || target.startsWith("@/") ? target.slice(2) : target;
|
|
13251
|
-
|
|
13314
|
+
const candidate = path.isAbsolute(normalized) ? normalized : path.resolve(configDirectory, normalized);
|
|
13315
|
+
return isRootBoundedTarget(candidate, rootDirectory);
|
|
13252
13316
|
};
|
|
13253
13317
|
const hasValidPackageImportTarget = (value, packageDirectory, rootDirectory) => {
|
|
13254
13318
|
if (!isRootBoundedTarget(packageDirectory, rootDirectory)) return false;
|
|
@@ -14442,7 +14506,9 @@ const isRustDocCommentLine = (line) => {
|
|
|
14442
14506
|
};
|
|
14443
14507
|
const collectBlocks = (sourceLines, syntax, content, ext) => {
|
|
14444
14508
|
const blocks = [];
|
|
14445
|
-
const
|
|
14509
|
+
const actualContent = content ?? sourceLines.join("\n");
|
|
14510
|
+
const actualExt = ext ?? (syntax.linePrefixes.includes("//") ? ".ts" : ".py");
|
|
14511
|
+
const commentLines = getCommentLines(actualContent, actualExt);
|
|
14446
14512
|
let i = 0;
|
|
14447
14513
|
while (i < sourceLines.length) {
|
|
14448
14514
|
const line = sourceLines[i];
|
|
@@ -14770,7 +14836,8 @@ const detectNarrativeComments = async (context) => {
|
|
|
14770
14836
|
} catch {
|
|
14771
14837
|
continue;
|
|
14772
14838
|
}
|
|
14773
|
-
const
|
|
14839
|
+
const lines = content.split("\n");
|
|
14840
|
+
const blocks = collectBlocks(lines, syntax, content, ext);
|
|
14774
14841
|
for (const block of blocks) {
|
|
14775
14842
|
const { matched, reason } = detectNarrativeInBlock(block, ext);
|
|
14776
14843
|
if (!matched) continue;
|
|
@@ -15242,7 +15309,7 @@ const detectRustPatterns = async (context) => {
|
|
|
15242
15309
|
};
|
|
15243
15310
|
//#endregion
|
|
15244
15311
|
//#region src/engines/ai-slop/silent-recovery.ts
|
|
15245
|
-
const JS_EXTS = /* @__PURE__ */ new Set([
|
|
15312
|
+
const JS_EXTS$1 = /* @__PURE__ */ new Set([
|
|
15246
15313
|
".ts",
|
|
15247
15314
|
".tsx",
|
|
15248
15315
|
".js",
|
|
@@ -15437,7 +15504,7 @@ const detectSilentRecovery = async (context) => {
|
|
|
15437
15504
|
for (const filePath of files) {
|
|
15438
15505
|
if (isAutoGenerated(filePath)) continue;
|
|
15439
15506
|
const ext = path.extname(filePath);
|
|
15440
|
-
const isJs = JS_EXTS.has(ext);
|
|
15507
|
+
const isJs = JS_EXTS$1.has(ext);
|
|
15441
15508
|
if (!isJs && !(ext === ".py")) continue;
|
|
15442
15509
|
const relPath = relativePosix(context.rootDirectory, filePath);
|
|
15443
15510
|
if (isNonProductionPath(relPath)) continue;
|
|
@@ -15722,7 +15789,7 @@ const detectUnusedCss = async (context) => {
|
|
|
15722
15789
|
rule: "ai-slop/unused-css",
|
|
15723
15790
|
severity: "warning",
|
|
15724
15791
|
message: `CSS class '.${def.name}' is defined but never referenced in the codebase`,
|
|
15725
|
-
help: "Remove the dead CSS rule, or confirm the class is used dynamically (and add
|
|
15792
|
+
help: "Remove the dead CSS rule, or confirm the class is used dynamically (and add a raigal-ignore comment if so)",
|
|
15726
15793
|
line: def.line,
|
|
15727
15794
|
column: 0,
|
|
15728
15795
|
category: "AI Slop",
|
|
@@ -16723,7 +16790,8 @@ const checkFunctionDiagnostics = (relativePath, fn, limits, ext) => {
|
|
|
16723
16790
|
const results = [];
|
|
16724
16791
|
const multiplier = functionLocMultiplier(fn, ext);
|
|
16725
16792
|
const fnMax = functionLocBudget(fn, ext, limits.maxFunctionLoc);
|
|
16726
|
-
|
|
16793
|
+
const effectiveLineCount = fn.lineCount - fn.templateLines;
|
|
16794
|
+
if (exceedsLimit(effectiveLineCount, limits.maxFunctionLoc, multiplier)) results.push({
|
|
16727
16795
|
filePath: relativePath,
|
|
16728
16796
|
engine: "code-quality",
|
|
16729
16797
|
rule: "complexity/function-too-long",
|
|
@@ -16813,14 +16881,14 @@ const isTrivialLine = (line) => {
|
|
|
16813
16881
|
};
|
|
16814
16882
|
const SVG_MARKUP_RE = /<\/?(?:svg|path|polyline|line|circle|rect|g)\b|(?:xmlns|viewBox|stroke(?:-width|-linecap|-linejoin)?|fill|fill-opacity|d|points|x1|x2|y1|y2)=/;
|
|
16815
16883
|
const DATA_LITERAL_RE = /^\s*(?:[A-Za-z_$][\w$-]*:\s*(?:["'`[{]|\d|true\b|false\b|null\b)|["'`][^"'`]*["'`],?\s*$|[{}\]],?\s*$|\),?\s*$)/;
|
|
16816
|
-
const SUPPRESS_RE = /aislop[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
16817
|
-
const FILE_SUPPRESS_RE = /aislop[- ]ignore-file\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
16884
|
+
const SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
16885
|
+
const FILE_SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore-file\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
16818
16886
|
const fileHasSuppression = (content) => FILE_SUPPRESS_RE.test(content);
|
|
16819
16887
|
const isLowSignalMarkupWindow = (lines) => {
|
|
16820
16888
|
return lines.filter((line) => SVG_MARKUP_RE.test(line)).length >= Math.ceil(WINDOW_SIZE / 2);
|
|
16821
16889
|
};
|
|
16822
16890
|
const isLowSignalDataWindow = (lines) => {
|
|
16823
|
-
return lines.filter((line) => DATA_LITERAL_RE.test(line)).length >=
|
|
16891
|
+
return lines.filter((line) => DATA_LITERAL_RE.test(line)).length >= 9;
|
|
16824
16892
|
};
|
|
16825
16893
|
const findSuppressedLines = (lines) => {
|
|
16826
16894
|
const suppressed = /* @__PURE__ */ new Set();
|
|
@@ -16852,7 +16920,7 @@ const collectMeaningfulLines = (content) => {
|
|
|
16852
16920
|
if (isLowSignalDataWindow(window)) continue;
|
|
16853
16921
|
if (window.every(isTrivialLine)) continue;
|
|
16854
16922
|
const normalised = window.map(normaliseLine);
|
|
16855
|
-
if (normalised.filter((n) => n.length > 0 && n !== "}" && n !== "{").length <
|
|
16923
|
+
if (normalised.filter((n) => n.length > 0 && n !== "}" && n !== "{").length < 9) continue;
|
|
16856
16924
|
if (new Set(normalised).size < MIN_DISTINCT_LINES) continue;
|
|
16857
16925
|
hits.push({
|
|
16858
16926
|
startLine: i + 1,
|
|
@@ -16956,8 +17024,8 @@ const shouldIncludeIssue = (issueType, filePath) => {
|
|
|
16956
17024
|
return !filePath.replace(/\\/g, "/").includes(".github/workflows/");
|
|
16957
17025
|
};
|
|
16958
17026
|
const DEPENDENCY_HELP = {
|
|
16959
|
-
dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `
|
|
16960
|
-
devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `
|
|
17027
|
+
dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
|
|
17028
|
+
devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
|
|
16961
17029
|
unlisted: "This package is imported in code but not declared in package.json. Run `npm install` to add it.",
|
|
16962
17030
|
unresolved: "This import cannot be resolved. Check for typos or missing packages.",
|
|
16963
17031
|
binaries: "This binary is used but its package is not in package.json."
|
|
@@ -17916,7 +17984,7 @@ const runGofmt = async (context) => {
|
|
|
17916
17984
|
rule: "go-formatting",
|
|
17917
17985
|
severity: "warning",
|
|
17918
17986
|
message: "Go file is not formatted correctly",
|
|
17919
|
-
help: "Run `
|
|
17987
|
+
help: "Run `raigal fix` to auto-format with gofmt",
|
|
17920
17988
|
line: 0,
|
|
17921
17989
|
column: 0,
|
|
17922
17990
|
category: "Format",
|
|
@@ -17944,7 +18012,8 @@ const resolvePackageRoot = (startFile) => {
|
|
|
17944
18012
|
while (true) {
|
|
17945
18013
|
const packageJsonPath = path.join(current, "package.json");
|
|
17946
18014
|
if (fs.existsSync(packageJsonPath)) try {
|
|
17947
|
-
|
|
18015
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
18016
|
+
if (packageJson.name === "aislop" || packageJson.name === "@methiu/raigal" || packageJson.name === "raigal") return current;
|
|
17948
18017
|
} catch {}
|
|
17949
18018
|
const parent = path.dirname(current);
|
|
17950
18019
|
if (parent === current) break;
|
|
@@ -18055,7 +18124,7 @@ const parseRuffFormatOutput = (output, rootDir) => {
|
|
|
18055
18124
|
rule: "python-formatting",
|
|
18056
18125
|
severity: "warning",
|
|
18057
18126
|
message: "Python file is not formatted correctly",
|
|
18058
|
-
help: "Run `
|
|
18127
|
+
help: "Run `raigal fix` to auto-format with ruff",
|
|
18059
18128
|
line: 0,
|
|
18060
18129
|
column: 0,
|
|
18061
18130
|
category: "Format",
|
|
@@ -18067,7 +18136,8 @@ const parseRuffFormatOutput = (output, rootDir) => {
|
|
|
18067
18136
|
const fixRuffFormat = async (context) => {
|
|
18068
18137
|
const targets = context.files ? getPythonTargets(context) : [context.rootDirectory];
|
|
18069
18138
|
if (context.files && targets.length === 0) return;
|
|
18070
|
-
const
|
|
18139
|
+
const ruffBinary = resolveToolBinary("ruff");
|
|
18140
|
+
const result = await runSubprocess(ruffBinary, ["format", ...targets], {
|
|
18071
18141
|
cwd: context.rootDirectory,
|
|
18072
18142
|
timeout: 6e4
|
|
18073
18143
|
});
|
|
@@ -18137,7 +18207,8 @@ const parseClangTidyOutput = (output, rootDirectory) => {
|
|
|
18137
18207
|
const runClangTidy = async (context) => {
|
|
18138
18208
|
const compileCommandsDir = findCompileCommandsDir(context);
|
|
18139
18209
|
if (!compileCommandsDir) return [];
|
|
18140
|
-
const
|
|
18210
|
+
const implSources = findCppSources(context).filter((f) => CPP_IMPL_EXTENSIONS.has(path.extname(f).toLowerCase()));
|
|
18211
|
+
const sources = filterSourcesInDatabase(implSources, readCompileCommandsFiles(compileCommandsDir));
|
|
18141
18212
|
if (sources.length === 0) return [];
|
|
18142
18213
|
const diagnostics = [];
|
|
18143
18214
|
for (const chunk of chunkFilePaths(sources, CLANG_TIDY_CHUNK_MAX_FILES)) try {
|
|
@@ -18507,7 +18578,7 @@ const runExpoDoctor = async (context) => {
|
|
|
18507
18578
|
rule: "expo-doctor/config-error",
|
|
18508
18579
|
severity: "warning",
|
|
18509
18580
|
message: configError,
|
|
18510
|
-
help: "Install project dependencies, then re-run `
|
|
18581
|
+
help: "Install project dependencies, then re-run `raigal scan`.",
|
|
18511
18582
|
line: 0,
|
|
18512
18583
|
column: 0,
|
|
18513
18584
|
category: "Expo",
|
|
@@ -18973,7 +19044,8 @@ const processFile = (filePath, fileTargets, result) => {
|
|
|
18973
19044
|
});
|
|
18974
19045
|
continue;
|
|
18975
19046
|
}
|
|
18976
|
-
const
|
|
19047
|
+
const shape = classifyIdentifier(best);
|
|
19048
|
+
const { edit, skipReason } = computeEdit(sourceFile, shape);
|
|
18977
19049
|
if (!edit) {
|
|
18978
19050
|
pendingSkips.push({
|
|
18979
19051
|
target,
|
|
@@ -19788,7 +19860,8 @@ const ruffLintFixArgs = (context, unsafe) => {
|
|
|
19788
19860
|
};
|
|
19789
19861
|
const fixRuffLint = async (context) => {
|
|
19790
19862
|
if (context.files && getPythonTargets(context).length === 0) return;
|
|
19791
|
-
const
|
|
19863
|
+
const ruffBinary = resolveToolBinary("ruff");
|
|
19864
|
+
const result = await runSubprocess(ruffBinary, ruffLintFixArgs(context, false), {
|
|
19792
19865
|
cwd: context.rootDirectory,
|
|
19793
19866
|
timeout: 6e4
|
|
19794
19867
|
});
|
|
@@ -19796,7 +19869,8 @@ const fixRuffLint = async (context) => {
|
|
|
19796
19869
|
};
|
|
19797
19870
|
const fixRuffLintForce = async (context) => {
|
|
19798
19871
|
if (context.files && getPythonTargets(context).length === 0) return;
|
|
19799
|
-
const
|
|
19872
|
+
const ruffBinary = resolveToolBinary("ruff");
|
|
19873
|
+
const result = await runSubprocess(ruffBinary, ruffLintFixArgs(context, true), {
|
|
19800
19874
|
cwd: context.rootDirectory,
|
|
19801
19875
|
timeout: 6e4
|
|
19802
19876
|
});
|
|
@@ -19837,7 +19911,8 @@ const resolveTrustedTscPath = () => {
|
|
|
19837
19911
|
};
|
|
19838
19912
|
const isReferenceOnlyConfig = (tsconfigPath) => {
|
|
19839
19913
|
try {
|
|
19840
|
-
const
|
|
19914
|
+
const raw = fs.readFileSync(tsconfigPath, "utf-8");
|
|
19915
|
+
const parsed = parseJsonc(raw);
|
|
19841
19916
|
if (!parsed) return false;
|
|
19842
19917
|
return Array.isArray(parsed.references) && !parsed.files && !parsed.include && !parsed.extends;
|
|
19843
19918
|
} catch {
|
|
@@ -19980,6 +20055,174 @@ const lintEngine = {
|
|
|
19980
20055
|
}
|
|
19981
20056
|
};
|
|
19982
20057
|
//#endregion
|
|
20058
|
+
//#region src/engines/security/ai-security.ts
|
|
20059
|
+
const JS_EXTS = /* @__PURE__ */ new Set([
|
|
20060
|
+
".ts",
|
|
20061
|
+
".tsx",
|
|
20062
|
+
".js",
|
|
20063
|
+
".jsx",
|
|
20064
|
+
".mjs",
|
|
20065
|
+
".cjs"
|
|
20066
|
+
]);
|
|
20067
|
+
const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
|
|
20068
|
+
const UNICODE_TAG_RE = /[\u{E0000}-\u{E007F}]/gu;
|
|
20069
|
+
const hasRuleIgnore = (lines, lineIndex, ruleSuffix) => {
|
|
20070
|
+
const start = Math.max(0, lineIndex - 2);
|
|
20071
|
+
const re = new RegExp(`(?:raigal|aislop)[- ]ignore(?:-file)?\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
|
|
20072
|
+
return lines.slice(start, lineIndex + 1).some((line) => re.test(line));
|
|
20073
|
+
};
|
|
20074
|
+
const hasFileIgnore = (lines, ruleSuffix) => {
|
|
20075
|
+
const re = new RegExp(`(?:raigal|aislop)[- ]ignore-file\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
|
|
20076
|
+
return lines.slice(0, 10).some((line) => re.test(line));
|
|
20077
|
+
};
|
|
20078
|
+
const makeDiagnostic = (filePath, rule, message, help, line, column) => ({
|
|
20079
|
+
filePath,
|
|
20080
|
+
engine: "security",
|
|
20081
|
+
rule,
|
|
20082
|
+
severity: "error",
|
|
20083
|
+
message,
|
|
20084
|
+
help,
|
|
20085
|
+
line,
|
|
20086
|
+
column,
|
|
20087
|
+
category: "Security",
|
|
20088
|
+
fixable: false
|
|
20089
|
+
});
|
|
20090
|
+
const P_BYPASS = "bypassPermissions";
|
|
20091
|
+
const P_EXEC_TYPE = "executor_type";
|
|
20092
|
+
const P_LOCAL = "local";
|
|
20093
|
+
const P_USE_DOCKER = "use_docker";
|
|
20094
|
+
const P_AUTO_RUN = "auto_run";
|
|
20095
|
+
const P_UNSAFE_MODE = "unsafe_mode";
|
|
20096
|
+
const P_PYTHON_REPL = "PythonREPLTool";
|
|
20097
|
+
const UNSANDBOXED_TOOL_PATTERNS = [
|
|
20098
|
+
{
|
|
20099
|
+
pattern: new RegExp(`\\bpermission_?mode["']?\\s*[:=]\\s*["']${P_BYPASS}["']`, "i"),
|
|
20100
|
+
message: `permission_mode '${P_BYPASS}' bypasses agent sandboxing and human confirmation`,
|
|
20101
|
+
help: `Do not use ${P_BYPASS} mode in production; use standard interactive approval`
|
|
20102
|
+
},
|
|
20103
|
+
{
|
|
20104
|
+
pattern: new RegExp(`\\b${P_EXEC_TYPE}["']?\\s*[:=]\\s*["']${P_LOCAL}["']`, "i"),
|
|
20105
|
+
message: `${P_EXEC_TYPE}='${P_LOCAL}' executes LLM-generated code directly on host machine without container isolation`,
|
|
20106
|
+
help: "Configure agent to use DockerCommandLineCodeExecutor or a containerized sandbox environment"
|
|
20107
|
+
},
|
|
20108
|
+
{
|
|
20109
|
+
pattern: new RegExp(`\\b(?:LocalCommandLineCodeExecutor|LocalCodeExecutor)\\s*\\(`),
|
|
20110
|
+
message: "Local code executor executes LLM-generated code on host machine without container isolation",
|
|
20111
|
+
help: "Use DockerCommandLineCodeExecutor or an isolated execution sandbox"
|
|
20112
|
+
},
|
|
20113
|
+
{
|
|
20114
|
+
pattern: new RegExp(`\\b${P_USE_DOCKER}\\s*[:=]\\s*(?:False|false)\\b|["']${P_USE_DOCKER}["']\\s*:\\s*(?:False|false)\\b`),
|
|
20115
|
+
message: `${P_USE_DOCKER}=False disables container sandboxing for agent code execution`,
|
|
20116
|
+
help: `Set ${P_USE_DOCKER}=True to isolate agent execution inside Docker containers`
|
|
20117
|
+
},
|
|
20118
|
+
{
|
|
20119
|
+
pattern: new RegExp(`\\b(?:interpreter\\.)?${P_AUTO_RUN}\\s*[:=]\\s*(?:True|true)\\b`),
|
|
20120
|
+
message: `${P_AUTO_RUN}=True allows autonomous shell execution without human confirmation`,
|
|
20121
|
+
help: `Keep ${P_AUTO_RUN}=False so dangerous commands require explicit human review`
|
|
20122
|
+
},
|
|
20123
|
+
{
|
|
20124
|
+
pattern: new RegExp(`\\bCodeInterpreterTool\\s*\\([^)]*${P_UNSAFE_MODE}\\s*=\\s*(?:True|true)\\b|\\b${P_UNSAFE_MODE}\\s*[:=]\\s*(?:True|true)\\b`),
|
|
20125
|
+
message: `${P_UNSAFE_MODE}=True allows code interpreter tool to execute without Docker isolation`,
|
|
20126
|
+
help: `Disable ${P_UNSAFE_MODE} and isolate code execution in containerized environments`
|
|
20127
|
+
},
|
|
20128
|
+
{
|
|
20129
|
+
pattern: new RegExp(`\\b(?:${P_PYTHON_REPL}|PythonAstREPLTool)\\s*\\(`),
|
|
20130
|
+
message: `${P_PYTHON_REPL} executes arbitrary Python code in host process without container isolation`,
|
|
20131
|
+
help: `Use containerized sandbox execution rather than unconfined ${P_PYTHON_REPL} in production`
|
|
20132
|
+
}
|
|
20133
|
+
];
|
|
20134
|
+
const P_SYS = "system";
|
|
20135
|
+
const P_DEV = "developer";
|
|
20136
|
+
const P_SM = "SystemMessage";
|
|
20137
|
+
const PROMPT_INJECTION_JS_PATTERNS = [
|
|
20138
|
+
new RegExp(`(?:role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`)`, "g"),
|
|
20139
|
+
new RegExp(`(?:content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`[^}{;]{0,120}?role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
|
|
20140
|
+
new RegExp(`(?:role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?content\\s*:\\s*(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_$]|[a-zA-Z_$][\\w$]*\\s*\\+\\s*["']))`, "g"),
|
|
20141
|
+
new RegExp(`(?:content\\s*:\\s*(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_$]|[a-zA-Z_$][\\w$]*\\s*\\+\\s*["'])[^}{;]{0,120}?role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
|
|
20142
|
+
new RegExp(`\\b(?:${P_SYS}|systemPrompt|system_prompt|system_instruction)\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\``, "g"),
|
|
20143
|
+
new RegExp(`\\b(?:${P_SYS}|systemPrompt|system_prompt|system_instruction)\\s*:\\s*(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_$]|[a-zA-Z_$][\\w$]*\\s*\\+\\s*["'])`, "g"),
|
|
20144
|
+
new RegExp(`\\b(?:new\\s+)?${P_SM}\\s*\\(\\s*(?:\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`|["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_$]|[a-zA-Z_$][\\w$]*\\s*\\+\\s*["']|\\{\\s*content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`)`, "g"),
|
|
20145
|
+
new RegExp(`\\[\\s*["']${P_SYS}["']\\s*,\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`\\s*\\]`, "g")
|
|
20146
|
+
];
|
|
20147
|
+
const PROMPT_INJECTION_PY_PATTERNS = [
|
|
20148
|
+
new RegExp(`(?:["']role["']\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?["']content["']\\s*:\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["']))`, "g"),
|
|
20149
|
+
new RegExp(`(?:["']content["']\\s*:\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])[^}{;]{0,120}?["']role["']\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
|
|
20150
|
+
new RegExp(`(?:["']role["']\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?["']content["']\\s*:\\s*(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_]|[a-zA-Z_]\\w*\\s*\\+\\s*["']|["'][^"'\\n]*["']\\.format\\())`, "g"),
|
|
20151
|
+
new RegExp(`(?:["']content["']\\s*:\\s*(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_]|[a-zA-Z_]\\w*\\s*\\+\\s*["']|["'][^"'\\n]*["']\\.format\\()[^}{;]{0,120}?["']role["']\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
|
|
20152
|
+
new RegExp(`\\b(?:${P_SYS}|system_prompt|system_message|system_instruction)\\s*=\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])`, "g"),
|
|
20153
|
+
new RegExp(`\\b(?:${P_SYS}|system_prompt|system_message|system_instruction)\\s*=\\s*(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_]|[a-zA-Z_]\\w*\\s*\\+\\s*["']|["'][^"'\\n]*["']\\.format\\()`, "g"),
|
|
20154
|
+
new RegExp(`\\b${P_SM}\\s*\\(\\s*(?:content\\s*=\\s*)?f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])`, "g"),
|
|
20155
|
+
new RegExp(`\\b${P_SM}\\s*\\(\\s*(?:content\\s*=\\s*)?(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_]|[a-zA-Z_]\\w*\\s*\\+\\s*["']|["'][^"'\\n]*["']\\.format\\()`, "g"),
|
|
20156
|
+
new RegExp(`\\(\\s*["']${P_SYS}["']\\s*,\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])\\s*\\)`, "g")
|
|
20157
|
+
];
|
|
20158
|
+
const checkInvisibleUnicode = (relPath, content, lines) => {
|
|
20159
|
+
if (hasFileIgnore(lines, "invisible-unicode-tags")) return [];
|
|
20160
|
+
const results = [];
|
|
20161
|
+
const tagRegex = new RegExp(UNICODE_TAG_RE.source, UNICODE_TAG_RE.flags);
|
|
20162
|
+
for (const match of content.matchAll(tagRegex)) {
|
|
20163
|
+
const matchIndex = match.index ?? 0;
|
|
20164
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
20165
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
20166
|
+
if (hasRuleIgnore(lines, line - 1, "invisible-unicode-tags")) continue;
|
|
20167
|
+
results.push(makeDiagnostic(relPath, "security/invisible-unicode-tags", "Invisible Unicode Tag character (U+E0000-U+E007F) detected: potential stealth prompt injection or ASCII smuggling", "Remove invisible Unicode tag characters. Unicode Tag block (U+E0000-U+E007F) has no legitimate printable presentation in source code", line, col));
|
|
20168
|
+
}
|
|
20169
|
+
return results;
|
|
20170
|
+
};
|
|
20171
|
+
const checkUnsandboxedTools = (relPath, content, lines) => {
|
|
20172
|
+
if (hasFileIgnore(lines, "unsandboxed-agent-tool")) return [];
|
|
20173
|
+
const results = [];
|
|
20174
|
+
for (const { pattern, message, help } of UNSANDBOXED_TOOL_PATTERNS) {
|
|
20175
|
+
const regex = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
20176
|
+
for (const match of content.matchAll(regex)) {
|
|
20177
|
+
const matchIndex = match.index ?? 0;
|
|
20178
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
20179
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
20180
|
+
if (hasRuleIgnore(lines, line - 1, "unsandboxed-agent-tool")) continue;
|
|
20181
|
+
results.push(makeDiagnostic(relPath, "security/unsandboxed-agent-tool", message, help, line, col));
|
|
20182
|
+
}
|
|
20183
|
+
}
|
|
20184
|
+
return results;
|
|
20185
|
+
};
|
|
20186
|
+
const checkPromptInjection = (relPath, content, lines, isJs) => {
|
|
20187
|
+
if (hasFileIgnore(lines, "prompt-injection")) return [];
|
|
20188
|
+
const results = [];
|
|
20189
|
+
const patterns = isJs ? PROMPT_INJECTION_JS_PATTERNS : PROMPT_INJECTION_PY_PATTERNS;
|
|
20190
|
+
for (const pattern of patterns) {
|
|
20191
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
20192
|
+
for (const match of content.matchAll(regex)) {
|
|
20193
|
+
const matchIndex = match.index ?? 0;
|
|
20194
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
20195
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
20196
|
+
if (hasRuleIgnore(lines, line - 1, "prompt-injection")) continue;
|
|
20197
|
+
results.push(makeDiagnostic(relPath, "security/prompt-injection", "Untrusted variable interpolation into system prompt creates prompt injection vulnerability", "Keep system prompts static. Pass dynamic user inputs via user messages or input parameters with strict delimiters", line, col));
|
|
20198
|
+
}
|
|
20199
|
+
}
|
|
20200
|
+
return results;
|
|
20201
|
+
};
|
|
20202
|
+
const scanAiSecurity = async (context) => {
|
|
20203
|
+
const files = getSourceFiles(context);
|
|
20204
|
+
const diagnostics = [];
|
|
20205
|
+
for (const filePath of files) {
|
|
20206
|
+
const ext = path.extname(filePath);
|
|
20207
|
+
let content;
|
|
20208
|
+
try {
|
|
20209
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
20210
|
+
} catch {
|
|
20211
|
+
continue;
|
|
20212
|
+
}
|
|
20213
|
+
const relativePath = relativePosix(context.rootDirectory, filePath);
|
|
20214
|
+
const lines = content.split("\n");
|
|
20215
|
+
diagnostics.push(...checkInvisibleUnicode(relativePath, content, lines));
|
|
20216
|
+
const isJs = JS_EXTS.has(ext);
|
|
20217
|
+
const isPy = PY_EXTS.has(ext);
|
|
20218
|
+
if (isJs || isPy) {
|
|
20219
|
+
diagnostics.push(...checkUnsandboxedTools(relativePath, content, lines));
|
|
20220
|
+
diagnostics.push(...checkPromptInjection(relativePath, content, lines, isJs));
|
|
20221
|
+
}
|
|
20222
|
+
}
|
|
20223
|
+
return diagnostics;
|
|
20224
|
+
};
|
|
20225
|
+
//#endregion
|
|
19983
20226
|
//#region src/ui/invocation.ts
|
|
19984
20227
|
const detectInvocation = () => "raigal";
|
|
19985
20228
|
//#endregion
|
|
@@ -20362,7 +20605,7 @@ const parseJsAudit = (output, source, manifest) => {
|
|
|
20362
20605
|
rule: "security/dependency-audit-skipped",
|
|
20363
20606
|
severity: "info",
|
|
20364
20607
|
message: `Dependency audit skipped (${source}): lockfile is missing`,
|
|
20365
|
-
help: errorDetail ?? "Generate a lockfile, then re-run `
|
|
20608
|
+
help: errorDetail ?? "Generate a lockfile, then re-run `raigal scan` for dependency vulnerability checks.",
|
|
20366
20609
|
line: 0,
|
|
20367
20610
|
column: 0,
|
|
20368
20611
|
category: "Security",
|
|
@@ -20536,10 +20779,11 @@ const auditSkippedDiagnostic = (source, help) => ({
|
|
|
20536
20779
|
});
|
|
20537
20780
|
const runNpmAudit = async (rootDir, timeout) => {
|
|
20538
20781
|
try {
|
|
20539
|
-
|
|
20782
|
+
const result = await runSubprocess("npm", ["audit", "--json"], {
|
|
20540
20783
|
cwd: rootDir,
|
|
20541
20784
|
timeout
|
|
20542
|
-
})
|
|
20785
|
+
});
|
|
20786
|
+
return parseJsAudit(result.stdout, "npm audit", readRuntimeDependencies(rootDir));
|
|
20543
20787
|
} catch (error) {
|
|
20544
20788
|
return [auditSkippedDiagnostic("npm audit", `Failed to run npm audit: ${errorMessageOf(error)}`)];
|
|
20545
20789
|
}
|
|
@@ -20560,10 +20804,11 @@ const runBunAudit = async (rootDir, timeout) => {
|
|
|
20560
20804
|
const runPnpmAuditWithFallback = async (rootDir, timeout) => {
|
|
20561
20805
|
const canFallbackToNpm = fs.existsSync(path.join(rootDir, "package-lock.json"));
|
|
20562
20806
|
try {
|
|
20563
|
-
const
|
|
20807
|
+
const result = await runSubprocess("pnpm", ["audit", "--json"], {
|
|
20564
20808
|
cwd: rootDir,
|
|
20565
20809
|
timeout
|
|
20566
|
-
})
|
|
20810
|
+
});
|
|
20811
|
+
const diagnostics = parseJsAudit(result.stdout, "pnpm audit", readRuntimeDependencies(rootDir));
|
|
20567
20812
|
if (diagnostics.some((d) => d.rule === "security/dependency-audit-skipped")) {
|
|
20568
20813
|
if (canFallbackToNpm) return runNpmAudit(rootDir, timeout);
|
|
20569
20814
|
return diagnostics;
|
|
@@ -20920,7 +21165,7 @@ const RISKY_PATTERNS = [
|
|
|
20920
21165
|
//#region src/engines/security/risky.ts
|
|
20921
21166
|
const hasDangerouslySetInnerHtmlIgnore = (lines, lineIndex) => {
|
|
20922
21167
|
const start = Math.max(0, lineIndex - 2);
|
|
20923
|
-
return lines.slice(start, lineIndex + 1).some((line) => /(?:biome-ignore|eslint-disable|aislop-ignore).*(?:noDangerouslySetInnerHtml|dangerouslySetInnerHTML|dangerously-set-innerhtml)/i.test(line));
|
|
21168
|
+
return lines.slice(start, lineIndex + 1).some((line) => /(?:biome-ignore|eslint-disable|raigal-ignore|aislop-ignore).*(?:noDangerouslySetInnerHtml|dangerouslySetInnerHTML|dangerously-set-innerhtml)/i.test(line));
|
|
20924
21169
|
};
|
|
20925
21170
|
const isStructuredDataScript = (content, matchIndex) => {
|
|
20926
21171
|
const before = content.slice(Math.max(0, matchIndex - 300), matchIndex);
|
|
@@ -21248,7 +21493,11 @@ const ALL_ENGINES = [
|
|
|
21248
21493
|
name: "security",
|
|
21249
21494
|
async run(context) {
|
|
21250
21495
|
const diagnostics = [];
|
|
21251
|
-
const promises = [
|
|
21496
|
+
const promises = [
|
|
21497
|
+
scanSecrets(context),
|
|
21498
|
+
detectRiskyConstructs(context),
|
|
21499
|
+
scanAiSecurity(context)
|
|
21500
|
+
];
|
|
21252
21501
|
if (context.config.security.audit) promises.push(runDependencyAudit(context));
|
|
21253
21502
|
const results = await Promise.allSettled(promises);
|
|
21254
21503
|
for (const result of results) if (result.status === "fulfilled") diagnostics.push(...result.value);
|
|
@@ -21293,7 +21542,10 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
|
|
|
21293
21542
|
failed: true,
|
|
21294
21543
|
skipReason: r.reason instanceof Error ? r.reason.message : String(r.reason)
|
|
21295
21544
|
});
|
|
21296
|
-
|
|
21545
|
+
const stripped = stripSecretDiagnostics(finalResults);
|
|
21546
|
+
const csharp = dedupeCSharpAsync(stripped);
|
|
21547
|
+
const comments = dedupeOverlappingComments(csharp);
|
|
21548
|
+
return dedupeEquivalenceDiagnostics(comments);
|
|
21297
21549
|
};
|
|
21298
21550
|
const stripSecretDiagnostics = (results) => {
|
|
21299
21551
|
for (const result of results) for (const d of result.diagnostics) if (d.redactSource || isSecretClassRule(d.rule)) {
|
|
@@ -21439,6 +21691,9 @@ const RULE_SCORE_IMPACTS = {
|
|
|
21439
21691
|
"security/shell-injection": strict("Interpolated shell commands can become command execution."),
|
|
21440
21692
|
"security/unsafe-deserialization": strict("Deserializing untrusted data with a legacy .NET formatter can become remote code execution."),
|
|
21441
21693
|
"security/unsafe-c-call": strict("Unbounded C string and process calls can become buffer overflows or command execution."),
|
|
21694
|
+
"security/prompt-injection": strict("Dynamic user interpolation in system/developer prompts enables prompt injection attacks."),
|
|
21695
|
+
"security/unsandboxed-agent-tool": strict("Unsandboxed agent code execution or tool permission bypass allows arbitrary host commands."),
|
|
21696
|
+
"security/invisible-unicode-tags": strict("Invisible Unicode Tag characters (U+E0000-U+E007F) hide stealth prompt injection payloads."),
|
|
21442
21697
|
"security/dependency-audit-skipped": advisory("An unavailable audit is visibility loss, not evidence of a vulnerability."),
|
|
21443
21698
|
"dotnet/projects-skipped": advisory("Skipping unrestored C# projects is visibility loss, not evidence of a defect."),
|
|
21444
21699
|
"cppcheck/chunks-skipped": advisory("A skipped cppcheck chunk is visibility loss, not evidence that the skipped files have a defect."),
|
|
@@ -21515,7 +21770,8 @@ const calculateScore = (diagnostics, weights, thresholds, sourceFileCount, smoot
|
|
|
21515
21770
|
const capScale = Math.max(1, fileCount.effectiveSourceFileCount / CAP_REFERENCE_FILES);
|
|
21516
21771
|
const defaultRuleCap = typeof maxPerRule === "number" && maxPerRule > 0 ? maxPerRule : null;
|
|
21517
21772
|
const capForRule = (key) => {
|
|
21518
|
-
const
|
|
21773
|
+
const rule = key.slice(key.indexOf(":") + 1);
|
|
21774
|
+
const ruleCap = scoreImpactForRule(rule).cap;
|
|
21519
21775
|
if (typeof ruleCap === "number") return defaultRuleCap ? Math.min(defaultRuleCap, ruleCap) : ruleCap;
|
|
21520
21776
|
return defaultRuleCap;
|
|
21521
21777
|
};
|
|
@@ -21628,8 +21884,10 @@ const detectSourceLanguages = (sourceFiles) => {
|
|
|
21628
21884
|
const detectLanguages = (directory, sourceFiles) => {
|
|
21629
21885
|
const languages = new Set(detectSourceLanguages(sourceFiles));
|
|
21630
21886
|
for (const [file, lang] of Object.entries(LANGUAGE_SIGNALS)) if (fs.existsSync(path.join(directory, file))) languages.add(lang);
|
|
21631
|
-
if (readPackageJson(path.join(directory, "package.json")))
|
|
21632
|
-
|
|
21887
|
+
if (readPackageJson(path.join(directory, "package.json"))) {
|
|
21888
|
+
if (fs.existsSync(path.join(directory, "tsconfig.json"))) languages.add("typescript");
|
|
21889
|
+
else languages.add("javascript");
|
|
21890
|
+
}
|
|
21633
21891
|
for (const signal of PYTHON_SIGNALS) if (fs.existsSync(path.join(directory, signal))) {
|
|
21634
21892
|
languages.add("python");
|
|
21635
21893
|
break;
|
|
@@ -21856,7 +22114,7 @@ const runScopedScan = async (cwd, filePaths) => {
|
|
|
21856
22114
|
const rulesPath = configDir ? path.join(configDir, RULES_FILE) : void 0;
|
|
21857
22115
|
const enumeratedPaths = new Set(projectCandidates.map((filePath) => path.resolve(rootDirectory, filePath)));
|
|
21858
22116
|
const scopedCandidates = filePaths.filter((filePath) => enumeratedPaths.has(path.resolve(rootDirectory, filePath)));
|
|
21859
|
-
const
|
|
22117
|
+
const context = {
|
|
21860
22118
|
rootDirectory: project.rootDirectory,
|
|
21861
22119
|
languages: project.languages,
|
|
21862
22120
|
frameworks: project.frameworks,
|
|
@@ -21879,14 +22137,17 @@ const runScopedScan = async (cwd, filePaths) => {
|
|
|
21879
22137
|
allowProjectLocalTools: false,
|
|
21880
22138
|
architectureRulesPath: config.engines.architecture ? rulesPath : void 0
|
|
21881
22139
|
}
|
|
21882
|
-
}
|
|
22140
|
+
};
|
|
22141
|
+
const enabled = {
|
|
21883
22142
|
format: false,
|
|
21884
22143
|
lint: false,
|
|
21885
22144
|
"code-quality": config.engines["code-quality"],
|
|
21886
22145
|
"ai-slop": config.engines["ai-slop"],
|
|
21887
22146
|
architecture: config.engines.architecture,
|
|
21888
22147
|
security: false
|
|
21889
|
-
}
|
|
22148
|
+
};
|
|
22149
|
+
const results = await runEngines(context, enabled);
|
|
22150
|
+
const diagnostics = applyRuleSeverities(results.flatMap((result) => result.diagnostics), config.overrides.length ? {} : config.rules);
|
|
21890
22151
|
const { score } = calculateScore(diagnostics, config.scoring.weights, config.scoring.thresholds, projectSourceFiles.length + projectTestFiles.length, config.scoring.smoothing, config.scoring.maxPerRule);
|
|
21891
22152
|
return {
|
|
21892
22153
|
diagnostics,
|
|
@@ -21944,14 +22205,26 @@ const hasExistingLiteralBackslashPath = (cwd, fingerprint) => {
|
|
|
21944
22205
|
}
|
|
21945
22206
|
};
|
|
21946
22207
|
const migrateMarkerlessFingerprint = (cwd, fingerprint) => hasExistingLiteralBackslashPath(cwd, fingerprint) ? fingerprint : normalizeLegacyFingerprint(fingerprint);
|
|
21947
|
-
const
|
|
21948
|
-
const
|
|
22208
|
+
const baselinePath = (cwd) => {
|
|
22209
|
+
const raigalCandidate = path.join(cwd, ".raigal", "baseline.json");
|
|
22210
|
+
if (fs.existsSync(raigalCandidate)) return raigalCandidate;
|
|
22211
|
+
const raigalDir = path.join(cwd, ".raigal");
|
|
22212
|
+
if (fs.existsSync(raigalDir)) return raigalCandidate;
|
|
22213
|
+
return path.join(cwd, ".aislop", "baseline.json");
|
|
22214
|
+
};
|
|
22215
|
+
const sessionPath = (cwd) => {
|
|
22216
|
+
const raigalCandidate = path.join(cwd, ".raigal", "session.jsonl");
|
|
22217
|
+
if (fs.existsSync(raigalCandidate)) return raigalCandidate;
|
|
22218
|
+
const raigalDir = path.join(cwd, ".raigal");
|
|
22219
|
+
if (fs.existsSync(raigalDir)) return raigalCandidate;
|
|
22220
|
+
return path.join(cwd, ".aislop", "session.jsonl");
|
|
22221
|
+
};
|
|
21949
22222
|
const readBaseline = (cwd) => {
|
|
21950
22223
|
const raw = readIfExists(baselinePath(cwd));
|
|
21951
22224
|
if (!raw) return null;
|
|
21952
22225
|
try {
|
|
21953
22226
|
const parsed = JSON.parse(raw);
|
|
21954
|
-
if (parsed.schema !== "aislop.baseline.v2" && parsed.schema !== "aislop.baseline.v1") return null;
|
|
22227
|
+
if (parsed.schema !== "aislop.baseline.v2" && parsed.schema !== "aislop.baseline.v1" && parsed.schema !== "raigal.baseline.v2" && parsed.schema !== "raigal.baseline.v1") return null;
|
|
21955
22228
|
const findingFingerprints = parsed.findingFingerprints ?? [];
|
|
21956
22229
|
const needsWindowsMigration = parsed.pathFormat !== "posix";
|
|
21957
22230
|
return {
|
|
@@ -21983,11 +22256,10 @@ const captureBaseline = async (cwd) => {
|
|
|
21983
22256
|
});
|
|
21984
22257
|
const project = await discoverProject(cwd);
|
|
21985
22258
|
const config = loadConfig(cwd);
|
|
21986
|
-
const
|
|
22259
|
+
const context = {
|
|
21987
22260
|
rootDirectory: project.rootDirectory,
|
|
21988
22261
|
languages: project.languages,
|
|
21989
22262
|
frameworks: project.frameworks,
|
|
21990
|
-
files: [],
|
|
21991
22263
|
installedTools: project.installedTools,
|
|
21992
22264
|
config: {
|
|
21993
22265
|
overrides: config.overrides,
|
|
@@ -22003,19 +22275,22 @@ const captureBaseline = async (cwd) => {
|
|
|
22003
22275
|
},
|
|
22004
22276
|
allowProjectLocalTools: false
|
|
22005
22277
|
}
|
|
22006
|
-
}
|
|
22278
|
+
};
|
|
22279
|
+
const enabled = {
|
|
22007
22280
|
format: false,
|
|
22008
22281
|
lint: false,
|
|
22009
22282
|
"code-quality": config.engines["code-quality"],
|
|
22010
22283
|
"ai-slop": config.engines["ai-slop"],
|
|
22011
22284
|
architecture: config.engines.architecture,
|
|
22012
22285
|
security: false
|
|
22013
|
-
}
|
|
22014
|
-
const
|
|
22286
|
+
};
|
|
22287
|
+
const results = await runEngines(context, enabled);
|
|
22288
|
+
const diagnostics = applyRuleSeverities(results.flatMap((r) => r.diagnostics), config.overrides.length ? {} : config.rules);
|
|
22015
22289
|
const { score } = calculateScore(diagnostics, config.scoring.weights, config.scoring.thresholds, project.sourceFileCount, config.scoring.smoothing, config.scoring.maxPerRule);
|
|
22016
22290
|
const byEngine = {};
|
|
22017
22291
|
for (const r of results) {
|
|
22018
|
-
const
|
|
22292
|
+
const engineDiags = diagnostics.filter((d) => d.engine === r.engine);
|
|
22293
|
+
const { score: engineScore } = calculateScore(engineDiags, config.scoring.weights, config.scoring.thresholds, project.sourceFileCount, config.scoring.smoothing, config.scoring.maxPerRule);
|
|
22019
22294
|
byEngine[r.engine] = engineScore;
|
|
22020
22295
|
}
|
|
22021
22296
|
const findingFingerprints = diagnostics.filter((d) => d.severity === "error" || d.severity === "warning").map((d) => fingerprintDiagnostic(d, project.rootDirectory));
|
|
@@ -22037,7 +22312,7 @@ const captureBaseline = async (cwd) => {
|
|
|
22037
22312
|
};
|
|
22038
22313
|
const appendSessionFiles = (cwd, files) => {
|
|
22039
22314
|
if (files.length === 0) return;
|
|
22040
|
-
const target =
|
|
22315
|
+
const target = sessionPath(cwd);
|
|
22041
22316
|
try {
|
|
22042
22317
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
22043
22318
|
const line = `${JSON.stringify({
|
|
@@ -22048,7 +22323,8 @@ const appendSessionFiles = (cwd, files) => {
|
|
|
22048
22323
|
} catch {}
|
|
22049
22324
|
};
|
|
22050
22325
|
const readSessionFiles = (cwd) => {
|
|
22051
|
-
const
|
|
22326
|
+
const target = sessionPath(cwd);
|
|
22327
|
+
const raw = readIfExists(target);
|
|
22052
22328
|
if (!raw) return [];
|
|
22053
22329
|
const files = /* @__PURE__ */ new Set();
|
|
22054
22330
|
for (const line of raw.split("\n")) {
|
|
@@ -22061,7 +22337,7 @@ const readSessionFiles = (cwd) => {
|
|
|
22061
22337
|
return Array.from(files);
|
|
22062
22338
|
};
|
|
22063
22339
|
const clearSessionFiles = (cwd) => {
|
|
22064
|
-
const target =
|
|
22340
|
+
const target = sessionPath(cwd);
|
|
22065
22341
|
try {
|
|
22066
22342
|
fs.unlinkSync(target);
|
|
22067
22343
|
} catch {}
|
|
@@ -22165,7 +22441,7 @@ const runClaudeFileChangedHook = async (deps = {}) => {
|
|
|
22165
22441
|
schema: "aislop.hook.v2",
|
|
22166
22442
|
event: "file_changed",
|
|
22167
22443
|
file: changed,
|
|
22168
|
-
message: `Watched file changed (${changed}).
|
|
22444
|
+
message: `Watched file changed (${changed}). raigal refreshed the baseline - score: ${result.score}.`,
|
|
22169
22445
|
baseline: {
|
|
22170
22446
|
score: result.score,
|
|
22171
22447
|
fileCount: result.fileCount
|
|
@@ -22521,7 +22797,9 @@ const applyRemoval = (result, opts, target, nextContent) => {
|
|
|
22521
22797
|
//#region src/hooks/install/rules-only.ts
|
|
22522
22798
|
const installRulesOnly = (opts, paths, summary) => {
|
|
22523
22799
|
const result = emptyResult();
|
|
22524
|
-
const
|
|
22800
|
+
const existing = readIfExists(paths.rules);
|
|
22801
|
+
const hash = sentinelHash(AISLOP_MD_BODY);
|
|
22802
|
+
const next = upsertMarkdownFence(existing, AISLOP_MD_BODY, hash).nextContent;
|
|
22525
22803
|
applyContent(result, opts, paths.rules, next, summary);
|
|
22526
22804
|
if (paths.host && paths.marker) {
|
|
22527
22805
|
const host = readIfExists(paths.host) ?? "";
|
|
@@ -22573,7 +22851,7 @@ const uninstallAntigravity = (opts) => {
|
|
|
22573
22851
|
//#endregion
|
|
22574
22852
|
//#region src/hooks/io/json-patch.ts
|
|
22575
22853
|
const AISLOP_SENTINEL_KEY = "__aislop";
|
|
22576
|
-
const isAislopManaged = (x) => typeof x === "object" && x !== null && "__aislop" in x && x["__aislop"] != null;
|
|
22854
|
+
const isAislopManaged = (x) => typeof x === "object" && x !== null && ("__aislop" in x && x["__aislop"] != null || "__raigal" in x && x["__raigal"] != null);
|
|
22577
22855
|
const groupIsAislop = (group) => {
|
|
22578
22856
|
if (typeof group !== "object" || group === null) return false;
|
|
22579
22857
|
const hooks = group.hooks;
|
|
@@ -22628,14 +22906,14 @@ const resolveClaudePaths = (opts) => {
|
|
|
22628
22906
|
};
|
|
22629
22907
|
const buildHookGroup$1 = () => {
|
|
22630
22908
|
const hashBody = JSON.stringify({
|
|
22631
|
-
command: "
|
|
22909
|
+
command: "raigal hook claude",
|
|
22632
22910
|
matcher: "Edit|Write|MultiEdit"
|
|
22633
22911
|
});
|
|
22634
22912
|
return {
|
|
22635
22913
|
matcher: "Edit|Write|MultiEdit",
|
|
22636
22914
|
hooks: [{
|
|
22637
22915
|
type: "command",
|
|
22638
|
-
command: "
|
|
22916
|
+
command: "raigal hook claude",
|
|
22639
22917
|
[AISLOP_SENTINEL_KEY]: {
|
|
22640
22918
|
v: 1,
|
|
22641
22919
|
managed: true,
|
|
@@ -22645,12 +22923,12 @@ const buildHookGroup$1 = () => {
|
|
|
22645
22923
|
};
|
|
22646
22924
|
};
|
|
22647
22925
|
const buildStopHookGroup = () => {
|
|
22648
|
-
const hashBody = JSON.stringify({ command: "
|
|
22926
|
+
const hashBody = JSON.stringify({ command: "raigal hook claude --stop" });
|
|
22649
22927
|
return {
|
|
22650
22928
|
matcher: "",
|
|
22651
22929
|
hooks: [{
|
|
22652
22930
|
type: "command",
|
|
22653
|
-
command: "
|
|
22931
|
+
command: "raigal hook claude --stop",
|
|
22654
22932
|
[AISLOP_SENTINEL_KEY]: {
|
|
22655
22933
|
v: 1,
|
|
22656
22934
|
managed: true,
|
|
@@ -22662,14 +22940,14 @@ const buildStopHookGroup = () => {
|
|
|
22662
22940
|
const FILE_CHANGED_MATCHER = ".raigal/config.yml|.raigal/rules.yml|.aislop/config.yml|.aislop/rules.yml|package.json";
|
|
22663
22941
|
const buildFileChangedHookGroup = () => {
|
|
22664
22942
|
const hashBody = JSON.stringify({
|
|
22665
|
-
command: "
|
|
22943
|
+
command: "raigal hook claude --on-file-changed",
|
|
22666
22944
|
matcher: FILE_CHANGED_MATCHER
|
|
22667
22945
|
});
|
|
22668
22946
|
return {
|
|
22669
22947
|
matcher: FILE_CHANGED_MATCHER,
|
|
22670
22948
|
hooks: [{
|
|
22671
22949
|
type: "command",
|
|
22672
|
-
command: "
|
|
22950
|
+
command: "raigal hook claude --on-file-changed",
|
|
22673
22951
|
[AISLOP_SENTINEL_KEY]: {
|
|
22674
22952
|
v: 1,
|
|
22675
22953
|
managed: true,
|
|
@@ -22697,7 +22975,8 @@ const installClaude = (opts) => {
|
|
|
22697
22975
|
const nextSettings = renderSettings$1(readIfExists(paths.settings), Boolean(opts.qualityGate));
|
|
22698
22976
|
applyContent(result, opts, paths.settings, nextSettings, "register PostToolUse + FileChanged hooks");
|
|
22699
22977
|
const mdHash = sentinelHash(AISLOP_MD_BODY);
|
|
22700
|
-
const
|
|
22978
|
+
const existingMd = readIfExists(paths.aislopMd);
|
|
22979
|
+
const fenced = upsertMarkdownFence(existingMd, AISLOP_MD_BODY, mdHash);
|
|
22701
22980
|
applyContent(result, opts, paths.aislopMd, fenced.nextContent, "write AISLOP.md rules");
|
|
22702
22981
|
const existingClaudeMd = readIfExists(paths.claudeMd) ?? "";
|
|
22703
22982
|
const marker = "@AISLOP.md";
|
|
@@ -22812,11 +23091,11 @@ const resolveCursorPaths = (opts) => {
|
|
|
22812
23091
|
};
|
|
22813
23092
|
const buildHookEntry = () => {
|
|
22814
23093
|
const hashBody = JSON.stringify({
|
|
22815
|
-
command: "
|
|
23094
|
+
command: "raigal hook cursor",
|
|
22816
23095
|
timeout: 5e3
|
|
22817
23096
|
});
|
|
22818
23097
|
return {
|
|
22819
|
-
command: "
|
|
23098
|
+
command: "raigal hook cursor",
|
|
22820
23099
|
type: "command",
|
|
22821
23100
|
timeout: 5e3,
|
|
22822
23101
|
[AISLOP_SENTINEL_KEY]: {
|
|
@@ -22843,7 +23122,9 @@ const installCursor = (opts) => {
|
|
|
22843
23122
|
const nextHooks = renderHooksJson(readIfExists(paths.hooks));
|
|
22844
23123
|
applyContent(result, opts, paths.hooks, nextHooks, "register afterFileEdit hook");
|
|
22845
23124
|
if (opts.scope === "project") {
|
|
22846
|
-
const
|
|
23125
|
+
const existingRules = readIfExists(paths.rules);
|
|
23126
|
+
const hash = sentinelHash(AISLOP_MD_BODY);
|
|
23127
|
+
const rules = upsertMarkdownFence(existingRules, AISLOP_MD_BODY, hash).nextContent;
|
|
22847
23128
|
applyContent(result, opts, paths.rules, rules, "write .cursor/rules/aislop.mdc");
|
|
22848
23129
|
}
|
|
22849
23130
|
return result;
|
|
@@ -22883,15 +23164,15 @@ const resolveGeminiPaths = (opts) => {
|
|
|
22883
23164
|
};
|
|
22884
23165
|
const buildHookGroup = () => {
|
|
22885
23166
|
const hashBody = JSON.stringify({
|
|
22886
|
-
command: "
|
|
23167
|
+
command: "raigal hook gemini",
|
|
22887
23168
|
matcher: "write_file|replace"
|
|
22888
23169
|
});
|
|
22889
23170
|
return {
|
|
22890
23171
|
matcher: "write_file|replace",
|
|
22891
23172
|
hooks: [{
|
|
22892
|
-
name: "
|
|
23173
|
+
name: "raigal",
|
|
22893
23174
|
type: "command",
|
|
22894
|
-
command: "
|
|
23175
|
+
command: "raigal hook gemini",
|
|
22895
23176
|
timeout: 5e3,
|
|
22896
23177
|
[AISLOP_SENTINEL_KEY]: {
|
|
22897
23178
|
v: 1,
|
|
@@ -22916,7 +23197,9 @@ const installGemini = (opts) => {
|
|
|
22916
23197
|
const result = emptyResult();
|
|
22917
23198
|
const next = renderSettings(readIfExists(paths.settings));
|
|
22918
23199
|
applyContent(result, opts, paths.settings, next, "register AfterTool hook");
|
|
22919
|
-
const
|
|
23200
|
+
const existingMd = readIfExists(paths.aislopMd);
|
|
23201
|
+
const hash = sentinelHash(AISLOP_MD_BODY);
|
|
23202
|
+
const fenced = upsertMarkdownFence(existingMd, AISLOP_MD_BODY, hash).nextContent;
|
|
22920
23203
|
applyContent(result, opts, paths.aislopMd, fenced, "write AISLOP.md rules");
|
|
22921
23204
|
const existingGeminiMd = readIfExists(paths.geminiMd) ?? "";
|
|
22922
23205
|
const marker = "@AISLOP.md";
|
|
@@ -22982,8 +23265,8 @@ const uninstallKilocode = (opts) => {
|
|
|
22982
23265
|
const resolvePiPaths = (opts) => {
|
|
22983
23266
|
return { extension: opts.scope === "project" ? path.join(opts.cwd, ".pi", "extensions", "aislop.js") : path.join(opts.home, ".pi", "agent", "extensions", "aislop.js") };
|
|
22984
23267
|
};
|
|
22985
|
-
const PI_EXTENSION_SOURCE = `//
|
|
22986
|
-
// Reinstall with:
|
|
23268
|
+
const PI_EXTENSION_SOURCE = `// raigal — auto-generated pi extension. Do not edit by hand.
|
|
23269
|
+
// Reinstall with: raigal hook install --pi
|
|
22987
23270
|
import { spawn } from "node:child_process";
|
|
22988
23271
|
|
|
22989
23272
|
// Runs the CLI async so a slow scan never blocks pi's event loop, and forwards
|
|
@@ -23028,7 +23311,7 @@ export default function (pi) {
|
|
|
23028
23311
|
const filePath = event.input && event.input.path;
|
|
23029
23312
|
if (typeof filePath !== "string" || filePath.length === 0) return;
|
|
23030
23313
|
|
|
23031
|
-
const bin = process.env.AISLOP_BIN || "
|
|
23314
|
+
const bin = process.env.RAIGAL_BIN || process.env.AISLOP_BIN || "raigal";
|
|
23032
23315
|
const payload = JSON.stringify({
|
|
23033
23316
|
cwd: ctx.cwd,
|
|
23034
23317
|
file_path: filePath,
|
|
@@ -23055,7 +23338,7 @@ export default function (pi) {
|
|
|
23055
23338
|
const installPi = (opts) => {
|
|
23056
23339
|
const paths = resolvePiPaths(opts);
|
|
23057
23340
|
const result = emptyResult();
|
|
23058
|
-
applyContent(result, opts, paths.extension, PI_EXTENSION_SOURCE, "write pi
|
|
23341
|
+
applyContent(result, opts, paths.extension, PI_EXTENSION_SOURCE, "write pi raigal extension");
|
|
23059
23342
|
return result;
|
|
23060
23343
|
};
|
|
23061
23344
|
const uninstallPi = (opts) => {
|
|
@@ -23414,8 +23697,10 @@ const runSearchPrompt = async (options) => {
|
|
|
23414
23697
|
}
|
|
23415
23698
|
if (key.name === "space" && options.mode === "multi") {
|
|
23416
23699
|
const item = filtered[cursor];
|
|
23417
|
-
if (item)
|
|
23418
|
-
|
|
23700
|
+
if (item) {
|
|
23701
|
+
if (selected.has(item.value)) selected.delete(item.value);
|
|
23702
|
+
else selected.add(item.value);
|
|
23703
|
+
}
|
|
23419
23704
|
render();
|
|
23420
23705
|
return;
|
|
23421
23706
|
}
|
|
@@ -23679,10 +23964,11 @@ const hookRun = async (agent, flags) => {
|
|
|
23679
23964
|
process.exit(0);
|
|
23680
23965
|
}
|
|
23681
23966
|
let exitCode = 0;
|
|
23682
|
-
if (agent === "claude")
|
|
23683
|
-
|
|
23684
|
-
|
|
23685
|
-
|
|
23967
|
+
if (agent === "claude") {
|
|
23968
|
+
if (flags?.onFileChanged) exitCode = await runClaudeFileChangedHook();
|
|
23969
|
+
else if (flags?.stop) exitCode = await runClaudeStopHook();
|
|
23970
|
+
else exitCode = await runClaudeHook();
|
|
23971
|
+
} else if (agent === "cursor") exitCode = await runCursorHook();
|
|
23686
23972
|
else if (agent === "gemini") exitCode = await runGeminiHook();
|
|
23687
23973
|
else if (agent === "pi") exitCode = await runPiHook();
|
|
23688
23974
|
else {
|
|
@@ -23693,7 +23979,8 @@ const hookRun = async (agent, flags) => {
|
|
|
23693
23979
|
process.exit(exitCode);
|
|
23694
23980
|
};
|
|
23695
23981
|
const hookBaseline = async () => {
|
|
23696
|
-
const
|
|
23982
|
+
const cwd = process.cwd();
|
|
23983
|
+
const result = await captureBaseline(cwd);
|
|
23697
23984
|
process.stdout.write(renderHookBaseline({
|
|
23698
23985
|
score: result.score,
|
|
23699
23986
|
fileCount: result.fileCount,
|
|
@@ -23924,7 +24211,7 @@ const registerHookAliases = (program) => {
|
|
|
23924
24211
|
};
|
|
23925
24212
|
//#endregion
|
|
23926
24213
|
//#region src/utils/github-slug.ts
|
|
23927
|
-
const GITHUB_REMOTE_RE = /^(?:git@github\.com
|
|
24214
|
+
const GITHUB_REMOTE_RE = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
|
|
23928
24215
|
const parseGithubSlug = (remoteUrl) => {
|
|
23929
24216
|
const match = remoteUrl.trim().match(GITHUB_REMOTE_RE);
|
|
23930
24217
|
if (!match) return null;
|
|
@@ -24122,6 +24409,9 @@ const RULE_LABELS = {
|
|
|
24122
24409
|
"security/shell-injection": "Possible shell injection",
|
|
24123
24410
|
"security/unsafe-deserialization": "Unsafe deserialization",
|
|
24124
24411
|
"security/unsafe-c-call": "Memory-unsafe C call",
|
|
24412
|
+
"security/prompt-injection": "Prompt injection (untrusted system prompt)",
|
|
24413
|
+
"security/unsandboxed-agent-tool": "Unsandboxed agent code execution tool",
|
|
24414
|
+
"security/invisible-unicode-tags": "Invisible Unicode tag character (stealth injection)",
|
|
24125
24415
|
"dotnet/projects-skipped": "C# projects skipped by build-backed lint",
|
|
24126
24416
|
"cppcheck/chunks-skipped": "C/C++ files skipped by cppcheck",
|
|
24127
24417
|
"eslint/no-undef": "Undefined identifier",
|
|
@@ -24210,6 +24500,9 @@ const RULE_DESCRIPTIONS = {
|
|
|
24210
24500
|
"security/shell-injection": "Shell command is built from unsanitized input.",
|
|
24211
24501
|
"security/unsafe-deserialization": "Legacy .NET formatter can deserialize arbitrary types (RCE).",
|
|
24212
24502
|
"security/unsafe-c-call": "Unbounded C string/exec function with buffer-overflow or shell risk.",
|
|
24503
|
+
"security/prompt-injection": "Untrusted dynamic variable interpolation into system or developer prompt.",
|
|
24504
|
+
"security/unsandboxed-agent-tool": "Agent executes code or commands on host without container or interactive gate.",
|
|
24505
|
+
"security/invisible-unicode-tags": "Invisible Unicode Tag block (U+E0000-U+E007F) used for prompt injection or ASCII smuggling.",
|
|
24213
24506
|
"dotnet/projects-skipped": "Build-backed C# lint only ran on restored projects; the rest were skipped.",
|
|
24214
24507
|
"cppcheck/chunks-skipped": "A cppcheck invocation timed out or failed on one or more chunks; their files were not analyzed.",
|
|
24215
24508
|
"oxlint/*": "JavaScript/TypeScript lint finding from oxlint.",
|
|
@@ -24404,7 +24697,8 @@ const BUILTIN_RULES = [
|
|
|
24404
24697
|
"typescript/*",
|
|
24405
24698
|
"dotnet/*",
|
|
24406
24699
|
"cppcheck/*",
|
|
24407
|
-
"clang-tidy/*"
|
|
24700
|
+
"clang-tidy/*",
|
|
24701
|
+
"expo-doctor/*"
|
|
24408
24702
|
]
|
|
24409
24703
|
},
|
|
24410
24704
|
{
|
|
@@ -24503,7 +24797,10 @@ const BUILTIN_RULES = [
|
|
|
24503
24797
|
"security/shell-injection",
|
|
24504
24798
|
"security/unsafe-deserialization",
|
|
24505
24799
|
"security/unsafe-c-call",
|
|
24506
|
-
"security/dependency-audit-skipped"
|
|
24800
|
+
"security/dependency-audit-skipped",
|
|
24801
|
+
"security/prompt-injection",
|
|
24802
|
+
"security/unsandboxed-agent-tool",
|
|
24803
|
+
"security/invisible-unicode-tags"
|
|
24507
24804
|
]
|
|
24508
24805
|
}
|
|
24509
24806
|
];
|
|
@@ -24513,7 +24810,8 @@ const collectRuleEntries = (directory) => {
|
|
|
24513
24810
|
for (const { engine, rules } of BUILTIN_RULES) for (const rule of rules) entries.push(toRuleEntry(engine, rule));
|
|
24514
24811
|
const configDir = findConfigDir(resolvedDir);
|
|
24515
24812
|
if (configDir) {
|
|
24516
|
-
const
|
|
24813
|
+
const rulesPath = path.join(configDir, RULES_FILE);
|
|
24814
|
+
const archRules = loadArchitectureRules(rulesPath);
|
|
24517
24815
|
for (const rule of archRules) entries.push({
|
|
24518
24816
|
id: `arch/${rule.name}`,
|
|
24519
24817
|
engine: "architecture",
|
|
@@ -24570,6 +24868,8 @@ const resolveHistoryDir = (directory) => {
|
|
|
24570
24868
|
const resolved = path.resolve(directory);
|
|
24571
24869
|
const primary = path.join(resolved, CONFIG_DIR);
|
|
24572
24870
|
if (fs.existsSync(primary)) return primary;
|
|
24871
|
+
const legacy = path.join(resolved, ".aislop");
|
|
24872
|
+
if (fs.existsSync(legacy)) return legacy;
|
|
24573
24873
|
return null;
|
|
24574
24874
|
};
|
|
24575
24875
|
const historyPath = (directory) => {
|
|
@@ -24709,7 +25009,7 @@ const trendCommand = async (directory, limit) => {
|
|
|
24709
25009
|
//#endregion
|
|
24710
25010
|
//#region src/update-notifier.ts
|
|
24711
25011
|
const REGISTRY_URL = "https://registry.npmjs.org/@methiu/raigal/latest";
|
|
24712
|
-
const CHECK_INTERVAL_MS =
|
|
25012
|
+
const CHECK_INTERVAL_MS = 864e5;
|
|
24713
25013
|
const REQUEST_TIMEOUT_MS = 2e3;
|
|
24714
25014
|
const CACHE_BASENAME = "update_check.json";
|
|
24715
25015
|
const isUpdateNotifierDisabled = (env = process.env) => {
|
|
@@ -24782,7 +25082,7 @@ const maybeNotifyUpdate = async (now = Date.now()) => {
|
|
|
24782
25082
|
if (!process.stderr.isTTY) return;
|
|
24783
25083
|
const cachePath = resolveUpdateCachePath();
|
|
24784
25084
|
const cache = readCache(cachePath);
|
|
24785
|
-
if (cache && isOutdated("0.2.
|
|
25085
|
+
if (cache && isOutdated("0.2.3", cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
|
|
24786
25086
|
if (!cache || now - cache.checkedAt > CHECK_INTERVAL_MS) {
|
|
24787
25087
|
const latest = await fetchLatestVersion$1();
|
|
24788
25088
|
if (latest) writeCache(cachePath, {
|
|
@@ -25302,7 +25602,7 @@ const renderCommandGroup = (group, commandWidth) => {
|
|
|
25302
25602
|
return lines;
|
|
25303
25603
|
};
|
|
25304
25604
|
const renderCommandReference = (input = {}) => {
|
|
25305
|
-
const version = input.version ?? "0.2.
|
|
25605
|
+
const version = input.version ?? "0.2.3";
|
|
25306
25606
|
const commandWidth = Math.max(...COMMAND_REFERENCE.map((item) => item.command.length));
|
|
25307
25607
|
const lines = [renderHeader({
|
|
25308
25608
|
version,
|
|
@@ -25524,8 +25824,9 @@ const renderHelpDetails = () => [
|
|
|
25524
25824
|
""
|
|
25525
25825
|
].map((line) => highlightRaigal(line, theme)).join("\n");
|
|
25526
25826
|
const renderHome = (input = {}) => {
|
|
25827
|
+
const version = input.version ?? "0.2.3";
|
|
25527
25828
|
let out = renderHeader({
|
|
25528
|
-
version
|
|
25829
|
+
version,
|
|
25529
25830
|
command: "--bare",
|
|
25530
25831
|
context: []
|
|
25531
25832
|
});
|
|
@@ -25585,10 +25886,10 @@ const registerExtraCommands = (program) => {
|
|
|
25585
25886
|
return { exitCode: 0 };
|
|
25586
25887
|
});
|
|
25587
25888
|
});
|
|
25588
|
-
program.command("update").alias("upgrade").description("Check npm for the latest
|
|
25889
|
+
program.command("update").alias("upgrade").description("Check npm for the latest raigal version").action(async () => {
|
|
25589
25890
|
await updateCommand();
|
|
25590
25891
|
});
|
|
25591
|
-
program.command("version").description("Print the installed
|
|
25892
|
+
program.command("version").description("Print the installed raigal version").action(() => {
|
|
25592
25893
|
process.stdout.write(`${APP_VERSION}\n`);
|
|
25593
25894
|
});
|
|
25594
25895
|
program.command("commands").description("List all commands and major flags").action(() => {
|
|
@@ -25776,7 +26077,8 @@ const saveCachedPolicy = (repoSlug, policy) => {
|
|
|
25776
26077
|
};
|
|
25777
26078
|
const fetchRemotePolicy = async (repoSlug, token) => {
|
|
25778
26079
|
try {
|
|
25779
|
-
const
|
|
26080
|
+
const endpoint = `/v1/policy?repo=${encodeURIComponent(repoSlug.toLowerCase())}`;
|
|
26081
|
+
const data = await requestApi(endpoint, {
|
|
25780
26082
|
method: "GET",
|
|
25781
26083
|
token,
|
|
25782
26084
|
timeoutMs: 4e3
|
|
@@ -26181,7 +26483,7 @@ const printEngineStatus = (result) => {
|
|
|
26181
26483
|
};
|
|
26182
26484
|
//#endregion
|
|
26183
26485
|
//#region src/utils/git.ts
|
|
26184
|
-
const MAX_BUFFER =
|
|
26486
|
+
const MAX_BUFFER = 52428800;
|
|
26185
26487
|
const baseRefExists = (cwd, ref) => {
|
|
26186
26488
|
const result = spawnSync("git", [
|
|
26187
26489
|
"rev-parse",
|
|
@@ -26704,7 +27006,8 @@ const renderActionRows = (steps, t) => {
|
|
|
26704
27006
|
const renderTextSteps = (steps, t, s) => {
|
|
26705
27007
|
return steps.filter((step) => step.text).map((step) => {
|
|
26706
27008
|
const glyph = step.emphasis === "primary" ? s.hint : s.bullet;
|
|
26707
|
-
|
|
27009
|
+
const tokenFor = step.emphasis === "primary" ? "accent" : "muted";
|
|
27010
|
+
return ` ${style$1(t, tokenFor, glyph)} ${highlightAislop(step.text ?? "", t)}`;
|
|
26708
27011
|
});
|
|
26709
27012
|
};
|
|
26710
27013
|
const renderSummary = (input, deps = {}) => {
|
|
@@ -26761,10 +27064,14 @@ const renderSummary = (input, deps = {}) => {
|
|
|
26761
27064
|
return lines.join("\n");
|
|
26762
27065
|
};
|
|
26763
27066
|
const renderStarCta = (deps = {}) => {
|
|
26764
|
-
|
|
27067
|
+
const t = deps.theme ?? theme;
|
|
27068
|
+
const repository = terminalLink("https://raigal.dev");
|
|
27069
|
+
return `\n ${style$1(t, "muted", `★ Found this useful? Consider purchasing this software at ${repository}`)}\n`;
|
|
26765
27070
|
};
|
|
26766
27071
|
const renderTeamCta = (deps = {}) => {
|
|
26767
|
-
|
|
27072
|
+
const t = deps.theme ?? theme;
|
|
27073
|
+
const href = terminalLink("https://raigal.dev/contact?intent=team-baseline");
|
|
27074
|
+
return `\n ${style$1(t, "muted", `→ Using raigal with a team? Get a 14-day team baseline at ${href}`)}\n`;
|
|
26768
27075
|
};
|
|
26769
27076
|
const renderCleanRun = (input, deps = {}) => {
|
|
26770
27077
|
const t = deps.theme ?? theme;
|
|
@@ -26998,7 +27305,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
26998
27305
|
architectureRulesPath: config.engines.architecture ? rulesPath : void 0,
|
|
26999
27306
|
engines: config.engines
|
|
27000
27307
|
};
|
|
27001
|
-
const
|
|
27308
|
+
const severityAdjusted = (await runEnginesWithProgress({
|
|
27002
27309
|
rootDirectory: resolvedDir,
|
|
27003
27310
|
languages: projectInfo.languages,
|
|
27004
27311
|
frameworks: projectInfo.frameworks,
|
|
@@ -27014,7 +27321,8 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
27014
27321
|
}, config.engines, machineOutput)).map((result) => ({
|
|
27015
27322
|
...result,
|
|
27016
27323
|
diagnostics: config.overrides.length ? result.diagnostics : applyRuleSeverities(result.diagnostics, config.rules)
|
|
27017
|
-
}))
|
|
27324
|
+
}));
|
|
27325
|
+
const { results: unannotated, suppressedCount } = applySuppressions(severityAdjusted, resolvedDir);
|
|
27018
27326
|
if (suppressedCount > 0 && !machineOutput) log.muted(`Suppressed ${suppressedCount} finding(s) via raigal-ignore directives`);
|
|
27019
27327
|
const changeMap = options.changes && !options.staged ? getChangedLineMap(resolvedDir, options.base) : null;
|
|
27020
27328
|
const results = changeMap ? unannotated.map((result) => ({
|
|
@@ -27075,7 +27383,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
27075
27383
|
scoreFileCount
|
|
27076
27384
|
});
|
|
27077
27385
|
if (options.sarif) {
|
|
27078
|
-
const { buildSarifLog } = await import("./sarif
|
|
27386
|
+
const { buildSarifLog } = await import("./sarif-CdtsAfJ1.js");
|
|
27079
27387
|
console.log(JSON.stringify(buildSarifLog(results), null, 2));
|
|
27080
27388
|
return completion;
|
|
27081
27389
|
}
|
|
@@ -27725,7 +28033,8 @@ const groupByFile = (diagnostics) => {
|
|
|
27725
28033
|
});
|
|
27726
28034
|
};
|
|
27727
28035
|
const isInstalled = (bin) => {
|
|
27728
|
-
|
|
28036
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
28037
|
+
return spawnSync(cmd, [bin], { encoding: "utf-8" }).status === 0;
|
|
27729
28038
|
};
|
|
27730
28039
|
const copyToClipboard = (text) => {
|
|
27731
28040
|
const args = {
|
|
@@ -28201,7 +28510,8 @@ const fixNarrativeComments = async (context) => {
|
|
|
28201
28510
|
for (let i = 0; i < lines.length; i += 1) if (!toRemove.has(i + 1)) kept.push(lines[i]);
|
|
28202
28511
|
const newContent = kept.join("\n");
|
|
28203
28512
|
if (newContent !== content) {
|
|
28204
|
-
|
|
28513
|
+
const afterLiterals = extractStringAndTemplateLiterals(newContent, ext);
|
|
28514
|
+
if (!areLiteralMultisetsEqual(beforeLiterals, afterLiterals)) continue;
|
|
28205
28515
|
fs.writeFileSync(filePath, newContent);
|
|
28206
28516
|
}
|
|
28207
28517
|
}
|
|
@@ -28571,7 +28881,7 @@ const detectUnusedDeclarations = async (context) => {
|
|
|
28571
28881
|
rule: "code-quality/unused-declaration",
|
|
28572
28882
|
severity: "warning",
|
|
28573
28883
|
message: `Unused ${extracted.kind}: ${extracted.name}`,
|
|
28574
|
-
help: "This top-level declaration is never used;
|
|
28884
|
+
help: "This top-level declaration is never used; raigal will remove it.",
|
|
28575
28885
|
line: d.line,
|
|
28576
28886
|
column: d.column,
|
|
28577
28887
|
category: "Dead Code",
|
|
@@ -28589,7 +28899,7 @@ const detectUnusedDeclarations = async (context) => {
|
|
|
28589
28899
|
rule: "code-quality/unused-declaration",
|
|
28590
28900
|
severity: "warning",
|
|
28591
28901
|
message: `Unused ${kind}: ${name}`,
|
|
28592
|
-
help: "This top-level declaration is never imported;
|
|
28902
|
+
help: "This top-level declaration is never imported; raigal will remove it.",
|
|
28593
28903
|
line: d.line,
|
|
28594
28904
|
column: d.column,
|
|
28595
28905
|
category: "Dead Code",
|
|
@@ -28695,7 +29005,7 @@ const removeUnusedDeclarations = (rootDirectory, declarations) => {
|
|
|
28695
29005
|
};
|
|
28696
29006
|
//#endregion
|
|
28697
29007
|
//#region src/commands/fix-expo.ts
|
|
28698
|
-
const INSTALL_TIMEOUT$1 =
|
|
29008
|
+
const INSTALL_TIMEOUT$1 = 18e5;
|
|
28699
29009
|
const fixExpoDependencies = async (context, onProgress) => {
|
|
28700
29010
|
await removeDisallowedExpoPackages(context.rootDirectory, onProgress);
|
|
28701
29011
|
onProgress?.("Expo dependency alignment · running expo install --fix (can take a few minutes)");
|
|
@@ -28749,8 +29059,8 @@ const removeDisallowedExpoPackages = async (rootDir, onProgress) => {
|
|
|
28749
29059
|
};
|
|
28750
29060
|
//#endregion
|
|
28751
29061
|
//#region src/commands/fix-force.ts
|
|
28752
|
-
const INSTALL_TIMEOUT =
|
|
28753
|
-
const AUDIT_TIMEOUT =
|
|
29062
|
+
const INSTALL_TIMEOUT = 18e5;
|
|
29063
|
+
const AUDIT_TIMEOUT = 6e4;
|
|
28754
29064
|
const detectPackageManager = (rootDirectory) => {
|
|
28755
29065
|
if (fs.existsSync(path.join(rootDirectory, "pnpm-lock.yaml"))) return "pnpm";
|
|
28756
29066
|
if (fs.existsSync(path.join(rootDirectory, "package-lock.json")) || fs.existsSync(path.join(rootDirectory, "package.json"))) return "npm";
|
|
@@ -29048,13 +29358,14 @@ const CANNOT_SCOPE_REASON = "cannot honour an explicit file list under --changes
|
|
|
29048
29358
|
const MISSING_MANIFEST_REASON = "no manifest or lockfile in the selected scope";
|
|
29049
29359
|
const fixScopeError = (resolvedDir, flags) => {
|
|
29050
29360
|
if (flags.changes && flags.staged) return "--changes and --staged cannot be used together.";
|
|
29051
|
-
|
|
29361
|
+
const options = {
|
|
29052
29362
|
changes: Boolean(flags.changes),
|
|
29053
29363
|
staged: Boolean(flags.staged),
|
|
29054
29364
|
base: flags.base,
|
|
29055
29365
|
verbose: false,
|
|
29056
29366
|
json: false
|
|
29057
|
-
}
|
|
29367
|
+
};
|
|
29368
|
+
return scanTargetError(resolvedDir, options);
|
|
29058
29369
|
};
|
|
29059
29370
|
const collectFixFileScope = (rootDirectory, excludePatterns, includePatterns, flags) => {
|
|
29060
29371
|
const mode = resolveScanScopeMode({
|
|
@@ -29113,9 +29424,10 @@ const runFormattingStep = async (deps) => {
|
|
|
29113
29424
|
else if (deps.projectInfo.languages.includes("python")) log.warn("Python detected but ruff is not installed; skipping Python formatting fixes.");
|
|
29114
29425
|
if (deps.projectInfo.languages.includes("go") && deps.projectInfo.installedTools.gofmt) await deps.runStep("Formatting (go)", () => runGofmt(deps.context), () => fixGofmt(deps.context));
|
|
29115
29426
|
else if (deps.projectInfo.languages.includes("go")) log.warn("Go detected but gofmt is not installed; skipping Go formatting fixes.");
|
|
29116
|
-
if (deps.projectInfo.languages.includes("rust") && deps.projectInfo.installedTools.rustfmt)
|
|
29117
|
-
|
|
29118
|
-
|
|
29427
|
+
if (deps.projectInfo.languages.includes("rust") && deps.projectInfo.installedTools.rustfmt) {
|
|
29428
|
+
if (isScopedFix(deps.context)) deps.skipStep?.("Formatting (rust)", CANNOT_SCOPE_REASON);
|
|
29429
|
+
else await deps.runStep("Formatting (rust)", () => runGenericFormatter(deps.context, "rust"), () => fixGenericFormatter(deps.context, "rust"));
|
|
29430
|
+
} else if (deps.projectInfo.languages.includes("rust")) log.warn("Rust detected but rustfmt is not installed; skipping Rust formatting fixes.");
|
|
29119
29431
|
if (deps.projectInfo.languages.includes("ruby") && deps.projectInfo.installedTools.rubocop) {
|
|
29120
29432
|
if (!skipUnsafeSafeFormatter(deps, "ruby")) await deps.runStep("Formatting (ruby)", () => runGenericFormatter(deps.context, "ruby"), () => fixGenericFormatter(deps.context, "ruby"));
|
|
29121
29433
|
} else if (deps.projectInfo.languages.includes("ruby")) log.warn("Ruby detected but rubocop is not installed; skipping Ruby formatting fixes.");
|
|
@@ -29164,7 +29476,8 @@ const runDeclarationStep = async (deps) => {
|
|
|
29164
29476
|
if (!deps.config.engines["code-quality"]) return;
|
|
29165
29477
|
if (!hasJsOrTs(deps.projectInfo)) return;
|
|
29166
29478
|
await deps.runStep("Unused declarations", () => detectUnusedDeclarations(deps.context), async () => {
|
|
29167
|
-
const
|
|
29479
|
+
const diagnostics = await detectUnusedDeclarations(deps.context);
|
|
29480
|
+
const declarations = diagnosticsToDeclarations(diagnostics);
|
|
29168
29481
|
removeUnusedDeclarations(deps.resolvedDir, declarations);
|
|
29169
29482
|
});
|
|
29170
29483
|
};
|
|
@@ -29195,10 +29508,14 @@ const runForceSteps = async (deps) => {
|
|
|
29195
29508
|
return (await runKnipUnusedFiles(deps.resolvedDir)).filter((diagnostic) => isPathInFixScope(deps.context, diagnostic.filePath));
|
|
29196
29509
|
}, () => fixUnusedFiles(deps.resolvedDir, (filePath) => isPathInFixScope(deps.context, filePath)));
|
|
29197
29510
|
const railUpdate = (label) => deps.rail.setActiveLabel(label);
|
|
29198
|
-
if (deps.config.engines.security)
|
|
29199
|
-
|
|
29200
|
-
|
|
29201
|
-
|
|
29511
|
+
if (deps.config.engines.security) {
|
|
29512
|
+
if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Dependency audit fixes", MISSING_MANIFEST_REASON);
|
|
29513
|
+
else await deps.runStep("Dependency audit fixes", () => runDependencyAudit(deps.context), () => fixDependencyAudit(deps.context, railUpdate));
|
|
29514
|
+
}
|
|
29515
|
+
if (deps.projectInfo.frameworks.includes("expo") && deps.config.lint.expoDoctor) {
|
|
29516
|
+
if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Expo dependency alignment", MISSING_MANIFEST_REASON);
|
|
29517
|
+
else await deps.runStep("Expo dependency alignment", () => runExpoDoctor(deps.context), () => fixExpoDependencies(deps.context, railUpdate));
|
|
29518
|
+
}
|
|
29202
29519
|
};
|
|
29203
29520
|
//#endregion
|
|
29204
29521
|
//#region src/commands/fix-plan.ts
|
|
@@ -29494,7 +29811,8 @@ const runFixBody = async (resolvedDir, config, options, projectInfo) => {
|
|
|
29494
29811
|
brand: options.printBrand !== false
|
|
29495
29812
|
}));
|
|
29496
29813
|
const safe = Boolean(options.safe);
|
|
29497
|
-
const
|
|
29814
|
+
const excludePatterns = [...config.exclude, ...readAislopIgnorePatterns(resolvedDir)];
|
|
29815
|
+
const scope = collectFixFileScope(resolvedDir, excludePatterns, config.include, options);
|
|
29498
29816
|
const scopedProjectInfo = scope ? {
|
|
29499
29817
|
...projectInfo,
|
|
29500
29818
|
languages: detectSourceLanguages([...scope.files, ...scope.testFiles])
|
|
@@ -29713,7 +30031,7 @@ const promptForConfigChoices = async () => {
|
|
|
29713
30031
|
initialValues: DEFAULT_ENGINE_SELECTION,
|
|
29714
30032
|
required: false
|
|
29715
30033
|
}));
|
|
29716
|
-
if (enginesSelection === void 0) return null;
|
|
30034
|
+
if (enginesSelection === void 0 || isCancel(enginesSelection)) return null;
|
|
29717
30035
|
const failBelowRaw = await text({
|
|
29718
30036
|
message: "CI quality gate — fail the build when the score drops below (0-100)",
|
|
29719
30037
|
initialValue: "70",
|
|
@@ -29844,16 +30162,18 @@ const initCommand = async (directory, options = {}) => {
|
|
|
29844
30162
|
status: "done",
|
|
29845
30163
|
label: `Wrote ${CONFIG_DIR}/${CONFIG_FILE}`
|
|
29846
30164
|
}];
|
|
29847
|
-
if (choices.engines.includes("architecture"))
|
|
29848
|
-
fs.
|
|
29849
|
-
|
|
29850
|
-
|
|
29851
|
-
|
|
30165
|
+
if (choices.engines.includes("architecture")) {
|
|
30166
|
+
if (!fs.existsSync(rulesPath)) {
|
|
30167
|
+
fs.writeFileSync(rulesPath, DEFAULT_RULES_YAML);
|
|
30168
|
+
steps.push({
|
|
30169
|
+
status: "done",
|
|
30170
|
+
label: `Wrote ${CONFIG_DIR}/${RULES_FILE}`
|
|
30171
|
+
});
|
|
30172
|
+
} else steps.push({
|
|
30173
|
+
status: "skipped",
|
|
30174
|
+
label: `${CONFIG_DIR}/${RULES_FILE} already exists`
|
|
29852
30175
|
});
|
|
29853
|
-
}
|
|
29854
|
-
status: "skipped",
|
|
29855
|
-
label: `${CONFIG_DIR}/${RULES_FILE} already exists`
|
|
29856
|
-
});
|
|
30176
|
+
}
|
|
29857
30177
|
const workflowResult = writeGithubWorkflow(resolvedDir, choices.writeGithubWorkflow);
|
|
29858
30178
|
if (workflowResult.status === "written") steps.push({
|
|
29859
30179
|
status: "done",
|
|
@@ -30245,11 +30565,13 @@ program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
|
30245
30565
|
"hook"
|
|
30246
30566
|
].includes(cmdName)) {
|
|
30247
30567
|
const flagNames = process.argv.slice(2).filter((arg) => arg.startsWith("-")).map((arg) => arg.replace(/^--?/, "").split("=")[0] ?? "");
|
|
30248
|
-
|
|
30568
|
+
const dir = actionCommand.args?.[0] || process.cwd();
|
|
30569
|
+
startRunRecorder(cmdName, dir, flagNames);
|
|
30249
30570
|
}
|
|
30250
30571
|
});
|
|
30251
30572
|
program.hook("postAction", async () => {
|
|
30252
|
-
|
|
30573
|
+
const code = typeof process.exitCode === "number" ? process.exitCode : 0;
|
|
30574
|
+
await finishActiveRecorder(code);
|
|
30253
30575
|
});
|
|
30254
30576
|
const main = async () => {
|
|
30255
30577
|
fireInstalledOnce();
|