@methiu/raigal 0.2.0 → 0.2.2
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/adapters/astro.d.ts +1 -6
- package/dist/adapters/astro.js +1 -5
- package/dist/adapters/expo.d.ts +2 -6
- package/dist/adapters/expo.js +2 -5
- package/dist/adapters/nuxt.d.ts +1 -6
- package/dist/adapters/nuxt.js +1 -5
- package/dist/adapters/sveltekit.d.ts +1 -6
- package/dist/adapters/sveltekit.js +3 -7
- package/dist/adapters/vite.d.ts +1 -7
- package/dist/adapters/vite.js +1 -6
- package/dist/cli.js +930 -535
- package/dist/index.d.ts +29 -0
- package/dist/index.js +1166 -52
- package/dist/{json-C9fBWj3V.js → json-BaycSoJP.js} +2 -1
- package/dist/{json-C2IwKr9o.js → json-DBoMe4xB.js} +3 -2
- package/dist/mcp.js +205 -27
- package/dist/{sarif-Do8E1xhZ.js → sarif-Ba5D8TPH.js} +1 -1
- package/dist/{version-v4OAaBLZ.js → version-9Cnt1TiK.js} +1 -1
- package/package.json +8 -6
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ import YAML, { parse } from "yaml";
|
|
|
15
15
|
import { confirm, isCancel, multiselect, select, text } from "@clack/prompts";
|
|
16
16
|
import * as readline$1 from "node:readline";
|
|
17
17
|
import readline from "node:readline";
|
|
18
|
-
import
|
|
18
|
+
import picomatch from "picomatch";
|
|
19
19
|
import ts from "typescript";
|
|
20
20
|
import { parse as parse$1 } from "smol-toml";
|
|
21
21
|
import { Writable } from "node:stream";
|
|
@@ -702,7 +702,7 @@ const highlightRaigal = (text, theme, baseToken) => {
|
|
|
702
702
|
const tail = text.slice(lastIndex);
|
|
703
703
|
return out + (baseToken ? style$1(theme, baseToken, tail) : tail);
|
|
704
704
|
};
|
|
705
|
-
const highlightAislop = highlightRaigal;
|
|
705
|
+
const highlightAislop = (...args) => highlightRaigal(...args);
|
|
706
706
|
//#endregion
|
|
707
707
|
//#region src/ui/width.ts
|
|
708
708
|
const ANSI_RE$1 = new RegExp(`\\[[0-9;]*m`, "g");
|
|
@@ -923,7 +923,7 @@ const createLogger = (deps = {}) => {
|
|
|
923
923
|
const log = createLogger();
|
|
924
924
|
//#endregion
|
|
925
925
|
//#region src/version.ts
|
|
926
|
-
const APP_VERSION = "0.2.
|
|
926
|
+
const APP_VERSION = "0.2.2";
|
|
927
927
|
//#endregion
|
|
928
928
|
//#region src/commands/agent-connect.ts
|
|
929
929
|
const commandText = (status) => [status.provider.loginCommand.command, ...status.provider.loginCommand.args].join(" ");
|
|
@@ -1422,7 +1422,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
|
|
|
1422
1422
|
const claims = parsed.data;
|
|
1423
1423
|
const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
|
|
1424
1424
|
if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
|
|
1425
|
-
if (!isVersionAtLeast("0.2.
|
|
1425
|
+
if (!isVersionAtLeast("0.2.2", 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
1426
|
const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
1427
1427
|
const grace = options.allowGraceSeconds ?? 0;
|
|
1428
1428
|
if (now > claims.exp + grace) {
|
|
@@ -1536,6 +1536,10 @@ const handleEntitlementFetchError = (err, cached, cachedClaims, now) => {
|
|
|
1536
1536
|
nowSeconds: now,
|
|
1537
1537
|
allowGraceSeconds: GRACE_PERIOD_SECONDS
|
|
1538
1538
|
});
|
|
1539
|
+
if (graceClaims.plan === "trial" && graceClaims.trial_ends_at && now > Math.floor(new Date(graceClaims.trial_ends_at).getTime() / 1e3)) {
|
|
1540
|
+
clearCachedEntitlement();
|
|
1541
|
+
throw new LicenceDeniedError("Organization trial has expired", "trial_expired");
|
|
1542
|
+
}
|
|
1539
1543
|
return {
|
|
1540
1544
|
jwt: cached.entitlement,
|
|
1541
1545
|
claims: graceClaims,
|
|
@@ -1543,6 +1547,7 @@ const handleEntitlementFetchError = (err, cached, cachedClaims, now) => {
|
|
|
1543
1547
|
grace: now > graceClaims.exp
|
|
1544
1548
|
};
|
|
1545
1549
|
} catch (jwtErr) {
|
|
1550
|
+
if (jwtErr instanceof LicenceDeniedError) throw jwtErr;
|
|
1546
1551
|
if (jwtErr instanceof JwtVerificationError && jwtErr.code === "expired_beyond_grace") throw new LicenceUnverifiableError(`Raigal license could not be verified online and the offline grace period expired on ${(/* @__PURE__ */ new Date((cachedClaims?.exp ?? 0) * 1e3)).toISOString()}. Check network connectivity or Raigal Cloud status.`);
|
|
1547
1552
|
}
|
|
1548
1553
|
throw new LicenceUnverifiableError(`Raigal license could not be verified online (${err instanceof Error ? err.message : String(err)}). Check network connectivity or Raigal Cloud status.`);
|
|
@@ -1557,7 +1562,8 @@ const getEntitlement = async () => {
|
|
|
1557
1562
|
} catch {
|
|
1558
1563
|
cachedClaims = null;
|
|
1559
1564
|
}
|
|
1560
|
-
|
|
1565
|
+
const isTrialExpired = cachedClaims?.plan === "trial" && cachedClaims.trial_ends_at && now > Math.floor(new Date(cachedClaims.trial_ends_at).getTime() / 1e3);
|
|
1566
|
+
if (cached && cachedClaims && !isTrialExpired && now < cached.refresh_after) return {
|
|
1561
1567
|
jwt: cached.entitlement,
|
|
1562
1568
|
claims: cachedClaims,
|
|
1563
1569
|
fromCache: true
|
|
@@ -1566,7 +1572,7 @@ const getEntitlement = async () => {
|
|
|
1566
1572
|
if (!activeToken) {
|
|
1567
1573
|
oidcToken = await fetchGitHubOidcToken();
|
|
1568
1574
|
if (!oidcToken) {
|
|
1569
|
-
if (cached && cachedClaims) return {
|
|
1575
|
+
if (cached && cachedClaims && !isTrialExpired) return {
|
|
1570
1576
|
jwt: cached.entitlement,
|
|
1571
1577
|
claims: cachedClaims,
|
|
1572
1578
|
fromCache: true
|
|
@@ -1727,6 +1733,23 @@ const requireEntitlement = async (options = {}) => {
|
|
|
1727
1733
|
try {
|
|
1728
1734
|
const entitlement = await getEntitlement();
|
|
1729
1735
|
const tokenKind = getActiveToken()?.kind ?? "session";
|
|
1736
|
+
if (entitlement.claims.plan === "trial" && entitlement.claims.trial_ends_at) {
|
|
1737
|
+
const trialEnd = new Date(entitlement.claims.trial_ends_at).getTime();
|
|
1738
|
+
if (Date.now() > trialEnd) {
|
|
1739
|
+
const message = "Raigal license denied: Organization trial has expired";
|
|
1740
|
+
if (mode === "throw") throw new LicenceDeniedError(message, "trial_expired");
|
|
1741
|
+
if (mode === "fatal") {
|
|
1742
|
+
process.stderr.write(`${message}\n`);
|
|
1743
|
+
process.exit(30);
|
|
1744
|
+
}
|
|
1745
|
+
process.stderr.write(`[raigal] ${message}\n`);
|
|
1746
|
+
return {
|
|
1747
|
+
ok: false,
|
|
1748
|
+
exitCode: 30,
|
|
1749
|
+
message
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1730
1753
|
const detectedOwner = detectLocalRepoOwner(directory);
|
|
1731
1754
|
if (options.repoOwner) detectedOwner.owner = options.repoOwner.toLowerCase();
|
|
1732
1755
|
const ownerCheck = checkOwnerAgainstAllowlist(detectedOwner, entitlement.claims.allowed_owners, tokenKind);
|
|
@@ -2026,267 +2049,6 @@ const renderBackgroundLaunch = (launch) => {
|
|
|
2026
2049
|
].join("\n")}`);
|
|
2027
2050
|
};
|
|
2028
2051
|
//#endregion
|
|
2029
|
-
//#region src/agents/prompt.ts
|
|
2030
|
-
const CONTEXT_LINES$1 = 3;
|
|
2031
|
-
const MAX_DIAGNOSTICS_PER_FILE$1 = 6;
|
|
2032
|
-
const MAX_FILES$1 = 12;
|
|
2033
|
-
const priority = (diagnostic) => {
|
|
2034
|
-
if (diagnostic.severity === "error") return 0;
|
|
2035
|
-
if (diagnostic.severity === "warning") return 1;
|
|
2036
|
-
return 2;
|
|
2037
|
-
};
|
|
2038
|
-
const selectAgentFindings = (diagnostics, limit) => diagnostics.filter((diagnostic) => diagnostic.severity !== "info").sort((a, b) => {
|
|
2039
|
-
const severityDelta = priority(a) - priority(b);
|
|
2040
|
-
if (severityDelta !== 0) return severityDelta;
|
|
2041
|
-
if (a.fixable !== b.fixable) return a.fixable ? 1 : -1;
|
|
2042
|
-
return a.filePath.localeCompare(b.filePath);
|
|
2043
|
-
}).slice(0, Math.max(1, limit));
|
|
2044
|
-
const groupByFile$1 = (diagnostics) => {
|
|
2045
|
-
const groups = /* @__PURE__ */ new Map();
|
|
2046
|
-
for (const diagnostic of diagnostics) {
|
|
2047
|
-
const list = groups.get(diagnostic.filePath) ?? [];
|
|
2048
|
-
list.push(diagnostic);
|
|
2049
|
-
groups.set(diagnostic.filePath, list);
|
|
2050
|
-
}
|
|
2051
|
-
return [...groups.entries()].map(([filePath, items]) => ({
|
|
2052
|
-
filePath,
|
|
2053
|
-
diagnostics: items
|
|
2054
|
-
})).slice(0, MAX_FILES$1);
|
|
2055
|
-
};
|
|
2056
|
-
const snippetFor = (rootDirectory, diagnostic) => {
|
|
2057
|
-
if (diagnostic.line <= 0 || diagnostic.redactSource || isSecretClassRule(diagnostic.rule)) return null;
|
|
2058
|
-
const absolutePath = path.resolve(rootDirectory, diagnostic.filePath);
|
|
2059
|
-
let content;
|
|
2060
|
-
try {
|
|
2061
|
-
content = fs.readFileSync(absolutePath, "utf-8");
|
|
2062
|
-
} catch {
|
|
2063
|
-
return null;
|
|
2064
|
-
}
|
|
2065
|
-
const lines = content.split("\n");
|
|
2066
|
-
const start = Math.max(0, diagnostic.line - 1 - CONTEXT_LINES$1);
|
|
2067
|
-
const end = Math.min(lines.length, diagnostic.line + CONTEXT_LINES$1);
|
|
2068
|
-
const out = [];
|
|
2069
|
-
for (let index = start; index < end; index += 1) {
|
|
2070
|
-
const lineNumber = index + 1;
|
|
2071
|
-
const marker = lineNumber === diagnostic.line ? ">" : " ";
|
|
2072
|
-
out.push(`${marker} ${String(lineNumber).padStart(4)} | ${lines[index]}`);
|
|
2073
|
-
}
|
|
2074
|
-
return out.join("\n");
|
|
2075
|
-
};
|
|
2076
|
-
const buildRepairPrompt = (input) => {
|
|
2077
|
-
const groups = groupByFile$1(input.findings);
|
|
2078
|
-
const lines = [
|
|
2079
|
-
"You are repairing AI slop findings in a local git worktree.",
|
|
2080
|
-
"",
|
|
2081
|
-
`Current raigal score: ${input.score == null ? "not scored" : `${input.score}/100`}`,
|
|
2082
|
-
`Target score: ${input.targetScore}/100`,
|
|
2083
|
-
`Turn budget: ${input.maxTurns}`,
|
|
2084
|
-
"",
|
|
2085
|
-
"Hard constraints:",
|
|
2086
|
-
"- Make the smallest code changes that improve the findings below.",
|
|
2087
|
-
"- Do not change public APIs, exports, database schemas, or test expectations unless a finding directly requires it.",
|
|
2088
|
-
"- Do not delete tests.",
|
|
2089
|
-
"- Do not add dependencies unless the existing project already clearly expects them.",
|
|
2090
|
-
"- Never print, copy, commit, or echo secret values or credentials. Replace all hardcoded credentials and secrets with environment-variable references (e.g. process.env.KEY).",
|
|
2091
|
-
"- If a finding looks like a false positive, leave the code alone and mention it in your final summary.",
|
|
2092
|
-
"- Run the relevant local verification command if it is obvious and cheap.",
|
|
2093
|
-
"",
|
|
2094
|
-
"raigal findings to repair:",
|
|
2095
|
-
""
|
|
2096
|
-
];
|
|
2097
|
-
for (const group of groups) {
|
|
2098
|
-
lines.push(`## ${group.filePath}`);
|
|
2099
|
-
for (const diagnostic of group.diagnostics.slice(0, MAX_DIAGNOSTICS_PER_FILE$1)) {
|
|
2100
|
-
const location = diagnostic.line > 0 ? `:${diagnostic.line}` : "";
|
|
2101
|
-
lines.push(`- ${diagnostic.severity.toUpperCase()} ${diagnostic.rule}${location}: ${diagnostic.message}`);
|
|
2102
|
-
if (diagnostic.help) lines.push(` Help: ${diagnostic.help}`);
|
|
2103
|
-
const snippet = snippetFor(input.rootDirectory, diagnostic);
|
|
2104
|
-
if (snippet) {
|
|
2105
|
-
lines.push("```");
|
|
2106
|
-
lines.push(snippet);
|
|
2107
|
-
lines.push("```");
|
|
2108
|
-
}
|
|
2109
|
-
}
|
|
2110
|
-
lines.push("");
|
|
2111
|
-
}
|
|
2112
|
-
if (input.findings.length === 0) lines.push("No findings were supplied. Inspect the codebase for obvious AI slop only if needed.");
|
|
2113
|
-
lines.push("After editing, stop and summarize what changed and what you intentionally skipped.");
|
|
2114
|
-
return maskSecrets(lines.join("\n"));
|
|
2115
|
-
};
|
|
2116
|
-
//#endregion
|
|
2117
|
-
//#region src/agents/publish.ts
|
|
2118
|
-
const defaultBranchSuffix = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\..+$/, "").replace("T", "-");
|
|
2119
|
-
const normalizeBranchName = (raw) => {
|
|
2120
|
-
const cleaned = raw.trim().replace(/[^A-Za-z0-9._/-]+/g, "-").replace(/\/+/g, "/").replace(/^-+|-+$/g, "").replace(/^\/+|\/+$/g, "");
|
|
2121
|
-
return cleaned.length > 0 ? cleaned.slice(0, 120) : `raigal/agent-${defaultBranchSuffix()}`;
|
|
2122
|
-
};
|
|
2123
|
-
const defaultBranchName = () => `raigal/agent-${defaultBranchSuffix()}`;
|
|
2124
|
-
const runGit = async (cwd, args) => {
|
|
2125
|
-
const result = await runSubprocess("git", args, {
|
|
2126
|
-
cwd,
|
|
2127
|
-
timeout: 9e4
|
|
2128
|
-
});
|
|
2129
|
-
if (result.exitCode !== 0) throw new Error(result.stderr || result.stdout || `git ${args.join(" ")} failed`);
|
|
2130
|
-
return result.stdout;
|
|
2131
|
-
};
|
|
2132
|
-
const currentBranch = async (cwd) => {
|
|
2133
|
-
const result = await runSubprocess("git", ["branch", "--show-current"], { cwd });
|
|
2134
|
-
if (result.exitCode !== 0) return null;
|
|
2135
|
-
return result.stdout.trim() || null;
|
|
2136
|
-
};
|
|
2137
|
-
const resolveBaseBranch = async (cwd, fallback) => {
|
|
2138
|
-
if (fallback && fallback !== "HEAD") return fallback;
|
|
2139
|
-
const cached = await runSubprocess("git", [
|
|
2140
|
-
"symbolic-ref",
|
|
2141
|
-
"refs/remotes/origin/HEAD",
|
|
2142
|
-
"--short"
|
|
2143
|
-
], { cwd });
|
|
2144
|
-
if (cached.exitCode === 0 && cached.stdout.trim()) return cached.stdout.trim().replace(/^origin\//, "");
|
|
2145
|
-
const remote = await runSubprocess("git", [
|
|
2146
|
-
"remote",
|
|
2147
|
-
"show",
|
|
2148
|
-
"origin"
|
|
2149
|
-
], {
|
|
2150
|
-
cwd,
|
|
2151
|
-
timeout: 3e4
|
|
2152
|
-
});
|
|
2153
|
-
if (remote.exitCode === 0) {
|
|
2154
|
-
const branch = remote.stdout.split("\n").find((entry) => entry.trim().startsWith("HEAD branch:"))?.split(":").slice(1).join(":").trim();
|
|
2155
|
-
if (branch) return branch;
|
|
2156
|
-
}
|
|
2157
|
-
return "main";
|
|
2158
|
-
};
|
|
2159
|
-
const ensureBranch = async (cwd, branch) => {
|
|
2160
|
-
if (await currentBranch(cwd) === branch) return;
|
|
2161
|
-
await runGit(cwd, [
|
|
2162
|
-
"switch",
|
|
2163
|
-
"-c",
|
|
2164
|
-
branch
|
|
2165
|
-
]);
|
|
2166
|
-
};
|
|
2167
|
-
const ensureGh = async () => {
|
|
2168
|
-
if ((await runSubprocess("gh", ["--version"], { timeout: 1e4 })).exitCode !== 0) throw new Error("Creating a PR requires the GitHub CLI. Install and authenticate `gh`, then retry.");
|
|
2169
|
-
};
|
|
2170
|
-
const buildPrBody = (input) => {
|
|
2171
|
-
const delta = input.beforeScore != null && input.afterScore != null ? input.afterScore - input.beforeScore : null;
|
|
2172
|
-
const deltaStr = delta != null ? ` (${delta >= 0 ? `+${delta}` : delta})` : "";
|
|
2173
|
-
const scoreStr = `${input.beforeScore ?? "not scored"} -> ${input.afterScore ?? "not scored"}${deltaStr}`;
|
|
2174
|
-
let diffStatLine = `- Files changed: ${input.changedFiles.length}`;
|
|
2175
|
-
if (typeof input.diffSummary === "string" && input.diffSummary.trim()) diffStatLine += ` (${input.diffSummary.trim()})`;
|
|
2176
|
-
else if (input.diffSummary && typeof input.diffSummary === "object") {
|
|
2177
|
-
const adds = input.diffSummary.additions ?? 0;
|
|
2178
|
-
const dels = input.diffSummary.deletions ?? 0;
|
|
2179
|
-
diffStatLine += ` (+${adds} -${dels})`;
|
|
2180
|
-
}
|
|
2181
|
-
const fixedFindingsSection = input.fixedFindings && input.fixedFindings.length > 0 ? [
|
|
2182
|
-
"### Fixed findings",
|
|
2183
|
-
"",
|
|
2184
|
-
"| Rule | File | Message |",
|
|
2185
|
-
"|---|---|---|",
|
|
2186
|
-
...input.fixedFindings.slice(0, 10).map((f) => {
|
|
2187
|
-
const loc = f.line ? `${f.path}:${f.line}` : f.path;
|
|
2188
|
-
const msg = (f.message || "").replace(/\|/g, "\\|");
|
|
2189
|
-
return `| \`${f.ruleId}\` | \`${loc}\` | ${msg} |`;
|
|
2190
|
-
}),
|
|
2191
|
-
input.fixedFindings.length > 10 ? `\n*...and ${input.fixedFindings.length - 10} more findings fixed.*` : "",
|
|
2192
|
-
""
|
|
2193
|
-
] : [];
|
|
2194
|
-
return [
|
|
2195
|
-
"This PR was generated locally by `raigal agent`.",
|
|
2196
|
-
"",
|
|
2197
|
-
"## Summary",
|
|
2198
|
-
"",
|
|
2199
|
-
`- Provider: ${input.providerId}`,
|
|
2200
|
-
`- Score: ${scoreStr}`,
|
|
2201
|
-
diffStatLine,
|
|
2202
|
-
"",
|
|
2203
|
-
...fixedFindingsSection,
|
|
2204
|
-
"## Changed files",
|
|
2205
|
-
"",
|
|
2206
|
-
...input.changedFiles.map((file) => `- ${file}`),
|
|
2207
|
-
"",
|
|
2208
|
-
"## Verification",
|
|
2209
|
-
"",
|
|
2210
|
-
"- `raigal scan --json`",
|
|
2211
|
-
"",
|
|
2212
|
-
"Review before merging. The deterministic scan is the gate; the provider only proposed the patch."
|
|
2213
|
-
].filter(Boolean).join("\n");
|
|
2214
|
-
};
|
|
2215
|
-
const publishAgentDiff = async (input) => {
|
|
2216
|
-
if (!input.options.commit && !input.options.pr) return null;
|
|
2217
|
-
if (input.changedFiles.length === 0) return null;
|
|
2218
|
-
const branch = normalizeBranchName(input.options.branch ?? defaultBranchName());
|
|
2219
|
-
await ensureBranch(input.cwd, branch);
|
|
2220
|
-
await runGit(input.cwd, ["add", "-A"]);
|
|
2221
|
-
if ((await runSubprocess("git", [
|
|
2222
|
-
"diff",
|
|
2223
|
-
"--cached",
|
|
2224
|
-
"--quiet"
|
|
2225
|
-
], { cwd: input.cwd })).exitCode === 0) return null;
|
|
2226
|
-
await runGit(input.cwd, [
|
|
2227
|
-
"commit",
|
|
2228
|
-
"-m",
|
|
2229
|
-
input.options.commitMessage,
|
|
2230
|
-
"-m",
|
|
2231
|
-
"Generated by raigal agent."
|
|
2232
|
-
]);
|
|
2233
|
-
const commitSha = (await runGit(input.cwd, [
|
|
2234
|
-
"rev-parse",
|
|
2235
|
-
"--short",
|
|
2236
|
-
"HEAD"
|
|
2237
|
-
])).trim();
|
|
2238
|
-
if (!input.options.pr) return {
|
|
2239
|
-
branch,
|
|
2240
|
-
commitSha,
|
|
2241
|
-
pushed: false,
|
|
2242
|
-
prUrl: null
|
|
2243
|
-
};
|
|
2244
|
-
await ensureGh();
|
|
2245
|
-
const base = input.options.base ?? await resolveBaseBranch(input.cwd, input.originalBranch);
|
|
2246
|
-
await runGit(input.cwd, [
|
|
2247
|
-
"push",
|
|
2248
|
-
"-u",
|
|
2249
|
-
"origin",
|
|
2250
|
-
branch
|
|
2251
|
-
]);
|
|
2252
|
-
let diffSummary = input.diffSummary;
|
|
2253
|
-
if (!diffSummary) {
|
|
2254
|
-
const stat = await runSubprocess("git", [
|
|
2255
|
-
"diff",
|
|
2256
|
-
"--shortstat",
|
|
2257
|
-
"HEAD~1"
|
|
2258
|
-
], { cwd: input.cwd });
|
|
2259
|
-
if (stat.exitCode === 0 && stat.stdout.trim()) diffSummary = stat.stdout.trim();
|
|
2260
|
-
}
|
|
2261
|
-
const args = [
|
|
2262
|
-
"pr",
|
|
2263
|
-
"create",
|
|
2264
|
-
"--base",
|
|
2265
|
-
base,
|
|
2266
|
-
"--head",
|
|
2267
|
-
branch,
|
|
2268
|
-
"--title",
|
|
2269
|
-
input.options.prTitle ?? input.options.commitMessage,
|
|
2270
|
-
"--body",
|
|
2271
|
-
buildPrBody({
|
|
2272
|
-
...input,
|
|
2273
|
-
diffSummary
|
|
2274
|
-
})
|
|
2275
|
-
];
|
|
2276
|
-
if (!input.options.ready) args.push("--draft");
|
|
2277
|
-
const pr = await runSubprocess("gh", args, {
|
|
2278
|
-
cwd: input.cwd,
|
|
2279
|
-
timeout: 9e4
|
|
2280
|
-
});
|
|
2281
|
-
if (pr.exitCode !== 0) throw new Error(pr.stderr || pr.stdout || "Failed to create pull request with gh.");
|
|
2282
|
-
return {
|
|
2283
|
-
branch,
|
|
2284
|
-
commitSha,
|
|
2285
|
-
pushed: true,
|
|
2286
|
-
prUrl: pr.stdout.trim() || null
|
|
2287
|
-
};
|
|
2288
|
-
};
|
|
2289
|
-
//#endregion
|
|
2290
2052
|
//#region src/agents/session-activity.ts
|
|
2291
2053
|
const createUsageTotals = () => ({
|
|
2292
2054
|
inputTokens: 0,
|
|
@@ -2693,6 +2455,239 @@ var AgentTui = class {
|
|
|
2693
2455
|
}
|
|
2694
2456
|
};
|
|
2695
2457
|
//#endregion
|
|
2458
|
+
//#region src/commands/agent-local-cli.ts
|
|
2459
|
+
const cliPath$1 = () => {
|
|
2460
|
+
const arg = process.argv[1];
|
|
2461
|
+
if (arg && fs.existsSync(arg)) return arg;
|
|
2462
|
+
return path.resolve("dist/cli.js");
|
|
2463
|
+
};
|
|
2464
|
+
const runSelf = (args, cwd) => {
|
|
2465
|
+
const result = spawnSync(process.execPath, [cliPath$1(), ...args], {
|
|
2466
|
+
cwd,
|
|
2467
|
+
encoding: "utf-8",
|
|
2468
|
+
env: {
|
|
2469
|
+
...process.env,
|
|
2470
|
+
AISLOP_NO_TELEMETRY: "1",
|
|
2471
|
+
AISLOP_NO_UPDATE_NOTIFIER: "1",
|
|
2472
|
+
NO_COLOR: "1"
|
|
2473
|
+
},
|
|
2474
|
+
maxBuffer: 50 * 1024 * 1024
|
|
2475
|
+
});
|
|
2476
|
+
return {
|
|
2477
|
+
exitCode: result.status,
|
|
2478
|
+
stdout: result.stdout,
|
|
2479
|
+
stderr: result.stderr
|
|
2480
|
+
};
|
|
2481
|
+
};
|
|
2482
|
+
const scanJson = (cwd) => {
|
|
2483
|
+
const result = runSelf([
|
|
2484
|
+
"scan",
|
|
2485
|
+
".",
|
|
2486
|
+
"--json"
|
|
2487
|
+
], cwd);
|
|
2488
|
+
if (!result.stdout.trim()) throw new Error(result.stderr || "raigal scan did not produce JSON output.");
|
|
2489
|
+
try {
|
|
2490
|
+
return JSON.parse(result.stdout);
|
|
2491
|
+
} catch (error) {
|
|
2492
|
+
throw new Error(`Failed to parse raigal scan output: ${error instanceof Error ? error.message : String(error)}`);
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
const runSafeFix = (cwd) => {
|
|
2496
|
+
const result = runSelf([
|
|
2497
|
+
"fix",
|
|
2498
|
+
".",
|
|
2499
|
+
"--safe"
|
|
2500
|
+
], cwd);
|
|
2501
|
+
if (result.exitCode !== 0 && result.stderr.trim()) log.warn(result.stderr.trim().split("\n")[0] ?? "Safe fix reported a non-zero exit.");
|
|
2502
|
+
};
|
|
2503
|
+
const applyDiff = async (root, patch) => {
|
|
2504
|
+
const child = spawnSync("git", [
|
|
2505
|
+
"apply",
|
|
2506
|
+
"--index",
|
|
2507
|
+
"--whitespace=nowarn",
|
|
2508
|
+
"-"
|
|
2509
|
+
], {
|
|
2510
|
+
cwd: root,
|
|
2511
|
+
input: patch,
|
|
2512
|
+
encoding: "utf-8",
|
|
2513
|
+
maxBuffer: 50 * 1024 * 1024
|
|
2514
|
+
});
|
|
2515
|
+
if (child.status !== 0) throw new Error(child.stderr || child.stdout || "Failed to apply agent diff.");
|
|
2516
|
+
};
|
|
2517
|
+
//#endregion
|
|
2518
|
+
//#region src/agents/publish.ts
|
|
2519
|
+
const defaultBranchSuffix = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\..+$/, "").replace("T", "-");
|
|
2520
|
+
const normalizeBranchName = (raw) => {
|
|
2521
|
+
const cleaned = raw.trim().replace(/[^A-Za-z0-9._/-]+/g, "-").replace(/\/+/g, "/").replace(/^-+|-+$/g, "").replace(/^\/+|\/+$/g, "");
|
|
2522
|
+
return cleaned.length > 0 ? cleaned.slice(0, 120) : `raigal/agent-${defaultBranchSuffix()}`;
|
|
2523
|
+
};
|
|
2524
|
+
const defaultBranchName = () => `raigal/agent-${defaultBranchSuffix()}`;
|
|
2525
|
+
const runGit = async (cwd, args) => {
|
|
2526
|
+
const result = await runSubprocess("git", args, {
|
|
2527
|
+
cwd,
|
|
2528
|
+
timeout: 9e4
|
|
2529
|
+
});
|
|
2530
|
+
if (result.exitCode !== 0) throw new Error(result.stderr || result.stdout || `git ${args.join(" ")} failed`);
|
|
2531
|
+
return result.stdout;
|
|
2532
|
+
};
|
|
2533
|
+
const currentBranch = async (cwd) => {
|
|
2534
|
+
const result = await runSubprocess("git", ["branch", "--show-current"], { cwd });
|
|
2535
|
+
if (result.exitCode !== 0) return null;
|
|
2536
|
+
return result.stdout.trim() || null;
|
|
2537
|
+
};
|
|
2538
|
+
const resolveBaseBranch = async (cwd, fallback) => {
|
|
2539
|
+
if (fallback && fallback !== "HEAD") return fallback;
|
|
2540
|
+
const cached = await runSubprocess("git", [
|
|
2541
|
+
"symbolic-ref",
|
|
2542
|
+
"refs/remotes/origin/HEAD",
|
|
2543
|
+
"--short"
|
|
2544
|
+
], { cwd });
|
|
2545
|
+
if (cached.exitCode === 0 && cached.stdout.trim()) return cached.stdout.trim().replace(/^origin\//, "");
|
|
2546
|
+
const remote = await runSubprocess("git", [
|
|
2547
|
+
"remote",
|
|
2548
|
+
"show",
|
|
2549
|
+
"origin"
|
|
2550
|
+
], {
|
|
2551
|
+
cwd,
|
|
2552
|
+
timeout: 3e4
|
|
2553
|
+
});
|
|
2554
|
+
if (remote.exitCode === 0) {
|
|
2555
|
+
const branch = remote.stdout.split("\n").find((entry) => entry.trim().startsWith("HEAD branch:"))?.split(":").slice(1).join(":").trim();
|
|
2556
|
+
if (branch) return branch;
|
|
2557
|
+
}
|
|
2558
|
+
return "main";
|
|
2559
|
+
};
|
|
2560
|
+
const ensureBranch = async (cwd, branch) => {
|
|
2561
|
+
if (await currentBranch(cwd) === branch) return;
|
|
2562
|
+
await runGit(cwd, [
|
|
2563
|
+
"switch",
|
|
2564
|
+
"-c",
|
|
2565
|
+
branch
|
|
2566
|
+
]);
|
|
2567
|
+
};
|
|
2568
|
+
const ensureGh = async () => {
|
|
2569
|
+
if ((await runSubprocess("gh", ["--version"], { timeout: 1e4 })).exitCode !== 0) throw new Error("Creating a PR requires the GitHub CLI. Install and authenticate `gh`, then retry.");
|
|
2570
|
+
};
|
|
2571
|
+
const buildPrBody = (input) => {
|
|
2572
|
+
const delta = input.beforeScore != null && input.afterScore != null ? input.afterScore - input.beforeScore : null;
|
|
2573
|
+
const deltaStr = delta != null ? ` (${delta >= 0 ? `+${delta}` : delta})` : "";
|
|
2574
|
+
const scoreStr = `${input.beforeScore ?? "not scored"} -> ${input.afterScore ?? "not scored"}${deltaStr}`;
|
|
2575
|
+
let diffStatLine = `- Files changed: ${input.changedFiles.length}`;
|
|
2576
|
+
if (typeof input.diffSummary === "string" && input.diffSummary.trim()) diffStatLine += ` (${input.diffSummary.trim()})`;
|
|
2577
|
+
else if (input.diffSummary && typeof input.diffSummary === "object") {
|
|
2578
|
+
const adds = input.diffSummary.additions ?? 0;
|
|
2579
|
+
const dels = input.diffSummary.deletions ?? 0;
|
|
2580
|
+
diffStatLine += ` (+${adds} -${dels})`;
|
|
2581
|
+
}
|
|
2582
|
+
const fixedFindingsSection = input.fixedFindings && input.fixedFindings.length > 0 ? [
|
|
2583
|
+
"### Fixed findings",
|
|
2584
|
+
"",
|
|
2585
|
+
"| Rule | File | Message |",
|
|
2586
|
+
"|---|---|---|",
|
|
2587
|
+
...input.fixedFindings.slice(0, 10).map((f) => {
|
|
2588
|
+
const loc = f.line ? `${f.path}:${f.line}` : f.path;
|
|
2589
|
+
const msg = (f.message || "").replace(/\|/g, "\\|");
|
|
2590
|
+
return `| \`${f.ruleId}\` | \`${loc}\` | ${msg} |`;
|
|
2591
|
+
}),
|
|
2592
|
+
input.fixedFindings.length > 10 ? `\n*...and ${input.fixedFindings.length - 10} more findings fixed.*` : "",
|
|
2593
|
+
""
|
|
2594
|
+
] : [];
|
|
2595
|
+
return [
|
|
2596
|
+
"This PR was generated locally by `raigal agent`.",
|
|
2597
|
+
"",
|
|
2598
|
+
"## Summary",
|
|
2599
|
+
"",
|
|
2600
|
+
`- Provider: ${input.providerId}`,
|
|
2601
|
+
`- Score: ${scoreStr}`,
|
|
2602
|
+
diffStatLine,
|
|
2603
|
+
"",
|
|
2604
|
+
...fixedFindingsSection,
|
|
2605
|
+
"## Changed files",
|
|
2606
|
+
"",
|
|
2607
|
+
...input.changedFiles.map((file) => `- ${file}`),
|
|
2608
|
+
"",
|
|
2609
|
+
"## Verification",
|
|
2610
|
+
"",
|
|
2611
|
+
"- `raigal scan --json`",
|
|
2612
|
+
"",
|
|
2613
|
+
"Review before merging. The deterministic scan is the gate; the provider only proposed the patch."
|
|
2614
|
+
].filter(Boolean).join("\n");
|
|
2615
|
+
};
|
|
2616
|
+
const publishAgentDiff = async (input) => {
|
|
2617
|
+
if (!input.options.commit && !input.options.pr) return null;
|
|
2618
|
+
if (input.changedFiles.length === 0) return null;
|
|
2619
|
+
const branch = normalizeBranchName(input.options.branch ?? defaultBranchName());
|
|
2620
|
+
await ensureBranch(input.cwd, branch);
|
|
2621
|
+
await runGit(input.cwd, ["add", "-A"]);
|
|
2622
|
+
if ((await runSubprocess("git", [
|
|
2623
|
+
"diff",
|
|
2624
|
+
"--cached",
|
|
2625
|
+
"--quiet"
|
|
2626
|
+
], { cwd: input.cwd })).exitCode === 0) return null;
|
|
2627
|
+
await runGit(input.cwd, [
|
|
2628
|
+
"commit",
|
|
2629
|
+
"-m",
|
|
2630
|
+
input.options.commitMessage,
|
|
2631
|
+
"-m",
|
|
2632
|
+
"Generated by raigal agent."
|
|
2633
|
+
]);
|
|
2634
|
+
const commitSha = (await runGit(input.cwd, [
|
|
2635
|
+
"rev-parse",
|
|
2636
|
+
"--short",
|
|
2637
|
+
"HEAD"
|
|
2638
|
+
])).trim();
|
|
2639
|
+
if (!input.options.pr) return {
|
|
2640
|
+
branch,
|
|
2641
|
+
commitSha,
|
|
2642
|
+
pushed: false,
|
|
2643
|
+
prUrl: null
|
|
2644
|
+
};
|
|
2645
|
+
await ensureGh();
|
|
2646
|
+
const base = input.options.base ?? await resolveBaseBranch(input.cwd, input.originalBranch);
|
|
2647
|
+
await runGit(input.cwd, [
|
|
2648
|
+
"push",
|
|
2649
|
+
"-u",
|
|
2650
|
+
"origin",
|
|
2651
|
+
branch
|
|
2652
|
+
]);
|
|
2653
|
+
let diffSummary = input.diffSummary;
|
|
2654
|
+
if (!diffSummary) {
|
|
2655
|
+
const stat = await runSubprocess("git", [
|
|
2656
|
+
"diff",
|
|
2657
|
+
"--shortstat",
|
|
2658
|
+
"HEAD~1"
|
|
2659
|
+
], { cwd: input.cwd });
|
|
2660
|
+
if (stat.exitCode === 0 && stat.stdout.trim()) diffSummary = stat.stdout.trim();
|
|
2661
|
+
}
|
|
2662
|
+
const args = [
|
|
2663
|
+
"pr",
|
|
2664
|
+
"create",
|
|
2665
|
+
"--base",
|
|
2666
|
+
base,
|
|
2667
|
+
"--head",
|
|
2668
|
+
branch,
|
|
2669
|
+
"--title",
|
|
2670
|
+
input.options.prTitle ?? input.options.commitMessage,
|
|
2671
|
+
"--body",
|
|
2672
|
+
buildPrBody({
|
|
2673
|
+
...input,
|
|
2674
|
+
diffSummary
|
|
2675
|
+
})
|
|
2676
|
+
];
|
|
2677
|
+
if (!input.options.ready) args.push("--draft");
|
|
2678
|
+
const pr = await runSubprocess("gh", args, {
|
|
2679
|
+
cwd: input.cwd,
|
|
2680
|
+
timeout: 9e4
|
|
2681
|
+
});
|
|
2682
|
+
if (pr.exitCode !== 0) throw new Error(pr.stderr || pr.stdout || "Failed to create pull request with gh.");
|
|
2683
|
+
return {
|
|
2684
|
+
branch,
|
|
2685
|
+
commitSha,
|
|
2686
|
+
pushed: true,
|
|
2687
|
+
prUrl: pr.stdout.trim() || null
|
|
2688
|
+
};
|
|
2689
|
+
};
|
|
2690
|
+
//#endregion
|
|
2696
2691
|
//#region src/cloud/context.ts
|
|
2697
2692
|
const execGit = (args, cwd) => {
|
|
2698
2693
|
try {
|
|
@@ -2766,7 +2761,8 @@ const detectGitDiff = (directory, baseRef) => {
|
|
|
2766
2761
|
"HEAD~1"
|
|
2767
2762
|
], directory);
|
|
2768
2763
|
let diffPreview;
|
|
2769
|
-
|
|
2764
|
+
const allowSnippets = process.env.RAIGAL_NO_SNIPPETS !== "true" && process.env.RAIGAL_PRIVACY_MODE !== "true";
|
|
2765
|
+
if (preview && allowSnippets) diffPreview = preview.split("\n").slice(0, 40).join("\n").slice(0, 1e4);
|
|
2770
2766
|
return {
|
|
2771
2767
|
diffSummary,
|
|
2772
2768
|
diffPreview
|
|
@@ -2919,7 +2915,7 @@ const writeRunsToOutbox = (runs) => {
|
|
|
2919
2915
|
recursive: true,
|
|
2920
2916
|
mode: 448
|
|
2921
2917
|
});
|
|
2922
|
-
|
|
2918
|
+
const lines = runs.map((r) => JSON.stringify(r));
|
|
2923
2919
|
let text = lines.length > 0 ? `${lines.join("\n")}\n` : "";
|
|
2924
2920
|
const maxBytes = getMaxOutboxBytes();
|
|
2925
2921
|
while (Buffer.byteLength(text, "utf-8") > maxBytes && lines.length > 1) {
|
|
@@ -2944,7 +2940,9 @@ const getLastRecordedRun = () => {
|
|
|
2944
2940
|
const runs = loadOutboxRuns();
|
|
2945
2941
|
return runs.length > 0 ? runs[runs.length - 1] ?? null : null;
|
|
2946
2942
|
};
|
|
2947
|
-
const flushOutbox = async (
|
|
2943
|
+
const flushOutbox = async (optionsOrToken) => {
|
|
2944
|
+
const token = typeof optionsOrToken === "string" ? optionsOrToken : optionsOrToken?.token;
|
|
2945
|
+
const oidcToken = typeof optionsOrToken === "object" ? optionsOrToken?.oidcToken : void 0;
|
|
2948
2946
|
const runs = loadOutboxRuns();
|
|
2949
2947
|
if (runs.length === 0) return {
|
|
2950
2948
|
sent: 0,
|
|
@@ -2960,6 +2958,7 @@ const flushOutbox = async (token) => {
|
|
|
2960
2958
|
await requestApi("/v1/runs", {
|
|
2961
2959
|
method: "POST",
|
|
2962
2960
|
token,
|
|
2961
|
+
oidcToken,
|
|
2963
2962
|
body: run,
|
|
2964
2963
|
timeoutMs: 4e3
|
|
2965
2964
|
});
|
|
@@ -3081,7 +3080,12 @@ var CloudRunRecorder = class {
|
|
|
3081
3080
|
try {
|
|
3082
3081
|
enqueueRun(this.buildRunPayload(exitCode));
|
|
3083
3082
|
const activeToken = getActiveToken();
|
|
3084
|
-
|
|
3083
|
+
let oidcToken;
|
|
3084
|
+
if (!activeToken) oidcToken = await fetchGitHubOidcToken();
|
|
3085
|
+
if (activeToken || oidcToken) await flushOutbox({
|
|
3086
|
+
token: activeToken?.token,
|
|
3087
|
+
oidcToken
|
|
3088
|
+
});
|
|
3085
3089
|
} catch {}
|
|
3086
3090
|
}
|
|
3087
3091
|
};
|
|
@@ -3091,7 +3095,10 @@ const startRunRecorder = (command, directory, flags = []) => {
|
|
|
3091
3095
|
const recorder = new CloudRunRecorder(command, directory, flags);
|
|
3092
3096
|
activeRecorder = recorder;
|
|
3093
3097
|
const activeToken = getActiveToken();
|
|
3094
|
-
if (activeToken) flushOutbox(activeToken.token).catch(() => {});
|
|
3098
|
+
if (activeToken) flushOutbox({ token: activeToken.token }).catch(() => {});
|
|
3099
|
+
else fetchGitHubOidcToken().then((oidcToken) => {
|
|
3100
|
+
if (oidcToken) return flushOutbox({ oidcToken });
|
|
3101
|
+
}).catch(() => {});
|
|
3095
3102
|
return recorder;
|
|
3096
3103
|
};
|
|
3097
3104
|
const finishActiveRecorder = async (exitCode = 0) => {
|
|
@@ -3102,64 +3109,185 @@ const finishActiveRecorder = async (exitCode = 0) => {
|
|
|
3102
3109
|
}
|
|
3103
3110
|
};
|
|
3104
3111
|
//#endregion
|
|
3105
|
-
//#region src/commands/agent-
|
|
3106
|
-
const
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3112
|
+
//#region src/commands/agent-session-publish.ts
|
|
3113
|
+
const maybePublishAgentDiff = async ({ tui, session, options, changedFiles, worktreePath, originalBranch, providerId, before, after }) => {
|
|
3114
|
+
if (changedFiles.length > 0 && (options.commit || options.pr)) {
|
|
3115
|
+
tui.start(options.pr ? "Creating local branch and PR" : "Creating local commit");
|
|
3116
|
+
session.append("publish.started", {
|
|
3117
|
+
commit: options.commit,
|
|
3118
|
+
pr: options.pr,
|
|
3119
|
+
branch: options.branch,
|
|
3120
|
+
base: options.base,
|
|
3121
|
+
ready: options.ready
|
|
3122
|
+
});
|
|
3123
|
+
const afterKeys = new Set(after.diagnostics.map((d) => `${d.rule}:${d.filePath}:${d.line}`));
|
|
3124
|
+
const fixedFindings = before.diagnostics.filter((d) => !afterKeys.has(`${d.rule}:${d.filePath}:${d.line}`)).map((d) => ({
|
|
3125
|
+
ruleId: d.rule,
|
|
3126
|
+
path: d.filePath,
|
|
3127
|
+
line: d.line,
|
|
3128
|
+
message: d.message
|
|
3129
|
+
}));
|
|
3130
|
+
const published = await publishAgentDiff({
|
|
3131
|
+
cwd: worktreePath,
|
|
3132
|
+
originalBranch,
|
|
3133
|
+
providerId,
|
|
3134
|
+
beforeScore: before.score,
|
|
3135
|
+
afterScore: after.score,
|
|
3136
|
+
changedFiles,
|
|
3137
|
+
fixedFindings,
|
|
3138
|
+
options: {
|
|
3139
|
+
commit: options.commit,
|
|
3140
|
+
pr: options.pr,
|
|
3141
|
+
branch: options.branch,
|
|
3142
|
+
base: options.base,
|
|
3143
|
+
commitMessage: options.commitMessage,
|
|
3144
|
+
prTitle: options.prTitle,
|
|
3145
|
+
ready: options.ready
|
|
3146
|
+
}
|
|
3147
|
+
});
|
|
3148
|
+
session.append(published ? "publish.finished" : "publish.skipped", { result: published });
|
|
3149
|
+
tui.complete({
|
|
3150
|
+
status: published ? "done" : "skipped",
|
|
3151
|
+
label: published?.prUrl ? `Opened PR ${published.prUrl}` : published ? `Committed ${published.commitSha}` : "No commit created"
|
|
3152
|
+
});
|
|
3153
|
+
return published;
|
|
3154
|
+
}
|
|
3155
|
+
if (options.commit || options.pr) session.append("publish.skipped", {
|
|
3156
|
+
reason: "no_changed_files",
|
|
3157
|
+
commit: options.commit,
|
|
3158
|
+
pr: options.pr
|
|
3159
|
+
});
|
|
3160
|
+
return null;
|
|
3110
3161
|
};
|
|
3111
|
-
const
|
|
3112
|
-
const
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3162
|
+
const recordAgentSessionTelemetry = ({ worktreePath, published, branchOption, providerId, beforeScore, afterScore, targetScore, durationMs, changedFilesCount }) => {
|
|
3163
|
+
const recorder = getActiveRecorder();
|
|
3164
|
+
if (!recorder) return;
|
|
3165
|
+
let patchDiff;
|
|
3166
|
+
if (worktreePath) try {
|
|
3167
|
+
patchDiff = (spawnSync("git", [
|
|
3168
|
+
"diff",
|
|
3169
|
+
"-U2",
|
|
3170
|
+
"--no-color",
|
|
3171
|
+
"HEAD"
|
|
3172
|
+
], {
|
|
3173
|
+
cwd: worktreePath,
|
|
3174
|
+
encoding: "utf-8"
|
|
3175
|
+
}).stdout || "").slice(0, 2e4) || void 0;
|
|
3176
|
+
} catch {}
|
|
3177
|
+
if (published) {
|
|
3178
|
+
const prMatch = published.prUrl?.match(/\/pull\/(\d+)/);
|
|
3179
|
+
const prNumber = prMatch ? Number.parseInt(prMatch[1], 10) : void 0;
|
|
3180
|
+
recorder.setGitContext({
|
|
3181
|
+
branch: published.branch,
|
|
3182
|
+
pr_number: prNumber
|
|
3183
|
+
});
|
|
3184
|
+
} else if (branchOption) recorder.setGitContext({ branch: branchOption });
|
|
3185
|
+
const agentTelemetry = {
|
|
3186
|
+
provider: providerId,
|
|
3187
|
+
before_score: beforeScore,
|
|
3188
|
+
after_score: afterScore,
|
|
3189
|
+
pr_url: published?.prUrl || void 0,
|
|
3190
|
+
patch_diff: patchDiff,
|
|
3191
|
+
duration_ms: durationMs
|
|
3192
|
+
};
|
|
3193
|
+
recorder.setAgentTelemetry(agentTelemetry);
|
|
3194
|
+
recorder.recordStep("agent:baseline", 50, true);
|
|
3195
|
+
recorder.recordStep("agent:verify", durationMs, afterScore !== null && afterScore >= targetScore);
|
|
3196
|
+
recorder.setReport({
|
|
3197
|
+
score: afterScore,
|
|
3198
|
+
scoreable: true,
|
|
3199
|
+
files_scanned: changedFilesCount,
|
|
3200
|
+
findings: [],
|
|
3201
|
+
agent_telemetry: agentTelemetry
|
|
3122
3202
|
});
|
|
3123
|
-
return {
|
|
3124
|
-
exitCode: result.status,
|
|
3125
|
-
stdout: result.stdout,
|
|
3126
|
-
stderr: result.stderr
|
|
3127
|
-
};
|
|
3128
3203
|
};
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
if (
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3204
|
+
//#endregion
|
|
3205
|
+
//#region src/agents/prompt.ts
|
|
3206
|
+
const CONTEXT_LINES$1 = 3;
|
|
3207
|
+
const MAX_DIAGNOSTICS_PER_FILE$1 = 6;
|
|
3208
|
+
const MAX_FILES$1 = 12;
|
|
3209
|
+
const priority = (diagnostic) => {
|
|
3210
|
+
if (diagnostic.severity === "error") return 0;
|
|
3211
|
+
if (diagnostic.severity === "warning") return 1;
|
|
3212
|
+
return 2;
|
|
3213
|
+
};
|
|
3214
|
+
const selectAgentFindings = (diagnostics, limit) => diagnostics.filter((diagnostic) => diagnostic.severity !== "info").sort((a, b) => {
|
|
3215
|
+
const severityDelta = priority(a) - priority(b);
|
|
3216
|
+
if (severityDelta !== 0) return severityDelta;
|
|
3217
|
+
if (a.fixable !== b.fixable) return a.fixable ? 1 : -1;
|
|
3218
|
+
return a.filePath.localeCompare(b.filePath);
|
|
3219
|
+
}).slice(0, Math.max(1, limit));
|
|
3220
|
+
const groupByFile$1 = (diagnostics) => {
|
|
3221
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3222
|
+
for (const diagnostic of diagnostics) {
|
|
3223
|
+
const list = groups.get(diagnostic.filePath) ?? [];
|
|
3224
|
+
list.push(diagnostic);
|
|
3225
|
+
groups.set(diagnostic.filePath, list);
|
|
3140
3226
|
}
|
|
3227
|
+
return [...groups.entries()].map(([filePath, items]) => ({
|
|
3228
|
+
filePath,
|
|
3229
|
+
diagnostics: items
|
|
3230
|
+
})).slice(0, MAX_FILES$1);
|
|
3141
3231
|
};
|
|
3142
|
-
const
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3232
|
+
const snippetFor = (rootDirectory, diagnostic) => {
|
|
3233
|
+
if (diagnostic.line <= 0 || diagnostic.redactSource || isSecretClassRule(diagnostic.rule)) return null;
|
|
3234
|
+
const absolutePath = path.resolve(rootDirectory, diagnostic.filePath);
|
|
3235
|
+
let content;
|
|
3236
|
+
try {
|
|
3237
|
+
content = fs.readFileSync(absolutePath, "utf-8");
|
|
3238
|
+
} catch {
|
|
3239
|
+
return null;
|
|
3240
|
+
}
|
|
3241
|
+
const lines = content.split("\n");
|
|
3242
|
+
const start = Math.max(0, diagnostic.line - 1 - CONTEXT_LINES$1);
|
|
3243
|
+
const end = Math.min(lines.length, diagnostic.line + CONTEXT_LINES$1);
|
|
3244
|
+
const out = [];
|
|
3245
|
+
for (let index = start; index < end; index += 1) {
|
|
3246
|
+
const lineNumber = index + 1;
|
|
3247
|
+
const marker = lineNumber === diagnostic.line ? ">" : " ";
|
|
3248
|
+
out.push(`${marker} ${String(lineNumber).padStart(4)} | ${lines[index]}`);
|
|
3249
|
+
}
|
|
3250
|
+
return out.join("\n");
|
|
3149
3251
|
};
|
|
3150
|
-
const
|
|
3151
|
-
const
|
|
3152
|
-
|
|
3153
|
-
"
|
|
3154
|
-
"
|
|
3155
|
-
"
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3252
|
+
const buildRepairPrompt = (input) => {
|
|
3253
|
+
const groups = groupByFile$1(input.findings);
|
|
3254
|
+
const lines = [
|
|
3255
|
+
"You are repairing AI slop findings in a local git worktree.",
|
|
3256
|
+
"",
|
|
3257
|
+
`Current raigal score: ${input.score == null ? "not scored" : `${input.score}/100`}`,
|
|
3258
|
+
`Target score: ${input.targetScore}/100`,
|
|
3259
|
+
`Turn budget: ${input.maxTurns}`,
|
|
3260
|
+
"",
|
|
3261
|
+
"Hard constraints:",
|
|
3262
|
+
"- Make the smallest code changes that improve the findings below.",
|
|
3263
|
+
"- Do not change public APIs, exports, database schemas, or test expectations unless a finding directly requires it.",
|
|
3264
|
+
"- Do not delete tests.",
|
|
3265
|
+
"- Do not add dependencies unless the existing project already clearly expects them.",
|
|
3266
|
+
"- Never print, copy, commit, or echo secret values or credentials. Replace all hardcoded credentials and secrets with environment-variable references (e.g. process.env.KEY).",
|
|
3267
|
+
"- If a finding looks like a false positive, leave the code alone and mention it in your final summary.",
|
|
3268
|
+
"- Run the relevant local verification command if it is obvious and cheap.",
|
|
3269
|
+
"",
|
|
3270
|
+
"raigal findings to repair:",
|
|
3271
|
+
""
|
|
3272
|
+
];
|
|
3273
|
+
for (const group of groups) {
|
|
3274
|
+
lines.push(`## ${group.filePath}`);
|
|
3275
|
+
for (const diagnostic of group.diagnostics.slice(0, MAX_DIAGNOSTICS_PER_FILE$1)) {
|
|
3276
|
+
const location = diagnostic.line > 0 ? `:${diagnostic.line}` : "";
|
|
3277
|
+
lines.push(`- ${diagnostic.severity.toUpperCase()} ${diagnostic.rule}${location}: ${diagnostic.message}`);
|
|
3278
|
+
if (diagnostic.help) lines.push(` Help: ${diagnostic.help}`);
|
|
3279
|
+
const snippet = snippetFor(input.rootDirectory, diagnostic);
|
|
3280
|
+
if (snippet) {
|
|
3281
|
+
lines.push("```");
|
|
3282
|
+
lines.push(snippet);
|
|
3283
|
+
lines.push("```");
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
lines.push("");
|
|
3287
|
+
}
|
|
3288
|
+
if (input.findings.length === 0) lines.push("No findings were supplied. Inspect the codebase for obvious AI slop only if needed.");
|
|
3289
|
+
lines.push("After editing, stop and summarize what changed and what you intentionally skipped.");
|
|
3290
|
+
return maskSecrets(lines.join("\n"));
|
|
3163
3291
|
};
|
|
3164
3292
|
//#endregion
|
|
3165
3293
|
//#region src/agents/provider-metadata.ts
|
|
@@ -3614,6 +3742,52 @@ const maybeApplyDiff = async (input) => {
|
|
|
3614
3742
|
await applyDiff(input.originalRoot, await readBinaryDiff(input.worktreePath));
|
|
3615
3743
|
return true;
|
|
3616
3744
|
};
|
|
3745
|
+
const resolveAgentSessionCwd = (created, requestedDirectory) => {
|
|
3746
|
+
const relative = path.relative(created.state.root, requestedDirectory);
|
|
3747
|
+
const outsideRoot = relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative);
|
|
3748
|
+
if (relative.length === 0 || outsideRoot) return created.worktree.path;
|
|
3749
|
+
return path.resolve(created.worktree.path, relative);
|
|
3750
|
+
};
|
|
3751
|
+
const prepareWorktree = async (tui, resolvedDir, options) => {
|
|
3752
|
+
tui.start("Preparing local session");
|
|
3753
|
+
const created = await createAgentWorktree(resolvedDir, { inPlace: options.inPlace });
|
|
3754
|
+
tui.setMetric("Worktree", created.worktree.created ? path.relative(created.state.root, created.worktree.path) : "current");
|
|
3755
|
+
tui.complete({
|
|
3756
|
+
status: "done",
|
|
3757
|
+
label: created.worktree.created ? `Created worktree ${path.relative(created.state.root, created.worktree.path)}` : "Using current worktree"
|
|
3758
|
+
});
|
|
3759
|
+
return created;
|
|
3760
|
+
};
|
|
3761
|
+
const scanBaseline = (tui, cwd) => {
|
|
3762
|
+
tui.start("Scanning baseline");
|
|
3763
|
+
const scan = scanJson(cwd);
|
|
3764
|
+
tui.setMetric("Score", `${scan.score ?? "not scored"} -> ...`);
|
|
3765
|
+
tui.setMetric("Findings", scan.diagnostics.length);
|
|
3766
|
+
tui.complete({
|
|
3767
|
+
status: scan.summary.errors > 0 ? "warn" : "done",
|
|
3768
|
+
label: `Baseline ${scan.score ?? "not scored"} / 100 · ${scan.diagnostics.length} findings`
|
|
3769
|
+
});
|
|
3770
|
+
return scan;
|
|
3771
|
+
};
|
|
3772
|
+
const runSafeFixStep = (tui, cwd, options) => {
|
|
3773
|
+
if (options.noFix) return;
|
|
3774
|
+
tui.start("Applying deterministic safe fixes");
|
|
3775
|
+
runSafeFix(cwd);
|
|
3776
|
+
tui.complete({
|
|
3777
|
+
status: "done",
|
|
3778
|
+
label: "Safe fixer finished"
|
|
3779
|
+
});
|
|
3780
|
+
};
|
|
3781
|
+
const selectFindings = (tui, cwd, limit, pass) => {
|
|
3782
|
+
tui.start(`Pass ${pass}: selecting findings`);
|
|
3783
|
+
const findings = selectAgentFindings(scanJson(cwd).diagnostics, limit);
|
|
3784
|
+
tui.setMetric("Selected", findings.length);
|
|
3785
|
+
tui.complete({
|
|
3786
|
+
status: findings.length > 0 ? "done" : "skipped",
|
|
3787
|
+
label: findings.length > 0 ? `Pass ${pass}: selected ${findings.length} finding${findings.length === 1 ? "" : "s"}` : "No remaining agent findings"
|
|
3788
|
+
});
|
|
3789
|
+
return findings;
|
|
3790
|
+
};
|
|
3617
3791
|
//#endregion
|
|
3618
3792
|
//#region src/utils/paths.ts
|
|
3619
3793
|
/** Normalize an OS path to forward-slash (POSIX) separators. */
|
|
@@ -3749,52 +3923,6 @@ const printAgentSessionSummary = (input) => {
|
|
|
3749
3923
|
//#endregion
|
|
3750
3924
|
//#region src/commands/agent-session.ts
|
|
3751
3925
|
const scoreDelta = (before, after) => typeof before === "number" && typeof after === "number" ? after - before : void 0;
|
|
3752
|
-
const resolveAgentSessionCwd = (created, requestedDirectory) => {
|
|
3753
|
-
const relative = path.relative(created.state.root, requestedDirectory);
|
|
3754
|
-
const outsideRoot = relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative);
|
|
3755
|
-
if (relative.length === 0 || outsideRoot) return created.worktree.path;
|
|
3756
|
-
return path.resolve(created.worktree.path, relative);
|
|
3757
|
-
};
|
|
3758
|
-
const prepareWorktree = async (tui, resolvedDir, options) => {
|
|
3759
|
-
tui.start("Preparing local session");
|
|
3760
|
-
const created = await createAgentWorktree(resolvedDir, { inPlace: options.inPlace });
|
|
3761
|
-
tui.setMetric("Worktree", created.worktree.created ? path.relative(created.state.root, created.worktree.path) : "current");
|
|
3762
|
-
tui.complete({
|
|
3763
|
-
status: "done",
|
|
3764
|
-
label: created.worktree.created ? `Created worktree ${path.relative(created.state.root, created.worktree.path)}` : "Using current worktree"
|
|
3765
|
-
});
|
|
3766
|
-
return created;
|
|
3767
|
-
};
|
|
3768
|
-
const scanBaseline = (tui, cwd) => {
|
|
3769
|
-
tui.start("Scanning baseline");
|
|
3770
|
-
const scan = scanJson(cwd);
|
|
3771
|
-
tui.setMetric("Score", `${scan.score ?? "not scored"} -> ...`);
|
|
3772
|
-
tui.setMetric("Findings", scan.diagnostics.length);
|
|
3773
|
-
tui.complete({
|
|
3774
|
-
status: scan.summary.errors > 0 ? "warn" : "done",
|
|
3775
|
-
label: `Baseline ${scan.score ?? "not scored"} / 100 · ${scan.diagnostics.length} findings`
|
|
3776
|
-
});
|
|
3777
|
-
return scan;
|
|
3778
|
-
};
|
|
3779
|
-
const runSafeFixStep = (tui, cwd, options) => {
|
|
3780
|
-
if (options.noFix) return;
|
|
3781
|
-
tui.start("Applying deterministic safe fixes");
|
|
3782
|
-
runSafeFix(cwd);
|
|
3783
|
-
tui.complete({
|
|
3784
|
-
status: "done",
|
|
3785
|
-
label: "Safe fixer finished"
|
|
3786
|
-
});
|
|
3787
|
-
};
|
|
3788
|
-
const selectFindings = (tui, cwd, limit, pass) => {
|
|
3789
|
-
tui.start(`Pass ${pass}: selecting findings`);
|
|
3790
|
-
const findings = selectAgentFindings(scanJson(cwd).diagnostics, limit);
|
|
3791
|
-
tui.setMetric("Selected", findings.length);
|
|
3792
|
-
tui.complete({
|
|
3793
|
-
status: findings.length > 0 ? "done" : "skipped",
|
|
3794
|
-
label: findings.length > 0 ? `Pass ${pass}: selected ${findings.length} finding${findings.length === 1 ? "" : "s"}` : "No remaining agent findings"
|
|
3795
|
-
});
|
|
3796
|
-
return findings;
|
|
3797
|
-
};
|
|
3798
3926
|
const runAgentSession = async (selected, resolvedDir, options, started) => {
|
|
3799
3927
|
const tui = new AgentTui({
|
|
3800
3928
|
provider: selected.provider.label,
|
|
@@ -3959,49 +4087,16 @@ const runAgentSession = async (selected, resolvedDir, options, started) => {
|
|
|
3959
4087
|
applyRequested: options.apply,
|
|
3960
4088
|
changedFiles: changedFiles.length
|
|
3961
4089
|
});
|
|
3962
|
-
|
|
3963
|
-
tui
|
|
3964
|
-
session
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
const fixedFindings = before.diagnostics.filter((d) => !afterKeys.has(`${d.rule}:${d.filePath}:${d.line}`)).map((d) => ({
|
|
3973
|
-
ruleId: d.rule,
|
|
3974
|
-
path: d.filePath,
|
|
3975
|
-
line: d.line,
|
|
3976
|
-
message: d.message
|
|
3977
|
-
}));
|
|
3978
|
-
published = await publishAgentDiff({
|
|
3979
|
-
cwd: created.worktree.path,
|
|
3980
|
-
originalBranch: created.state.branch,
|
|
3981
|
-
providerId: selected.provider.id,
|
|
3982
|
-
beforeScore: before.score,
|
|
3983
|
-
afterScore: verified.after.score,
|
|
3984
|
-
changedFiles,
|
|
3985
|
-
fixedFindings,
|
|
3986
|
-
options: {
|
|
3987
|
-
commit: options.commit,
|
|
3988
|
-
pr: options.pr,
|
|
3989
|
-
branch: options.branch,
|
|
3990
|
-
base: options.base,
|
|
3991
|
-
commitMessage: options.commitMessage,
|
|
3992
|
-
prTitle: options.prTitle,
|
|
3993
|
-
ready: options.ready
|
|
3994
|
-
}
|
|
3995
|
-
});
|
|
3996
|
-
session.append(published ? "publish.finished" : "publish.skipped", { result: published });
|
|
3997
|
-
tui.complete({
|
|
3998
|
-
status: published ? "done" : "skipped",
|
|
3999
|
-
label: published?.prUrl ? `Opened PR ${published.prUrl}` : published ? `Committed ${published.commitSha}` : "No commit created"
|
|
4000
|
-
});
|
|
4001
|
-
} else if (options.commit || options.pr) session.append("publish.skipped", {
|
|
4002
|
-
reason: "no_changed_files",
|
|
4003
|
-
commit: options.commit,
|
|
4004
|
-
pr: options.pr
|
|
4090
|
+
published = await maybePublishAgentDiff({
|
|
4091
|
+
tui,
|
|
4092
|
+
session,
|
|
4093
|
+
options,
|
|
4094
|
+
changedFiles,
|
|
4095
|
+
worktreePath: created.worktree.path,
|
|
4096
|
+
originalBranch: created.state.branch,
|
|
4097
|
+
providerId: selected.provider.id,
|
|
4098
|
+
before,
|
|
4099
|
+
after: verified.after
|
|
4005
4100
|
});
|
|
4006
4101
|
session.append("session.completed", {
|
|
4007
4102
|
durationMs: Math.round(performance.now() - started),
|
|
@@ -4017,47 +4112,17 @@ const runAgentSession = async (selected, resolvedDir, options, started) => {
|
|
|
4017
4112
|
published: Boolean(published)
|
|
4018
4113
|
});
|
|
4019
4114
|
await tui.finish({ footer: `Done · ${selected.provider.id} · ${Math.round(performance.now() - started)}ms` });
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
encoding: "utf-8"
|
|
4032
|
-
}).stdout || "").slice(0, 2e4) || void 0;
|
|
4033
|
-
} catch {}
|
|
4034
|
-
if (published) {
|
|
4035
|
-
const prMatch = published.prUrl?.match(/\/pull\/(\d+)/);
|
|
4036
|
-
const prNumber = prMatch ? Number.parseInt(prMatch[1], 10) : void 0;
|
|
4037
|
-
recorder.setGitContext({
|
|
4038
|
-
branch: published.branch,
|
|
4039
|
-
pr_number: prNumber
|
|
4040
|
-
});
|
|
4041
|
-
} else if (options.branch) recorder.setGitContext({ branch: options.branch });
|
|
4042
|
-
const agentTelemetry = {
|
|
4043
|
-
provider: selected.provider.id,
|
|
4044
|
-
before_score: before.score,
|
|
4045
|
-
after_score: verified.after.score,
|
|
4046
|
-
pr_url: published?.prUrl || void 0,
|
|
4047
|
-
patch_diff: patchDiff,
|
|
4048
|
-
duration_ms: Math.round(performance.now() - started)
|
|
4049
|
-
};
|
|
4050
|
-
recorder.setAgentTelemetry(agentTelemetry);
|
|
4051
|
-
recorder.recordStep("agent:baseline", 50, true);
|
|
4052
|
-
recorder.recordStep("agent:verify", Math.round(performance.now() - started), verified.after.score !== null && verified.after.score >= options.targetScore);
|
|
4053
|
-
recorder.setReport({
|
|
4054
|
-
score: verified.after.score,
|
|
4055
|
-
scoreable: true,
|
|
4056
|
-
files_scanned: changedFiles.length,
|
|
4057
|
-
findings: [],
|
|
4058
|
-
agent_telemetry: agentTelemetry
|
|
4059
|
-
});
|
|
4060
|
-
}
|
|
4115
|
+
recordAgentSessionTelemetry({
|
|
4116
|
+
worktreePath: created?.worktree?.path,
|
|
4117
|
+
published,
|
|
4118
|
+
branchOption: options.branch,
|
|
4119
|
+
providerId: selected.provider.id,
|
|
4120
|
+
beforeScore: before.score,
|
|
4121
|
+
afterScore: verified.after.score,
|
|
4122
|
+
targetScore: options.targetScore,
|
|
4123
|
+
durationMs: Math.round(performance.now() - started),
|
|
4124
|
+
changedFilesCount: changedFiles.length
|
|
4125
|
+
});
|
|
4061
4126
|
printAgentSessionSummary({
|
|
4062
4127
|
before,
|
|
4063
4128
|
after: verified.after,
|
|
@@ -4704,7 +4769,8 @@ const DEFAULT_CONFIG = {
|
|
|
4704
4769
|
format: "json"
|
|
4705
4770
|
},
|
|
4706
4771
|
telemetry: { enabled: true },
|
|
4707
|
-
rules: {}
|
|
4772
|
+
rules: {},
|
|
4773
|
+
overrides: []
|
|
4708
4774
|
};
|
|
4709
4775
|
const GITHUB_WORKFLOW_DIR = ".github/workflows";
|
|
4710
4776
|
const GITHUB_WORKFLOW_FILE = "raigal.yml";
|
|
@@ -4934,6 +5000,16 @@ const RuleSeverityOverride = z.enum([
|
|
|
4934
5000
|
"off"
|
|
4935
5001
|
]);
|
|
4936
5002
|
const RulesSchema = z.record(z.string(), RuleSeverityOverride).default(() => ({}));
|
|
5003
|
+
const FileOverrideSchema = z.object({
|
|
5004
|
+
files: z.array(z.string().min(1)).min(1),
|
|
5005
|
+
quality: z.object({
|
|
5006
|
+
maxFunctionLoc: QualitySchema.shape.maxFunctionLoc.unwrap().optional(),
|
|
5007
|
+
maxFileLoc: QualitySchema.shape.maxFileLoc.unwrap().optional(),
|
|
5008
|
+
maxNesting: QualitySchema.shape.maxNesting.unwrap().optional(),
|
|
5009
|
+
maxParams: QualitySchema.shape.maxParams.unwrap().optional()
|
|
5010
|
+
}).optional(),
|
|
5011
|
+
rules: z.record(z.string(), RuleSeverityOverride).optional()
|
|
5012
|
+
});
|
|
4937
5013
|
const AislopConfigSchema = z.object({
|
|
4938
5014
|
version: z.number().default(1),
|
|
4939
5015
|
extends: z.union([z.string(), z.array(z.string())]).optional(),
|
|
@@ -4989,6 +5065,7 @@ const AislopConfigSchema = z.object({
|
|
|
4989
5065
|
})),
|
|
4990
5066
|
telemetry: TelemetrySchema.default(() => ({ enabled: true })),
|
|
4991
5067
|
rules: RulesSchema,
|
|
5068
|
+
overrides: z.array(FileOverrideSchema).default(() => []),
|
|
4992
5069
|
exclude: z.array(z.string()).default(() => [
|
|
4993
5070
|
"node_modules",
|
|
4994
5071
|
".git",
|
|
@@ -5360,6 +5437,10 @@ const SAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set([
|
|
|
5360
5437
|
"exit_code",
|
|
5361
5438
|
"duration_ms",
|
|
5362
5439
|
"error_kind",
|
|
5440
|
+
"error_name",
|
|
5441
|
+
"error_code",
|
|
5442
|
+
"failed_stage",
|
|
5443
|
+
"engines_failed",
|
|
5363
5444
|
"score",
|
|
5364
5445
|
"score_bucket",
|
|
5365
5446
|
"finding_count",
|
|
@@ -5615,6 +5696,8 @@ const buildCommandCompletedProps = (input) => {
|
|
|
5615
5696
|
if (typeof input.fixSteps === "number") props.fix_steps = input.fixSteps;
|
|
5616
5697
|
if (typeof input.fixResolved === "number") props.fix_resolved = input.fixResolved;
|
|
5617
5698
|
if (typeof input.fixScoreDelta === "number") props.fix_score_delta = input.fixScoreDelta;
|
|
5699
|
+
if (input.errorName) props.error_name = input.errorName;
|
|
5700
|
+
if (input.errorCode) props.error_code = input.errorCode;
|
|
5618
5701
|
return props;
|
|
5619
5702
|
};
|
|
5620
5703
|
const buildHookScanCompletedProps = (input) => {
|
|
@@ -5635,6 +5718,50 @@ const errorKindFromException = (error) => {
|
|
|
5635
5718
|
if (message.includes("engine") && message.includes("crash")) return "engine_crash";
|
|
5636
5719
|
return "unknown";
|
|
5637
5720
|
};
|
|
5721
|
+
const SAFE_ERROR_NAME = /^[A-Za-z_$][A-Za-z0-9_$]{0,39}$/;
|
|
5722
|
+
const SAFE_ERROR_CODE = /^[A-Za-z][A-Za-z0-9_]{0,39}$/;
|
|
5723
|
+
const errorIdentity = (error) => {
|
|
5724
|
+
let name;
|
|
5725
|
+
if (error instanceof Error && typeof error.name === "string" && error.name.length > 0) name = error.name;
|
|
5726
|
+
else if (error === null) name = "null";
|
|
5727
|
+
else if (typeof error === "object") name = "object";
|
|
5728
|
+
else name = typeof error;
|
|
5729
|
+
const identity = { error_name: SAFE_ERROR_NAME.test(name) ? name : "Error" };
|
|
5730
|
+
const code = error?.code;
|
|
5731
|
+
if (typeof code === "string" && SAFE_ERROR_CODE.test(code)) identity.error_code = code;
|
|
5732
|
+
else if (typeof code === "number" && Number.isFinite(code)) identity.error_code = String(code);
|
|
5733
|
+
return identity;
|
|
5734
|
+
};
|
|
5735
|
+
const buildCommandFailedProps = (input) => {
|
|
5736
|
+
const props = {
|
|
5737
|
+
failed_stage: input.stage,
|
|
5738
|
+
...errorIdentity(input.error)
|
|
5739
|
+
};
|
|
5740
|
+
if (input.command) props.command = input.command;
|
|
5741
|
+
return props;
|
|
5742
|
+
};
|
|
5743
|
+
//#endregion
|
|
5744
|
+
//#region src/telemetry/fatal.ts
|
|
5745
|
+
const reportedErrors = /* @__PURE__ */ new Set();
|
|
5746
|
+
let activeTelemetryConfig;
|
|
5747
|
+
const markErrorReported = (error) => {
|
|
5748
|
+
reportedErrors.add(error);
|
|
5749
|
+
};
|
|
5750
|
+
const rememberTelemetryConfig = (config) => {
|
|
5751
|
+
activeTelemetryConfig = config;
|
|
5752
|
+
};
|
|
5753
|
+
const reportFatalError = async (error, stage, config) => {
|
|
5754
|
+
if (reportedErrors.has(error)) return;
|
|
5755
|
+
track({
|
|
5756
|
+
event: "cli_command_failed",
|
|
5757
|
+
properties: buildCommandFailedProps({
|
|
5758
|
+
error,
|
|
5759
|
+
stage
|
|
5760
|
+
}),
|
|
5761
|
+
config: activeTelemetryConfig ?? config
|
|
5762
|
+
});
|
|
5763
|
+
await flushTelemetry(2e3);
|
|
5764
|
+
};
|
|
5638
5765
|
//#endregion
|
|
5639
5766
|
//#region src/telemetry/lifecycle.ts
|
|
5640
5767
|
const STARTED_FLUSH_TIMEOUT_MS = 100;
|
|
@@ -5645,6 +5772,7 @@ const withCommandLifecycle = async (start, run) => {
|
|
|
5645
5772
|
fileCount: start.fileCount,
|
|
5646
5773
|
properties: start.properties
|
|
5647
5774
|
});
|
|
5775
|
+
rememberTelemetryConfig(start.config);
|
|
5648
5776
|
track({
|
|
5649
5777
|
event: "cli_command_started",
|
|
5650
5778
|
properties: startProps,
|
|
@@ -5661,6 +5789,7 @@ const withCommandLifecycle = async (start, run) => {
|
|
|
5661
5789
|
startProps,
|
|
5662
5790
|
exitCode: result.exitCode,
|
|
5663
5791
|
durationMs,
|
|
5792
|
+
errorKind: result.errorKind,
|
|
5664
5793
|
score: result.score ?? void 0,
|
|
5665
5794
|
findingCount: result.findingCount,
|
|
5666
5795
|
errorCount: result.errorCount,
|
|
@@ -5678,16 +5807,21 @@ const withCommandLifecycle = async (start, run) => {
|
|
|
5678
5807
|
await flushTelemetry();
|
|
5679
5808
|
return result;
|
|
5680
5809
|
} catch (error) {
|
|
5810
|
+
const durationMs = performance.now() - startedAt;
|
|
5811
|
+
const identity = errorIdentity(error);
|
|
5681
5812
|
track({
|
|
5682
5813
|
event: "cli_command_completed",
|
|
5683
5814
|
properties: buildCommandCompletedProps({
|
|
5684
5815
|
startProps,
|
|
5685
5816
|
exitCode: 1,
|
|
5686
|
-
durationMs
|
|
5687
|
-
errorKind: errorKindFromException(error)
|
|
5817
|
+
durationMs,
|
|
5818
|
+
errorKind: errorKindFromException(error),
|
|
5819
|
+
errorName: identity.error_name,
|
|
5820
|
+
errorCode: identity.error_code
|
|
5688
5821
|
}),
|
|
5689
5822
|
config: start.config
|
|
5690
5823
|
});
|
|
5824
|
+
markErrorReported(error);
|
|
5691
5825
|
await flushTelemetry();
|
|
5692
5826
|
throw error;
|
|
5693
5827
|
}
|
|
@@ -6849,6 +6983,13 @@ const registerAgentMonitorSubcommands = (agent) => {
|
|
|
6849
6983
|
registerMonitorStopCommand(monitor);
|
|
6850
6984
|
};
|
|
6851
6985
|
//#endregion
|
|
6986
|
+
//#region src/ui/prompts.ts
|
|
6987
|
+
const runCancellable = async (fn) => {
|
|
6988
|
+
const value = await fn();
|
|
6989
|
+
if (isCancel(value)) return void 0;
|
|
6990
|
+
return value;
|
|
6991
|
+
};
|
|
6992
|
+
//#endregion
|
|
6852
6993
|
//#region src/commands/agent-apply.ts
|
|
6853
6994
|
const asString = (value) => typeof value === "string" && value.length > 0 ? value : null;
|
|
6854
6995
|
const isInside = (base, target) => {
|
|
@@ -7909,7 +8050,11 @@ const whoamiCommand = async () => {
|
|
|
7909
8050
|
process.stdout.write(` Organization: ${orgName} (${orgId})\n`);
|
|
7910
8051
|
if (userEmail) process.stdout.write(` User: ${userEmail}\n`);
|
|
7911
8052
|
process.stdout.write(` Credential: ${active.kind === "api_key" ? "Org API Key" : "User Session"} (via ${active.source})\n`);
|
|
7912
|
-
|
|
8053
|
+
let planDisplay = plan;
|
|
8054
|
+
if (plan === "active") planDisplay = "active (enterprise license)";
|
|
8055
|
+
else if (plan === "expired") planDisplay = `expired${trialEnd ? ` (trial ended ${trialEnd})` : ""}`;
|
|
8056
|
+
else if (trialEnd) planDisplay = `trial (trial ends ${trialEnd})`;
|
|
8057
|
+
process.stdout.write(` Plan: ${planDisplay}\n`);
|
|
7913
8058
|
process.stdout.write(` Allowed Owners: ${owners || "none"}\n`);
|
|
7914
8059
|
if (claims) {
|
|
7915
8060
|
const expDate = (/* @__PURE__ */ new Date(claims.exp * 1e3)).toISOString();
|
|
@@ -8107,6 +8252,132 @@ const acquireHookLock = (cwd) => {
|
|
|
8107
8252
|
} catch {}
|
|
8108
8253
|
};
|
|
8109
8254
|
};
|
|
8255
|
+
//#endregion
|
|
8256
|
+
//#region src/utils/globs.ts
|
|
8257
|
+
const MAX_BRACE_EXPANSIONS = 256;
|
|
8258
|
+
const braceListAt = (pattern, start) => {
|
|
8259
|
+
let depth = 0;
|
|
8260
|
+
const commas = [];
|
|
8261
|
+
for (let i = start; i < pattern.length; i += 1) {
|
|
8262
|
+
const char = pattern[i];
|
|
8263
|
+
if (char === "\\") i += 1;
|
|
8264
|
+
else if (char === "{") depth += 1;
|
|
8265
|
+
else if (char === ",") {
|
|
8266
|
+
if (depth === 1) commas.push(i);
|
|
8267
|
+
} else if (char === "}") {
|
|
8268
|
+
depth -= 1;
|
|
8269
|
+
if (depth === 0) return commas.length > 0 ? {
|
|
8270
|
+
start,
|
|
8271
|
+
end: i,
|
|
8272
|
+
commas
|
|
8273
|
+
} : null;
|
|
8274
|
+
}
|
|
8275
|
+
}
|
|
8276
|
+
return null;
|
|
8277
|
+
};
|
|
8278
|
+
const findBraceList = (pattern) => {
|
|
8279
|
+
for (let start = pattern.indexOf("{"); start !== -1; start = pattern.indexOf("{", start + 1)) {
|
|
8280
|
+
if (start > 0 && pattern[start - 1] === "\\") continue;
|
|
8281
|
+
const list = braceListAt(pattern, start);
|
|
8282
|
+
if (list) return list;
|
|
8283
|
+
}
|
|
8284
|
+
return null;
|
|
8285
|
+
};
|
|
8286
|
+
const expandInto = (pattern, results) => {
|
|
8287
|
+
const list = findBraceList(pattern);
|
|
8288
|
+
if (!list) {
|
|
8289
|
+
results.push(pattern);
|
|
8290
|
+
return results.length <= MAX_BRACE_EXPANSIONS;
|
|
8291
|
+
}
|
|
8292
|
+
const bounds = [
|
|
8293
|
+
list.start,
|
|
8294
|
+
...list.commas,
|
|
8295
|
+
list.end
|
|
8296
|
+
];
|
|
8297
|
+
const prefix = pattern.slice(0, list.start);
|
|
8298
|
+
const suffix = pattern.slice(list.end + 1);
|
|
8299
|
+
for (let i = 0; i < bounds.length - 1; i += 1) {
|
|
8300
|
+
const option = pattern.slice(bounds[i] + 1, bounds[i + 1]);
|
|
8301
|
+
if (!expandInto(`${prefix}${option}${suffix}`, results)) return false;
|
|
8302
|
+
}
|
|
8303
|
+
return true;
|
|
8304
|
+
};
|
|
8305
|
+
const expandBraceLists = (pattern) => {
|
|
8306
|
+
const results = [];
|
|
8307
|
+
return expandInto(pattern, results) ? results : [pattern];
|
|
8308
|
+
};
|
|
8309
|
+
const isNegated = (pattern) => picomatch.scan(pattern).negated;
|
|
8310
|
+
const matchesGlobList = (value, patterns, options = {}) => {
|
|
8311
|
+
const expanded = patterns.flatMap(expandBraceLists);
|
|
8312
|
+
const positives = expanded.filter((pattern) => !isNegated(pattern));
|
|
8313
|
+
const negatives = expanded.filter(isNegated).map((pattern) => pattern.slice(1));
|
|
8314
|
+
const included = positives.length === 0 || picomatch.isMatch(value, positives, options);
|
|
8315
|
+
const excluded = negatives.length > 0 && picomatch.isMatch(value, negatives, options);
|
|
8316
|
+
return included && !excluded;
|
|
8317
|
+
};
|
|
8318
|
+
//#endregion
|
|
8319
|
+
//#region src/scoring/rule-severity.ts
|
|
8320
|
+
/**
|
|
8321
|
+
* Apply per-rule severity overrides from config: "off" drops the diagnostic,
|
|
8322
|
+
* "error"/"warning" rewrite its severity before scoring and rendering.
|
|
8323
|
+
*/
|
|
8324
|
+
const applyRuleSeverities = (diagnostics, overrides) => {
|
|
8325
|
+
if (Object.keys(overrides).length === 0) return diagnostics;
|
|
8326
|
+
const result = [];
|
|
8327
|
+
for (const diagnostic of diagnostics) {
|
|
8328
|
+
const override = overrides[diagnostic.rule];
|
|
8329
|
+
if (!override) {
|
|
8330
|
+
result.push(diagnostic);
|
|
8331
|
+
continue;
|
|
8332
|
+
}
|
|
8333
|
+
if (override === "off") continue;
|
|
8334
|
+
result.push(override === diagnostic.severity ? diagnostic : {
|
|
8335
|
+
...diagnostic,
|
|
8336
|
+
severity: override
|
|
8337
|
+
});
|
|
8338
|
+
}
|
|
8339
|
+
return result;
|
|
8340
|
+
};
|
|
8341
|
+
//#endregion
|
|
8342
|
+
//#region src/config/overrides.ts
|
|
8343
|
+
const overrideRelativePath = (rootDirectory, filePath) => {
|
|
8344
|
+
const paths = path.win32.isAbsolute(rootDirectory) && !path.posix.isAbsolute(rootDirectory) ? path.win32 : path.posix;
|
|
8345
|
+
const root = rootDirectory.replaceAll("\\", "/");
|
|
8346
|
+
const file = filePath.replaceAll("\\", "/");
|
|
8347
|
+
return paths.normalize(paths.isAbsolute(file) ? paths.relative(root, file) : file).replaceAll("\\", "/");
|
|
8348
|
+
};
|
|
8349
|
+
const matchesEntry = (relativePath, patterns) => matchesGlobList(relativePath, patterns, { dot: true });
|
|
8350
|
+
const resolveFilePolicy = (config, relativePath) => {
|
|
8351
|
+
let quality = config.quality;
|
|
8352
|
+
let rules = config.rules ?? {};
|
|
8353
|
+
if (relativePath === ".." || relativePath.startsWith("../") || path.win32.isAbsolute(relativePath)) return {
|
|
8354
|
+
quality,
|
|
8355
|
+
rules
|
|
8356
|
+
};
|
|
8357
|
+
for (const entry of config.overrides ?? []) {
|
|
8358
|
+
if (!matchesEntry(relativePath, entry.files)) continue;
|
|
8359
|
+
if (entry.quality) quality = {
|
|
8360
|
+
...quality,
|
|
8361
|
+
...entry.quality
|
|
8362
|
+
};
|
|
8363
|
+
if (entry.rules) rules = {
|
|
8364
|
+
...rules,
|
|
8365
|
+
...entry.rules
|
|
8366
|
+
};
|
|
8367
|
+
}
|
|
8368
|
+
return {
|
|
8369
|
+
quality,
|
|
8370
|
+
rules
|
|
8371
|
+
};
|
|
8372
|
+
};
|
|
8373
|
+
const applyFileOverrides = (diagnostics, context) => {
|
|
8374
|
+
if (!context.config.overrides?.length) return diagnostics;
|
|
8375
|
+
return diagnostics.flatMap((diagnostic) => {
|
|
8376
|
+
const relativePath = overrideRelativePath(context.rootDirectory, diagnostic.filePath);
|
|
8377
|
+
const { rules } = resolveFilePolicy(context.config, relativePath);
|
|
8378
|
+
return applyRuleSeverities([diagnostic], rules);
|
|
8379
|
+
});
|
|
8380
|
+
};
|
|
8110
8381
|
const supportedGlobPatterns = (patterns) => patterns.filter((pattern) => pattern.length <= 256);
|
|
8111
8382
|
const normalizeExcludePatterns = (patterns) => patterns.flatMap((pattern) => {
|
|
8112
8383
|
const trimmed = pattern.trim();
|
|
@@ -8116,13 +8387,13 @@ const normalizeExcludePatterns = (patterns) => patterns.flatMap((pattern) => {
|
|
|
8116
8387
|
const normalized = withoutProjectPrefix.slice(0, end);
|
|
8117
8388
|
if (normalized.length === 0) return [];
|
|
8118
8389
|
if (normalized.length > 256) return [];
|
|
8119
|
-
if (
|
|
8390
|
+
if (picomatch.scan(normalized).isGlob) return [normalized];
|
|
8120
8391
|
if (normalized.startsWith(".") && !normalized.includes("/")) return supportedGlobPatterns([`**/${normalized}`, `**/${normalized}/**`]);
|
|
8121
8392
|
return supportedGlobPatterns([normalized, `${normalized}/**`]);
|
|
8122
8393
|
});
|
|
8123
8394
|
const isPathExcluded = (relativePath, normalizedPatterns) => {
|
|
8124
8395
|
if (normalizedPatterns.length === 0) return false;
|
|
8125
|
-
return
|
|
8396
|
+
return picomatch.isMatch(relativePath, normalizedPatterns, { dot: true });
|
|
8126
8397
|
};
|
|
8127
8398
|
const filterExcludedDiagnostics = (diagnostics, rootDirectory, excludePatterns) => {
|
|
8128
8399
|
if (!excludePatterns || excludePatterns.length === 0) return diagnostics;
|
|
@@ -8189,7 +8460,7 @@ const isRootBoundedTarget = (candidate, rootDirectory) => {
|
|
|
8189
8460
|
};
|
|
8190
8461
|
//#endregion
|
|
8191
8462
|
//#region src/utils/aislop-ignore.ts
|
|
8192
|
-
const
|
|
8463
|
+
const readAislopIgnorePatterns = (rootDirectory) => {
|
|
8193
8464
|
const patterns = [];
|
|
8194
8465
|
for (const fileName of [".raigalignore", ".aislopignore"]) {
|
|
8195
8466
|
const targetPath = safeProjectFilePath(path.join(rootDirectory, fileName), rootDirectory);
|
|
@@ -8200,7 +8471,6 @@ const readRaigalIgnorePatterns = (rootDirectory) => {
|
|
|
8200
8471
|
}
|
|
8201
8472
|
return patterns;
|
|
8202
8473
|
};
|
|
8203
|
-
const readAislopIgnorePatterns = readRaigalIgnorePatterns;
|
|
8204
8474
|
//#endregion
|
|
8205
8475
|
//#region src/utils/generated-files.ts
|
|
8206
8476
|
const AUTO_GENERATED_PATTERNS = [
|
|
@@ -8559,12 +8829,13 @@ const normalizeIncludePatterns = (patterns) => patterns.flatMap((pattern) => {
|
|
|
8559
8829
|
const normalized = pattern.trim().replace(/^\.\//, "").replace(/\/$/, "");
|
|
8560
8830
|
if (normalized === "" || normalized === ".") return ["**"];
|
|
8561
8831
|
if (normalized.length > 256) return [];
|
|
8562
|
-
if (
|
|
8832
|
+
if (picomatch.scan(normalized).isGlob) return [normalized];
|
|
8563
8833
|
return supportedGlobPatterns([normalized, `${normalized}/**`]);
|
|
8564
8834
|
});
|
|
8565
8835
|
const createPathMatcher = (patterns) => {
|
|
8566
|
-
|
|
8567
|
-
|
|
8836
|
+
if (patterns.length === 0) return () => false;
|
|
8837
|
+
const isMatch = picomatch(patterns, { dot: true });
|
|
8838
|
+
return (filePath) => isMatch(filePath);
|
|
8568
8839
|
};
|
|
8569
8840
|
const filterFiles = (rootDirectory, files, options) => {
|
|
8570
8841
|
const extraExtensions = new Set(options.extraExtensions);
|
|
@@ -16509,10 +16780,10 @@ const checkFileComplexity = (filePath, rootDirectory, limits) => {
|
|
|
16509
16780
|
};
|
|
16510
16781
|
const checkComplexity = async (context) => {
|
|
16511
16782
|
const files = getSourceFiles(context);
|
|
16512
|
-
const limits = context.config.quality;
|
|
16513
16783
|
const diagnostics = [];
|
|
16514
16784
|
for (const filePath of files) {
|
|
16515
16785
|
if (isAutoGenerated(filePath)) continue;
|
|
16786
|
+
const { quality: limits } = resolveFilePolicy(context.config, overrideRelativePath(context.rootDirectory, filePath));
|
|
16516
16787
|
diagnostics.push(...checkFileComplexity(filePath, context.rootDirectory, limits));
|
|
16517
16788
|
}
|
|
16518
16789
|
return diagnostics;
|
|
@@ -17404,13 +17675,23 @@ const withReportPath = async (run) => {
|
|
|
17404
17675
|
};
|
|
17405
17676
|
const UNSUPPORTED_GLOB_SYNTAX = /[?[\]{}!|()\\]/;
|
|
17406
17677
|
const isDotnetFormatPath = (pattern) => !pattern.startsWith("-") && !UNSUPPORTED_GLOB_SYNTAX.test(pattern);
|
|
17678
|
+
const expandBraces = (pattern, depth = 0) => {
|
|
17679
|
+
if (depth > 10) return [pattern];
|
|
17680
|
+
const match = /\{([^{}]+)\}/.exec(pattern);
|
|
17681
|
+
if (!match) return [pattern];
|
|
17682
|
+
const [fullMatch, options] = match;
|
|
17683
|
+
const parts = options.split(",");
|
|
17684
|
+
const prefix = pattern.slice(0, match.index);
|
|
17685
|
+
const suffix = pattern.slice(match.index + fullMatch.length);
|
|
17686
|
+
return parts.flatMap((part) => expandBraces(`${prefix}${part}${suffix}`, depth + 1));
|
|
17687
|
+
};
|
|
17407
17688
|
const buildDotnetFormatExcludeScope = (excludePatterns) => {
|
|
17408
17689
|
const scope = {
|
|
17409
17690
|
excludeArguments: [],
|
|
17410
17691
|
unsupportedPatterns: []
|
|
17411
17692
|
};
|
|
17412
17693
|
for (const pattern of normalizeExcludePatterns(excludePatterns ?? [])) {
|
|
17413
|
-
const expanded =
|
|
17694
|
+
const expanded = expandBraces(pattern);
|
|
17414
17695
|
if (expanded.every(isDotnetFormatPath)) scope.excludeArguments.push(...expanded);
|
|
17415
17696
|
else scope.unsupportedPatterns.push(pattern);
|
|
17416
17697
|
}
|
|
@@ -19273,7 +19554,7 @@ const normalizeEslintIgnorePatterns = (patterns) => patterns.map((pattern) => pa
|
|
|
19273
19554
|
if (!pattern.includes("*") && !path.extname(pattern)) return [`${pattern}/**`];
|
|
19274
19555
|
return [pattern];
|
|
19275
19556
|
});
|
|
19276
|
-
const isEslintIgnored = (relativePath, patterns) => patterns.length > 0 &&
|
|
19557
|
+
const isEslintIgnored = (relativePath, patterns) => patterns.length > 0 && picomatch.isMatch(relativePath, patterns, { dot: true });
|
|
19277
19558
|
const getOxlintTargets = (context) => {
|
|
19278
19559
|
const eslintIgnorePatterns = normalizeEslintIgnorePatterns(collectEslintIgnorePatterns(context.rootDirectory));
|
|
19279
19560
|
return getSourceFiles(context).filter((filePath) => OXLINT_EXTENSIONS.has(path.extname(filePath).toLowerCase())).filter((filePath) => !isAutoGenerated(filePath)).map((filePath) => path.relative(context.rootDirectory, filePath).split(path.sep).join("/")).filter((filePath) => !isEslintIgnored(filePath, eslintIgnorePatterns));
|
|
@@ -20192,7 +20473,8 @@ const readRuntimeDependencies = (rootDir) => {
|
|
|
20192
20473
|
collectRuntimeNames(rootManifest, names);
|
|
20193
20474
|
const globs = [...npmWorkspaceGlobs(rootManifest), ...pnpmWorkspaceGlobs(rootDir)];
|
|
20194
20475
|
if (globs.length === 0) return { runtimeDependencies: names };
|
|
20195
|
-
|
|
20476
|
+
const isMatch = picomatch(globs, { dot: true });
|
|
20477
|
+
for (const relativeDir of listPackageDirectories(rootDir).filter((dir) => isMatch(dir))) {
|
|
20196
20478
|
const memberManifest = readJsonFile(path.join(rootDir, relativeDir, "package.json"));
|
|
20197
20479
|
if (memberManifest) collectRuntimeNames(memberManifest, names);
|
|
20198
20480
|
}
|
|
@@ -20988,6 +21270,7 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
|
|
|
20988
21270
|
const result = await engine.run(context);
|
|
20989
21271
|
result.diagnostics = filterExcludedDiagnostics(result.diagnostics, context.rootDirectory, context.excludePatterns);
|
|
20990
21272
|
result.elapsed = performance.now() - start;
|
|
21273
|
+
result.diagnostics = applyFileOverrides(result.diagnostics, context);
|
|
20991
21274
|
onComplete?.(result);
|
|
20992
21275
|
return result;
|
|
20993
21276
|
} catch (error) {
|
|
@@ -20996,6 +21279,7 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
|
|
|
20996
21279
|
diagnostics: [],
|
|
20997
21280
|
elapsed: performance.now() - start,
|
|
20998
21281
|
skipped: true,
|
|
21282
|
+
failed: true,
|
|
20999
21283
|
skipReason: error instanceof Error ? error.message : String(error)
|
|
21000
21284
|
};
|
|
21001
21285
|
onComplete?.(result);
|
|
@@ -21006,6 +21290,7 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
|
|
|
21006
21290
|
diagnostics: [],
|
|
21007
21291
|
elapsed: 0,
|
|
21008
21292
|
skipped: true,
|
|
21293
|
+
failed: true,
|
|
21009
21294
|
skipReason: r.reason instanceof Error ? r.reason.message : String(r.reason)
|
|
21010
21295
|
});
|
|
21011
21296
|
return dedupeEquivalenceDiagnostics(dedupeOverlappingComments(dedupeCSharpAsync(stripSecretDiagnostics(finalResults))));
|
|
@@ -21246,29 +21531,6 @@ const calculateScore = (diagnostics, weights, thresholds, sourceFileCount, smoot
|
|
|
21246
21531
|
};
|
|
21247
21532
|
};
|
|
21248
21533
|
//#endregion
|
|
21249
|
-
//#region src/scoring/rule-severity.ts
|
|
21250
|
-
/**
|
|
21251
|
-
* Apply per-rule severity overrides from config: "off" drops the diagnostic,
|
|
21252
|
-
* "error"/"warning" rewrite its severity before scoring and rendering.
|
|
21253
|
-
*/
|
|
21254
|
-
const applyRuleSeverities = (diagnostics, overrides) => {
|
|
21255
|
-
if (Object.keys(overrides).length === 0) return diagnostics;
|
|
21256
|
-
const result = [];
|
|
21257
|
-
for (const diagnostic of diagnostics) {
|
|
21258
|
-
const override = overrides[diagnostic.rule];
|
|
21259
|
-
if (!override) {
|
|
21260
|
-
result.push(diagnostic);
|
|
21261
|
-
continue;
|
|
21262
|
-
}
|
|
21263
|
-
if (override === "off") continue;
|
|
21264
|
-
result.push(override === diagnostic.severity ? diagnostic : {
|
|
21265
|
-
...diagnostic,
|
|
21266
|
-
severity: override
|
|
21267
|
-
});
|
|
21268
|
-
}
|
|
21269
|
-
return result;
|
|
21270
|
-
};
|
|
21271
|
-
//#endregion
|
|
21272
21534
|
//#region src/utils/discovery-classifiers.ts
|
|
21273
21535
|
const LANGUAGE_SIGNALS = {
|
|
21274
21536
|
"tsconfig.json": "typescript",
|
|
@@ -21603,6 +21865,8 @@ const runScopedScan = async (cwd, filePaths) => {
|
|
|
21603
21865
|
projectFiles: [.../* @__PURE__ */ new Set([...projectSourceFiles, ...filterProjectDeclarationFiles(project.rootDirectory, projectCandidates, excludePatterns, config.include)])],
|
|
21604
21866
|
installedTools: project.installedTools,
|
|
21605
21867
|
config: {
|
|
21868
|
+
overrides: config.overrides,
|
|
21869
|
+
rules: config.rules,
|
|
21606
21870
|
quality: config.quality,
|
|
21607
21871
|
security: {
|
|
21608
21872
|
audit: false,
|
|
@@ -21622,7 +21886,7 @@ const runScopedScan = async (cwd, filePaths) => {
|
|
|
21622
21886
|
"ai-slop": config.engines["ai-slop"],
|
|
21623
21887
|
architecture: config.engines.architecture,
|
|
21624
21888
|
security: false
|
|
21625
|
-
})).flatMap((result) => result.diagnostics), config.rules);
|
|
21889
|
+
})).flatMap((result) => result.diagnostics), config.overrides.length ? {} : config.rules);
|
|
21626
21890
|
const { score } = calculateScore(diagnostics, config.scoring.weights, config.scoring.thresholds, projectSourceFiles.length + projectTestFiles.length, config.scoring.smoothing, config.scoring.maxPerRule);
|
|
21627
21891
|
return {
|
|
21628
21892
|
diagnostics,
|
|
@@ -21726,6 +21990,8 @@ const captureBaseline = async (cwd) => {
|
|
|
21726
21990
|
files: [],
|
|
21727
21991
|
installedTools: project.installedTools,
|
|
21728
21992
|
config: {
|
|
21993
|
+
overrides: config.overrides,
|
|
21994
|
+
rules: config.rules,
|
|
21729
21995
|
quality: config.quality,
|
|
21730
21996
|
security: {
|
|
21731
21997
|
audit: false,
|
|
@@ -22945,6 +23211,31 @@ const detectInstalledAgents = (opts) => {
|
|
|
22945
23211
|
}
|
|
22946
23212
|
return hits;
|
|
22947
23213
|
};
|
|
23214
|
+
const HOOK_MARKERS = [
|
|
23215
|
+
"__raigal",
|
|
23216
|
+
"raigal:begin",
|
|
23217
|
+
"@RAIGAL.md",
|
|
23218
|
+
"__aislop",
|
|
23219
|
+
"aislop:begin",
|
|
23220
|
+
"@AISLOP.md"
|
|
23221
|
+
];
|
|
23222
|
+
const hasHook = (target) => {
|
|
23223
|
+
try {
|
|
23224
|
+
if (!fs.statSync(target).isFile()) return false;
|
|
23225
|
+
const base = path.basename(target).toLowerCase();
|
|
23226
|
+
if (base.startsWith("raigal") || base.startsWith("aislop")) return true;
|
|
23227
|
+
const content = fs.readFileSync(target, "utf8");
|
|
23228
|
+
return HOOK_MARKERS.some((marker) => content.includes(marker));
|
|
23229
|
+
} catch {
|
|
23230
|
+
return false;
|
|
23231
|
+
}
|
|
23232
|
+
};
|
|
23233
|
+
const detectionScopes = (agent) => AGENTS_SUPPORTING_BOTH_SCOPES.includes(agent) ? ["global", "project"] : [defaultScopeFor(agent)];
|
|
23234
|
+
const detectRaigalHooks = (opts) => ALL_AGENTS.filter((agent) => detectionScopes(agent).some((scope) => REGISTRY[agent].paths({
|
|
23235
|
+
home: opts.home,
|
|
23236
|
+
cwd: opts.cwd,
|
|
23237
|
+
scope
|
|
23238
|
+
}).some(hasHook)));
|
|
22948
23239
|
//#endregion
|
|
22949
23240
|
//#region src/ui/search-select.ts
|
|
22950
23241
|
const silentOutput = new Writable({ write(_chunk, _encoding, callback) {
|
|
@@ -23632,8 +23923,38 @@ const registerHookAliases = (program) => {
|
|
|
23632
23923
|
});
|
|
23633
23924
|
};
|
|
23634
23925
|
//#endregion
|
|
23926
|
+
//#region src/utils/github-slug.ts
|
|
23927
|
+
const GITHUB_REMOTE_RE = /^(?:git@github\.com:|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
|
|
23928
|
+
const parseGithubSlug = (remoteUrl) => {
|
|
23929
|
+
const match = remoteUrl.trim().match(GITHUB_REMOTE_RE);
|
|
23930
|
+
if (!match) return null;
|
|
23931
|
+
const owner = match[1];
|
|
23932
|
+
const repo = match[2];
|
|
23933
|
+
if (!owner || !repo) return null;
|
|
23934
|
+
return {
|
|
23935
|
+
owner,
|
|
23936
|
+
repo
|
|
23937
|
+
};
|
|
23938
|
+
};
|
|
23939
|
+
const detectGithubSlug = (directory) => {
|
|
23940
|
+
let raw;
|
|
23941
|
+
try {
|
|
23942
|
+
raw = execSync("git remote get-url origin", {
|
|
23943
|
+
cwd: path.resolve(directory),
|
|
23944
|
+
encoding: "utf-8",
|
|
23945
|
+
stdio: [
|
|
23946
|
+
"ignore",
|
|
23947
|
+
"pipe",
|
|
23948
|
+
"ignore"
|
|
23949
|
+
]
|
|
23950
|
+
});
|
|
23951
|
+
} catch {
|
|
23952
|
+
return null;
|
|
23953
|
+
}
|
|
23954
|
+
return parseGithubSlug(raw);
|
|
23955
|
+
};
|
|
23956
|
+
//#endregion
|
|
23635
23957
|
//#region src/commands/badge.ts
|
|
23636
|
-
const GITHUB_REMOTE_RE = /^(?:git@github\.com:|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/.\s]+?)(?:\.git)?\s*$/;
|
|
23637
23958
|
const renderBadgeOutput = ({ owner, repo, svgUrl, pageUrl }) => {
|
|
23638
23959
|
const slug = `${owner}/${repo}`;
|
|
23639
23960
|
const markdown = `[](${pageUrl})`;
|
|
@@ -23677,31 +23998,6 @@ const renderBadgeOutput = ({ owner, repo, svgUrl, pageUrl }) => {
|
|
|
23677
23998
|
""
|
|
23678
23999
|
].join("\n");
|
|
23679
24000
|
};
|
|
23680
|
-
const detectGithubSlugFromGit = (directory) => {
|
|
23681
|
-
let raw;
|
|
23682
|
-
try {
|
|
23683
|
-
raw = execSync("git remote get-url origin", {
|
|
23684
|
-
cwd: path.resolve(directory),
|
|
23685
|
-
encoding: "utf-8",
|
|
23686
|
-
stdio: [
|
|
23687
|
-
"ignore",
|
|
23688
|
-
"pipe",
|
|
23689
|
-
"ignore"
|
|
23690
|
-
]
|
|
23691
|
-
});
|
|
23692
|
-
} catch {
|
|
23693
|
-
return null;
|
|
23694
|
-
}
|
|
23695
|
-
const match = raw.trim().match(GITHUB_REMOTE_RE);
|
|
23696
|
-
if (!match) return null;
|
|
23697
|
-
const owner = match[1];
|
|
23698
|
-
const repo = match[2];
|
|
23699
|
-
if (!owner || !repo) return null;
|
|
23700
|
-
return {
|
|
23701
|
-
owner,
|
|
23702
|
-
repo
|
|
23703
|
-
};
|
|
23704
|
-
};
|
|
23705
24001
|
const badgeCommand = async (options = {}) => {
|
|
23706
24002
|
await requireEntitlement({
|
|
23707
24003
|
directory: options.directory,
|
|
@@ -23710,7 +24006,7 @@ const badgeCommand = async (options = {}) => {
|
|
|
23710
24006
|
let owner = options.owner?.trim();
|
|
23711
24007
|
let repo = options.repo?.trim();
|
|
23712
24008
|
if (!owner || !repo) {
|
|
23713
|
-
const detected =
|
|
24009
|
+
const detected = detectGithubSlug(options.directory ?? ".");
|
|
23714
24010
|
if (!detected) throw new Error("Could not detect a GitHub remote. Run from a repo with `git remote get-url origin` set, or pass --owner and --repo.");
|
|
23715
24011
|
owner ??= detected.owner;
|
|
23716
24012
|
repo ??= detected.repo;
|
|
@@ -24486,7 +24782,7 @@ const maybeNotifyUpdate = async (now = Date.now()) => {
|
|
|
24486
24782
|
if (!process.stderr.isTTY) return;
|
|
24487
24783
|
const cachePath = resolveUpdateCachePath();
|
|
24488
24784
|
const cache = readCache(cachePath);
|
|
24489
|
-
if (cache && isOutdated("0.2.
|
|
24785
|
+
if (cache && isOutdated("0.2.2", cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
|
|
24490
24786
|
if (!cache || now - cache.checkedAt > CHECK_INTERVAL_MS) {
|
|
24491
24787
|
const latest = await fetchLatestVersion$1();
|
|
24492
24788
|
if (latest) writeCache(cachePath, {
|
|
@@ -25006,7 +25302,7 @@ const renderCommandGroup = (group, commandWidth) => {
|
|
|
25006
25302
|
return lines;
|
|
25007
25303
|
};
|
|
25008
25304
|
const renderCommandReference = (input = {}) => {
|
|
25009
|
-
const version = input.version ?? "0.2.
|
|
25305
|
+
const version = input.version ?? "0.2.2";
|
|
25010
25306
|
const commandWidth = Math.max(...COMMAND_REFERENCE.map((item) => item.command.length));
|
|
25011
25307
|
const lines = [renderHeader({
|
|
25012
25308
|
version,
|
|
@@ -25229,7 +25525,7 @@ const renderHelpDetails = () => [
|
|
|
25229
25525
|
].map((line) => highlightRaigal(line, theme)).join("\n");
|
|
25230
25526
|
const renderHome = (input = {}) => {
|
|
25231
25527
|
let out = renderHeader({
|
|
25232
|
-
version: input.version ?? "0.2.
|
|
25528
|
+
version: input.version ?? "0.2.2",
|
|
25233
25529
|
command: "--bare",
|
|
25234
25530
|
context: []
|
|
25235
25531
|
});
|
|
@@ -25974,6 +26270,18 @@ const getChangedLineMap = (cwd, base) => {
|
|
|
25974
26270
|
return map;
|
|
25975
26271
|
};
|
|
25976
26272
|
//#endregion
|
|
26273
|
+
//#region src/commands/scan-hook-nudge.ts
|
|
26274
|
+
const shouldShowHookNudge = (ctx) => ctx.isTty && !ctx.isCi && ctx.installedAgentCount === 0;
|
|
26275
|
+
const buildHookNudge = (ctx) => {
|
|
26276
|
+
if (!shouldShowHookNudge(ctx)) return null;
|
|
26277
|
+
return [
|
|
26278
|
+
"",
|
|
26279
|
+
"Next: install the per-edit hook so your coding agent fixes slop before it lands.",
|
|
26280
|
+
ctx.invocation === "raigal" ? " raigal hook install" : " npm install -g @methiu/raigal && raigal hook install",
|
|
26281
|
+
""
|
|
26282
|
+
].join("\n");
|
|
26283
|
+
};
|
|
26284
|
+
//#endregion
|
|
25977
26285
|
//#region src/commands/scan-coverage.ts
|
|
25978
26286
|
const coverageReason = (c) => {
|
|
25979
26287
|
if (c.supportedFiles === 0 && c.dominantUnsupported) return `This repository is ${c.dominantUnsupported} (${c.unsupportedFiles} files), which Raigal does not analyze. No score — it would not reflect this code.`;
|
|
@@ -26515,7 +26823,7 @@ const buildScanNextSteps = (input) => {
|
|
|
26515
26823
|
emphasis: "primary",
|
|
26516
26824
|
label: "Provider",
|
|
26517
26825
|
command: `${input.invocation} agent --provider codex`,
|
|
26518
|
-
detail: "or use --provider claude/opencode"
|
|
26826
|
+
detail: "or use --provider claude/opencode/pi"
|
|
26519
26827
|
}, {
|
|
26520
26828
|
emphasis: "muted",
|
|
26521
26829
|
label: "Preview",
|
|
@@ -26682,6 +26990,8 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
26682
26990
|
const configDir = findConfigDir(resolvedDir);
|
|
26683
26991
|
const rulesPath = configDir ? path.join(configDir, RULES_FILE) : void 0;
|
|
26684
26992
|
const engineConfig = {
|
|
26993
|
+
overrides: config.overrides,
|
|
26994
|
+
rules: config.rules,
|
|
26685
26995
|
quality: config.quality,
|
|
26686
26996
|
security: config.security,
|
|
26687
26997
|
lint: config.lint,
|
|
@@ -26703,7 +27013,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
26703
27013
|
config: engineConfig
|
|
26704
27014
|
}, config.engines, machineOutput)).map((result) => ({
|
|
26705
27015
|
...result,
|
|
26706
|
-
diagnostics: applyRuleSeverities(result.diagnostics, config.rules)
|
|
27016
|
+
diagnostics: config.overrides.length ? result.diagnostics : applyRuleSeverities(result.diagnostics, config.rules)
|
|
26707
27017
|
})), resolvedDir);
|
|
26708
27018
|
if (suppressedCount > 0 && !machineOutput) log.muted(`Suppressed ${suppressedCount} finding(s) via raigal-ignore directives`);
|
|
26709
27019
|
const changeMap = options.changes && !options.staged ? getChangedLineMap(resolvedDir, options.base) : null;
|
|
@@ -26741,6 +27051,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
26741
27051
|
engineIssues[r.engine] = r.diagnostics.length;
|
|
26742
27052
|
engineTimings[r.engine] = Math.round(r.elapsed);
|
|
26743
27053
|
}
|
|
27054
|
+
const enginesFailed = results.filter((r) => r.failed).map((r) => r.engine);
|
|
26744
27055
|
const completion = {
|
|
26745
27056
|
exitCode,
|
|
26746
27057
|
score: scoreable ? scoreResult.score : null,
|
|
@@ -26750,7 +27061,8 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
26750
27061
|
warningCount: allDiagnostics.filter((d) => d.severity === "warning").length,
|
|
26751
27062
|
fixableCount: allDiagnostics.filter((d) => d.fixable).length,
|
|
26752
27063
|
engineIssues,
|
|
26753
|
-
engineTimings
|
|
27064
|
+
engineTimings,
|
|
27065
|
+
...enginesFailed.length > 0 ? { properties: { engines_failed: enginesFailed } } : {}
|
|
26754
27066
|
};
|
|
26755
27067
|
recordScanToCloud({
|
|
26756
27068
|
results,
|
|
@@ -26768,7 +27080,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
26768
27080
|
return completion;
|
|
26769
27081
|
}
|
|
26770
27082
|
if (options.json) {
|
|
26771
|
-
const { buildJsonOutput } = await import("./json-
|
|
27083
|
+
const { buildJsonOutput } = await import("./json-BaycSoJP.js");
|
|
26772
27084
|
const jsonOut = buildJsonOutput(results, scoreResult, scoreFileCount, elapsedMs, scanCoverage);
|
|
26773
27085
|
console.log(JSON.stringify(jsonOut, null, 2));
|
|
26774
27086
|
return completion;
|
|
@@ -26801,6 +27113,18 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
26801
27113
|
printBrand: options.printBrand,
|
|
26802
27114
|
showPilotInvitation
|
|
26803
27115
|
}));
|
|
27116
|
+
if (options.command !== "ci" && options.printBrand !== false) {
|
|
27117
|
+
const nudge = buildHookNudge({
|
|
27118
|
+
installedAgentCount: detectRaigalHooks({
|
|
27119
|
+
home: os.homedir(),
|
|
27120
|
+
cwd: resolvedDir
|
|
27121
|
+
}).length,
|
|
27122
|
+
isTty: Boolean(process.stdout.isTTY),
|
|
27123
|
+
isCi: isCiEnv(),
|
|
27124
|
+
invocation: detectInvocation()
|
|
27125
|
+
});
|
|
27126
|
+
if (nudge) process.stdout.write(nudge);
|
|
27127
|
+
}
|
|
26804
27128
|
return completion;
|
|
26805
27129
|
};
|
|
26806
27130
|
//#endregion
|
|
@@ -27540,7 +27864,9 @@ const createEngineContext = (rootDirectory, projectInfo, config, options = {}) =
|
|
|
27540
27864
|
quality: config.quality,
|
|
27541
27865
|
security: config.security,
|
|
27542
27866
|
lint: config.lint,
|
|
27543
|
-
engines: config.engines
|
|
27867
|
+
engines: config.engines,
|
|
27868
|
+
overrides: config.overrides,
|
|
27869
|
+
rules: config.rules
|
|
27544
27870
|
},
|
|
27545
27871
|
...options.scope ? {
|
|
27546
27872
|
files: options.scope.files,
|
|
@@ -28057,6 +28383,56 @@ const computeRemovalRange = (sourceFile, node, content) => {
|
|
|
28057
28383
|
end: finalEnd
|
|
28058
28384
|
};
|
|
28059
28385
|
};
|
|
28386
|
+
const isReferencedOutside = (sourceFile, statement, name) => {
|
|
28387
|
+
const start = statement.getStart(sourceFile);
|
|
28388
|
+
const end = statement.getEnd();
|
|
28389
|
+
let found = false;
|
|
28390
|
+
const visit = (node) => {
|
|
28391
|
+
if (found) return;
|
|
28392
|
+
if (ts.isIdentifier(node) && node.text === name) {
|
|
28393
|
+
const pos = node.getStart(sourceFile);
|
|
28394
|
+
if (pos < start || pos >= end) found = true;
|
|
28395
|
+
return;
|
|
28396
|
+
}
|
|
28397
|
+
ts.forEachChild(node, visit);
|
|
28398
|
+
};
|
|
28399
|
+
visit(sourceFile);
|
|
28400
|
+
return found;
|
|
28401
|
+
};
|
|
28402
|
+
const exportKeywordRange = (sourceFile, statement, content) => {
|
|
28403
|
+
const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) ?? [] : [];
|
|
28404
|
+
if (modifiers.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)) return null;
|
|
28405
|
+
const exportKeyword = modifiers.find((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
28406
|
+
if (!exportKeyword) return null;
|
|
28407
|
+
let end = exportKeyword.getEnd();
|
|
28408
|
+
while (end < content.length && (content[end] === " " || content[end] === " ")) end++;
|
|
28409
|
+
return {
|
|
28410
|
+
start: exportKeyword.getStart(sourceFile),
|
|
28411
|
+
end
|
|
28412
|
+
};
|
|
28413
|
+
};
|
|
28414
|
+
const removalFor = (sourceFile, statement, content, decl) => {
|
|
28415
|
+
if (!isReferencedOutside(sourceFile, statement, decl.name)) return null;
|
|
28416
|
+
const range = exportKeywordRange(sourceFile, statement, content);
|
|
28417
|
+
if (!range) return {
|
|
28418
|
+
type: "skip",
|
|
28419
|
+
reason: "still referenced in its own file",
|
|
28420
|
+
declaration: decl
|
|
28421
|
+
};
|
|
28422
|
+
return {
|
|
28423
|
+
type: "match",
|
|
28424
|
+
removal: {
|
|
28425
|
+
...range,
|
|
28426
|
+
declaration: decl
|
|
28427
|
+
}
|
|
28428
|
+
};
|
|
28429
|
+
};
|
|
28430
|
+
const declaredNames = (statement) => {
|
|
28431
|
+
if (ts.isVariableStatement(statement)) return statement.declarationList.declarations.map((d) => ts.isIdentifier(d.name) ? d.name.text : null).filter((name) => name !== null);
|
|
28432
|
+
if (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement) || ts.isEnumDeclaration(statement) || ts.isModuleDeclaration(statement)) return statement.name && ts.isIdentifier(statement.name) ? [statement.name.text] : [];
|
|
28433
|
+
return [];
|
|
28434
|
+
};
|
|
28435
|
+
const isDeclaredMoreThanOnce = (sourceFile, name) => sourceFile.statements.filter((s) => declaredNames(s).includes(name)).length > 1;
|
|
28060
28436
|
const kindOfStatement = (node) => {
|
|
28061
28437
|
if (ts.isVariableStatement(node)) return "variable";
|
|
28062
28438
|
if (ts.isFunctionDeclaration(node)) return "function";
|
|
@@ -28068,6 +28444,11 @@ const kindOfStatement = (node) => {
|
|
|
28068
28444
|
};
|
|
28069
28445
|
const matchStatement = (sourceFile, statement, content, decl) => {
|
|
28070
28446
|
if (!kindOfStatement(statement)) return { type: "none" };
|
|
28447
|
+
if (declaredNames(statement).includes(decl.name) && isDeclaredMoreThanOnce(sourceFile, decl.name)) return {
|
|
28448
|
+
type: "skip",
|
|
28449
|
+
reason: "merged declaration",
|
|
28450
|
+
declaration: decl
|
|
28451
|
+
};
|
|
28071
28452
|
if (ts.isVariableStatement(statement)) {
|
|
28072
28453
|
const varDecls = statement.declarationList.declarations;
|
|
28073
28454
|
if (varDecls.length === 0) return { type: "none" };
|
|
@@ -28083,6 +28464,8 @@ const matchStatement = (sourceFile, statement, content, decl) => {
|
|
|
28083
28464
|
reason: "multi-declaration variable statement",
|
|
28084
28465
|
declaration: decl
|
|
28085
28466
|
};
|
|
28467
|
+
const referenced = removalFor(sourceFile, statement, content, decl);
|
|
28468
|
+
if (referenced) return referenced;
|
|
28086
28469
|
if (initializerHasSideEffects(match.initializer)) return {
|
|
28087
28470
|
type: "skip",
|
|
28088
28471
|
reason: "initializer may have side effects",
|
|
@@ -28100,6 +28483,8 @@ const matchStatement = (sourceFile, statement, content, decl) => {
|
|
|
28100
28483
|
if (!statement.name) return { type: "none" };
|
|
28101
28484
|
if (statement.name.text !== decl.name) return { type: "none" };
|
|
28102
28485
|
if (!nodeContainsLine(sourceFile, statement, decl.line)) return { type: "none" };
|
|
28486
|
+
const referenced = removalFor(sourceFile, statement, content, decl);
|
|
28487
|
+
if (referenced) return referenced;
|
|
28103
28488
|
return {
|
|
28104
28489
|
type: "match",
|
|
28105
28490
|
removal: {
|
|
@@ -28111,6 +28496,8 @@ const matchStatement = (sourceFile, statement, content, decl) => {
|
|
|
28111
28496
|
if (ts.isTypeAliasDeclaration(statement) || ts.isInterfaceDeclaration(statement) || ts.isEnumDeclaration(statement)) {
|
|
28112
28497
|
if (statement.name.text !== decl.name) return { type: "none" };
|
|
28113
28498
|
if (!nodeContainsLine(sourceFile, statement, decl.line)) return { type: "none" };
|
|
28499
|
+
const referenced = removalFor(sourceFile, statement, content, decl);
|
|
28500
|
+
if (referenced) return referenced;
|
|
28114
28501
|
return {
|
|
28115
28502
|
type: "match",
|
|
28116
28503
|
removal: {
|
|
@@ -29162,6 +29549,8 @@ const runFixBody = async (resolvedDir, config, options, projectInfo) => {
|
|
|
29162
29549
|
const configDir = findConfigDir(resolvedDir);
|
|
29163
29550
|
const rulesPath = configDir ? path.join(configDir, RULES_FILE) : void 0;
|
|
29164
29551
|
const engineConfig = {
|
|
29552
|
+
overrides: config.overrides,
|
|
29553
|
+
rules: config.rules,
|
|
29165
29554
|
quality: config.quality,
|
|
29166
29555
|
security: config.security,
|
|
29167
29556
|
lint: config.lint,
|
|
@@ -29187,7 +29576,7 @@ const runFixBody = async (resolvedDir, config, options, projectInfo) => {
|
|
|
29187
29576
|
status: "done",
|
|
29188
29577
|
label: "Verification complete"
|
|
29189
29578
|
});
|
|
29190
|
-
const scanResults = appendPostFixLintResult(verificationResults, collectPostFixLintDiagnostics(steps));
|
|
29579
|
+
const scanResults = appendPostFixLintResult(verificationResults, applyFileOverrides(collectPostFixLintDiagnostics(steps), context));
|
|
29191
29580
|
const allDiagnostics = scanResults.flatMap((r) => r.diagnostics);
|
|
29192
29581
|
const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds, scope ? scope.scoreFileCount : projectInfo.sourceFileCount, config.scoring.smoothing, config.scoring.maxPerRule);
|
|
29193
29582
|
const errors = allDiagnostics.filter((d) => d.severity === "error").length;
|
|
@@ -29318,13 +29707,13 @@ const writeGithubWorkflow = (rootDirectory, enabled) => {
|
|
|
29318
29707
|
};
|
|
29319
29708
|
};
|
|
29320
29709
|
const promptForConfigChoices = async () => {
|
|
29321
|
-
const enginesSelection = await multiselect({
|
|
29710
|
+
const enginesSelection = await runCancellable(() => multiselect({
|
|
29322
29711
|
message: "Which engines should run?",
|
|
29323
29712
|
options: ENGINE_CHOICES,
|
|
29324
29713
|
initialValues: DEFAULT_ENGINE_SELECTION,
|
|
29325
29714
|
required: false
|
|
29326
|
-
});
|
|
29327
|
-
if (
|
|
29715
|
+
}));
|
|
29716
|
+
if (enginesSelection === void 0) return null;
|
|
29328
29717
|
const failBelowRaw = await text({
|
|
29329
29718
|
message: "CI quality gate — fail the build when the score drops below (0-100)",
|
|
29330
29719
|
initialValue: "70",
|
|
@@ -29679,13 +30068,19 @@ const suggestClosest = (token, candidates) => {
|
|
|
29679
30068
|
};
|
|
29680
30069
|
//#endregion
|
|
29681
30070
|
//#region src/cli.ts
|
|
29682
|
-
const safeTelemetryConfig = (dir) => {
|
|
30071
|
+
const safeTelemetryConfig = (dir = process.cwd()) => {
|
|
29683
30072
|
try {
|
|
29684
30073
|
return loadConfig(dir).telemetry;
|
|
29685
30074
|
} catch {
|
|
29686
30075
|
return;
|
|
29687
30076
|
}
|
|
29688
30077
|
};
|
|
30078
|
+
const exitOnFatal = (error, stage) => {
|
|
30079
|
+
console.error(error);
|
|
30080
|
+
reportFatalError(error, stage, safeTelemetryConfig()).finally(() => process.exit(1));
|
|
30081
|
+
};
|
|
30082
|
+
process.on("uncaughtException", (error) => exitOnFatal(error, "uncaught_exception"));
|
|
30083
|
+
process.on("unhandledRejection", (reason) => exitOnFatal(reason, "unhandled_rejection"));
|
|
29689
30084
|
const handleTerminationSignal = async (signal) => {
|
|
29690
30085
|
killActiveChildren();
|
|
29691
30086
|
const exitCode = signal === "SIGINT" ? 130 : 143;
|
|
@@ -29784,7 +30179,7 @@ fixProgram.action(async (directory = ".", _flags, command) => {
|
|
|
29784
30179
|
}
|
|
29785
30180
|
});
|
|
29786
30181
|
registerAgentCommand(program);
|
|
29787
|
-
program.command("init [directory]").description("Create
|
|
30182
|
+
program.command("init [directory]").description("Create raigal config and optional CI workflow").option("--strict", "write an enterprise-grade default config: all engines, typecheck on, CI failBelow 85, workflow included").action(async (directory = ".", _flags, command) => {
|
|
29788
30183
|
const flags = command.optsWithGlobals();
|
|
29789
30184
|
await withCommandLifecycle({
|
|
29790
30185
|
command: "init",
|
|
@@ -29882,6 +30277,6 @@ const main = async () => {
|
|
|
29882
30277
|
await flushTelemetry();
|
|
29883
30278
|
await maybeNotifyUpdate();
|
|
29884
30279
|
};
|
|
29885
|
-
main();
|
|
30280
|
+
main().catch((error) => exitOnFatal(error, "main"));
|
|
29886
30281
|
//#endregion
|
|
29887
30282
|
export { requestApi as a, log as c, detectRepoRef as i, FIXED_SECRET_DIAGNOSTIC_MESSAGE as l, withFindingAssessments as n, getActiveToken as o, ENGINE_INFO as r, APP_VERSION as s, summarizeFindingAssessments as t, isSecretClassRule as u };
|