@methiu/raigal 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +330 -82
- package/dist/{finding-assessment-M5fUTE-C.js → finding-assessment-CGpt5YUR.js} +3 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +255 -58
- package/dist/{json-DBoMe4xB.js → json-BBZKvm0E.js} +2 -2
- package/dist/mcp.js +254 -79
- package/dist/{sarif--DI_2p2r.js → sarif-CdtsAfJ1.js} +1 -1
- package/dist/{sarif-Ba5D8TPH.js → sarif-Lt31Ld5t.js} +2 -2
- package/dist/{version-9Cnt1TiK.js → version-CukuIVX4.js} +1 -1
- package/package.json +1 -1
- package/scripts/release-guard.mjs +2 -2
package/dist/cli.js
CHANGED
|
@@ -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.4";
|
|
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.4", 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) {
|
|
@@ -1595,7 +1595,7 @@ const getEntitlement = async () => {
|
|
|
1595
1595
|
};
|
|
1596
1596
|
//#endregion
|
|
1597
1597
|
//#region src/cloud/owner.ts
|
|
1598
|
-
const GITHUB_REMOTE_RE$1 = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([
|
|
1598
|
+
const GITHUB_REMOTE_RE$1 = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
|
|
1599
1599
|
const parseGitRemote = (rawUrl) => {
|
|
1600
1600
|
if (!rawUrl) return {
|
|
1601
1601
|
owner: null,
|
|
@@ -3164,7 +3164,7 @@ const recordAgentSessionTelemetry = ({ worktreePath, published, branchOption, pr
|
|
|
3164
3164
|
if (!recorder) return;
|
|
3165
3165
|
let patchDiff;
|
|
3166
3166
|
if (worktreePath) try {
|
|
3167
|
-
|
|
3167
|
+
const rawDiff = spawnSync("git", [
|
|
3168
3168
|
"diff",
|
|
3169
3169
|
"-U2",
|
|
3170
3170
|
"--no-color",
|
|
@@ -3172,7 +3172,8 @@ const recordAgentSessionTelemetry = ({ worktreePath, published, branchOption, pr
|
|
|
3172
3172
|
], {
|
|
3173
3173
|
cwd: worktreePath,
|
|
3174
3174
|
encoding: "utf-8"
|
|
3175
|
-
}).stdout || ""
|
|
3175
|
+
}).stdout || "";
|
|
3176
|
+
patchDiff = rawDiff ? maskSecrets(rawDiff).slice(0, 2e4) : void 0;
|
|
3176
3177
|
} catch {}
|
|
3177
3178
|
if (published) {
|
|
3178
3179
|
const prMatch = published.prUrl?.match(/\/pull\/(\d+)/);
|
|
@@ -4994,6 +4995,9 @@ const CiSchema = z.object({
|
|
|
4994
4995
|
format: z.enum(["json"]).default("json")
|
|
4995
4996
|
}).strict();
|
|
4996
4997
|
const TelemetrySchema = z.object({ enabled: z.boolean().default(true) }).strict();
|
|
4998
|
+
const BaselineConfigSchema = z.object({ mode: z.enum(["strict", "relaxed"]).default("relaxed") }).strict();
|
|
4999
|
+
const LicensingConfigSchema = z.object({ onLapse: z.enum(["fail-open", "fail-closed"]).default("fail-open") }).strict();
|
|
5000
|
+
const WorkspaceConfigSchema = z.object({ minimumFloor: z.number().min(0).max(100).default(50) }).strict();
|
|
4997
5001
|
const RuleSeverityOverride = z.enum([
|
|
4998
5002
|
"error",
|
|
4999
5003
|
"warning",
|
|
@@ -5064,6 +5068,9 @@ const AislopConfigSchema = z.object({
|
|
|
5064
5068
|
format: "json"
|
|
5065
5069
|
})),
|
|
5066
5070
|
telemetry: TelemetrySchema.default(() => ({ enabled: true })),
|
|
5071
|
+
baseline: BaselineConfigSchema.optional(),
|
|
5072
|
+
licensing: LicensingConfigSchema.optional(),
|
|
5073
|
+
workspace: WorkspaceConfigSchema.optional(),
|
|
5067
5074
|
rules: RulesSchema,
|
|
5068
5075
|
overrides: z.array(FileOverrideSchema).default(() => []),
|
|
5069
5076
|
exclude: z.array(z.string()).default(() => [
|
|
@@ -5097,6 +5104,9 @@ const KNOWN_KEYS_BY_PATH = {
|
|
|
5097
5104
|
"scoring",
|
|
5098
5105
|
"ci",
|
|
5099
5106
|
"telemetry",
|
|
5107
|
+
"baseline",
|
|
5108
|
+
"licensing",
|
|
5109
|
+
"workspace",
|
|
5100
5110
|
"rules",
|
|
5101
5111
|
"exclude",
|
|
5102
5112
|
"include"
|
|
@@ -5147,7 +5157,10 @@ const KNOWN_KEYS_BY_PATH = {
|
|
|
5147
5157
|
],
|
|
5148
5158
|
"scoring.thresholds": ["good", "ok"],
|
|
5149
5159
|
ci: ["failBelow", "format"],
|
|
5150
|
-
telemetry: ["enabled"]
|
|
5160
|
+
telemetry: ["enabled"],
|
|
5161
|
+
baseline: ["mode"],
|
|
5162
|
+
licensing: ["onLapse"],
|
|
5163
|
+
workspace: ["minimumFloor"]
|
|
5151
5164
|
};
|
|
5152
5165
|
const levenshtein = (a, b) => {
|
|
5153
5166
|
const m = a.length;
|
|
@@ -8130,9 +8143,9 @@ const buildSuggestedActions = (diagnostics, findings, regressed, delta) => {
|
|
|
8130
8143
|
if (fixableDiags.length > 0) {
|
|
8131
8144
|
const ruleIds = Array.from(new Set(fixableDiags.map((d) => d.rule)));
|
|
8132
8145
|
actions.push({
|
|
8133
|
-
id: "
|
|
8134
|
-
label: `Run
|
|
8135
|
-
command: "
|
|
8146
|
+
id: "run_raigal_fix",
|
|
8147
|
+
label: `Run raigal fix to clear ${fixableDiags.length} mechanical finding${fixableDiags.length === 1 ? "" : "s"}.`,
|
|
8148
|
+
command: "raigal fix",
|
|
8136
8149
|
rationale: "These findings have deterministic fixes (formatting, unused imports, trivial comments). Running this before any manual work avoids burning agent tokens on what the CLI handles for free.",
|
|
8137
8150
|
ruleIds
|
|
8138
8151
|
});
|
|
@@ -8193,8 +8206,40 @@ const buildFeedback = (diagnostics, score, rootDirectory, baseline, meta) => {
|
|
|
8193
8206
|
const regressed = typeof delta === "number" ? delta < 0 : false;
|
|
8194
8207
|
let newSinceBaseline;
|
|
8195
8208
|
if (baselineSnapshot && baselineSnapshot.findingFingerprints.length > 0) {
|
|
8196
|
-
const
|
|
8197
|
-
|
|
8209
|
+
const remainingExact = new Set(baselineSnapshot.findingFingerprints);
|
|
8210
|
+
const unmatchedFindings = [];
|
|
8211
|
+
for (const f of all) {
|
|
8212
|
+
const exactFp = fingerprintFinding(f);
|
|
8213
|
+
if (remainingExact.has(exactFp)) remainingExact.delete(exactFp);
|
|
8214
|
+
else unmatchedFindings.push(f);
|
|
8215
|
+
}
|
|
8216
|
+
const remainingPool = [];
|
|
8217
|
+
for (const fp of remainingExact) {
|
|
8218
|
+
const parts = fp.split(":");
|
|
8219
|
+
if (parts.length >= 3) {
|
|
8220
|
+
const file = parts[0];
|
|
8221
|
+
const line = Number.parseInt(parts[1], 10);
|
|
8222
|
+
const rule = parts.slice(2).join(":");
|
|
8223
|
+
remainingPool.push({
|
|
8224
|
+
file,
|
|
8225
|
+
line: Number.isNaN(line) ? 0 : line,
|
|
8226
|
+
rule
|
|
8227
|
+
});
|
|
8228
|
+
}
|
|
8229
|
+
}
|
|
8230
|
+
const stillUnmatched = [];
|
|
8231
|
+
for (const f of unmatchedFindings) {
|
|
8232
|
+
const candidateIdx = remainingPool.findIndex((b) => b.file === f.file && b.rule === f.ruleId && Math.abs(b.line - f.line) <= 5);
|
|
8233
|
+
if (candidateIdx >= 0) remainingPool.splice(candidateIdx, 1);
|
|
8234
|
+
else stillUnmatched.push(f);
|
|
8235
|
+
}
|
|
8236
|
+
const fresh = [];
|
|
8237
|
+
for (const f of stillUnmatched) {
|
|
8238
|
+
const candidateIdx = remainingPool.findIndex((b) => b.file === f.file && b.rule === f.ruleId);
|
|
8239
|
+
if (candidateIdx >= 0) remainingPool.splice(candidateIdx, 1);
|
|
8240
|
+
else fresh.push(f);
|
|
8241
|
+
}
|
|
8242
|
+
newSinceBaseline = fresh.slice(0, MAX_NEW_SINCE_BASELINE);
|
|
8198
8243
|
}
|
|
8199
8244
|
return {
|
|
8200
8245
|
schema: "aislop.hook.v2",
|
|
@@ -9549,7 +9594,7 @@ const tokenizeJs = (content, ext = ".ts") => {
|
|
|
9549
9594
|
};
|
|
9550
9595
|
//#endregion
|
|
9551
9596
|
//#region src/utils/source-masker.ts
|
|
9552
|
-
const JS_EXTS$
|
|
9597
|
+
const JS_EXTS$3 = /* @__PURE__ */ new Set([
|
|
9553
9598
|
".ts",
|
|
9554
9599
|
".tsx",
|
|
9555
9600
|
".js",
|
|
@@ -9557,7 +9602,7 @@ const JS_EXTS$2 = /* @__PURE__ */ new Set([
|
|
|
9557
9602
|
".mjs",
|
|
9558
9603
|
".cjs"
|
|
9559
9604
|
]);
|
|
9560
|
-
const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
|
|
9605
|
+
const PY_EXTS$1 = /* @__PURE__ */ new Set([".py"]);
|
|
9561
9606
|
const RB_EXTS = /* @__PURE__ */ new Set([".rb"]);
|
|
9562
9607
|
const PHP_EXTS = /* @__PURE__ */ new Set([".php"]);
|
|
9563
9608
|
const CSHARP_EXTS = /* @__PURE__ */ new Set([".cs"]);
|
|
@@ -9581,8 +9626,8 @@ const C_STYLE_COMMENT_EXTS = /* @__PURE__ */ new Set([
|
|
|
9581
9626
|
".zig"
|
|
9582
9627
|
]);
|
|
9583
9628
|
const familyForExt = (ext) => {
|
|
9584
|
-
if (JS_EXTS$
|
|
9585
|
-
if (PY_EXTS.has(ext)) return "py";
|
|
9629
|
+
if (JS_EXTS$3.has(ext)) return "js";
|
|
9630
|
+
if (PY_EXTS$1.has(ext)) return "py";
|
|
9586
9631
|
if (RB_EXTS.has(ext)) return "rb";
|
|
9587
9632
|
if (PHP_EXTS.has(ext)) return "php";
|
|
9588
9633
|
if (CSHARP_EXTS.has(ext)) return "csharp";
|
|
@@ -9852,7 +9897,7 @@ const areLiteralMultisetsEqual = (a, b) => {
|
|
|
9852
9897
|
};
|
|
9853
9898
|
//#endregion
|
|
9854
9899
|
//#region src/engines/ai-slop/abstractions.ts
|
|
9855
|
-
const JS_EXTS$
|
|
9900
|
+
const JS_EXTS$2 = /* @__PURE__ */ new Set([
|
|
9856
9901
|
".ts",
|
|
9857
9902
|
".tsx",
|
|
9858
9903
|
".js",
|
|
@@ -9863,11 +9908,11 @@ const JS_EXTS$1 = /* @__PURE__ */ new Set([
|
|
|
9863
9908
|
const THIN_WRAPPER_PATTERNS = [
|
|
9864
9909
|
{
|
|
9865
9910
|
pattern: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*(?::\s*\w[^{]*)?\{\s*\n?\s*return\s+\w+\([^)]*\);\s*\n?\s*\}/g,
|
|
9866
|
-
extensions: JS_EXTS$
|
|
9911
|
+
extensions: JS_EXTS$2
|
|
9867
9912
|
},
|
|
9868
9913
|
{
|
|
9869
9914
|
pattern: /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w[^=]*)?\s*=>\s*\w+\([^)]*\);/g,
|
|
9870
|
-
extensions: JS_EXTS$
|
|
9915
|
+
extensions: JS_EXTS$2
|
|
9871
9916
|
},
|
|
9872
9917
|
{
|
|
9873
9918
|
pattern: /def\s+(\w+)\s*\([^)]*\)(?:\s*->[^:]*)?:\s*\n\s+return\s+\w+\([^)]*\)\s*$/gm,
|
|
@@ -11115,7 +11160,7 @@ const scriptKindFor$1 = (ext) => {
|
|
|
11115
11160
|
}
|
|
11116
11161
|
};
|
|
11117
11162
|
const lineFor$1 = (sourceFile, node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
11118
|
-
const makeDiagnostic = (filePath, line, rule, message, help) => ({
|
|
11163
|
+
const makeDiagnostic$1 = (filePath, line, rule, message, help) => ({
|
|
11119
11164
|
filePath,
|
|
11120
11165
|
engine: "ai-slop",
|
|
11121
11166
|
rule,
|
|
@@ -11165,7 +11210,7 @@ const detectRedundantTryCatch = (sourceFile, relativePath) => {
|
|
|
11165
11210
|
if (ts.isTryStatement(node) && node.catchClause && !node.finallyBlock) {
|
|
11166
11211
|
const catchNameNode = node.catchClause.variableDeclaration?.name;
|
|
11167
11212
|
const [onlyStatement] = node.catchClause.block.statements;
|
|
11168
|
-
if (catchNameNode && ts.isIdentifier(catchNameNode) && node.catchClause.block.statements.length === 1 && onlyStatement && (isRethrowStatement(onlyStatement, catchNameNode.text) || isPromiseRejectRethrow(onlyStatement, catchNameNode.text))) diagnostics.push(makeDiagnostic(relativePath, lineFor$1(sourceFile, node.catchClause), "ai-slop/redundant-try-catch", "Catch block only rethrows the same error", "Remove the try/catch or add useful context, cleanup, or recovery. Rethrowing unchanged errors is usually defensive agent noise."));
|
|
11213
|
+
if (catchNameNode && ts.isIdentifier(catchNameNode) && node.catchClause.block.statements.length === 1 && onlyStatement && (isRethrowStatement(onlyStatement, catchNameNode.text) || isPromiseRejectRethrow(onlyStatement, catchNameNode.text))) diagnostics.push(makeDiagnostic$1(relativePath, lineFor$1(sourceFile, node.catchClause), "ai-slop/redundant-try-catch", "Catch block only rethrows the same error", "Remove the try/catch or add useful context, cleanup, or recovery. Rethrowing unchanged errors is usually defensive agent noise."));
|
|
11169
11214
|
}
|
|
11170
11215
|
ts.forEachChild(node, visit);
|
|
11171
11216
|
};
|
|
@@ -11183,7 +11228,7 @@ const detectPrimitiveCoercions = (sourceFile, relativePath) => {
|
|
|
11183
11228
|
const [arg] = child.arguments;
|
|
11184
11229
|
if (arg && ts.isIdentifier(arg)) {
|
|
11185
11230
|
const primitive = params.get(arg.text);
|
|
11186
|
-
if (primitive && child.expression.text === COERCION_CTORS[primitive]) diagnostics.push(makeDiagnostic(relativePath, lineFor$1(sourceFile, child), "ai-slop/redundant-type-coercion", `Parameter '${arg.text}' is already typed as ${primitive} but is coerced again`, "Trust the typed boundary or validate unknown input before this function. Re-coercing already typed parameters is usually defensive agent noise."));
|
|
11231
|
+
if (primitive && child.expression.text === COERCION_CTORS[primitive]) diagnostics.push(makeDiagnostic$1(relativePath, lineFor$1(sourceFile, child), "ai-slop/redundant-type-coercion", `Parameter '${arg.text}' is already typed as ${primitive} but is coerced again`, "Trust the typed boundary or validate unknown input before this function. Re-coercing already typed parameters is usually defensive agent noise."));
|
|
11187
11232
|
}
|
|
11188
11233
|
}
|
|
11189
11234
|
ts.forEachChild(child, visitBody);
|
|
@@ -11226,7 +11271,7 @@ const detectDuplicateExportedTypes = (parsedSources) => {
|
|
|
11226
11271
|
continue;
|
|
11227
11272
|
}
|
|
11228
11273
|
if (previous.filePath === declaration.filePath) continue;
|
|
11229
|
-
diagnostics.push(makeDiagnostic(declaration.filePath, declaration.line, "ai-slop/duplicate-type-declaration", `Exported type '${declaration.name}' duplicates an existing declaration`, `Reuse or import the existing type from ${previous.filePath} instead of re-declaring the same shape in another file.`));
|
|
11274
|
+
diagnostics.push(makeDiagnostic$1(declaration.filePath, declaration.line, "ai-slop/duplicate-type-declaration", `Exported type '${declaration.name}' duplicates an existing declaration`, `Reuse or import the existing type from ${previous.filePath} instead of re-declaring the same shape in another file.`));
|
|
11230
11275
|
}
|
|
11231
11276
|
}
|
|
11232
11277
|
return diagnostics;
|
|
@@ -11328,7 +11373,7 @@ const detectDuplicateImports = async (context) => {
|
|
|
11328
11373
|
rule: "ai-slop/duplicate-import",
|
|
11329
11374
|
severity: "warning",
|
|
11330
11375
|
message: `"${spec}" is also imported on line ${firstLine}. Merge into a single import statement.`,
|
|
11331
|
-
help: "Two imports from the same module split readers' attention and grow the import block. Run
|
|
11376
|
+
help: "Two imports from the same module split readers' attention and grow the import block. Run raigal fix to merge them automatically.",
|
|
11332
11377
|
line: dup.line,
|
|
11333
11378
|
column: 1,
|
|
11334
11379
|
category: "AI Slop",
|
|
@@ -15242,7 +15287,7 @@ const detectRustPatterns = async (context) => {
|
|
|
15242
15287
|
};
|
|
15243
15288
|
//#endregion
|
|
15244
15289
|
//#region src/engines/ai-slop/silent-recovery.ts
|
|
15245
|
-
const JS_EXTS = /* @__PURE__ */ new Set([
|
|
15290
|
+
const JS_EXTS$1 = /* @__PURE__ */ new Set([
|
|
15246
15291
|
".ts",
|
|
15247
15292
|
".tsx",
|
|
15248
15293
|
".js",
|
|
@@ -15437,7 +15482,7 @@ const detectSilentRecovery = async (context) => {
|
|
|
15437
15482
|
for (const filePath of files) {
|
|
15438
15483
|
if (isAutoGenerated(filePath)) continue;
|
|
15439
15484
|
const ext = path.extname(filePath);
|
|
15440
|
-
const isJs = JS_EXTS.has(ext);
|
|
15485
|
+
const isJs = JS_EXTS$1.has(ext);
|
|
15441
15486
|
if (!isJs && !(ext === ".py")) continue;
|
|
15442
15487
|
const relPath = relativePosix(context.rootDirectory, filePath);
|
|
15443
15488
|
if (isNonProductionPath(relPath)) continue;
|
|
@@ -15722,7 +15767,7 @@ const detectUnusedCss = async (context) => {
|
|
|
15722
15767
|
rule: "ai-slop/unused-css",
|
|
15723
15768
|
severity: "warning",
|
|
15724
15769
|
message: `CSS class '.${def.name}' is defined but never referenced in the codebase`,
|
|
15725
|
-
help: "Remove the dead CSS rule, or confirm the class is used dynamically (and add
|
|
15770
|
+
help: "Remove the dead CSS rule, or confirm the class is used dynamically (and add a raigal-ignore comment if so)",
|
|
15726
15771
|
line: def.line,
|
|
15727
15772
|
column: 0,
|
|
15728
15773
|
category: "AI Slop",
|
|
@@ -16813,8 +16858,8 @@ const isTrivialLine = (line) => {
|
|
|
16813
16858
|
};
|
|
16814
16859
|
const SVG_MARKUP_RE = /<\/?(?:svg|path|polyline|line|circle|rect|g)\b|(?:xmlns|viewBox|stroke(?:-width|-linecap|-linejoin)?|fill|fill-opacity|d|points|x1|x2|y1|y2)=/;
|
|
16815
16860
|
const DATA_LITERAL_RE = /^\s*(?:[A-Za-z_$][\w$-]*:\s*(?:["'`[{]|\d|true\b|false\b|null\b)|["'`][^"'`]*["'`],?\s*$|[{}\]],?\s*$|\),?\s*$)/;
|
|
16816
|
-
const SUPPRESS_RE = /aislop[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
16817
|
-
const FILE_SUPPRESS_RE = /aislop[- ]ignore-file\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
16861
|
+
const SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
16862
|
+
const FILE_SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore-file\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
16818
16863
|
const fileHasSuppression = (content) => FILE_SUPPRESS_RE.test(content);
|
|
16819
16864
|
const isLowSignalMarkupWindow = (lines) => {
|
|
16820
16865
|
return lines.filter((line) => SVG_MARKUP_RE.test(line)).length >= Math.ceil(WINDOW_SIZE / 2);
|
|
@@ -16956,8 +17001,8 @@ const shouldIncludeIssue = (issueType, filePath) => {
|
|
|
16956
17001
|
return !filePath.replace(/\\/g, "/").includes(".github/workflows/");
|
|
16957
17002
|
};
|
|
16958
17003
|
const DEPENDENCY_HELP = {
|
|
16959
|
-
dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `
|
|
16960
|
-
devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `
|
|
17004
|
+
dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
|
|
17005
|
+
devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
|
|
16961
17006
|
unlisted: "This package is imported in code but not declared in package.json. Run `npm install` to add it.",
|
|
16962
17007
|
unresolved: "This import cannot be resolved. Check for typos or missing packages.",
|
|
16963
17008
|
binaries: "This binary is used but its package is not in package.json."
|
|
@@ -17627,7 +17672,7 @@ const fixClangFormat = async (context) => {
|
|
|
17627
17672
|
};
|
|
17628
17673
|
//#endregion
|
|
17629
17674
|
//#region src/engines/format/dotnet-format.ts
|
|
17630
|
-
const REPORT_FILENAME = ".
|
|
17675
|
+
const REPORT_FILENAME = ".raigal-dotnet-format.json";
|
|
17631
17676
|
const parseDotnetFormatReport = (json, rootDirectory) => {
|
|
17632
17677
|
if (!json) return [];
|
|
17633
17678
|
let report;
|
|
@@ -17916,7 +17961,7 @@ const runGofmt = async (context) => {
|
|
|
17916
17961
|
rule: "go-formatting",
|
|
17917
17962
|
severity: "warning",
|
|
17918
17963
|
message: "Go file is not formatted correctly",
|
|
17919
|
-
help: "Run `
|
|
17964
|
+
help: "Run `raigal fix` to auto-format with gofmt",
|
|
17920
17965
|
line: 0,
|
|
17921
17966
|
column: 0,
|
|
17922
17967
|
category: "Format",
|
|
@@ -17944,7 +17989,8 @@ const resolvePackageRoot = (startFile) => {
|
|
|
17944
17989
|
while (true) {
|
|
17945
17990
|
const packageJsonPath = path.join(current, "package.json");
|
|
17946
17991
|
if (fs.existsSync(packageJsonPath)) try {
|
|
17947
|
-
|
|
17992
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
17993
|
+
if (packageJson.name === "aislop" || packageJson.name === "@methiu/raigal" || packageJson.name === "raigal") return current;
|
|
17948
17994
|
} catch {}
|
|
17949
17995
|
const parent = path.dirname(current);
|
|
17950
17996
|
if (parent === current) break;
|
|
@@ -18055,7 +18101,7 @@ const parseRuffFormatOutput = (output, rootDir) => {
|
|
|
18055
18101
|
rule: "python-formatting",
|
|
18056
18102
|
severity: "warning",
|
|
18057
18103
|
message: "Python file is not formatted correctly",
|
|
18058
|
-
help: "Run `
|
|
18104
|
+
help: "Run `raigal fix` to auto-format with ruff",
|
|
18059
18105
|
line: 0,
|
|
18060
18106
|
column: 0,
|
|
18061
18107
|
category: "Format",
|
|
@@ -18507,7 +18553,7 @@ const runExpoDoctor = async (context) => {
|
|
|
18507
18553
|
rule: "expo-doctor/config-error",
|
|
18508
18554
|
severity: "warning",
|
|
18509
18555
|
message: configError,
|
|
18510
|
-
help: "Install project dependencies, then re-run `
|
|
18556
|
+
help: "Install project dependencies, then re-run `raigal scan`.",
|
|
18511
18557
|
line: 0,
|
|
18512
18558
|
column: 0,
|
|
18513
18559
|
category: "Expo",
|
|
@@ -19980,6 +20026,174 @@ const lintEngine = {
|
|
|
19980
20026
|
}
|
|
19981
20027
|
};
|
|
19982
20028
|
//#endregion
|
|
20029
|
+
//#region src/engines/security/ai-security.ts
|
|
20030
|
+
const JS_EXTS = /* @__PURE__ */ new Set([
|
|
20031
|
+
".ts",
|
|
20032
|
+
".tsx",
|
|
20033
|
+
".js",
|
|
20034
|
+
".jsx",
|
|
20035
|
+
".mjs",
|
|
20036
|
+
".cjs"
|
|
20037
|
+
]);
|
|
20038
|
+
const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
|
|
20039
|
+
const UNICODE_TAG_RE = /[\u{E0000}-\u{E007F}]/gu;
|
|
20040
|
+
const hasRuleIgnore = (lines, lineIndex, ruleSuffix) => {
|
|
20041
|
+
const start = Math.max(0, lineIndex - 2);
|
|
20042
|
+
const re = new RegExp(`(?:raigal|aislop)[- ]ignore(?:-file)?\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
|
|
20043
|
+
return lines.slice(start, lineIndex + 1).some((line) => re.test(line));
|
|
20044
|
+
};
|
|
20045
|
+
const hasFileIgnore = (lines, ruleSuffix) => {
|
|
20046
|
+
const re = new RegExp(`(?:raigal|aislop)[- ]ignore-file\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
|
|
20047
|
+
return lines.slice(0, 10).some((line) => re.test(line));
|
|
20048
|
+
};
|
|
20049
|
+
const makeDiagnostic = (filePath, rule, message, help, line, column) => ({
|
|
20050
|
+
filePath,
|
|
20051
|
+
engine: "security",
|
|
20052
|
+
rule,
|
|
20053
|
+
severity: "error",
|
|
20054
|
+
message,
|
|
20055
|
+
help,
|
|
20056
|
+
line,
|
|
20057
|
+
column,
|
|
20058
|
+
category: "Security",
|
|
20059
|
+
fixable: false
|
|
20060
|
+
});
|
|
20061
|
+
const P_BYPASS = "bypassPermissions";
|
|
20062
|
+
const P_EXEC_TYPE = "executor_type";
|
|
20063
|
+
const P_LOCAL = "local";
|
|
20064
|
+
const P_USE_DOCKER = "use_docker";
|
|
20065
|
+
const P_AUTO_RUN = "auto_run";
|
|
20066
|
+
const P_UNSAFE_MODE = "unsafe_mode";
|
|
20067
|
+
const P_PYTHON_REPL = "PythonREPLTool";
|
|
20068
|
+
const UNSANDBOXED_TOOL_PATTERNS = [
|
|
20069
|
+
{
|
|
20070
|
+
pattern: new RegExp(`\\bpermission_?mode["']?\\s*[:=]\\s*["']${P_BYPASS}["']`, "i"),
|
|
20071
|
+
message: `permission_mode '${P_BYPASS}' bypasses agent sandboxing and human confirmation`,
|
|
20072
|
+
help: `Do not use ${P_BYPASS} mode in production; use standard interactive approval`
|
|
20073
|
+
},
|
|
20074
|
+
{
|
|
20075
|
+
pattern: new RegExp(`\\b${P_EXEC_TYPE}["']?\\s*[:=]\\s*["']${P_LOCAL}["']`, "i"),
|
|
20076
|
+
message: `${P_EXEC_TYPE}='${P_LOCAL}' executes LLM-generated code directly on host machine without container isolation`,
|
|
20077
|
+
help: "Configure agent to use DockerCommandLineCodeExecutor or a containerized sandbox environment"
|
|
20078
|
+
},
|
|
20079
|
+
{
|
|
20080
|
+
pattern: new RegExp(`\\b(?:LocalCommandLineCodeExecutor|LocalCodeExecutor)\\s*\\(`),
|
|
20081
|
+
message: "Local code executor executes LLM-generated code on host machine without container isolation",
|
|
20082
|
+
help: "Use DockerCommandLineCodeExecutor or an isolated execution sandbox"
|
|
20083
|
+
},
|
|
20084
|
+
{
|
|
20085
|
+
pattern: new RegExp(`\\b${P_USE_DOCKER}\\s*[:=]\\s*(?:False|false)\\b|["']${P_USE_DOCKER}["']\\s*:\\s*(?:False|false)\\b`),
|
|
20086
|
+
message: `${P_USE_DOCKER}=False disables container sandboxing for agent code execution`,
|
|
20087
|
+
help: `Set ${P_USE_DOCKER}=True to isolate agent execution inside Docker containers`
|
|
20088
|
+
},
|
|
20089
|
+
{
|
|
20090
|
+
pattern: new RegExp(`\\b(?:interpreter\\.)?${P_AUTO_RUN}\\s*[:=]\\s*(?:True|true)\\b`),
|
|
20091
|
+
message: `${P_AUTO_RUN}=True allows autonomous shell execution without human confirmation`,
|
|
20092
|
+
help: `Keep ${P_AUTO_RUN}=False so dangerous commands require explicit human review`
|
|
20093
|
+
},
|
|
20094
|
+
{
|
|
20095
|
+
pattern: new RegExp(`\\bCodeInterpreterTool\\s*\\([^)]*${P_UNSAFE_MODE}\\s*=\\s*(?:True|true)\\b|\\b${P_UNSAFE_MODE}\\s*[:=]\\s*(?:True|true)\\b`),
|
|
20096
|
+
message: `${P_UNSAFE_MODE}=True allows code interpreter tool to execute without Docker isolation`,
|
|
20097
|
+
help: `Disable ${P_UNSAFE_MODE} and isolate code execution in containerized environments`
|
|
20098
|
+
},
|
|
20099
|
+
{
|
|
20100
|
+
pattern: new RegExp(`\\b(?:${P_PYTHON_REPL}|PythonAstREPLTool)\\s*\\(`),
|
|
20101
|
+
message: `${P_PYTHON_REPL} executes arbitrary Python code in host process without container isolation`,
|
|
20102
|
+
help: `Use containerized sandbox execution rather than unconfined ${P_PYTHON_REPL} in production`
|
|
20103
|
+
}
|
|
20104
|
+
];
|
|
20105
|
+
const P_SYS = "system";
|
|
20106
|
+
const P_DEV = "developer";
|
|
20107
|
+
const P_SM = "SystemMessage";
|
|
20108
|
+
const PROMPT_INJECTION_JS_PATTERNS = [
|
|
20109
|
+
new RegExp(`(?:role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`)`, "g"),
|
|
20110
|
+
new RegExp(`(?:content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`[^}{;]{0,120}?role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
|
|
20111
|
+
new RegExp(`(?:role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?content\\s*:\\s*(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_$]|[a-zA-Z_$][\\w$]*\\s*\\+\\s*["']))`, "g"),
|
|
20112
|
+
new RegExp(`(?:content\\s*:\\s*(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_$]|[a-zA-Z_$][\\w$]*\\s*\\+\\s*["'])[^}{;]{0,120}?role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
|
|
20113
|
+
new RegExp(`\\b(?:${P_SYS}|systemPrompt|system_prompt|system_instruction)\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\``, "g"),
|
|
20114
|
+
new RegExp(`\\b(?:${P_SYS}|systemPrompt|system_prompt|system_instruction)\\s*:\\s*(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_$]|[a-zA-Z_$][\\w$]*\\s*\\+\\s*["'])`, "g"),
|
|
20115
|
+
new RegExp(`\\b(?:new\\s+)?${P_SM}\\s*\\(\\s*(?:\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`|["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_$]|[a-zA-Z_$][\\w$]*\\s*\\+\\s*["']|\\{\\s*content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`)`, "g"),
|
|
20116
|
+
new RegExp(`\\[\\s*["']${P_SYS}["']\\s*,\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`\\s*\\]`, "g")
|
|
20117
|
+
];
|
|
20118
|
+
const PROMPT_INJECTION_PY_PATTERNS = [
|
|
20119
|
+
new RegExp(`(?:["']role["']\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?["']content["']\\s*:\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["']))`, "g"),
|
|
20120
|
+
new RegExp(`(?:["']content["']\\s*:\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])[^}{;]{0,120}?["']role["']\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
|
|
20121
|
+
new RegExp(`(?:["']role["']\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?["']content["']\\s*:\\s*(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_]|[a-zA-Z_]\\w*\\s*\\+\\s*["']|["'][^"'\\n]*["']\\.format\\())`, "g"),
|
|
20122
|
+
new RegExp(`(?:["']content["']\\s*:\\s*(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_]|[a-zA-Z_]\\w*\\s*\\+\\s*["']|["'][^"'\\n]*["']\\.format\\()[^}{;]{0,120}?["']role["']\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
|
|
20123
|
+
new RegExp(`\\b(?:${P_SYS}|system_prompt|system_message|system_instruction)\\s*=\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])`, "g"),
|
|
20124
|
+
new RegExp(`\\b(?:${P_SYS}|system_prompt|system_message|system_instruction)\\s*=\\s*(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_]|[a-zA-Z_]\\w*\\s*\\+\\s*["']|["'][^"'\\n]*["']\\.format\\()`, "g"),
|
|
20125
|
+
new RegExp(`\\b${P_SM}\\s*\\(\\s*(?:content\\s*=\\s*)?f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])`, "g"),
|
|
20126
|
+
new RegExp(`\\b${P_SM}\\s*\\(\\s*(?:content\\s*=\\s*)?(?:["'][^"'\\n]*["']\\s*\\+\\s*[a-zA-Z_]|[a-zA-Z_]\\w*\\s*\\+\\s*["']|["'][^"'\\n]*["']\\.format\\()`, "g"),
|
|
20127
|
+
new RegExp(`\\(\\s*["']${P_SYS}["']\\s*,\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])\\s*\\)`, "g")
|
|
20128
|
+
];
|
|
20129
|
+
const checkInvisibleUnicode = (relPath, content, lines) => {
|
|
20130
|
+
if (hasFileIgnore(lines, "invisible-unicode-tags")) return [];
|
|
20131
|
+
const results = [];
|
|
20132
|
+
const tagRegex = new RegExp(UNICODE_TAG_RE.source, UNICODE_TAG_RE.flags);
|
|
20133
|
+
for (const match of content.matchAll(tagRegex)) {
|
|
20134
|
+
const matchIndex = match.index ?? 0;
|
|
20135
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
20136
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
20137
|
+
if (hasRuleIgnore(lines, line - 1, "invisible-unicode-tags")) continue;
|
|
20138
|
+
results.push(makeDiagnostic(relPath, "security/invisible-unicode-tags", "Invisible Unicode Tag character (U+E0000-U+E007F) detected: potential stealth prompt injection or ASCII smuggling", "Remove invisible Unicode tag characters. Unicode Tag block (U+E0000-U+E007F) has no legitimate printable presentation in source code", line, col));
|
|
20139
|
+
}
|
|
20140
|
+
return results;
|
|
20141
|
+
};
|
|
20142
|
+
const checkUnsandboxedTools = (relPath, content, lines) => {
|
|
20143
|
+
if (hasFileIgnore(lines, "unsandboxed-agent-tool")) return [];
|
|
20144
|
+
const results = [];
|
|
20145
|
+
for (const { pattern, message, help } of UNSANDBOXED_TOOL_PATTERNS) {
|
|
20146
|
+
const regex = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
20147
|
+
for (const match of content.matchAll(regex)) {
|
|
20148
|
+
const matchIndex = match.index ?? 0;
|
|
20149
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
20150
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
20151
|
+
if (hasRuleIgnore(lines, line - 1, "unsandboxed-agent-tool")) continue;
|
|
20152
|
+
results.push(makeDiagnostic(relPath, "security/unsandboxed-agent-tool", message, help, line, col));
|
|
20153
|
+
}
|
|
20154
|
+
}
|
|
20155
|
+
return results;
|
|
20156
|
+
};
|
|
20157
|
+
const checkPromptInjection = (relPath, content, lines, isJs) => {
|
|
20158
|
+
if (hasFileIgnore(lines, "prompt-injection")) return [];
|
|
20159
|
+
const results = [];
|
|
20160
|
+
const patterns = isJs ? PROMPT_INJECTION_JS_PATTERNS : PROMPT_INJECTION_PY_PATTERNS;
|
|
20161
|
+
for (const pattern of patterns) {
|
|
20162
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
20163
|
+
for (const match of content.matchAll(regex)) {
|
|
20164
|
+
const matchIndex = match.index ?? 0;
|
|
20165
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
20166
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
20167
|
+
if (hasRuleIgnore(lines, line - 1, "prompt-injection")) continue;
|
|
20168
|
+
results.push(makeDiagnostic(relPath, "security/prompt-injection", "Untrusted variable interpolation into system prompt creates prompt injection vulnerability", "Keep system prompts static. Pass dynamic user inputs via user messages or input parameters with strict delimiters", line, col));
|
|
20169
|
+
}
|
|
20170
|
+
}
|
|
20171
|
+
return results;
|
|
20172
|
+
};
|
|
20173
|
+
const scanAiSecurity = async (context) => {
|
|
20174
|
+
const files = getSourceFiles(context);
|
|
20175
|
+
const diagnostics = [];
|
|
20176
|
+
for (const filePath of files) {
|
|
20177
|
+
const ext = path.extname(filePath);
|
|
20178
|
+
let content;
|
|
20179
|
+
try {
|
|
20180
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
20181
|
+
} catch {
|
|
20182
|
+
continue;
|
|
20183
|
+
}
|
|
20184
|
+
const relativePath = relativePosix(context.rootDirectory, filePath);
|
|
20185
|
+
const lines = content.split("\n");
|
|
20186
|
+
diagnostics.push(...checkInvisibleUnicode(relativePath, content, lines));
|
|
20187
|
+
const isJs = JS_EXTS.has(ext);
|
|
20188
|
+
const isPy = PY_EXTS.has(ext);
|
|
20189
|
+
if (isJs || isPy) {
|
|
20190
|
+
diagnostics.push(...checkUnsandboxedTools(relativePath, content, lines));
|
|
20191
|
+
diagnostics.push(...checkPromptInjection(relativePath, content, lines, isJs));
|
|
20192
|
+
}
|
|
20193
|
+
}
|
|
20194
|
+
return diagnostics;
|
|
20195
|
+
};
|
|
20196
|
+
//#endregion
|
|
19983
20197
|
//#region src/ui/invocation.ts
|
|
19984
20198
|
const detectInvocation = () => "raigal";
|
|
19985
20199
|
//#endregion
|
|
@@ -20362,7 +20576,7 @@ const parseJsAudit = (output, source, manifest) => {
|
|
|
20362
20576
|
rule: "security/dependency-audit-skipped",
|
|
20363
20577
|
severity: "info",
|
|
20364
20578
|
message: `Dependency audit skipped (${source}): lockfile is missing`,
|
|
20365
|
-
help: errorDetail ?? "Generate a lockfile, then re-run `
|
|
20579
|
+
help: errorDetail ?? "Generate a lockfile, then re-run `raigal scan` for dependency vulnerability checks.",
|
|
20366
20580
|
line: 0,
|
|
20367
20581
|
column: 0,
|
|
20368
20582
|
category: "Security",
|
|
@@ -20920,7 +21134,7 @@ const RISKY_PATTERNS = [
|
|
|
20920
21134
|
//#region src/engines/security/risky.ts
|
|
20921
21135
|
const hasDangerouslySetInnerHtmlIgnore = (lines, lineIndex) => {
|
|
20922
21136
|
const start = Math.max(0, lineIndex - 2);
|
|
20923
|
-
return lines.slice(start, lineIndex + 1).some((line) => /(?:biome-ignore|eslint-disable|aislop-ignore).*(?:noDangerouslySetInnerHtml|dangerouslySetInnerHTML|dangerously-set-innerhtml)/i.test(line));
|
|
21137
|
+
return lines.slice(start, lineIndex + 1).some((line) => /(?:biome-ignore|eslint-disable|raigal-ignore|aislop-ignore).*(?:noDangerouslySetInnerHtml|dangerouslySetInnerHTML|dangerously-set-innerhtml)/i.test(line));
|
|
20924
21138
|
};
|
|
20925
21139
|
const isStructuredDataScript = (content, matchIndex) => {
|
|
20926
21140
|
const before = content.slice(Math.max(0, matchIndex - 300), matchIndex);
|
|
@@ -21248,7 +21462,11 @@ const ALL_ENGINES = [
|
|
|
21248
21462
|
name: "security",
|
|
21249
21463
|
async run(context) {
|
|
21250
21464
|
const diagnostics = [];
|
|
21251
|
-
const promises = [
|
|
21465
|
+
const promises = [
|
|
21466
|
+
scanSecrets(context),
|
|
21467
|
+
detectRiskyConstructs(context),
|
|
21468
|
+
scanAiSecurity(context)
|
|
21469
|
+
];
|
|
21252
21470
|
if (context.config.security.audit) promises.push(runDependencyAudit(context));
|
|
21253
21471
|
const results = await Promise.allSettled(promises);
|
|
21254
21472
|
for (const result of results) if (result.status === "fulfilled") diagnostics.push(...result.value);
|
|
@@ -21439,6 +21657,9 @@ const RULE_SCORE_IMPACTS = {
|
|
|
21439
21657
|
"security/shell-injection": strict("Interpolated shell commands can become command execution."),
|
|
21440
21658
|
"security/unsafe-deserialization": strict("Deserializing untrusted data with a legacy .NET formatter can become remote code execution."),
|
|
21441
21659
|
"security/unsafe-c-call": strict("Unbounded C string and process calls can become buffer overflows or command execution."),
|
|
21660
|
+
"security/prompt-injection": strict("Dynamic user interpolation in system/developer prompts enables prompt injection attacks."),
|
|
21661
|
+
"security/unsandboxed-agent-tool": strict("Unsandboxed agent code execution or tool permission bypass allows arbitrary host commands."),
|
|
21662
|
+
"security/invisible-unicode-tags": strict("Invisible Unicode Tag characters (U+E0000-U+E007F) hide stealth prompt injection payloads."),
|
|
21442
21663
|
"security/dependency-audit-skipped": advisory("An unavailable audit is visibility loss, not evidence of a vulnerability."),
|
|
21443
21664
|
"dotnet/projects-skipped": advisory("Skipping unrestored C# projects is visibility loss, not evidence of a defect."),
|
|
21444
21665
|
"cppcheck/chunks-skipped": advisory("A skipped cppcheck chunk is visibility loss, not evidence that the skipped files have a defect."),
|
|
@@ -21900,7 +22121,7 @@ const atomicWrite = (targetPath, content) => {
|
|
|
21900
22121
|
const dir = path.dirname(targetPath);
|
|
21901
22122
|
fs.mkdirSync(dir, { recursive: true });
|
|
21902
22123
|
const rand = Math.random().toString(36).slice(2, 10);
|
|
21903
|
-
const tmp = path.join(dir, `.
|
|
22124
|
+
const tmp = path.join(dir, `.raigal-tmp-${process.pid}-${rand}`);
|
|
21904
22125
|
fs.writeFileSync(tmp, content, "utf-8");
|
|
21905
22126
|
try {
|
|
21906
22127
|
fs.renameSync(tmp, targetPath);
|
|
@@ -21944,18 +22165,34 @@ const hasExistingLiteralBackslashPath = (cwd, fingerprint) => {
|
|
|
21944
22165
|
}
|
|
21945
22166
|
};
|
|
21946
22167
|
const migrateMarkerlessFingerprint = (cwd, fingerprint) => hasExistingLiteralBackslashPath(cwd, fingerprint) ? fingerprint : normalizeLegacyFingerprint(fingerprint);
|
|
21947
|
-
const
|
|
21948
|
-
const
|
|
22168
|
+
const baselinePath = (cwd) => {
|
|
22169
|
+
const raigalCandidate = path.join(cwd, ".raigal", "baseline.json");
|
|
22170
|
+
if (fs.existsSync(raigalCandidate)) return raigalCandidate;
|
|
22171
|
+
const raigalDir = path.join(cwd, ".raigal");
|
|
22172
|
+
if (fs.existsSync(raigalDir)) return raigalCandidate;
|
|
22173
|
+
const legacyCandidate = path.join(cwd, ".aislop", "baseline.json");
|
|
22174
|
+
if (fs.existsSync(legacyCandidate)) return legacyCandidate;
|
|
22175
|
+
return raigalCandidate;
|
|
22176
|
+
};
|
|
22177
|
+
const sessionPath = (cwd) => {
|
|
22178
|
+
const raigalCandidate = path.join(cwd, ".raigal", "session.jsonl");
|
|
22179
|
+
if (fs.existsSync(raigalCandidate)) return raigalCandidate;
|
|
22180
|
+
const raigalDir = path.join(cwd, ".raigal");
|
|
22181
|
+
if (fs.existsSync(raigalDir)) return raigalCandidate;
|
|
22182
|
+
const legacyCandidate = path.join(cwd, ".aislop", "session.jsonl");
|
|
22183
|
+
if (fs.existsSync(legacyCandidate)) return legacyCandidate;
|
|
22184
|
+
return raigalCandidate;
|
|
22185
|
+
};
|
|
21949
22186
|
const readBaseline = (cwd) => {
|
|
21950
22187
|
const raw = readIfExists(baselinePath(cwd));
|
|
21951
22188
|
if (!raw) return null;
|
|
21952
22189
|
try {
|
|
21953
22190
|
const parsed = JSON.parse(raw);
|
|
21954
|
-
if (parsed.schema !== "aislop.baseline.v2" && parsed.schema !== "aislop.baseline.v1") return null;
|
|
22191
|
+
if (parsed.schema !== "aislop.baseline.v2" && parsed.schema !== "aislop.baseline.v1" && parsed.schema !== "raigal.baseline.v2" && parsed.schema !== "raigal.baseline.v1") return null;
|
|
21955
22192
|
const findingFingerprints = parsed.findingFingerprints ?? [];
|
|
21956
22193
|
const needsWindowsMigration = parsed.pathFormat !== "posix";
|
|
21957
22194
|
return {
|
|
21958
|
-
schema: "
|
|
22195
|
+
schema: "raigal.baseline.v2",
|
|
21959
22196
|
pathFormat: "posix",
|
|
21960
22197
|
updatedAt: parsed.updatedAt ?? "",
|
|
21961
22198
|
score: parsed.score ?? 0,
|
|
@@ -21969,7 +22206,7 @@ const readBaseline = (cwd) => {
|
|
|
21969
22206
|
}
|
|
21970
22207
|
};
|
|
21971
22208
|
const writeBaseline = (cwd, baseline) => {
|
|
21972
|
-
const target =
|
|
22209
|
+
const target = path.join(cwd, ".raigal", "baseline.json");
|
|
21973
22210
|
atomicWrite(target, `${JSON.stringify({
|
|
21974
22211
|
...baseline,
|
|
21975
22212
|
pathFormat: "posix"
|
|
@@ -21987,7 +22224,6 @@ const captureBaseline = async (cwd) => {
|
|
|
21987
22224
|
rootDirectory: project.rootDirectory,
|
|
21988
22225
|
languages: project.languages,
|
|
21989
22226
|
frameworks: project.frameworks,
|
|
21990
|
-
files: [],
|
|
21991
22227
|
installedTools: project.installedTools,
|
|
21992
22228
|
config: {
|
|
21993
22229
|
overrides: config.overrides,
|
|
@@ -22011,16 +22247,16 @@ const captureBaseline = async (cwd) => {
|
|
|
22011
22247
|
architecture: config.engines.architecture,
|
|
22012
22248
|
security: false
|
|
22013
22249
|
});
|
|
22014
|
-
const diagnostics = results.flatMap((r) => r.diagnostics);
|
|
22250
|
+
const diagnostics = applyRuleSeverities(results.flatMap((r) => r.diagnostics), config.overrides.length ? {} : config.rules);
|
|
22015
22251
|
const { score } = calculateScore(diagnostics, config.scoring.weights, config.scoring.thresholds, project.sourceFileCount, config.scoring.smoothing, config.scoring.maxPerRule);
|
|
22016
22252
|
const byEngine = {};
|
|
22017
22253
|
for (const r of results) {
|
|
22018
|
-
const { score: engineScore } = calculateScore(diagnostics.filter((d) => r.
|
|
22254
|
+
const { score: engineScore } = calculateScore(diagnostics.filter((d) => d.engine === r.engine), config.scoring.weights, config.scoring.thresholds, project.sourceFileCount, config.scoring.smoothing, config.scoring.maxPerRule);
|
|
22019
22255
|
byEngine[r.engine] = engineScore;
|
|
22020
22256
|
}
|
|
22021
22257
|
const findingFingerprints = diagnostics.filter((d) => d.severity === "error" || d.severity === "warning").map((d) => fingerprintDiagnostic(d, project.rootDirectory));
|
|
22022
22258
|
const baseline = {
|
|
22023
|
-
schema: "
|
|
22259
|
+
schema: "raigal.baseline.v2",
|
|
22024
22260
|
pathFormat: "posix",
|
|
22025
22261
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22026
22262
|
score,
|
|
@@ -22037,7 +22273,7 @@ const captureBaseline = async (cwd) => {
|
|
|
22037
22273
|
};
|
|
22038
22274
|
const appendSessionFiles = (cwd, files) => {
|
|
22039
22275
|
if (files.length === 0) return;
|
|
22040
|
-
const target =
|
|
22276
|
+
const target = sessionPath(cwd);
|
|
22041
22277
|
try {
|
|
22042
22278
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
22043
22279
|
const line = `${JSON.stringify({
|
|
@@ -22048,7 +22284,7 @@ const appendSessionFiles = (cwd, files) => {
|
|
|
22048
22284
|
} catch {}
|
|
22049
22285
|
};
|
|
22050
22286
|
const readSessionFiles = (cwd) => {
|
|
22051
|
-
const raw = readIfExists(
|
|
22287
|
+
const raw = readIfExists(sessionPath(cwd));
|
|
22052
22288
|
if (!raw) return [];
|
|
22053
22289
|
const files = /* @__PURE__ */ new Set();
|
|
22054
22290
|
for (const line of raw.split("\n")) {
|
|
@@ -22061,7 +22297,7 @@ const readSessionFiles = (cwd) => {
|
|
|
22061
22297
|
return Array.from(files);
|
|
22062
22298
|
};
|
|
22063
22299
|
const clearSessionFiles = (cwd) => {
|
|
22064
|
-
const target =
|
|
22300
|
+
const target = sessionPath(cwd);
|
|
22065
22301
|
try {
|
|
22066
22302
|
fs.unlinkSync(target);
|
|
22067
22303
|
} catch {}
|
|
@@ -22165,7 +22401,7 @@ const runClaudeFileChangedHook = async (deps = {}) => {
|
|
|
22165
22401
|
schema: "aislop.hook.v2",
|
|
22166
22402
|
event: "file_changed",
|
|
22167
22403
|
file: changed,
|
|
22168
|
-
message: `Watched file changed (${changed}).
|
|
22404
|
+
message: `Watched file changed (${changed}). raigal refreshed the baseline - score: ${result.score}.`,
|
|
22169
22405
|
baseline: {
|
|
22170
22406
|
score: result.score,
|
|
22171
22407
|
fileCount: result.fileCount
|
|
@@ -22438,8 +22674,8 @@ A PostToolUse hook runs \`raigal hook claude\` after every Edit, Write, or Multi
|
|
|
22438
22674
|
|
|
22439
22675
|
## Rules
|
|
22440
22676
|
|
|
22441
|
-
- \`.raigal/config.yaml\`
|
|
22442
|
-
- \`.raigal/rules.yaml\`
|
|
22677
|
+
- \`.raigal/config.yaml\` - thresholds and engine toggles. Treat as authoritative; don't edit without user consent.
|
|
22678
|
+
- \`.raigal/rules.yaml\` - project-specific architecture rules (may be absent). When a finding cites \`architecture/*\`, open this file and follow it.
|
|
22443
22679
|
- Custom rules can change between sessions. Trust what the scan returns, not a cached understanding of what the rules are.
|
|
22444
22680
|
|
|
22445
22681
|
## Principles
|
|
@@ -22573,7 +22809,7 @@ const uninstallAntigravity = (opts) => {
|
|
|
22573
22809
|
//#endregion
|
|
22574
22810
|
//#region src/hooks/io/json-patch.ts
|
|
22575
22811
|
const AISLOP_SENTINEL_KEY = "__aislop";
|
|
22576
|
-
const isAislopManaged = (x) => typeof x === "object" && x !== null && "__aislop" in x && x["__aislop"] != null;
|
|
22812
|
+
const isAislopManaged = (x) => typeof x === "object" && x !== null && ("__aislop" in x && x["__aislop"] != null || "__raigal" in x && x["__raigal"] != null);
|
|
22577
22813
|
const groupIsAislop = (group) => {
|
|
22578
22814
|
if (typeof group !== "object" || group === null) return false;
|
|
22579
22815
|
const hooks = group.hooks;
|
|
@@ -22628,14 +22864,14 @@ const resolveClaudePaths = (opts) => {
|
|
|
22628
22864
|
};
|
|
22629
22865
|
const buildHookGroup$1 = () => {
|
|
22630
22866
|
const hashBody = JSON.stringify({
|
|
22631
|
-
command: "
|
|
22867
|
+
command: "raigal hook claude",
|
|
22632
22868
|
matcher: "Edit|Write|MultiEdit"
|
|
22633
22869
|
});
|
|
22634
22870
|
return {
|
|
22635
22871
|
matcher: "Edit|Write|MultiEdit",
|
|
22636
22872
|
hooks: [{
|
|
22637
22873
|
type: "command",
|
|
22638
|
-
command: "
|
|
22874
|
+
command: "raigal hook claude",
|
|
22639
22875
|
[AISLOP_SENTINEL_KEY]: {
|
|
22640
22876
|
v: 1,
|
|
22641
22877
|
managed: true,
|
|
@@ -22645,12 +22881,12 @@ const buildHookGroup$1 = () => {
|
|
|
22645
22881
|
};
|
|
22646
22882
|
};
|
|
22647
22883
|
const buildStopHookGroup = () => {
|
|
22648
|
-
const hashBody = JSON.stringify({ command: "
|
|
22884
|
+
const hashBody = JSON.stringify({ command: "raigal hook claude --stop" });
|
|
22649
22885
|
return {
|
|
22650
22886
|
matcher: "",
|
|
22651
22887
|
hooks: [{
|
|
22652
22888
|
type: "command",
|
|
22653
|
-
command: "
|
|
22889
|
+
command: "raigal hook claude --stop",
|
|
22654
22890
|
[AISLOP_SENTINEL_KEY]: {
|
|
22655
22891
|
v: 1,
|
|
22656
22892
|
managed: true,
|
|
@@ -22662,14 +22898,14 @@ const buildStopHookGroup = () => {
|
|
|
22662
22898
|
const FILE_CHANGED_MATCHER = ".raigal/config.yml|.raigal/rules.yml|.aislop/config.yml|.aislop/rules.yml|package.json";
|
|
22663
22899
|
const buildFileChangedHookGroup = () => {
|
|
22664
22900
|
const hashBody = JSON.stringify({
|
|
22665
|
-
command: "
|
|
22901
|
+
command: "raigal hook claude --on-file-changed",
|
|
22666
22902
|
matcher: FILE_CHANGED_MATCHER
|
|
22667
22903
|
});
|
|
22668
22904
|
return {
|
|
22669
22905
|
matcher: FILE_CHANGED_MATCHER,
|
|
22670
22906
|
hooks: [{
|
|
22671
22907
|
type: "command",
|
|
22672
|
-
command: "
|
|
22908
|
+
command: "raigal hook claude --on-file-changed",
|
|
22673
22909
|
[AISLOP_SENTINEL_KEY]: {
|
|
22674
22910
|
v: 1,
|
|
22675
22911
|
managed: true,
|
|
@@ -22812,11 +23048,11 @@ const resolveCursorPaths = (opts) => {
|
|
|
22812
23048
|
};
|
|
22813
23049
|
const buildHookEntry = () => {
|
|
22814
23050
|
const hashBody = JSON.stringify({
|
|
22815
|
-
command: "
|
|
23051
|
+
command: "raigal hook cursor",
|
|
22816
23052
|
timeout: 5e3
|
|
22817
23053
|
});
|
|
22818
23054
|
return {
|
|
22819
|
-
command: "
|
|
23055
|
+
command: "raigal hook cursor",
|
|
22820
23056
|
type: "command",
|
|
22821
23057
|
timeout: 5e3,
|
|
22822
23058
|
[AISLOP_SENTINEL_KEY]: {
|
|
@@ -22883,15 +23119,15 @@ const resolveGeminiPaths = (opts) => {
|
|
|
22883
23119
|
};
|
|
22884
23120
|
const buildHookGroup = () => {
|
|
22885
23121
|
const hashBody = JSON.stringify({
|
|
22886
|
-
command: "
|
|
23122
|
+
command: "raigal hook gemini",
|
|
22887
23123
|
matcher: "write_file|replace"
|
|
22888
23124
|
});
|
|
22889
23125
|
return {
|
|
22890
23126
|
matcher: "write_file|replace",
|
|
22891
23127
|
hooks: [{
|
|
22892
|
-
name: "
|
|
23128
|
+
name: "raigal",
|
|
22893
23129
|
type: "command",
|
|
22894
|
-
command: "
|
|
23130
|
+
command: "raigal hook gemini",
|
|
22895
23131
|
timeout: 5e3,
|
|
22896
23132
|
[AISLOP_SENTINEL_KEY]: {
|
|
22897
23133
|
v: 1,
|
|
@@ -22982,8 +23218,8 @@ const uninstallKilocode = (opts) => {
|
|
|
22982
23218
|
const resolvePiPaths = (opts) => {
|
|
22983
23219
|
return { extension: opts.scope === "project" ? path.join(opts.cwd, ".pi", "extensions", "aislop.js") : path.join(opts.home, ".pi", "agent", "extensions", "aislop.js") };
|
|
22984
23220
|
};
|
|
22985
|
-
const PI_EXTENSION_SOURCE = `//
|
|
22986
|
-
// Reinstall with:
|
|
23221
|
+
const PI_EXTENSION_SOURCE = `// raigal — auto-generated pi extension. Do not edit by hand.
|
|
23222
|
+
// Reinstall with: raigal hook install --pi
|
|
22987
23223
|
import { spawn } from "node:child_process";
|
|
22988
23224
|
|
|
22989
23225
|
// Runs the CLI async so a slow scan never blocks pi's event loop, and forwards
|
|
@@ -23028,7 +23264,7 @@ export default function (pi) {
|
|
|
23028
23264
|
const filePath = event.input && event.input.path;
|
|
23029
23265
|
if (typeof filePath !== "string" || filePath.length === 0) return;
|
|
23030
23266
|
|
|
23031
|
-
const bin = process.env.AISLOP_BIN || "
|
|
23267
|
+
const bin = process.env.RAIGAL_BIN || process.env.AISLOP_BIN || "raigal";
|
|
23032
23268
|
const payload = JSON.stringify({
|
|
23033
23269
|
cwd: ctx.cwd,
|
|
23034
23270
|
file_path: filePath,
|
|
@@ -23055,7 +23291,7 @@ export default function (pi) {
|
|
|
23055
23291
|
const installPi = (opts) => {
|
|
23056
23292
|
const paths = resolvePiPaths(opts);
|
|
23057
23293
|
const result = emptyResult();
|
|
23058
|
-
applyContent(result, opts, paths.extension, PI_EXTENSION_SOURCE, "write pi
|
|
23294
|
+
applyContent(result, opts, paths.extension, PI_EXTENSION_SOURCE, "write pi raigal extension");
|
|
23059
23295
|
return result;
|
|
23060
23296
|
};
|
|
23061
23297
|
const uninstallPi = (opts) => {
|
|
@@ -23924,7 +24160,7 @@ const registerHookAliases = (program) => {
|
|
|
23924
24160
|
};
|
|
23925
24161
|
//#endregion
|
|
23926
24162
|
//#region src/utils/github-slug.ts
|
|
23927
|
-
const GITHUB_REMOTE_RE = /^(?:git@github\.com
|
|
24163
|
+
const GITHUB_REMOTE_RE = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
|
|
23928
24164
|
const parseGithubSlug = (remoteUrl) => {
|
|
23929
24165
|
const match = remoteUrl.trim().match(GITHUB_REMOTE_RE);
|
|
23930
24166
|
if (!match) return null;
|
|
@@ -24122,6 +24358,9 @@ const RULE_LABELS = {
|
|
|
24122
24358
|
"security/shell-injection": "Possible shell injection",
|
|
24123
24359
|
"security/unsafe-deserialization": "Unsafe deserialization",
|
|
24124
24360
|
"security/unsafe-c-call": "Memory-unsafe C call",
|
|
24361
|
+
"security/prompt-injection": "Prompt injection (untrusted system prompt)",
|
|
24362
|
+
"security/unsandboxed-agent-tool": "Unsandboxed agent code execution tool",
|
|
24363
|
+
"security/invisible-unicode-tags": "Invisible Unicode tag character (stealth injection)",
|
|
24125
24364
|
"dotnet/projects-skipped": "C# projects skipped by build-backed lint",
|
|
24126
24365
|
"cppcheck/chunks-skipped": "C/C++ files skipped by cppcheck",
|
|
24127
24366
|
"eslint/no-undef": "Undefined identifier",
|
|
@@ -24210,6 +24449,9 @@ const RULE_DESCRIPTIONS = {
|
|
|
24210
24449
|
"security/shell-injection": "Shell command is built from unsanitized input.",
|
|
24211
24450
|
"security/unsafe-deserialization": "Legacy .NET formatter can deserialize arbitrary types (RCE).",
|
|
24212
24451
|
"security/unsafe-c-call": "Unbounded C string/exec function with buffer-overflow or shell risk.",
|
|
24452
|
+
"security/prompt-injection": "Untrusted dynamic variable interpolation into system or developer prompt.",
|
|
24453
|
+
"security/unsandboxed-agent-tool": "Agent executes code or commands on host without container or interactive gate.",
|
|
24454
|
+
"security/invisible-unicode-tags": "Invisible Unicode Tag block (U+E0000-U+E007F) used for prompt injection or ASCII smuggling.",
|
|
24213
24455
|
"dotnet/projects-skipped": "Build-backed C# lint only ran on restored projects; the rest were skipped.",
|
|
24214
24456
|
"cppcheck/chunks-skipped": "A cppcheck invocation timed out or failed on one or more chunks; their files were not analyzed.",
|
|
24215
24457
|
"oxlint/*": "JavaScript/TypeScript lint finding from oxlint.",
|
|
@@ -24404,7 +24646,8 @@ const BUILTIN_RULES = [
|
|
|
24404
24646
|
"typescript/*",
|
|
24405
24647
|
"dotnet/*",
|
|
24406
24648
|
"cppcheck/*",
|
|
24407
|
-
"clang-tidy/*"
|
|
24649
|
+
"clang-tidy/*",
|
|
24650
|
+
"expo-doctor/*"
|
|
24408
24651
|
]
|
|
24409
24652
|
},
|
|
24410
24653
|
{
|
|
@@ -24503,7 +24746,10 @@ const BUILTIN_RULES = [
|
|
|
24503
24746
|
"security/shell-injection",
|
|
24504
24747
|
"security/unsafe-deserialization",
|
|
24505
24748
|
"security/unsafe-c-call",
|
|
24506
|
-
"security/dependency-audit-skipped"
|
|
24749
|
+
"security/dependency-audit-skipped",
|
|
24750
|
+
"security/prompt-injection",
|
|
24751
|
+
"security/unsandboxed-agent-tool",
|
|
24752
|
+
"security/invisible-unicode-tags"
|
|
24507
24753
|
]
|
|
24508
24754
|
}
|
|
24509
24755
|
];
|
|
@@ -24570,6 +24816,8 @@ const resolveHistoryDir = (directory) => {
|
|
|
24570
24816
|
const resolved = path.resolve(directory);
|
|
24571
24817
|
const primary = path.join(resolved, CONFIG_DIR);
|
|
24572
24818
|
if (fs.existsSync(primary)) return primary;
|
|
24819
|
+
const legacy = path.join(resolved, ".aislop");
|
|
24820
|
+
if (fs.existsSync(legacy)) return legacy;
|
|
24573
24821
|
return null;
|
|
24574
24822
|
};
|
|
24575
24823
|
const historyPath = (directory) => {
|
|
@@ -24782,7 +25030,7 @@ const maybeNotifyUpdate = async (now = Date.now()) => {
|
|
|
24782
25030
|
if (!process.stderr.isTTY) return;
|
|
24783
25031
|
const cachePath = resolveUpdateCachePath();
|
|
24784
25032
|
const cache = readCache(cachePath);
|
|
24785
|
-
if (cache && isOutdated("0.2.
|
|
25033
|
+
if (cache && isOutdated("0.2.4", cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
|
|
24786
25034
|
if (!cache || now - cache.checkedAt > CHECK_INTERVAL_MS) {
|
|
24787
25035
|
const latest = await fetchLatestVersion$1();
|
|
24788
25036
|
if (latest) writeCache(cachePath, {
|
|
@@ -25302,7 +25550,7 @@ const renderCommandGroup = (group, commandWidth) => {
|
|
|
25302
25550
|
return lines;
|
|
25303
25551
|
};
|
|
25304
25552
|
const renderCommandReference = (input = {}) => {
|
|
25305
|
-
const version = input.version ?? "0.2.
|
|
25553
|
+
const version = input.version ?? "0.2.4";
|
|
25306
25554
|
const commandWidth = Math.max(...COMMAND_REFERENCE.map((item) => item.command.length));
|
|
25307
25555
|
const lines = [renderHeader({
|
|
25308
25556
|
version,
|
|
@@ -25525,7 +25773,7 @@ const renderHelpDetails = () => [
|
|
|
25525
25773
|
].map((line) => highlightRaigal(line, theme)).join("\n");
|
|
25526
25774
|
const renderHome = (input = {}) => {
|
|
25527
25775
|
let out = renderHeader({
|
|
25528
|
-
version: input.version ?? "0.2.
|
|
25776
|
+
version: input.version ?? "0.2.4",
|
|
25529
25777
|
command: "--bare",
|
|
25530
25778
|
context: []
|
|
25531
25779
|
});
|
|
@@ -25585,10 +25833,10 @@ const registerExtraCommands = (program) => {
|
|
|
25585
25833
|
return { exitCode: 0 };
|
|
25586
25834
|
});
|
|
25587
25835
|
});
|
|
25588
|
-
program.command("update").alias("upgrade").description("Check npm for the latest
|
|
25836
|
+
program.command("update").alias("upgrade").description("Check npm for the latest raigal version").action(async () => {
|
|
25589
25837
|
await updateCommand();
|
|
25590
25838
|
});
|
|
25591
|
-
program.command("version").description("Print the installed
|
|
25839
|
+
program.command("version").description("Print the installed raigal version").action(() => {
|
|
25592
25840
|
process.stdout.write(`${APP_VERSION}\n`);
|
|
25593
25841
|
});
|
|
25594
25842
|
program.command("commands").description("List all commands and major flags").action(() => {
|
|
@@ -27075,7 +27323,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
27075
27323
|
scoreFileCount
|
|
27076
27324
|
});
|
|
27077
27325
|
if (options.sarif) {
|
|
27078
|
-
const { buildSarifLog } = await import("./sarif
|
|
27326
|
+
const { buildSarifLog } = await import("./sarif-CdtsAfJ1.js");
|
|
27079
27327
|
console.log(JSON.stringify(buildSarifLog(results), null, 2));
|
|
27080
27328
|
return completion;
|
|
27081
27329
|
}
|
|
@@ -28571,7 +28819,7 @@ const detectUnusedDeclarations = async (context) => {
|
|
|
28571
28819
|
rule: "code-quality/unused-declaration",
|
|
28572
28820
|
severity: "warning",
|
|
28573
28821
|
message: `Unused ${extracted.kind}: ${extracted.name}`,
|
|
28574
|
-
help: "This top-level declaration is never used;
|
|
28822
|
+
help: "This top-level declaration is never used; raigal will remove it.",
|
|
28575
28823
|
line: d.line,
|
|
28576
28824
|
column: d.column,
|
|
28577
28825
|
category: "Dead Code",
|
|
@@ -28589,7 +28837,7 @@ const detectUnusedDeclarations = async (context) => {
|
|
|
28589
28837
|
rule: "code-quality/unused-declaration",
|
|
28590
28838
|
severity: "warning",
|
|
28591
28839
|
message: `Unused ${kind}: ${name}`,
|
|
28592
|
-
help: "This top-level declaration is never imported;
|
|
28840
|
+
help: "This top-level declaration is never imported; raigal will remove it.",
|
|
28593
28841
|
line: d.line,
|
|
28594
28842
|
column: d.column,
|
|
28595
28843
|
category: "Dead Code",
|
|
@@ -29713,7 +29961,7 @@ const promptForConfigChoices = async () => {
|
|
|
29713
29961
|
initialValues: DEFAULT_ENGINE_SELECTION,
|
|
29714
29962
|
required: false
|
|
29715
29963
|
}));
|
|
29716
|
-
if (enginesSelection === void 0) return null;
|
|
29964
|
+
if (enginesSelection === void 0 || isCancel(enginesSelection)) return null;
|
|
29717
29965
|
const failBelowRaw = await text({
|
|
29718
29966
|
message: "CI quality gate — fail the build when the score drops below (0-100)",
|
|
29719
29967
|
initialValue: "70",
|