@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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as APP_VERSION } from "./version-
|
|
2
|
-
import { i as ENGINE_INFO, n as withFindingAssessments, t as summarizeFindingAssessments } from "./finding-assessment-
|
|
1
|
+
import { t as APP_VERSION } from "./version-CukuIVX4.js";
|
|
2
|
+
import { i as ENGINE_INFO, n as withFindingAssessments, t as summarizeFindingAssessments } from "./finding-assessment-CGpt5YUR.js";
|
|
3
3
|
//#region src/output/json.ts
|
|
4
4
|
const buildJsonOutput = (results, scoreResult, fileCount, elapsedMs, coverage) => {
|
|
5
5
|
const allDiagnostics = results.flatMap((r) => r.diagnostics);
|
package/dist/mcp.js
CHANGED
|
@@ -84,7 +84,7 @@ const getActiveToken = () => {
|
|
|
84
84
|
};
|
|
85
85
|
//#endregion
|
|
86
86
|
//#region src/version.ts
|
|
87
|
-
const APP_VERSION = "0.2.
|
|
87
|
+
const APP_VERSION = "0.2.4";
|
|
88
88
|
//#endregion
|
|
89
89
|
//#region src/cloud/contract.ts
|
|
90
90
|
const Severity = z.enum(["error", "warning"]);
|
|
@@ -359,7 +359,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
|
|
|
359
359
|
const claims = parsed.data;
|
|
360
360
|
const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
|
|
361
361
|
if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
|
|
362
|
-
if (!isVersionAtLeast("0.2.
|
|
362
|
+
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");
|
|
363
363
|
const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
364
364
|
const grace = options.allowGraceSeconds ?? 0;
|
|
365
365
|
if (now > claims.exp + grace) {
|
|
@@ -532,7 +532,7 @@ const getEntitlement = async () => {
|
|
|
532
532
|
};
|
|
533
533
|
//#endregion
|
|
534
534
|
//#region src/cloud/owner.ts
|
|
535
|
-
const GITHUB_REMOTE_RE = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([
|
|
535
|
+
const GITHUB_REMOTE_RE = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
|
|
536
536
|
const parseGitRemote = (rawUrl) => {
|
|
537
537
|
if (!rawUrl) return {
|
|
538
538
|
owner: null,
|
|
@@ -994,6 +994,9 @@ const CiSchema = z.object({
|
|
|
994
994
|
format: z.enum(["json"]).default("json")
|
|
995
995
|
}).strict();
|
|
996
996
|
const TelemetrySchema = z.object({ enabled: z.boolean().default(true) }).strict();
|
|
997
|
+
const BaselineConfigSchema = z.object({ mode: z.enum(["strict", "relaxed"]).default("relaxed") }).strict();
|
|
998
|
+
const LicensingConfigSchema = z.object({ onLapse: z.enum(["fail-open", "fail-closed"]).default("fail-open") }).strict();
|
|
999
|
+
const WorkspaceConfigSchema = z.object({ minimumFloor: z.number().min(0).max(100).default(50) }).strict();
|
|
997
1000
|
const RuleSeverityOverride = z.enum([
|
|
998
1001
|
"error",
|
|
999
1002
|
"warning",
|
|
@@ -1064,6 +1067,9 @@ const AislopConfigSchema = z.object({
|
|
|
1064
1067
|
format: "json"
|
|
1065
1068
|
})),
|
|
1066
1069
|
telemetry: TelemetrySchema.default(() => ({ enabled: true })),
|
|
1070
|
+
baseline: BaselineConfigSchema.optional(),
|
|
1071
|
+
licensing: LicensingConfigSchema.optional(),
|
|
1072
|
+
workspace: WorkspaceConfigSchema.optional(),
|
|
1067
1073
|
rules: RulesSchema,
|
|
1068
1074
|
overrides: z.array(FileOverrideSchema).default(() => []),
|
|
1069
1075
|
exclude: z.array(z.string()).default(() => [
|
|
@@ -1097,6 +1103,9 @@ const KNOWN_KEYS_BY_PATH = {
|
|
|
1097
1103
|
"scoring",
|
|
1098
1104
|
"ci",
|
|
1099
1105
|
"telemetry",
|
|
1106
|
+
"baseline",
|
|
1107
|
+
"licensing",
|
|
1108
|
+
"workspace",
|
|
1100
1109
|
"rules",
|
|
1101
1110
|
"exclude",
|
|
1102
1111
|
"include"
|
|
@@ -1147,7 +1156,10 @@ const KNOWN_KEYS_BY_PATH = {
|
|
|
1147
1156
|
],
|
|
1148
1157
|
"scoring.thresholds": ["good", "ok"],
|
|
1149
1158
|
ci: ["failBelow", "format"],
|
|
1150
|
-
telemetry: ["enabled"]
|
|
1159
|
+
telemetry: ["enabled"],
|
|
1160
|
+
baseline: ["mode"],
|
|
1161
|
+
licensing: ["onLapse"],
|
|
1162
|
+
workspace: ["minimumFloor"]
|
|
1151
1163
|
};
|
|
1152
1164
|
const levenshtein = (a, b) => {
|
|
1153
1165
|
const m = a.length;
|
|
@@ -2567,7 +2579,7 @@ const tokenizeJs = (content, ext = ".ts") => {
|
|
|
2567
2579
|
};
|
|
2568
2580
|
//#endregion
|
|
2569
2581
|
//#region src/utils/source-masker.ts
|
|
2570
|
-
const JS_EXTS$
|
|
2582
|
+
const JS_EXTS$3 = /* @__PURE__ */ new Set([
|
|
2571
2583
|
".ts",
|
|
2572
2584
|
".tsx",
|
|
2573
2585
|
".js",
|
|
@@ -2575,7 +2587,7 @@ const JS_EXTS$2 = /* @__PURE__ */ new Set([
|
|
|
2575
2587
|
".mjs",
|
|
2576
2588
|
".cjs"
|
|
2577
2589
|
]);
|
|
2578
|
-
const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
|
|
2590
|
+
const PY_EXTS$1 = /* @__PURE__ */ new Set([".py"]);
|
|
2579
2591
|
const RB_EXTS = /* @__PURE__ */ new Set([".rb"]);
|
|
2580
2592
|
const PHP_EXTS = /* @__PURE__ */ new Set([".php"]);
|
|
2581
2593
|
const CSHARP_EXTS = /* @__PURE__ */ new Set([".cs"]);
|
|
@@ -2599,8 +2611,8 @@ const C_STYLE_COMMENT_EXTS = /* @__PURE__ */ new Set([
|
|
|
2599
2611
|
".zig"
|
|
2600
2612
|
]);
|
|
2601
2613
|
const familyForExt = (ext) => {
|
|
2602
|
-
if (JS_EXTS$
|
|
2603
|
-
if (PY_EXTS.has(ext)) return "py";
|
|
2614
|
+
if (JS_EXTS$3.has(ext)) return "js";
|
|
2615
|
+
if (PY_EXTS$1.has(ext)) return "py";
|
|
2604
2616
|
if (RB_EXTS.has(ext)) return "rb";
|
|
2605
2617
|
if (PHP_EXTS.has(ext)) return "php";
|
|
2606
2618
|
if (CSHARP_EXTS.has(ext)) return "csharp";
|
|
@@ -2832,7 +2844,7 @@ const getCommentLines = (content, ext) => {
|
|
|
2832
2844
|
};
|
|
2833
2845
|
//#endregion
|
|
2834
2846
|
//#region src/engines/ai-slop/abstractions.ts
|
|
2835
|
-
const JS_EXTS$
|
|
2847
|
+
const JS_EXTS$2 = /* @__PURE__ */ new Set([
|
|
2836
2848
|
".ts",
|
|
2837
2849
|
".tsx",
|
|
2838
2850
|
".js",
|
|
@@ -2843,11 +2855,11 @@ const JS_EXTS$1 = /* @__PURE__ */ new Set([
|
|
|
2843
2855
|
const THIN_WRAPPER_PATTERNS = [
|
|
2844
2856
|
{
|
|
2845
2857
|
pattern: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*(?::\s*\w[^{]*)?\{\s*\n?\s*return\s+\w+\([^)]*\);\s*\n?\s*\}/g,
|
|
2846
|
-
extensions: JS_EXTS$
|
|
2858
|
+
extensions: JS_EXTS$2
|
|
2847
2859
|
},
|
|
2848
2860
|
{
|
|
2849
2861
|
pattern: /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w[^=]*)?\s*=>\s*\w+\([^)]*\);/g,
|
|
2850
|
-
extensions: JS_EXTS$
|
|
2862
|
+
extensions: JS_EXTS$2
|
|
2851
2863
|
},
|
|
2852
2864
|
{
|
|
2853
2865
|
pattern: /def\s+(\w+)\s*\([^)]*\)(?:\s*->[^:]*)?:\s*\n\s+return\s+\w+\([^)]*\)\s*$/gm,
|
|
@@ -4095,7 +4107,7 @@ const scriptKindFor$1 = (ext) => {
|
|
|
4095
4107
|
}
|
|
4096
4108
|
};
|
|
4097
4109
|
const lineFor$1 = (sourceFile, node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
4098
|
-
const makeDiagnostic = (filePath, line, rule, message, help) => ({
|
|
4110
|
+
const makeDiagnostic$1 = (filePath, line, rule, message, help) => ({
|
|
4099
4111
|
filePath,
|
|
4100
4112
|
engine: "ai-slop",
|
|
4101
4113
|
rule,
|
|
@@ -4145,7 +4157,7 @@ const detectRedundantTryCatch = (sourceFile, relativePath) => {
|
|
|
4145
4157
|
if (ts.isTryStatement(node) && node.catchClause && !node.finallyBlock) {
|
|
4146
4158
|
const catchNameNode = node.catchClause.variableDeclaration?.name;
|
|
4147
4159
|
const [onlyStatement] = node.catchClause.block.statements;
|
|
4148
|
-
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."));
|
|
4160
|
+
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."));
|
|
4149
4161
|
}
|
|
4150
4162
|
ts.forEachChild(node, visit);
|
|
4151
4163
|
};
|
|
@@ -4163,7 +4175,7 @@ const detectPrimitiveCoercions = (sourceFile, relativePath) => {
|
|
|
4163
4175
|
const [arg] = child.arguments;
|
|
4164
4176
|
if (arg && ts.isIdentifier(arg)) {
|
|
4165
4177
|
const primitive = params.get(arg.text);
|
|
4166
|
-
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."));
|
|
4178
|
+
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."));
|
|
4167
4179
|
}
|
|
4168
4180
|
}
|
|
4169
4181
|
ts.forEachChild(child, visitBody);
|
|
@@ -4206,7 +4218,7 @@ const detectDuplicateExportedTypes = (parsedSources) => {
|
|
|
4206
4218
|
continue;
|
|
4207
4219
|
}
|
|
4208
4220
|
if (previous.filePath === declaration.filePath) continue;
|
|
4209
|
-
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.`));
|
|
4221
|
+
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.`));
|
|
4210
4222
|
}
|
|
4211
4223
|
}
|
|
4212
4224
|
return diagnostics;
|
|
@@ -4308,7 +4320,7 @@ const detectDuplicateImports = async (context) => {
|
|
|
4308
4320
|
rule: "ai-slop/duplicate-import",
|
|
4309
4321
|
severity: "warning",
|
|
4310
4322
|
message: `"${spec}" is also imported on line ${firstLine}. Merge into a single import statement.`,
|
|
4311
|
-
help: "Two imports from the same module split readers' attention and grow the import block. Run
|
|
4323
|
+
help: "Two imports from the same module split readers' attention and grow the import block. Run raigal fix to merge them automatically.",
|
|
4312
4324
|
line: dup.line,
|
|
4313
4325
|
column: 1,
|
|
4314
4326
|
category: "AI Slop",
|
|
@@ -8148,7 +8160,7 @@ const detectRustPatterns = async (context) => {
|
|
|
8148
8160
|
};
|
|
8149
8161
|
//#endregion
|
|
8150
8162
|
//#region src/engines/ai-slop/silent-recovery.ts
|
|
8151
|
-
const JS_EXTS = /* @__PURE__ */ new Set([
|
|
8163
|
+
const JS_EXTS$1 = /* @__PURE__ */ new Set([
|
|
8152
8164
|
".ts",
|
|
8153
8165
|
".tsx",
|
|
8154
8166
|
".js",
|
|
@@ -8343,7 +8355,7 @@ const detectSilentRecovery = async (context) => {
|
|
|
8343
8355
|
for (const filePath of files) {
|
|
8344
8356
|
if (isAutoGenerated(filePath)) continue;
|
|
8345
8357
|
const ext = path.extname(filePath);
|
|
8346
|
-
const isJs = JS_EXTS.has(ext);
|
|
8358
|
+
const isJs = JS_EXTS$1.has(ext);
|
|
8347
8359
|
if (!isJs && !(ext === ".py")) continue;
|
|
8348
8360
|
const relPath = relativePosix(context.rootDirectory, filePath);
|
|
8349
8361
|
if (isNonProductionPath(relPath)) continue;
|
|
@@ -8628,7 +8640,7 @@ const detectUnusedCss = async (context) => {
|
|
|
8628
8640
|
rule: "ai-slop/unused-css",
|
|
8629
8641
|
severity: "warning",
|
|
8630
8642
|
message: `CSS class '.${def.name}' is defined but never referenced in the codebase`,
|
|
8631
|
-
help: "Remove the dead CSS rule, or confirm the class is used dynamically (and add
|
|
8643
|
+
help: "Remove the dead CSS rule, or confirm the class is used dynamically (and add a raigal-ignore comment if so)",
|
|
8632
8644
|
line: def.line,
|
|
8633
8645
|
column: 0,
|
|
8634
8646
|
category: "AI Slop",
|
|
@@ -9718,8 +9730,8 @@ const isTrivialLine = (line) => {
|
|
|
9718
9730
|
};
|
|
9719
9731
|
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)=/;
|
|
9720
9732
|
const DATA_LITERAL_RE = /^\s*(?:[A-Za-z_$][\w$-]*:\s*(?:["'`[{]|\d|true\b|false\b|null\b)|["'`][^"'`]*["'`],?\s*$|[{}\]],?\s*$|\),?\s*$)/;
|
|
9721
|
-
const SUPPRESS_RE = /aislop[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
9722
|
-
const FILE_SUPPRESS_RE = /aislop[- ]ignore-file\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
9733
|
+
const SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
9734
|
+
const FILE_SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore-file\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
9723
9735
|
const fileHasSuppression = (content) => FILE_SUPPRESS_RE.test(content);
|
|
9724
9736
|
const isLowSignalMarkupWindow = (lines) => {
|
|
9725
9737
|
return lines.filter((line) => SVG_MARKUP_RE.test(line)).length >= Math.ceil(WINDOW_SIZE / 2);
|
|
@@ -10045,8 +10057,8 @@ const shouldIncludeIssue = (issueType, filePath) => {
|
|
|
10045
10057
|
return !filePath.replace(/\\/g, "/").includes(".github/workflows/");
|
|
10046
10058
|
};
|
|
10047
10059
|
const DEPENDENCY_HELP = {
|
|
10048
|
-
dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `
|
|
10049
|
-
devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `
|
|
10060
|
+
dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
|
|
10061
|
+
devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
|
|
10050
10062
|
unlisted: "This package is imported in code but not declared in package.json. Run `npm install` to add it.",
|
|
10051
10063
|
unresolved: "This import cannot be resolved. Check for typos or missing packages.",
|
|
10052
10064
|
binaries: "This binary is used but its package is not in package.json."
|
|
@@ -10630,7 +10642,7 @@ const runClangFormat = async (context) => {
|
|
|
10630
10642
|
};
|
|
10631
10643
|
//#endregion
|
|
10632
10644
|
//#region src/engines/format/dotnet-format.ts
|
|
10633
|
-
const REPORT_FILENAME = ".
|
|
10645
|
+
const REPORT_FILENAME = ".raigal-dotnet-format.json";
|
|
10634
10646
|
const parseDotnetFormatReport = (json, rootDirectory) => {
|
|
10635
10647
|
if (!json) return [];
|
|
10636
10648
|
let report;
|
|
@@ -10888,7 +10900,7 @@ const runGofmt = async (context) => {
|
|
|
10888
10900
|
rule: "go-formatting",
|
|
10889
10901
|
severity: "warning",
|
|
10890
10902
|
message: "Go file is not formatted correctly",
|
|
10891
|
-
help: "Run `
|
|
10903
|
+
help: "Run `raigal fix` to auto-format with gofmt",
|
|
10892
10904
|
line: 0,
|
|
10893
10905
|
column: 0,
|
|
10894
10906
|
category: "Format",
|
|
@@ -10907,7 +10919,8 @@ const resolvePackageRoot = (startFile) => {
|
|
|
10907
10919
|
while (true) {
|
|
10908
10920
|
const packageJsonPath = path.join(current, "package.json");
|
|
10909
10921
|
if (fs.existsSync(packageJsonPath)) try {
|
|
10910
|
-
|
|
10922
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
10923
|
+
if (packageJson.name === "aislop" || packageJson.name === "@methiu/raigal" || packageJson.name === "raigal") return current;
|
|
10911
10924
|
} catch {}
|
|
10912
10925
|
const parent = path.dirname(current);
|
|
10913
10926
|
if (parent === current) break;
|
|
@@ -11018,7 +11031,7 @@ const parseRuffFormatOutput = (output, rootDir) => {
|
|
|
11018
11031
|
rule: "python-formatting",
|
|
11019
11032
|
severity: "warning",
|
|
11020
11033
|
message: "Python file is not formatted correctly",
|
|
11021
|
-
help: "Run `
|
|
11034
|
+
help: "Run `raigal fix` to auto-format with ruff",
|
|
11022
11035
|
line: 0,
|
|
11023
11036
|
column: 0,
|
|
11024
11037
|
category: "Format",
|
|
@@ -11461,7 +11474,7 @@ const runExpoDoctor = async (context) => {
|
|
|
11461
11474
|
rule: "expo-doctor/config-error",
|
|
11462
11475
|
severity: "warning",
|
|
11463
11476
|
message: configError,
|
|
11464
|
-
help: "Install project dependencies, then re-run `
|
|
11477
|
+
help: "Install project dependencies, then re-run `raigal scan`.",
|
|
11465
11478
|
line: 0,
|
|
11466
11479
|
column: 0,
|
|
11467
11480
|
category: "Expo",
|
|
@@ -12568,6 +12581,174 @@ const lintEngine = {
|
|
|
12568
12581
|
}
|
|
12569
12582
|
};
|
|
12570
12583
|
//#endregion
|
|
12584
|
+
//#region src/engines/security/ai-security.ts
|
|
12585
|
+
const JS_EXTS = /* @__PURE__ */ new Set([
|
|
12586
|
+
".ts",
|
|
12587
|
+
".tsx",
|
|
12588
|
+
".js",
|
|
12589
|
+
".jsx",
|
|
12590
|
+
".mjs",
|
|
12591
|
+
".cjs"
|
|
12592
|
+
]);
|
|
12593
|
+
const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
|
|
12594
|
+
const UNICODE_TAG_RE = /[\u{E0000}-\u{E007F}]/gu;
|
|
12595
|
+
const hasRuleIgnore = (lines, lineIndex, ruleSuffix) => {
|
|
12596
|
+
const start = Math.max(0, lineIndex - 2);
|
|
12597
|
+
const re = new RegExp(`(?:raigal|aislop)[- ]ignore(?:-file)?\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
|
|
12598
|
+
return lines.slice(start, lineIndex + 1).some((line) => re.test(line));
|
|
12599
|
+
};
|
|
12600
|
+
const hasFileIgnore = (lines, ruleSuffix) => {
|
|
12601
|
+
const re = new RegExp(`(?:raigal|aislop)[- ]ignore-file\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
|
|
12602
|
+
return lines.slice(0, 10).some((line) => re.test(line));
|
|
12603
|
+
};
|
|
12604
|
+
const makeDiagnostic = (filePath, rule, message, help, line, column) => ({
|
|
12605
|
+
filePath,
|
|
12606
|
+
engine: "security",
|
|
12607
|
+
rule,
|
|
12608
|
+
severity: "error",
|
|
12609
|
+
message,
|
|
12610
|
+
help,
|
|
12611
|
+
line,
|
|
12612
|
+
column,
|
|
12613
|
+
category: "Security",
|
|
12614
|
+
fixable: false
|
|
12615
|
+
});
|
|
12616
|
+
const P_BYPASS = "bypassPermissions";
|
|
12617
|
+
const P_EXEC_TYPE = "executor_type";
|
|
12618
|
+
const P_LOCAL = "local";
|
|
12619
|
+
const P_USE_DOCKER = "use_docker";
|
|
12620
|
+
const P_AUTO_RUN = "auto_run";
|
|
12621
|
+
const P_UNSAFE_MODE = "unsafe_mode";
|
|
12622
|
+
const P_PYTHON_REPL = "PythonREPLTool";
|
|
12623
|
+
const UNSANDBOXED_TOOL_PATTERNS = [
|
|
12624
|
+
{
|
|
12625
|
+
pattern: new RegExp(`\\bpermission_?mode["']?\\s*[:=]\\s*["']${P_BYPASS}["']`, "i"),
|
|
12626
|
+
message: `permission_mode '${P_BYPASS}' bypasses agent sandboxing and human confirmation`,
|
|
12627
|
+
help: `Do not use ${P_BYPASS} mode in production; use standard interactive approval`
|
|
12628
|
+
},
|
|
12629
|
+
{
|
|
12630
|
+
pattern: new RegExp(`\\b${P_EXEC_TYPE}["']?\\s*[:=]\\s*["']${P_LOCAL}["']`, "i"),
|
|
12631
|
+
message: `${P_EXEC_TYPE}='${P_LOCAL}' executes LLM-generated code directly on host machine without container isolation`,
|
|
12632
|
+
help: "Configure agent to use DockerCommandLineCodeExecutor or a containerized sandbox environment"
|
|
12633
|
+
},
|
|
12634
|
+
{
|
|
12635
|
+
pattern: new RegExp(`\\b(?:LocalCommandLineCodeExecutor|LocalCodeExecutor)\\s*\\(`),
|
|
12636
|
+
message: "Local code executor executes LLM-generated code on host machine without container isolation",
|
|
12637
|
+
help: "Use DockerCommandLineCodeExecutor or an isolated execution sandbox"
|
|
12638
|
+
},
|
|
12639
|
+
{
|
|
12640
|
+
pattern: new RegExp(`\\b${P_USE_DOCKER}\\s*[:=]\\s*(?:False|false)\\b|["']${P_USE_DOCKER}["']\\s*:\\s*(?:False|false)\\b`),
|
|
12641
|
+
message: `${P_USE_DOCKER}=False disables container sandboxing for agent code execution`,
|
|
12642
|
+
help: `Set ${P_USE_DOCKER}=True to isolate agent execution inside Docker containers`
|
|
12643
|
+
},
|
|
12644
|
+
{
|
|
12645
|
+
pattern: new RegExp(`\\b(?:interpreter\\.)?${P_AUTO_RUN}\\s*[:=]\\s*(?:True|true)\\b`),
|
|
12646
|
+
message: `${P_AUTO_RUN}=True allows autonomous shell execution without human confirmation`,
|
|
12647
|
+
help: `Keep ${P_AUTO_RUN}=False so dangerous commands require explicit human review`
|
|
12648
|
+
},
|
|
12649
|
+
{
|
|
12650
|
+
pattern: new RegExp(`\\bCodeInterpreterTool\\s*\\([^)]*${P_UNSAFE_MODE}\\s*=\\s*(?:True|true)\\b|\\b${P_UNSAFE_MODE}\\s*[:=]\\s*(?:True|true)\\b`),
|
|
12651
|
+
message: `${P_UNSAFE_MODE}=True allows code interpreter tool to execute without Docker isolation`,
|
|
12652
|
+
help: `Disable ${P_UNSAFE_MODE} and isolate code execution in containerized environments`
|
|
12653
|
+
},
|
|
12654
|
+
{
|
|
12655
|
+
pattern: new RegExp(`\\b(?:${P_PYTHON_REPL}|PythonAstREPLTool)\\s*\\(`),
|
|
12656
|
+
message: `${P_PYTHON_REPL} executes arbitrary Python code in host process without container isolation`,
|
|
12657
|
+
help: `Use containerized sandbox execution rather than unconfined ${P_PYTHON_REPL} in production`
|
|
12658
|
+
}
|
|
12659
|
+
];
|
|
12660
|
+
const P_SYS = "system";
|
|
12661
|
+
const P_DEV = "developer";
|
|
12662
|
+
const P_SM = "SystemMessage";
|
|
12663
|
+
const PROMPT_INJECTION_JS_PATTERNS = [
|
|
12664
|
+
new RegExp(`(?:role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`)`, "g"),
|
|
12665
|
+
new RegExp(`(?:content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`[^}{;]{0,120}?role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
|
|
12666
|
+
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"),
|
|
12667
|
+
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"),
|
|
12668
|
+
new RegExp(`\\b(?:${P_SYS}|systemPrompt|system_prompt|system_instruction)\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\``, "g"),
|
|
12669
|
+
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"),
|
|
12670
|
+
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"),
|
|
12671
|
+
new RegExp(`\\[\\s*["']${P_SYS}["']\\s*,\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`\\s*\\]`, "g")
|
|
12672
|
+
];
|
|
12673
|
+
const PROMPT_INJECTION_PY_PATTERNS = [
|
|
12674
|
+
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"),
|
|
12675
|
+
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"),
|
|
12676
|
+
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"),
|
|
12677
|
+
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"),
|
|
12678
|
+
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"),
|
|
12679
|
+
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"),
|
|
12680
|
+
new RegExp(`\\b${P_SM}\\s*\\(\\s*(?:content\\s*=\\s*)?f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])`, "g"),
|
|
12681
|
+
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"),
|
|
12682
|
+
new RegExp(`\\(\\s*["']${P_SYS}["']\\s*,\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])\\s*\\)`, "g")
|
|
12683
|
+
];
|
|
12684
|
+
const checkInvisibleUnicode = (relPath, content, lines) => {
|
|
12685
|
+
if (hasFileIgnore(lines, "invisible-unicode-tags")) return [];
|
|
12686
|
+
const results = [];
|
|
12687
|
+
const tagRegex = new RegExp(UNICODE_TAG_RE.source, UNICODE_TAG_RE.flags);
|
|
12688
|
+
for (const match of content.matchAll(tagRegex)) {
|
|
12689
|
+
const matchIndex = match.index ?? 0;
|
|
12690
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
12691
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
12692
|
+
if (hasRuleIgnore(lines, line - 1, "invisible-unicode-tags")) continue;
|
|
12693
|
+
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));
|
|
12694
|
+
}
|
|
12695
|
+
return results;
|
|
12696
|
+
};
|
|
12697
|
+
const checkUnsandboxedTools = (relPath, content, lines) => {
|
|
12698
|
+
if (hasFileIgnore(lines, "unsandboxed-agent-tool")) return [];
|
|
12699
|
+
const results = [];
|
|
12700
|
+
for (const { pattern, message, help } of UNSANDBOXED_TOOL_PATTERNS) {
|
|
12701
|
+
const regex = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
12702
|
+
for (const match of content.matchAll(regex)) {
|
|
12703
|
+
const matchIndex = match.index ?? 0;
|
|
12704
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
12705
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
12706
|
+
if (hasRuleIgnore(lines, line - 1, "unsandboxed-agent-tool")) continue;
|
|
12707
|
+
results.push(makeDiagnostic(relPath, "security/unsandboxed-agent-tool", message, help, line, col));
|
|
12708
|
+
}
|
|
12709
|
+
}
|
|
12710
|
+
return results;
|
|
12711
|
+
};
|
|
12712
|
+
const checkPromptInjection = (relPath, content, lines, isJs) => {
|
|
12713
|
+
if (hasFileIgnore(lines, "prompt-injection")) return [];
|
|
12714
|
+
const results = [];
|
|
12715
|
+
const patterns = isJs ? PROMPT_INJECTION_JS_PATTERNS : PROMPT_INJECTION_PY_PATTERNS;
|
|
12716
|
+
for (const pattern of patterns) {
|
|
12717
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
12718
|
+
for (const match of content.matchAll(regex)) {
|
|
12719
|
+
const matchIndex = match.index ?? 0;
|
|
12720
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
12721
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
12722
|
+
if (hasRuleIgnore(lines, line - 1, "prompt-injection")) continue;
|
|
12723
|
+
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));
|
|
12724
|
+
}
|
|
12725
|
+
}
|
|
12726
|
+
return results;
|
|
12727
|
+
};
|
|
12728
|
+
const scanAiSecurity = async (context) => {
|
|
12729
|
+
const files = getSourceFiles(context);
|
|
12730
|
+
const diagnostics = [];
|
|
12731
|
+
for (const filePath of files) {
|
|
12732
|
+
const ext = path.extname(filePath);
|
|
12733
|
+
let content;
|
|
12734
|
+
try {
|
|
12735
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
12736
|
+
} catch {
|
|
12737
|
+
continue;
|
|
12738
|
+
}
|
|
12739
|
+
const relativePath = relativePosix(context.rootDirectory, filePath);
|
|
12740
|
+
const lines = content.split("\n");
|
|
12741
|
+
diagnostics.push(...checkInvisibleUnicode(relativePath, content, lines));
|
|
12742
|
+
const isJs = JS_EXTS.has(ext);
|
|
12743
|
+
const isPy = PY_EXTS.has(ext);
|
|
12744
|
+
if (isJs || isPy) {
|
|
12745
|
+
diagnostics.push(...checkUnsandboxedTools(relativePath, content, lines));
|
|
12746
|
+
diagnostics.push(...checkPromptInjection(relativePath, content, lines, isJs));
|
|
12747
|
+
}
|
|
12748
|
+
}
|
|
12749
|
+
return diagnostics;
|
|
12750
|
+
};
|
|
12751
|
+
//#endregion
|
|
12571
12752
|
//#region src/ui/invocation.ts
|
|
12572
12753
|
const detectInvocation = () => "raigal";
|
|
12573
12754
|
//#endregion
|
|
@@ -12950,7 +13131,7 @@ const parseJsAudit = (output, source, manifest) => {
|
|
|
12950
13131
|
rule: "security/dependency-audit-skipped",
|
|
12951
13132
|
severity: "info",
|
|
12952
13133
|
message: `Dependency audit skipped (${source}): lockfile is missing`,
|
|
12953
|
-
help: errorDetail ?? "Generate a lockfile, then re-run `
|
|
13134
|
+
help: errorDetail ?? "Generate a lockfile, then re-run `raigal scan` for dependency vulnerability checks.",
|
|
12954
13135
|
line: 0,
|
|
12955
13136
|
column: 0,
|
|
12956
13137
|
category: "Security",
|
|
@@ -13508,7 +13689,7 @@ const RISKY_PATTERNS = [
|
|
|
13508
13689
|
//#region src/engines/security/risky.ts
|
|
13509
13690
|
const hasDangerouslySetInnerHtmlIgnore = (lines, lineIndex) => {
|
|
13510
13691
|
const start = Math.max(0, lineIndex - 2);
|
|
13511
|
-
return lines.slice(start, lineIndex + 1).some((line) => /(?:biome-ignore|eslint-disable|aislop-ignore).*(?:noDangerouslySetInnerHtml|dangerouslySetInnerHTML|dangerously-set-innerhtml)/i.test(line));
|
|
13692
|
+
return lines.slice(start, lineIndex + 1).some((line) => /(?:biome-ignore|eslint-disable|raigal-ignore|aislop-ignore).*(?:noDangerouslySetInnerHtml|dangerouslySetInnerHTML|dangerously-set-innerhtml)/i.test(line));
|
|
13512
13693
|
};
|
|
13513
13694
|
const isStructuredDataScript = (content, matchIndex) => {
|
|
13514
13695
|
const before = content.slice(Math.max(0, matchIndex - 300), matchIndex);
|
|
@@ -13830,7 +14011,11 @@ const securityEngine = {
|
|
|
13830
14011
|
name: "security",
|
|
13831
14012
|
async run(context) {
|
|
13832
14013
|
const diagnostics = [];
|
|
13833
|
-
const promises = [
|
|
14014
|
+
const promises = [
|
|
14015
|
+
scanSecrets(context),
|
|
14016
|
+
detectRiskyConstructs(context),
|
|
14017
|
+
scanAiSecurity(context)
|
|
14018
|
+
];
|
|
13834
14019
|
if (context.config.security.audit) promises.push(runDependencyAudit(context));
|
|
13835
14020
|
const results = await Promise.allSettled(promises);
|
|
13836
14021
|
for (const result of results) if (result.status === "fulfilled") diagnostics.push(...result.value);
|
|
@@ -14100,6 +14285,9 @@ const RULE_SCORE_IMPACTS = {
|
|
|
14100
14285
|
"security/shell-injection": strict("Interpolated shell commands can become command execution."),
|
|
14101
14286
|
"security/unsafe-deserialization": strict("Deserializing untrusted data with a legacy .NET formatter can become remote code execution."),
|
|
14102
14287
|
"security/unsafe-c-call": strict("Unbounded C string and process calls can become buffer overflows or command execution."),
|
|
14288
|
+
"security/prompt-injection": strict("Dynamic user interpolation in system/developer prompts enables prompt injection attacks."),
|
|
14289
|
+
"security/unsandboxed-agent-tool": strict("Unsandboxed agent code execution or tool permission bypass allows arbitrary host commands."),
|
|
14290
|
+
"security/invisible-unicode-tags": strict("Invisible Unicode Tag characters (U+E0000-U+E007F) hide stealth prompt injection payloads."),
|
|
14103
14291
|
"security/dependency-audit-skipped": advisory("An unavailable audit is visibility loss, not evidence of a vulnerability."),
|
|
14104
14292
|
"dotnet/projects-skipped": advisory("Skipping unrestored C# projects is visibility loss, not evidence of a defect."),
|
|
14105
14293
|
"cppcheck/chunks-skipped": advisory("A skipped cppcheck chunk is visibility loss, not evidence that the skipped files have a defect."),
|
|
@@ -14481,18 +14669,25 @@ const hasExistingLiteralBackslashPath = (cwd, fingerprint) => {
|
|
|
14481
14669
|
}
|
|
14482
14670
|
};
|
|
14483
14671
|
const migrateMarkerlessFingerprint = (cwd, fingerprint) => hasExistingLiteralBackslashPath(cwd, fingerprint) ? fingerprint : normalizeLegacyFingerprint(fingerprint);
|
|
14484
|
-
const
|
|
14485
|
-
const
|
|
14672
|
+
const baselinePath = (cwd) => {
|
|
14673
|
+
const raigalCandidate = path.join(cwd, ".raigal", "baseline.json");
|
|
14674
|
+
if (fs.existsSync(raigalCandidate)) return raigalCandidate;
|
|
14675
|
+
const raigalDir = path.join(cwd, ".raigal");
|
|
14676
|
+
if (fs.existsSync(raigalDir)) return raigalCandidate;
|
|
14677
|
+
const legacyCandidate = path.join(cwd, ".aislop", "baseline.json");
|
|
14678
|
+
if (fs.existsSync(legacyCandidate)) return legacyCandidate;
|
|
14679
|
+
return raigalCandidate;
|
|
14680
|
+
};
|
|
14486
14681
|
const readBaseline = (cwd) => {
|
|
14487
14682
|
const raw = readIfExists(baselinePath(cwd));
|
|
14488
14683
|
if (!raw) return null;
|
|
14489
14684
|
try {
|
|
14490
14685
|
const parsed = JSON.parse(raw);
|
|
14491
|
-
if (parsed.schema !== "aislop.baseline.v2" && parsed.schema !== "aislop.baseline.v1") return null;
|
|
14686
|
+
if (parsed.schema !== "aislop.baseline.v2" && parsed.schema !== "aislop.baseline.v1" && parsed.schema !== "raigal.baseline.v2" && parsed.schema !== "raigal.baseline.v1") return null;
|
|
14492
14687
|
const findingFingerprints = parsed.findingFingerprints ?? [];
|
|
14493
14688
|
const needsWindowsMigration = parsed.pathFormat !== "posix";
|
|
14494
14689
|
return {
|
|
14495
|
-
schema: "
|
|
14690
|
+
schema: "raigal.baseline.v2",
|
|
14496
14691
|
pathFormat: "posix",
|
|
14497
14692
|
updatedAt: parsed.updatedAt ?? "",
|
|
14498
14693
|
score: parsed.score ?? 0,
|
|
@@ -14688,12 +14883,11 @@ const runScan = async (cwd) => {
|
|
|
14688
14883
|
}
|
|
14689
14884
|
};
|
|
14690
14885
|
};
|
|
14691
|
-
const
|
|
14692
|
-
const
|
|
14886
|
+
const raigalScanInputSchema = z$1.object({ path: z$1.string().optional().describe("Project directory to scan, confined to the MCP server's cwd. Defaults to that cwd.") });
|
|
14887
|
+
const raigalScanTool = {
|
|
14693
14888
|
name: "raigal_scan",
|
|
14694
14889
|
description: "Scan a project with Raigal. Runs the deterministic engines (format, lint, code-quality, ai-slop, security, architecture), returns a 0–100 score and the top findings. Use this before deciding whether the agent's recent changes are ready to ship.",
|
|
14695
|
-
inputSchema:
|
|
14696
|
-
name: "aislop_scan"
|
|
14890
|
+
inputSchema: raigalScanInputSchema
|
|
14697
14891
|
};
|
|
14698
14892
|
const handleRaigalScan = async (input) => {
|
|
14699
14893
|
const cwd = resolveCwd(input.path);
|
|
@@ -14708,7 +14902,7 @@ const handleRaigalScan = async (input) => {
|
|
|
14708
14902
|
...summary
|
|
14709
14903
|
};
|
|
14710
14904
|
};
|
|
14711
|
-
const
|
|
14905
|
+
const raigalFixInputSchema = z$1.object({
|
|
14712
14906
|
path: z$1.string().optional().describe("Project directory to fix, confined to the MCP server's cwd. Defaults to that cwd."),
|
|
14713
14907
|
force: z$1.boolean().optional().describe("Run aggressive fixes (dependency audit overrides, unused-file removal, framework alignment). Off by default; on means writes to package.json and may delete files.")
|
|
14714
14908
|
});
|
|
@@ -14738,11 +14932,10 @@ const runRaigalFix = (cwd, force) => {
|
|
|
14738
14932
|
}));
|
|
14739
14933
|
});
|
|
14740
14934
|
};
|
|
14741
|
-
const
|
|
14935
|
+
const raigalFixTool = {
|
|
14742
14936
|
name: "raigal_fix",
|
|
14743
14937
|
description: "Apply mechanical fixes (formatting, unused imports, narrative comments, duplicate imports, etc.). Returns counts before/after so the agent can see how many issues remain. Use BEFORE handing off to the agent — saves tokens by clearing what the CLI handles deterministically.",
|
|
14744
|
-
inputSchema:
|
|
14745
|
-
name: "aislop_fix"
|
|
14938
|
+
inputSchema: raigalFixInputSchema
|
|
14746
14939
|
};
|
|
14747
14940
|
const handleRaigalFix = async (input) => {
|
|
14748
14941
|
const cwd = resolveCwd(input.path);
|
|
@@ -14764,12 +14957,11 @@ const handleRaigalFix = async (input) => {
|
|
|
14764
14957
|
elided: summary.elided
|
|
14765
14958
|
};
|
|
14766
14959
|
};
|
|
14767
|
-
const
|
|
14768
|
-
const
|
|
14960
|
+
const raigalWhyInputSchema = z$1.object({ rule_id: z$1.string().describe("Full rule id (e.g. `ai-slop/narrative-comment`, `complexity/function-too-long`, `security/sql-injection`).") });
|
|
14961
|
+
const raigalWhyTool = {
|
|
14769
14962
|
name: "raigal_why",
|
|
14770
14963
|
description: "Explain a Raigal rule: what it catches, why an AI agent typically produces it, severity, and whether it's auto-fixable. Use when a finding's message alone isn't enough to act on.",
|
|
14771
|
-
inputSchema:
|
|
14772
|
-
name: "aislop_why"
|
|
14964
|
+
inputSchema: raigalWhyInputSchema
|
|
14773
14965
|
};
|
|
14774
14966
|
const handleRaigalWhy = (input) => {
|
|
14775
14967
|
const ruleId = input.rule_id.trim();
|
|
@@ -14782,12 +14974,11 @@ const handleRaigalWhy = (input) => {
|
|
|
14782
14974
|
hint: "Run `raigal rules` for the full list of rules and their auto-fix status. The /patterns page has bad/good code examples for every named ai-slop pattern."
|
|
14783
14975
|
};
|
|
14784
14976
|
};
|
|
14785
|
-
const
|
|
14786
|
-
const
|
|
14977
|
+
const raigalBaselineInputSchema = z$1.object({ path: z$1.string().optional().describe("Project directory, confined to the MCP server's cwd. Defaults to that cwd.") });
|
|
14978
|
+
const raigalBaselineTool = {
|
|
14787
14979
|
name: "raigal_baseline",
|
|
14788
14980
|
description: "Read the project's baseline (the last captured score the per-edit hook compares against). Returns score / lastScanAt / fileCount, or null if no baseline exists yet (run `raigal hook baseline` to capture).",
|
|
14789
|
-
inputSchema:
|
|
14790
|
-
name: "aislop_baseline"
|
|
14981
|
+
inputSchema: raigalBaselineInputSchema
|
|
14791
14982
|
};
|
|
14792
14983
|
const handleRaigalBaseline = (input) => {
|
|
14793
14984
|
const baseline = readBaseline(resolveCwd(input.path));
|
|
@@ -15164,37 +15355,21 @@ const buildServer = () => {
|
|
|
15164
15355
|
name: "raigal",
|
|
15165
15356
|
version: APP_VERSION
|
|
15166
15357
|
});
|
|
15167
|
-
server.registerTool(
|
|
15168
|
-
description:
|
|
15169
|
-
inputSchema:
|
|
15170
|
-
}, (input) => instrument("aislop_scan", () => handleRaigalScan(input)));
|
|
15171
|
-
server.registerTool(aislopFixTool.name, {
|
|
15172
|
-
description: aislopFixTool.description,
|
|
15173
|
-
inputSchema: aislopFixInputSchema.shape
|
|
15174
|
-
}, (input) => instrument("aislop_fix", () => handleRaigalFix(input)));
|
|
15175
|
-
server.registerTool(aislopWhyTool.name, {
|
|
15176
|
-
description: aislopWhyTool.description,
|
|
15177
|
-
inputSchema: aislopWhyInputSchema.shape
|
|
15178
|
-
}, (input) => instrument("aislop_why", () => handleRaigalWhy(input)));
|
|
15179
|
-
server.registerTool(aislopBaselineTool.name, {
|
|
15180
|
-
description: aislopBaselineTool.description,
|
|
15181
|
-
inputSchema: aislopBaselineInputSchema.shape
|
|
15182
|
-
}, (input) => instrument("aislop_baseline", () => handleRaigalBaseline(input)));
|
|
15183
|
-
server.registerTool("raigal_scan", {
|
|
15184
|
-
description: aislopScanTool.description,
|
|
15185
|
-
inputSchema: aislopScanInputSchema.shape
|
|
15358
|
+
server.registerTool(raigalScanTool.name, {
|
|
15359
|
+
description: raigalScanTool.description,
|
|
15360
|
+
inputSchema: raigalScanInputSchema.shape
|
|
15186
15361
|
}, (input) => instrument("raigal_scan", () => handleRaigalScan(input)));
|
|
15187
|
-
server.registerTool(
|
|
15188
|
-
description:
|
|
15189
|
-
inputSchema:
|
|
15362
|
+
server.registerTool(raigalFixTool.name, {
|
|
15363
|
+
description: raigalFixTool.description,
|
|
15364
|
+
inputSchema: raigalFixInputSchema.shape
|
|
15190
15365
|
}, (input) => instrument("raigal_fix", () => handleRaigalFix(input)));
|
|
15191
|
-
server.registerTool(
|
|
15192
|
-
description:
|
|
15193
|
-
inputSchema:
|
|
15366
|
+
server.registerTool(raigalWhyTool.name, {
|
|
15367
|
+
description: raigalWhyTool.description,
|
|
15368
|
+
inputSchema: raigalWhyInputSchema.shape
|
|
15194
15369
|
}, (input) => instrument("raigal_why", () => handleRaigalWhy(input)));
|
|
15195
|
-
server.registerTool(
|
|
15196
|
-
description:
|
|
15197
|
-
inputSchema:
|
|
15370
|
+
server.registerTool(raigalBaselineTool.name, {
|
|
15371
|
+
description: raigalBaselineTool.description,
|
|
15372
|
+
inputSchema: raigalBaselineInputSchema.shape
|
|
15198
15373
|
}, (input) => instrument("raigal_baseline", () => handleRaigalBaseline(input)));
|
|
15199
15374
|
return server;
|
|
15200
15375
|
};
|