@methiu/raigal 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AgentApp-CIiRfOun.js → AgentApp-DJYdD1VH.js} +4 -2
- package/dist/adapters/vite.js +2 -1
- package/dist/cli.js +500 -178
- package/dist/{finding-assessment-M5fUTE-C.js → finding-assessment-CGpt5YUR.js} +3 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +388 -130
- package/dist/{json-DBoMe4xB.js → json-BflWt4dX.js} +2 -2
- package/dist/mcp.js +301 -105
- package/dist/{mount-DTc8WaIt.js → mount-CHGc3gB6.js} +1 -1
- package/dist/{sarif--DI_2p2r.js → sarif-CdtsAfJ1.js} +1 -1
- package/dist/{sarif-Ba5D8TPH.js → sarif-CpuYM3aK.js} +2 -2
- package/dist/{version-9Cnt1TiK.js → version-B4eYJn3o.js} +1 -1
- package/package.json +1 -1
- package/scripts/release-guard.mjs +2 -2
package/dist/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.3";
|
|
88
88
|
//#endregion
|
|
89
89
|
//#region src/cloud/contract.ts
|
|
90
90
|
const Severity = z.enum(["error", "warning"]);
|
|
@@ -347,7 +347,9 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
|
|
|
347
347
|
if (!header.kid) throw new JwtVerificationError("JWT header missing 'kid'", "unknown_kid");
|
|
348
348
|
const publicKey = getPublicKey(header.kid);
|
|
349
349
|
if (!publicKey) throw new JwtVerificationError(`Unknown or untrusted signing key id (kid: ${header.kid})`, "unknown_kid");
|
|
350
|
-
|
|
350
|
+
const signedData = Buffer.from(`${headerB64}.${payloadB64}`, "utf-8");
|
|
351
|
+
const signature = Buffer.from(signatureB64, "base64url");
|
|
352
|
+
if (!verify(null, signedData, publicKey, signature)) throw new JwtVerificationError("Invalid JWT cryptographic signature", "invalid_signature");
|
|
351
353
|
let rawPayload;
|
|
352
354
|
try {
|
|
353
355
|
rawPayload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf-8"));
|
|
@@ -359,7 +361,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
|
|
|
359
361
|
const claims = parsed.data;
|
|
360
362
|
const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
|
|
361
363
|
if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
|
|
362
|
-
if (!isVersionAtLeast("0.2.
|
|
364
|
+
if (!isVersionAtLeast("0.2.3", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
|
|
363
365
|
const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
364
366
|
const grace = options.allowGraceSeconds ?? 0;
|
|
365
367
|
if (now > claims.exp + grace) {
|
|
@@ -393,7 +395,7 @@ const fetchGitHubOidcToken = async () => {
|
|
|
393
395
|
};
|
|
394
396
|
//#endregion
|
|
395
397
|
//#region src/cloud/entitlement.ts
|
|
396
|
-
const GRACE_PERIOD_SECONDS =
|
|
398
|
+
const GRACE_PERIOD_SECONDS = 259200;
|
|
397
399
|
var MissingCredentialError = class extends Error {
|
|
398
400
|
constructor(message = "No organization credential found") {
|
|
399
401
|
super(message);
|
|
@@ -518,7 +520,8 @@ const getEntitlement = async () => {
|
|
|
518
520
|
}
|
|
519
521
|
} else oidcToken = await fetchGitHubOidcToken();
|
|
520
522
|
try {
|
|
521
|
-
const
|
|
523
|
+
const tokenToPass = activeToken?.token ?? "";
|
|
524
|
+
const response = await fetchEntitlementFromApi(tokenToPass, oidcToken);
|
|
522
525
|
const claims = verifyEntitlementJwt(response.entitlement, { nowSeconds: now });
|
|
523
526
|
saveCachedEntitlement(response.entitlement, response.refresh_after);
|
|
524
527
|
return {
|
|
@@ -532,7 +535,7 @@ const getEntitlement = async () => {
|
|
|
532
535
|
};
|
|
533
536
|
//#endregion
|
|
534
537
|
//#region src/cloud/owner.ts
|
|
535
|
-
const GITHUB_REMOTE_RE = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([
|
|
538
|
+
const GITHUB_REMOTE_RE = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
|
|
536
539
|
const parseGitRemote = (rawUrl) => {
|
|
537
540
|
if (!rawUrl) return {
|
|
538
541
|
owner: null,
|
|
@@ -813,7 +816,7 @@ const DEFAULT_CONFIG = {
|
|
|
813
816
|
//#endregion
|
|
814
817
|
//#region src/config/extends.ts
|
|
815
818
|
const MAX_DEPTH$1 = 5;
|
|
816
|
-
const MAX_CONFIG_BYTES =
|
|
819
|
+
const MAX_CONFIG_BYTES = 1048576;
|
|
817
820
|
const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
818
821
|
const deepMerge = (...sources) => {
|
|
819
822
|
const result = {};
|
|
@@ -994,6 +997,9 @@ const CiSchema = z.object({
|
|
|
994
997
|
format: z.enum(["json"]).default("json")
|
|
995
998
|
}).strict();
|
|
996
999
|
const TelemetrySchema = z.object({ enabled: z.boolean().default(true) }).strict();
|
|
1000
|
+
const BaselineConfigSchema = z.object({ mode: z.enum(["strict", "relaxed"]).default("relaxed") }).strict();
|
|
1001
|
+
const LicensingConfigSchema = z.object({ onLapse: z.enum(["fail-open", "fail-closed"]).default("fail-open") }).strict();
|
|
1002
|
+
const WorkspaceConfigSchema = z.object({ minimumFloor: z.number().min(0).max(100).default(50) }).strict();
|
|
997
1003
|
const RuleSeverityOverride = z.enum([
|
|
998
1004
|
"error",
|
|
999
1005
|
"warning",
|
|
@@ -1064,6 +1070,9 @@ const AislopConfigSchema = z.object({
|
|
|
1064
1070
|
format: "json"
|
|
1065
1071
|
})),
|
|
1066
1072
|
telemetry: TelemetrySchema.default(() => ({ enabled: true })),
|
|
1073
|
+
baseline: BaselineConfigSchema.optional(),
|
|
1074
|
+
licensing: LicensingConfigSchema.optional(),
|
|
1075
|
+
workspace: WorkspaceConfigSchema.optional(),
|
|
1067
1076
|
rules: RulesSchema,
|
|
1068
1077
|
overrides: z.array(FileOverrideSchema).default(() => []),
|
|
1069
1078
|
exclude: z.array(z.string()).default(() => [
|
|
@@ -1097,6 +1106,9 @@ const KNOWN_KEYS_BY_PATH = {
|
|
|
1097
1106
|
"scoring",
|
|
1098
1107
|
"ci",
|
|
1099
1108
|
"telemetry",
|
|
1109
|
+
"baseline",
|
|
1110
|
+
"licensing",
|
|
1111
|
+
"workspace",
|
|
1100
1112
|
"rules",
|
|
1101
1113
|
"exclude",
|
|
1102
1114
|
"include"
|
|
@@ -1147,7 +1159,10 @@ const KNOWN_KEYS_BY_PATH = {
|
|
|
1147
1159
|
],
|
|
1148
1160
|
"scoring.thresholds": ["good", "ok"],
|
|
1149
1161
|
ci: ["failBelow", "format"],
|
|
1150
|
-
telemetry: ["enabled"]
|
|
1162
|
+
telemetry: ["enabled"],
|
|
1163
|
+
baseline: ["mode"],
|
|
1164
|
+
licensing: ["onLapse"],
|
|
1165
|
+
workspace: ["minimumFloor"]
|
|
1151
1166
|
};
|
|
1152
1167
|
const levenshtein = (a, b) => {
|
|
1153
1168
|
const m = a.length;
|
|
@@ -1277,7 +1292,8 @@ const loadConfig = (directory) => {
|
|
|
1277
1292
|
const configPath = findConfigFile(directory);
|
|
1278
1293
|
if (!configPath) return DEFAULT_CONFIG;
|
|
1279
1294
|
try {
|
|
1280
|
-
|
|
1295
|
+
const merged = loadConfigChain(configPath);
|
|
1296
|
+
return validateConfig(merged, configPath);
|
|
1281
1297
|
} catch (error) {
|
|
1282
1298
|
if (error instanceof ConfigError) throw error;
|
|
1283
1299
|
throw new ConfigError(configPath, `Failed to parse ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1455,7 +1471,7 @@ const filterExcludedDiagnostics = (diagnostics, rootDirectory, excludePatterns)
|
|
|
1455
1471
|
};
|
|
1456
1472
|
//#endregion
|
|
1457
1473
|
//#region src/utils/project-path-safety.ts
|
|
1458
|
-
const MAX_PROJECT_CONFIG_BYTES =
|
|
1474
|
+
const MAX_PROJECT_CONFIG_BYTES = 1048576;
|
|
1459
1475
|
const isWithinDirectory$2 = (rootDirectory, candidate) => {
|
|
1460
1476
|
const relative = path.relative(rootDirectory, candidate);
|
|
1461
1477
|
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
@@ -1542,7 +1558,7 @@ const isAutoGenerated = (filePath) => {
|
|
|
1542
1558
|
};
|
|
1543
1559
|
//#endregion
|
|
1544
1560
|
//#region src/utils/git-ignore.ts
|
|
1545
|
-
const MAX_BUFFER$1 =
|
|
1561
|
+
const MAX_BUFFER$1 = 52428800;
|
|
1546
1562
|
const NOT_IGNORED_ARGUMENTS = [
|
|
1547
1563
|
"ls-files",
|
|
1548
1564
|
"--cached",
|
|
@@ -1653,7 +1669,7 @@ const dropGitIgnoredPaths = (rootDirectory, absolutePaths) => {
|
|
|
1653
1669
|
};
|
|
1654
1670
|
//#endregion
|
|
1655
1671
|
//#region src/utils/project-file-list.ts
|
|
1656
|
-
const MAX_BUFFER =
|
|
1672
|
+
const MAX_BUFFER = 52428800;
|
|
1657
1673
|
const normalizePruneDirectories = (directories) => new Set([...directories].map((directory) => directory.toLowerCase()));
|
|
1658
1674
|
const isInPrunedDirectory = (filePath, pruneDirectories) => {
|
|
1659
1675
|
const pathSegments = filePath.split("/");
|
|
@@ -2567,7 +2583,7 @@ const tokenizeJs = (content, ext = ".ts") => {
|
|
|
2567
2583
|
};
|
|
2568
2584
|
//#endregion
|
|
2569
2585
|
//#region src/utils/source-masker.ts
|
|
2570
|
-
const JS_EXTS$
|
|
2586
|
+
const JS_EXTS$3 = /* @__PURE__ */ new Set([
|
|
2571
2587
|
".ts",
|
|
2572
2588
|
".tsx",
|
|
2573
2589
|
".js",
|
|
@@ -2575,7 +2591,7 @@ const JS_EXTS$2 = /* @__PURE__ */ new Set([
|
|
|
2575
2591
|
".mjs",
|
|
2576
2592
|
".cjs"
|
|
2577
2593
|
]);
|
|
2578
|
-
const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
|
|
2594
|
+
const PY_EXTS$1 = /* @__PURE__ */ new Set([".py"]);
|
|
2579
2595
|
const RB_EXTS = /* @__PURE__ */ new Set([".rb"]);
|
|
2580
2596
|
const PHP_EXTS = /* @__PURE__ */ new Set([".php"]);
|
|
2581
2597
|
const CSHARP_EXTS = /* @__PURE__ */ new Set([".cs"]);
|
|
@@ -2599,8 +2615,8 @@ const C_STYLE_COMMENT_EXTS = /* @__PURE__ */ new Set([
|
|
|
2599
2615
|
".zig"
|
|
2600
2616
|
]);
|
|
2601
2617
|
const familyForExt = (ext) => {
|
|
2602
|
-
if (JS_EXTS$
|
|
2603
|
-
if (PY_EXTS.has(ext)) return "py";
|
|
2618
|
+
if (JS_EXTS$3.has(ext)) return "js";
|
|
2619
|
+
if (PY_EXTS$1.has(ext)) return "py";
|
|
2604
2620
|
if (RB_EXTS.has(ext)) return "rb";
|
|
2605
2621
|
if (PHP_EXTS.has(ext)) return "php";
|
|
2606
2622
|
if (CSHARP_EXTS.has(ext)) return "csharp";
|
|
@@ -2832,7 +2848,7 @@ const getCommentLines = (content, ext) => {
|
|
|
2832
2848
|
};
|
|
2833
2849
|
//#endregion
|
|
2834
2850
|
//#region src/engines/ai-slop/abstractions.ts
|
|
2835
|
-
const JS_EXTS$
|
|
2851
|
+
const JS_EXTS$2 = /* @__PURE__ */ new Set([
|
|
2836
2852
|
".ts",
|
|
2837
2853
|
".tsx",
|
|
2838
2854
|
".js",
|
|
@@ -2843,11 +2859,11 @@ const JS_EXTS$1 = /* @__PURE__ */ new Set([
|
|
|
2843
2859
|
const THIN_WRAPPER_PATTERNS = [
|
|
2844
2860
|
{
|
|
2845
2861
|
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$
|
|
2862
|
+
extensions: JS_EXTS$2
|
|
2847
2863
|
},
|
|
2848
2864
|
{
|
|
2849
2865
|
pattern: /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w[^=]*)?\s*=>\s*\w+\([^)]*\);/g,
|
|
2850
|
-
extensions: JS_EXTS$
|
|
2866
|
+
extensions: JS_EXTS$2
|
|
2851
2867
|
},
|
|
2852
2868
|
{
|
|
2853
2869
|
pattern: /def\s+(\w+)\s*\([^)]*\)(?:\s*->[^:]*)?:\s*\n\s+return\s+\w+\([^)]*\)\s*$/gm,
|
|
@@ -4095,7 +4111,7 @@ const scriptKindFor$1 = (ext) => {
|
|
|
4095
4111
|
}
|
|
4096
4112
|
};
|
|
4097
4113
|
const lineFor$1 = (sourceFile, node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
4098
|
-
const makeDiagnostic = (filePath, line, rule, message, help) => ({
|
|
4114
|
+
const makeDiagnostic$1 = (filePath, line, rule, message, help) => ({
|
|
4099
4115
|
filePath,
|
|
4100
4116
|
engine: "ai-slop",
|
|
4101
4117
|
rule,
|
|
@@ -4145,7 +4161,7 @@ const detectRedundantTryCatch = (sourceFile, relativePath) => {
|
|
|
4145
4161
|
if (ts.isTryStatement(node) && node.catchClause && !node.finallyBlock) {
|
|
4146
4162
|
const catchNameNode = node.catchClause.variableDeclaration?.name;
|
|
4147
4163
|
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."));
|
|
4164
|
+
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
4165
|
}
|
|
4150
4166
|
ts.forEachChild(node, visit);
|
|
4151
4167
|
};
|
|
@@ -4163,7 +4179,7 @@ const detectPrimitiveCoercions = (sourceFile, relativePath) => {
|
|
|
4163
4179
|
const [arg] = child.arguments;
|
|
4164
4180
|
if (arg && ts.isIdentifier(arg)) {
|
|
4165
4181
|
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."));
|
|
4182
|
+
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
4183
|
}
|
|
4168
4184
|
}
|
|
4169
4185
|
ts.forEachChild(child, visitBody);
|
|
@@ -4206,7 +4222,7 @@ const detectDuplicateExportedTypes = (parsedSources) => {
|
|
|
4206
4222
|
continue;
|
|
4207
4223
|
}
|
|
4208
4224
|
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.`));
|
|
4225
|
+
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
4226
|
}
|
|
4211
4227
|
}
|
|
4212
4228
|
return diagnostics;
|
|
@@ -4308,7 +4324,7 @@ const detectDuplicateImports = async (context) => {
|
|
|
4308
4324
|
rule: "ai-slop/duplicate-import",
|
|
4309
4325
|
severity: "warning",
|
|
4310
4326
|
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
|
|
4327
|
+
help: "Two imports from the same module split readers' attention and grow the import block. Run raigal fix to merge them automatically.",
|
|
4312
4328
|
line: dup.line,
|
|
4313
4329
|
column: 1,
|
|
4314
4330
|
category: "AI Slop",
|
|
@@ -6016,7 +6032,8 @@ const isJsRelativeOrAbsolute = (spec) => spec.startsWith(".") || spec.startsWith
|
|
|
6016
6032
|
const RUNTIME_BUILTINS = /* @__PURE__ */ new Set(["bun"]);
|
|
6017
6033
|
const isJsBuiltin = (spec) => {
|
|
6018
6034
|
if (RUNTIME_BUILTINS.has(spec)) return true;
|
|
6019
|
-
|
|
6035
|
+
const stripped = spec.startsWith("node:") ? spec.slice(5) : spec;
|
|
6036
|
+
return isBuiltin(stripped) || isBuiltin(spec);
|
|
6020
6037
|
};
|
|
6021
6038
|
const VIRTUAL_MODULE_PREFIXES = [
|
|
6022
6039
|
"astro:",
|
|
@@ -6228,7 +6245,8 @@ const isLocalAliasReplacement = (rawValue, configDirectory, rootDirectory) => {
|
|
|
6228
6245
|
if (!target) return false;
|
|
6229
6246
|
if (!target.startsWith(".") && !target.startsWith("/") && !target.startsWith("~/") && !target.startsWith("@/")) return false;
|
|
6230
6247
|
const normalized = target.startsWith("~/") || target.startsWith("@/") ? target.slice(2) : target;
|
|
6231
|
-
|
|
6248
|
+
const candidate = path.isAbsolute(normalized) ? normalized : path.resolve(configDirectory, normalized);
|
|
6249
|
+
return isRootBoundedTarget(candidate, rootDirectory);
|
|
6232
6250
|
};
|
|
6233
6251
|
const hasValidPackageImportTarget = (value, packageDirectory, rootDirectory) => {
|
|
6234
6252
|
if (!isRootBoundedTarget(packageDirectory, rootDirectory)) return false;
|
|
@@ -7348,7 +7366,9 @@ const isRustDocCommentLine = (line) => {
|
|
|
7348
7366
|
};
|
|
7349
7367
|
const collectBlocks = (sourceLines, syntax, content, ext) => {
|
|
7350
7368
|
const blocks = [];
|
|
7351
|
-
const
|
|
7369
|
+
const actualContent = content ?? sourceLines.join("\n");
|
|
7370
|
+
const actualExt = ext ?? (syntax.linePrefixes.includes("//") ? ".ts" : ".py");
|
|
7371
|
+
const commentLines = getCommentLines(actualContent, actualExt);
|
|
7352
7372
|
let i = 0;
|
|
7353
7373
|
while (i < sourceLines.length) {
|
|
7354
7374
|
const line = sourceLines[i];
|
|
@@ -7676,7 +7696,8 @@ const detectNarrativeComments = async (context) => {
|
|
|
7676
7696
|
} catch {
|
|
7677
7697
|
continue;
|
|
7678
7698
|
}
|
|
7679
|
-
const
|
|
7699
|
+
const lines = content.split("\n");
|
|
7700
|
+
const blocks = collectBlocks(lines, syntax, content, ext);
|
|
7680
7701
|
for (const block of blocks) {
|
|
7681
7702
|
const { matched, reason } = detectNarrativeInBlock(block, ext);
|
|
7682
7703
|
if (!matched) continue;
|
|
@@ -8148,7 +8169,7 @@ const detectRustPatterns = async (context) => {
|
|
|
8148
8169
|
};
|
|
8149
8170
|
//#endregion
|
|
8150
8171
|
//#region src/engines/ai-slop/silent-recovery.ts
|
|
8151
|
-
const JS_EXTS = /* @__PURE__ */ new Set([
|
|
8172
|
+
const JS_EXTS$1 = /* @__PURE__ */ new Set([
|
|
8152
8173
|
".ts",
|
|
8153
8174
|
".tsx",
|
|
8154
8175
|
".js",
|
|
@@ -8343,7 +8364,7 @@ const detectSilentRecovery = async (context) => {
|
|
|
8343
8364
|
for (const filePath of files) {
|
|
8344
8365
|
if (isAutoGenerated(filePath)) continue;
|
|
8345
8366
|
const ext = path.extname(filePath);
|
|
8346
|
-
const isJs = JS_EXTS.has(ext);
|
|
8367
|
+
const isJs = JS_EXTS$1.has(ext);
|
|
8347
8368
|
if (!isJs && !(ext === ".py")) continue;
|
|
8348
8369
|
const relPath = relativePosix(context.rootDirectory, filePath);
|
|
8349
8370
|
if (isNonProductionPath(relPath)) continue;
|
|
@@ -8628,7 +8649,7 @@ const detectUnusedCss = async (context) => {
|
|
|
8628
8649
|
rule: "ai-slop/unused-css",
|
|
8629
8650
|
severity: "warning",
|
|
8630
8651
|
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
|
|
8652
|
+
help: "Remove the dead CSS rule, or confirm the class is used dynamically (and add a raigal-ignore comment if so)",
|
|
8632
8653
|
line: def.line,
|
|
8633
8654
|
column: 0,
|
|
8634
8655
|
category: "AI Slop",
|
|
@@ -9628,7 +9649,8 @@ const checkFunctionDiagnostics = (relativePath, fn, limits, ext) => {
|
|
|
9628
9649
|
const results = [];
|
|
9629
9650
|
const multiplier = functionLocMultiplier(fn, ext);
|
|
9630
9651
|
const fnMax = functionLocBudget(fn, ext, limits.maxFunctionLoc);
|
|
9631
|
-
|
|
9652
|
+
const effectiveLineCount = fn.lineCount - fn.templateLines;
|
|
9653
|
+
if (exceedsLimit(effectiveLineCount, limits.maxFunctionLoc, multiplier)) results.push({
|
|
9632
9654
|
filePath: relativePath,
|
|
9633
9655
|
engine: "code-quality",
|
|
9634
9656
|
rule: "complexity/function-too-long",
|
|
@@ -9718,14 +9740,14 @@ const isTrivialLine = (line) => {
|
|
|
9718
9740
|
};
|
|
9719
9741
|
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
9742
|
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/;
|
|
9743
|
+
const SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
9744
|
+
const FILE_SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore-file\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
9723
9745
|
const fileHasSuppression = (content) => FILE_SUPPRESS_RE.test(content);
|
|
9724
9746
|
const isLowSignalMarkupWindow = (lines) => {
|
|
9725
9747
|
return lines.filter((line) => SVG_MARKUP_RE.test(line)).length >= Math.ceil(WINDOW_SIZE / 2);
|
|
9726
9748
|
};
|
|
9727
9749
|
const isLowSignalDataWindow = (lines) => {
|
|
9728
|
-
return lines.filter((line) => DATA_LITERAL_RE.test(line)).length >=
|
|
9750
|
+
return lines.filter((line) => DATA_LITERAL_RE.test(line)).length >= 9;
|
|
9729
9751
|
};
|
|
9730
9752
|
const findSuppressedLines = (lines) => {
|
|
9731
9753
|
const suppressed = /* @__PURE__ */ new Set();
|
|
@@ -9757,7 +9779,7 @@ const collectMeaningfulLines = (content) => {
|
|
|
9757
9779
|
if (isLowSignalDataWindow(window)) continue;
|
|
9758
9780
|
if (window.every(isTrivialLine)) continue;
|
|
9759
9781
|
const normalised = window.map(normaliseLine);
|
|
9760
|
-
if (normalised.filter((n) => n.length > 0 && n !== "}" && n !== "{").length <
|
|
9782
|
+
if (normalised.filter((n) => n.length > 0 && n !== "}" && n !== "{").length < 9) continue;
|
|
9761
9783
|
if (new Set(normalised).size < MIN_DISTINCT_LINES) continue;
|
|
9762
9784
|
hits.push({
|
|
9763
9785
|
startLine: i + 1,
|
|
@@ -10045,8 +10067,8 @@ const shouldIncludeIssue = (issueType, filePath) => {
|
|
|
10045
10067
|
return !filePath.replace(/\\/g, "/").includes(".github/workflows/");
|
|
10046
10068
|
};
|
|
10047
10069
|
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 `
|
|
10070
|
+
dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
|
|
10071
|
+
devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
|
|
10050
10072
|
unlisted: "This package is imported in code but not declared in package.json. Run `npm install` to add it.",
|
|
10051
10073
|
unresolved: "This import cannot be resolved. Check for typos or missing packages.",
|
|
10052
10074
|
binaries: "This binary is used but its package is not in package.json."
|
|
@@ -10888,7 +10910,7 @@ const runGofmt = async (context) => {
|
|
|
10888
10910
|
rule: "go-formatting",
|
|
10889
10911
|
severity: "warning",
|
|
10890
10912
|
message: "Go file is not formatted correctly",
|
|
10891
|
-
help: "Run `
|
|
10913
|
+
help: "Run `raigal fix` to auto-format with gofmt",
|
|
10892
10914
|
line: 0,
|
|
10893
10915
|
column: 0,
|
|
10894
10916
|
category: "Format",
|
|
@@ -10907,7 +10929,8 @@ const resolvePackageRoot = (startFile) => {
|
|
|
10907
10929
|
while (true) {
|
|
10908
10930
|
const packageJsonPath = path.join(current, "package.json");
|
|
10909
10931
|
if (fs.existsSync(packageJsonPath)) try {
|
|
10910
|
-
|
|
10932
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
10933
|
+
if (packageJson.name === "aislop" || packageJson.name === "@methiu/raigal" || packageJson.name === "raigal") return current;
|
|
10911
10934
|
} catch {}
|
|
10912
10935
|
const parent = path.dirname(current);
|
|
10913
10936
|
if (parent === current) break;
|
|
@@ -11018,7 +11041,7 @@ const parseRuffFormatOutput = (output, rootDir) => {
|
|
|
11018
11041
|
rule: "python-formatting",
|
|
11019
11042
|
severity: "warning",
|
|
11020
11043
|
message: "Python file is not formatted correctly",
|
|
11021
|
-
help: "Run `
|
|
11044
|
+
help: "Run `raigal fix` to auto-format with ruff",
|
|
11022
11045
|
line: 0,
|
|
11023
11046
|
column: 0,
|
|
11024
11047
|
category: "Format",
|
|
@@ -11091,7 +11114,8 @@ const parseClangTidyOutput = (output, rootDirectory) => {
|
|
|
11091
11114
|
const runClangTidy = async (context) => {
|
|
11092
11115
|
const compileCommandsDir = findCompileCommandsDir(context);
|
|
11093
11116
|
if (!compileCommandsDir) return [];
|
|
11094
|
-
const
|
|
11117
|
+
const implSources = findCppSources(context).filter((f) => CPP_IMPL_EXTENSIONS.has(path.extname(f).toLowerCase()));
|
|
11118
|
+
const sources = filterSourcesInDatabase(implSources, readCompileCommandsFiles(compileCommandsDir));
|
|
11095
11119
|
if (sources.length === 0) return [];
|
|
11096
11120
|
const diagnostics = [];
|
|
11097
11121
|
for (const chunk of chunkFilePaths(sources, CLANG_TIDY_CHUNK_MAX_FILES)) try {
|
|
@@ -11461,7 +11485,7 @@ const runExpoDoctor = async (context) => {
|
|
|
11461
11485
|
rule: "expo-doctor/config-error",
|
|
11462
11486
|
severity: "warning",
|
|
11463
11487
|
message: configError,
|
|
11464
|
-
help: "Install project dependencies, then re-run `
|
|
11488
|
+
help: "Install project dependencies, then re-run `raigal scan`.",
|
|
11465
11489
|
line: 0,
|
|
11466
11490
|
column: 0,
|
|
11467
11491
|
category: "Expo",
|
|
@@ -12425,7 +12449,8 @@ const resolveTrustedTscPath = () => {
|
|
|
12425
12449
|
};
|
|
12426
12450
|
const isReferenceOnlyConfig = (tsconfigPath) => {
|
|
12427
12451
|
try {
|
|
12428
|
-
const
|
|
12452
|
+
const raw = fs.readFileSync(tsconfigPath, "utf-8");
|
|
12453
|
+
const parsed = parseJsonc(raw);
|
|
12429
12454
|
if (!parsed) return false;
|
|
12430
12455
|
return Array.isArray(parsed.references) && !parsed.files && !parsed.include && !parsed.extends;
|
|
12431
12456
|
} catch {
|
|
@@ -12568,6 +12593,174 @@ const lintEngine = {
|
|
|
12568
12593
|
}
|
|
12569
12594
|
};
|
|
12570
12595
|
//#endregion
|
|
12596
|
+
//#region src/engines/security/ai-security.ts
|
|
12597
|
+
const JS_EXTS = /* @__PURE__ */ new Set([
|
|
12598
|
+
".ts",
|
|
12599
|
+
".tsx",
|
|
12600
|
+
".js",
|
|
12601
|
+
".jsx",
|
|
12602
|
+
".mjs",
|
|
12603
|
+
".cjs"
|
|
12604
|
+
]);
|
|
12605
|
+
const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
|
|
12606
|
+
const UNICODE_TAG_RE = /[\u{E0000}-\u{E007F}]/gu;
|
|
12607
|
+
const hasRuleIgnore = (lines, lineIndex, ruleSuffix) => {
|
|
12608
|
+
const start = Math.max(0, lineIndex - 2);
|
|
12609
|
+
const re = new RegExp(`(?:raigal|aislop)[- ]ignore(?:-file)?\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
|
|
12610
|
+
return lines.slice(start, lineIndex + 1).some((line) => re.test(line));
|
|
12611
|
+
};
|
|
12612
|
+
const hasFileIgnore = (lines, ruleSuffix) => {
|
|
12613
|
+
const re = new RegExp(`(?:raigal|aislop)[- ]ignore-file\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
|
|
12614
|
+
return lines.slice(0, 10).some((line) => re.test(line));
|
|
12615
|
+
};
|
|
12616
|
+
const makeDiagnostic = (filePath, rule, message, help, line, column) => ({
|
|
12617
|
+
filePath,
|
|
12618
|
+
engine: "security",
|
|
12619
|
+
rule,
|
|
12620
|
+
severity: "error",
|
|
12621
|
+
message,
|
|
12622
|
+
help,
|
|
12623
|
+
line,
|
|
12624
|
+
column,
|
|
12625
|
+
category: "Security",
|
|
12626
|
+
fixable: false
|
|
12627
|
+
});
|
|
12628
|
+
const P_BYPASS = "bypassPermissions";
|
|
12629
|
+
const P_EXEC_TYPE = "executor_type";
|
|
12630
|
+
const P_LOCAL = "local";
|
|
12631
|
+
const P_USE_DOCKER = "use_docker";
|
|
12632
|
+
const P_AUTO_RUN = "auto_run";
|
|
12633
|
+
const P_UNSAFE_MODE = "unsafe_mode";
|
|
12634
|
+
const P_PYTHON_REPL = "PythonREPLTool";
|
|
12635
|
+
const UNSANDBOXED_TOOL_PATTERNS = [
|
|
12636
|
+
{
|
|
12637
|
+
pattern: new RegExp(`\\bpermission_?mode["']?\\s*[:=]\\s*["']${P_BYPASS}["']`, "i"),
|
|
12638
|
+
message: `permission_mode '${P_BYPASS}' bypasses agent sandboxing and human confirmation`,
|
|
12639
|
+
help: `Do not use ${P_BYPASS} mode in production; use standard interactive approval`
|
|
12640
|
+
},
|
|
12641
|
+
{
|
|
12642
|
+
pattern: new RegExp(`\\b${P_EXEC_TYPE}["']?\\s*[:=]\\s*["']${P_LOCAL}["']`, "i"),
|
|
12643
|
+
message: `${P_EXEC_TYPE}='${P_LOCAL}' executes LLM-generated code directly on host machine without container isolation`,
|
|
12644
|
+
help: "Configure agent to use DockerCommandLineCodeExecutor or a containerized sandbox environment"
|
|
12645
|
+
},
|
|
12646
|
+
{
|
|
12647
|
+
pattern: new RegExp(`\\b(?:LocalCommandLineCodeExecutor|LocalCodeExecutor)\\s*\\(`),
|
|
12648
|
+
message: "Local code executor executes LLM-generated code on host machine without container isolation",
|
|
12649
|
+
help: "Use DockerCommandLineCodeExecutor or an isolated execution sandbox"
|
|
12650
|
+
},
|
|
12651
|
+
{
|
|
12652
|
+
pattern: new RegExp(`\\b${P_USE_DOCKER}\\s*[:=]\\s*(?:False|false)\\b|["']${P_USE_DOCKER}["']\\s*:\\s*(?:False|false)\\b`),
|
|
12653
|
+
message: `${P_USE_DOCKER}=False disables container sandboxing for agent code execution`,
|
|
12654
|
+
help: `Set ${P_USE_DOCKER}=True to isolate agent execution inside Docker containers`
|
|
12655
|
+
},
|
|
12656
|
+
{
|
|
12657
|
+
pattern: new RegExp(`\\b(?:interpreter\\.)?${P_AUTO_RUN}\\s*[:=]\\s*(?:True|true)\\b`),
|
|
12658
|
+
message: `${P_AUTO_RUN}=True allows autonomous shell execution without human confirmation`,
|
|
12659
|
+
help: `Keep ${P_AUTO_RUN}=False so dangerous commands require explicit human review`
|
|
12660
|
+
},
|
|
12661
|
+
{
|
|
12662
|
+
pattern: new RegExp(`\\bCodeInterpreterTool\\s*\\([^)]*${P_UNSAFE_MODE}\\s*=\\s*(?:True|true)\\b|\\b${P_UNSAFE_MODE}\\s*[:=]\\s*(?:True|true)\\b`),
|
|
12663
|
+
message: `${P_UNSAFE_MODE}=True allows code interpreter tool to execute without Docker isolation`,
|
|
12664
|
+
help: `Disable ${P_UNSAFE_MODE} and isolate code execution in containerized environments`
|
|
12665
|
+
},
|
|
12666
|
+
{
|
|
12667
|
+
pattern: new RegExp(`\\b(?:${P_PYTHON_REPL}|PythonAstREPLTool)\\s*\\(`),
|
|
12668
|
+
message: `${P_PYTHON_REPL} executes arbitrary Python code in host process without container isolation`,
|
|
12669
|
+
help: `Use containerized sandbox execution rather than unconfined ${P_PYTHON_REPL} in production`
|
|
12670
|
+
}
|
|
12671
|
+
];
|
|
12672
|
+
const P_SYS = "system";
|
|
12673
|
+
const P_DEV = "developer";
|
|
12674
|
+
const P_SM = "SystemMessage";
|
|
12675
|
+
const PROMPT_INJECTION_JS_PATTERNS = [
|
|
12676
|
+
new RegExp(`(?:role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`)`, "g"),
|
|
12677
|
+
new RegExp(`(?:content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`[^}{;]{0,120}?role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
|
|
12678
|
+
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"),
|
|
12679
|
+
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"),
|
|
12680
|
+
new RegExp(`\\b(?:${P_SYS}|systemPrompt|system_prompt|system_instruction)\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\``, "g"),
|
|
12681
|
+
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"),
|
|
12682
|
+
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"),
|
|
12683
|
+
new RegExp(`\\[\\s*["']${P_SYS}["']\\s*,\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`\\s*\\]`, "g")
|
|
12684
|
+
];
|
|
12685
|
+
const PROMPT_INJECTION_PY_PATTERNS = [
|
|
12686
|
+
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"),
|
|
12687
|
+
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"),
|
|
12688
|
+
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"),
|
|
12689
|
+
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"),
|
|
12690
|
+
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"),
|
|
12691
|
+
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"),
|
|
12692
|
+
new RegExp(`\\b${P_SM}\\s*\\(\\s*(?:content\\s*=\\s*)?f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])`, "g"),
|
|
12693
|
+
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"),
|
|
12694
|
+
new RegExp(`\\(\\s*["']${P_SYS}["']\\s*,\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])\\s*\\)`, "g")
|
|
12695
|
+
];
|
|
12696
|
+
const checkInvisibleUnicode = (relPath, content, lines) => {
|
|
12697
|
+
if (hasFileIgnore(lines, "invisible-unicode-tags")) return [];
|
|
12698
|
+
const results = [];
|
|
12699
|
+
const tagRegex = new RegExp(UNICODE_TAG_RE.source, UNICODE_TAG_RE.flags);
|
|
12700
|
+
for (const match of content.matchAll(tagRegex)) {
|
|
12701
|
+
const matchIndex = match.index ?? 0;
|
|
12702
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
12703
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
12704
|
+
if (hasRuleIgnore(lines, line - 1, "invisible-unicode-tags")) continue;
|
|
12705
|
+
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));
|
|
12706
|
+
}
|
|
12707
|
+
return results;
|
|
12708
|
+
};
|
|
12709
|
+
const checkUnsandboxedTools = (relPath, content, lines) => {
|
|
12710
|
+
if (hasFileIgnore(lines, "unsandboxed-agent-tool")) return [];
|
|
12711
|
+
const results = [];
|
|
12712
|
+
for (const { pattern, message, help } of UNSANDBOXED_TOOL_PATTERNS) {
|
|
12713
|
+
const regex = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
12714
|
+
for (const match of content.matchAll(regex)) {
|
|
12715
|
+
const matchIndex = match.index ?? 0;
|
|
12716
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
12717
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
12718
|
+
if (hasRuleIgnore(lines, line - 1, "unsandboxed-agent-tool")) continue;
|
|
12719
|
+
results.push(makeDiagnostic(relPath, "security/unsandboxed-agent-tool", message, help, line, col));
|
|
12720
|
+
}
|
|
12721
|
+
}
|
|
12722
|
+
return results;
|
|
12723
|
+
};
|
|
12724
|
+
const checkPromptInjection = (relPath, content, lines, isJs) => {
|
|
12725
|
+
if (hasFileIgnore(lines, "prompt-injection")) return [];
|
|
12726
|
+
const results = [];
|
|
12727
|
+
const patterns = isJs ? PROMPT_INJECTION_JS_PATTERNS : PROMPT_INJECTION_PY_PATTERNS;
|
|
12728
|
+
for (const pattern of patterns) {
|
|
12729
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
12730
|
+
for (const match of content.matchAll(regex)) {
|
|
12731
|
+
const matchIndex = match.index ?? 0;
|
|
12732
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
12733
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
12734
|
+
if (hasRuleIgnore(lines, line - 1, "prompt-injection")) continue;
|
|
12735
|
+
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));
|
|
12736
|
+
}
|
|
12737
|
+
}
|
|
12738
|
+
return results;
|
|
12739
|
+
};
|
|
12740
|
+
const scanAiSecurity = async (context) => {
|
|
12741
|
+
const files = getSourceFiles(context);
|
|
12742
|
+
const diagnostics = [];
|
|
12743
|
+
for (const filePath of files) {
|
|
12744
|
+
const ext = path.extname(filePath);
|
|
12745
|
+
let content;
|
|
12746
|
+
try {
|
|
12747
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
12748
|
+
} catch {
|
|
12749
|
+
continue;
|
|
12750
|
+
}
|
|
12751
|
+
const relativePath = relativePosix(context.rootDirectory, filePath);
|
|
12752
|
+
const lines = content.split("\n");
|
|
12753
|
+
diagnostics.push(...checkInvisibleUnicode(relativePath, content, lines));
|
|
12754
|
+
const isJs = JS_EXTS.has(ext);
|
|
12755
|
+
const isPy = PY_EXTS.has(ext);
|
|
12756
|
+
if (isJs || isPy) {
|
|
12757
|
+
diagnostics.push(...checkUnsandboxedTools(relativePath, content, lines));
|
|
12758
|
+
diagnostics.push(...checkPromptInjection(relativePath, content, lines, isJs));
|
|
12759
|
+
}
|
|
12760
|
+
}
|
|
12761
|
+
return diagnostics;
|
|
12762
|
+
};
|
|
12763
|
+
//#endregion
|
|
12571
12764
|
//#region src/ui/invocation.ts
|
|
12572
12765
|
const detectInvocation = () => "raigal";
|
|
12573
12766
|
//#endregion
|
|
@@ -12950,7 +13143,7 @@ const parseJsAudit = (output, source, manifest) => {
|
|
|
12950
13143
|
rule: "security/dependency-audit-skipped",
|
|
12951
13144
|
severity: "info",
|
|
12952
13145
|
message: `Dependency audit skipped (${source}): lockfile is missing`,
|
|
12953
|
-
help: errorDetail ?? "Generate a lockfile, then re-run `
|
|
13146
|
+
help: errorDetail ?? "Generate a lockfile, then re-run `raigal scan` for dependency vulnerability checks.",
|
|
12954
13147
|
line: 0,
|
|
12955
13148
|
column: 0,
|
|
12956
13149
|
category: "Security",
|
|
@@ -13124,10 +13317,11 @@ const auditSkippedDiagnostic = (source, help) => ({
|
|
|
13124
13317
|
});
|
|
13125
13318
|
const runNpmAudit = async (rootDir, timeout) => {
|
|
13126
13319
|
try {
|
|
13127
|
-
|
|
13320
|
+
const result = await runSubprocess("npm", ["audit", "--json"], {
|
|
13128
13321
|
cwd: rootDir,
|
|
13129
13322
|
timeout
|
|
13130
|
-
})
|
|
13323
|
+
});
|
|
13324
|
+
return parseJsAudit(result.stdout, "npm audit", readRuntimeDependencies(rootDir));
|
|
13131
13325
|
} catch (error) {
|
|
13132
13326
|
return [auditSkippedDiagnostic("npm audit", `Failed to run npm audit: ${errorMessageOf(error)}`)];
|
|
13133
13327
|
}
|
|
@@ -13148,10 +13342,11 @@ const runBunAudit = async (rootDir, timeout) => {
|
|
|
13148
13342
|
const runPnpmAuditWithFallback = async (rootDir, timeout) => {
|
|
13149
13343
|
const canFallbackToNpm = fs.existsSync(path.join(rootDir, "package-lock.json"));
|
|
13150
13344
|
try {
|
|
13151
|
-
const
|
|
13345
|
+
const result = await runSubprocess("pnpm", ["audit", "--json"], {
|
|
13152
13346
|
cwd: rootDir,
|
|
13153
13347
|
timeout
|
|
13154
|
-
})
|
|
13348
|
+
});
|
|
13349
|
+
const diagnostics = parseJsAudit(result.stdout, "pnpm audit", readRuntimeDependencies(rootDir));
|
|
13155
13350
|
if (diagnostics.some((d) => d.rule === "security/dependency-audit-skipped")) {
|
|
13156
13351
|
if (canFallbackToNpm) return runNpmAudit(rootDir, timeout);
|
|
13157
13352
|
return diagnostics;
|
|
@@ -13508,7 +13703,7 @@ const RISKY_PATTERNS = [
|
|
|
13508
13703
|
//#region src/engines/security/risky.ts
|
|
13509
13704
|
const hasDangerouslySetInnerHtmlIgnore = (lines, lineIndex) => {
|
|
13510
13705
|
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));
|
|
13706
|
+
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
13707
|
};
|
|
13513
13708
|
const isStructuredDataScript = (content, matchIndex) => {
|
|
13514
13709
|
const before = content.slice(Math.max(0, matchIndex - 300), matchIndex);
|
|
@@ -13830,7 +14025,11 @@ const securityEngine = {
|
|
|
13830
14025
|
name: "security",
|
|
13831
14026
|
async run(context) {
|
|
13832
14027
|
const diagnostics = [];
|
|
13833
|
-
const promises = [
|
|
14028
|
+
const promises = [
|
|
14029
|
+
scanSecrets(context),
|
|
14030
|
+
detectRiskyConstructs(context),
|
|
14031
|
+
scanAiSecurity(context)
|
|
14032
|
+
];
|
|
13834
14033
|
if (context.config.security.audit) promises.push(runDependencyAudit(context));
|
|
13835
14034
|
const results = await Promise.allSettled(promises);
|
|
13836
14035
|
for (const result of results) if (result.status === "fulfilled") diagnostics.push(...result.value);
|
|
@@ -13954,7 +14153,10 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
|
|
|
13954
14153
|
failed: true,
|
|
13955
14154
|
skipReason: r.reason instanceof Error ? r.reason.message : String(r.reason)
|
|
13956
14155
|
});
|
|
13957
|
-
|
|
14156
|
+
const stripped = stripSecretDiagnostics(finalResults);
|
|
14157
|
+
const csharp = dedupeCSharpAsync(stripped);
|
|
14158
|
+
const comments = dedupeOverlappingComments(csharp);
|
|
14159
|
+
return dedupeEquivalenceDiagnostics(comments);
|
|
13958
14160
|
};
|
|
13959
14161
|
const stripSecretDiagnostics = (results) => {
|
|
13960
14162
|
for (const result of results) for (const d of result.diagnostics) if (d.redactSource || isSecretClassRule(d.rule)) {
|
|
@@ -14100,6 +14302,9 @@ const RULE_SCORE_IMPACTS = {
|
|
|
14100
14302
|
"security/shell-injection": strict("Interpolated shell commands can become command execution."),
|
|
14101
14303
|
"security/unsafe-deserialization": strict("Deserializing untrusted data with a legacy .NET formatter can become remote code execution."),
|
|
14102
14304
|
"security/unsafe-c-call": strict("Unbounded C string and process calls can become buffer overflows or command execution."),
|
|
14305
|
+
"security/prompt-injection": strict("Dynamic user interpolation in system/developer prompts enables prompt injection attacks."),
|
|
14306
|
+
"security/unsandboxed-agent-tool": strict("Unsandboxed agent code execution or tool permission bypass allows arbitrary host commands."),
|
|
14307
|
+
"security/invisible-unicode-tags": strict("Invisible Unicode Tag characters (U+E0000-U+E007F) hide stealth prompt injection payloads."),
|
|
14103
14308
|
"security/dependency-audit-skipped": advisory("An unavailable audit is visibility loss, not evidence of a vulnerability."),
|
|
14104
14309
|
"dotnet/projects-skipped": advisory("Skipping unrestored C# projects is visibility loss, not evidence of a defect."),
|
|
14105
14310
|
"cppcheck/chunks-skipped": advisory("A skipped cppcheck chunk is visibility loss, not evidence that the skipped files have a defect."),
|
|
@@ -14176,7 +14381,8 @@ const calculateScore = (diagnostics, weights, thresholds, sourceFileCount, smoot
|
|
|
14176
14381
|
const capScale = Math.max(1, fileCount.effectiveSourceFileCount / CAP_REFERENCE_FILES);
|
|
14177
14382
|
const defaultRuleCap = typeof maxPerRule === "number" && maxPerRule > 0 ? maxPerRule : null;
|
|
14178
14383
|
const capForRule = (key) => {
|
|
14179
|
-
const
|
|
14384
|
+
const rule = key.slice(key.indexOf(":") + 1);
|
|
14385
|
+
const ruleCap = scoreImpactForRule(rule).cap;
|
|
14180
14386
|
if (typeof ruleCap === "number") return defaultRuleCap ? Math.min(defaultRuleCap, ruleCap) : ruleCap;
|
|
14181
14387
|
return defaultRuleCap;
|
|
14182
14388
|
};
|
|
@@ -14289,8 +14495,10 @@ const detectSourceLanguages = (sourceFiles) => {
|
|
|
14289
14495
|
const detectLanguages = (directory, sourceFiles) => {
|
|
14290
14496
|
const languages = new Set(detectSourceLanguages(sourceFiles));
|
|
14291
14497
|
for (const [file, lang] of Object.entries(LANGUAGE_SIGNALS)) if (fs.existsSync(path.join(directory, file))) languages.add(lang);
|
|
14292
|
-
if (readPackageJson(path.join(directory, "package.json")))
|
|
14293
|
-
|
|
14498
|
+
if (readPackageJson(path.join(directory, "package.json"))) {
|
|
14499
|
+
if (fs.existsSync(path.join(directory, "tsconfig.json"))) languages.add("typescript");
|
|
14500
|
+
else languages.add("javascript");
|
|
14501
|
+
}
|
|
14294
14502
|
for (const signal of PYTHON_SIGNALS) if (fs.existsSync(path.join(directory, signal))) {
|
|
14295
14503
|
languages.add("python");
|
|
14296
14504
|
break;
|
|
@@ -14481,14 +14689,19 @@ const hasExistingLiteralBackslashPath = (cwd, fingerprint) => {
|
|
|
14481
14689
|
}
|
|
14482
14690
|
};
|
|
14483
14691
|
const migrateMarkerlessFingerprint = (cwd, fingerprint) => hasExistingLiteralBackslashPath(cwd, fingerprint) ? fingerprint : normalizeLegacyFingerprint(fingerprint);
|
|
14484
|
-
const
|
|
14485
|
-
const
|
|
14692
|
+
const baselinePath = (cwd) => {
|
|
14693
|
+
const raigalCandidate = path.join(cwd, ".raigal", "baseline.json");
|
|
14694
|
+
if (fs.existsSync(raigalCandidate)) return raigalCandidate;
|
|
14695
|
+
const raigalDir = path.join(cwd, ".raigal");
|
|
14696
|
+
if (fs.existsSync(raigalDir)) return raigalCandidate;
|
|
14697
|
+
return path.join(cwd, ".aislop", "baseline.json");
|
|
14698
|
+
};
|
|
14486
14699
|
const readBaseline = (cwd) => {
|
|
14487
14700
|
const raw = readIfExists(baselinePath(cwd));
|
|
14488
14701
|
if (!raw) return null;
|
|
14489
14702
|
try {
|
|
14490
14703
|
const parsed = JSON.parse(raw);
|
|
14491
|
-
if (parsed.schema !== "aislop.baseline.v2" && parsed.schema !== "aislop.baseline.v1") return null;
|
|
14704
|
+
if (parsed.schema !== "aislop.baseline.v2" && parsed.schema !== "aislop.baseline.v1" && parsed.schema !== "raigal.baseline.v2" && parsed.schema !== "raigal.baseline.v1") return null;
|
|
14492
14705
|
const findingFingerprints = parsed.findingFingerprints ?? [];
|
|
14493
14706
|
const needsWindowsMigration = parsed.pathFormat !== "posix";
|
|
14494
14707
|
return {
|
|
@@ -14673,7 +14886,9 @@ const summariseDiagnostics = (diagnostics, rootDirectory) => {
|
|
|
14673
14886
|
const runScan = async (cwd) => {
|
|
14674
14887
|
const project = await discoverProject(cwd);
|
|
14675
14888
|
const config = loadConfig(cwd);
|
|
14676
|
-
const
|
|
14889
|
+
const context = buildEngineContext(project.rootDirectory, project, config);
|
|
14890
|
+
const enabled = enabledEnginesFromConfig(config);
|
|
14891
|
+
const diagnostics = (await runEngines(context, enabled)).flatMap((r) => r.diagnostics);
|
|
14677
14892
|
const { score } = calculateScore(diagnostics, config.scoring.weights, config.scoring.thresholds, project.sourceFileCount, config.scoring.smoothing, config.scoring.maxPerRule);
|
|
14678
14893
|
const errorCount = diagnostics.filter((d) => d.severity === "error").length;
|
|
14679
14894
|
const failBelow = config.ci.failBelow;
|
|
@@ -14688,12 +14903,11 @@ const runScan = async (cwd) => {
|
|
|
14688
14903
|
}
|
|
14689
14904
|
};
|
|
14690
14905
|
};
|
|
14691
|
-
const
|
|
14692
|
-
const
|
|
14906
|
+
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.") });
|
|
14907
|
+
const raigalScanTool = {
|
|
14693
14908
|
name: "raigal_scan",
|
|
14694
14909
|
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"
|
|
14910
|
+
inputSchema: raigalScanInputSchema
|
|
14697
14911
|
};
|
|
14698
14912
|
const handleRaigalScan = async (input) => {
|
|
14699
14913
|
const cwd = resolveCwd(input.path);
|
|
@@ -14708,7 +14922,7 @@ const handleRaigalScan = async (input) => {
|
|
|
14708
14922
|
...summary
|
|
14709
14923
|
};
|
|
14710
14924
|
};
|
|
14711
|
-
const
|
|
14925
|
+
const raigalFixInputSchema = z$1.object({
|
|
14712
14926
|
path: z$1.string().optional().describe("Project directory to fix, confined to the MCP server's cwd. Defaults to that cwd."),
|
|
14713
14927
|
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
14928
|
});
|
|
@@ -14738,11 +14952,10 @@ const runRaigalFix = (cwd, force) => {
|
|
|
14738
14952
|
}));
|
|
14739
14953
|
});
|
|
14740
14954
|
};
|
|
14741
|
-
const
|
|
14955
|
+
const raigalFixTool = {
|
|
14742
14956
|
name: "raigal_fix",
|
|
14743
14957
|
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"
|
|
14958
|
+
inputSchema: raigalFixInputSchema
|
|
14746
14959
|
};
|
|
14747
14960
|
const handleRaigalFix = async (input) => {
|
|
14748
14961
|
const cwd = resolveCwd(input.path);
|
|
@@ -14764,12 +14977,11 @@ const handleRaigalFix = async (input) => {
|
|
|
14764
14977
|
elided: summary.elided
|
|
14765
14978
|
};
|
|
14766
14979
|
};
|
|
14767
|
-
const
|
|
14768
|
-
const
|
|
14980
|
+
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`).") });
|
|
14981
|
+
const raigalWhyTool = {
|
|
14769
14982
|
name: "raigal_why",
|
|
14770
14983
|
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"
|
|
14984
|
+
inputSchema: raigalWhyInputSchema
|
|
14773
14985
|
};
|
|
14774
14986
|
const handleRaigalWhy = (input) => {
|
|
14775
14987
|
const ruleId = input.rule_id.trim();
|
|
@@ -14782,12 +14994,11 @@ const handleRaigalWhy = (input) => {
|
|
|
14782
14994
|
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
14995
|
};
|
|
14784
14996
|
};
|
|
14785
|
-
const
|
|
14786
|
-
const
|
|
14997
|
+
const raigalBaselineInputSchema = z$1.object({ path: z$1.string().optional().describe("Project directory, confined to the MCP server's cwd. Defaults to that cwd.") });
|
|
14998
|
+
const raigalBaselineTool = {
|
|
14787
14999
|
name: "raigal_baseline",
|
|
14788
15000
|
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"
|
|
15001
|
+
inputSchema: raigalBaselineInputSchema
|
|
14791
15002
|
};
|
|
14792
15003
|
const handleRaigalBaseline = (input) => {
|
|
14793
15004
|
const baseline = readBaseline(resolveCwd(input.path));
|
|
@@ -15057,10 +15268,11 @@ const track = (input) => {
|
|
|
15057
15268
|
cachedInstallId = ensured.installId;
|
|
15058
15269
|
installCreated = ensured.created;
|
|
15059
15270
|
}
|
|
15060
|
-
const
|
|
15271
|
+
const merged = {
|
|
15061
15272
|
...baseProperties(cachedInstallId),
|
|
15062
15273
|
...input.properties
|
|
15063
|
-
}
|
|
15274
|
+
};
|
|
15275
|
+
const { clean, dropped } = redactProperties(merged);
|
|
15064
15276
|
if (isDebug()) {
|
|
15065
15277
|
const compact = JSON.stringify({
|
|
15066
15278
|
event: input.event,
|
|
@@ -15164,37 +15376,21 @@ const buildServer = () => {
|
|
|
15164
15376
|
name: "raigal",
|
|
15165
15377
|
version: APP_VERSION
|
|
15166
15378
|
});
|
|
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
|
|
15379
|
+
server.registerTool(raigalScanTool.name, {
|
|
15380
|
+
description: raigalScanTool.description,
|
|
15381
|
+
inputSchema: raigalScanInputSchema.shape
|
|
15186
15382
|
}, (input) => instrument("raigal_scan", () => handleRaigalScan(input)));
|
|
15187
|
-
server.registerTool(
|
|
15188
|
-
description:
|
|
15189
|
-
inputSchema:
|
|
15383
|
+
server.registerTool(raigalFixTool.name, {
|
|
15384
|
+
description: raigalFixTool.description,
|
|
15385
|
+
inputSchema: raigalFixInputSchema.shape
|
|
15190
15386
|
}, (input) => instrument("raigal_fix", () => handleRaigalFix(input)));
|
|
15191
|
-
server.registerTool(
|
|
15192
|
-
description:
|
|
15193
|
-
inputSchema:
|
|
15387
|
+
server.registerTool(raigalWhyTool.name, {
|
|
15388
|
+
description: raigalWhyTool.description,
|
|
15389
|
+
inputSchema: raigalWhyInputSchema.shape
|
|
15194
15390
|
}, (input) => instrument("raigal_why", () => handleRaigalWhy(input)));
|
|
15195
|
-
server.registerTool(
|
|
15196
|
-
description:
|
|
15197
|
-
inputSchema:
|
|
15391
|
+
server.registerTool(raigalBaselineTool.name, {
|
|
15392
|
+
description: raigalBaselineTool.description,
|
|
15393
|
+
inputSchema: raigalBaselineInputSchema.shape
|
|
15198
15394
|
}, (input) => instrument("raigal_baseline", () => handleRaigalBaseline(input)));
|
|
15199
15395
|
return server;
|
|
15200
15396
|
};
|