@methiu/raigal 0.2.0 → 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/astro.d.ts +1 -6
- package/dist/adapters/astro.js +1 -5
- package/dist/adapters/expo.d.ts +2 -6
- package/dist/adapters/expo.js +2 -5
- package/dist/adapters/nuxt.d.ts +1 -6
- package/dist/adapters/nuxt.js +1 -5
- package/dist/adapters/sveltekit.d.ts +1 -6
- package/dist/adapters/sveltekit.js +3 -7
- package/dist/adapters/vite.d.ts +1 -7
- package/dist/adapters/vite.js +3 -7
- package/dist/cli.js +1582 -865
- package/dist/{finding-assessment-M5fUTE-C.js → finding-assessment-CGpt5YUR.js} +3 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +1529 -157
- package/dist/{json-C9fBWj3V.js → json-BaycSoJP.js} +2 -1
- package/dist/{json-C2IwKr9o.js → json-BflWt4dX.js} +4 -3
- package/dist/mcp.js +500 -126
- package/dist/{mount-DTc8WaIt.js → mount-CHGc3gB6.js} +1 -1
- package/dist/{sarif--DI_2p2r.js → sarif-CdtsAfJ1.js} +1 -1
- package/dist/{sarif-Do8E1xhZ.js → sarif-CpuYM3aK.js} +2 -2
- package/dist/{version-v4OAaBLZ.js → version-B4eYJn3o.js} +1 -1
- package/package.json +8 -6
- package/scripts/release-guard.mjs +2 -2
package/dist/mcp.js
CHANGED
|
@@ -12,7 +12,7 @@ import { createPublicKey, randomUUID, verify } from "node:crypto";
|
|
|
12
12
|
import { spawn, spawnSync } from "node:child_process";
|
|
13
13
|
import { z as z$1 } from "zod";
|
|
14
14
|
import YAML, { parse } from "yaml";
|
|
15
|
-
import
|
|
15
|
+
import picomatch from "picomatch";
|
|
16
16
|
import ts from "typescript";
|
|
17
17
|
import { parse as parse$1 } from "smol-toml";
|
|
18
18
|
//#region src/cloud/paths.ts
|
|
@@ -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);
|
|
@@ -473,6 +475,10 @@ const handleEntitlementFetchError = (err, cached, cachedClaims, now) => {
|
|
|
473
475
|
nowSeconds: now,
|
|
474
476
|
allowGraceSeconds: GRACE_PERIOD_SECONDS
|
|
475
477
|
});
|
|
478
|
+
if (graceClaims.plan === "trial" && graceClaims.trial_ends_at && now > Math.floor(new Date(graceClaims.trial_ends_at).getTime() / 1e3)) {
|
|
479
|
+
clearCachedEntitlement();
|
|
480
|
+
throw new LicenceDeniedError("Organization trial has expired", "trial_expired");
|
|
481
|
+
}
|
|
476
482
|
return {
|
|
477
483
|
jwt: cached.entitlement,
|
|
478
484
|
claims: graceClaims,
|
|
@@ -480,6 +486,7 @@ const handleEntitlementFetchError = (err, cached, cachedClaims, now) => {
|
|
|
480
486
|
grace: now > graceClaims.exp
|
|
481
487
|
};
|
|
482
488
|
} catch (jwtErr) {
|
|
489
|
+
if (jwtErr instanceof LicenceDeniedError) throw jwtErr;
|
|
483
490
|
if (jwtErr instanceof JwtVerificationError && jwtErr.code === "expired_beyond_grace") throw new LicenceUnverifiableError(`Raigal license could not be verified online and the offline grace period expired on ${(/* @__PURE__ */ new Date((cachedClaims?.exp ?? 0) * 1e3)).toISOString()}. Check network connectivity or Raigal Cloud status.`);
|
|
484
491
|
}
|
|
485
492
|
throw new LicenceUnverifiableError(`Raigal license could not be verified online (${err instanceof Error ? err.message : String(err)}). Check network connectivity or Raigal Cloud status.`);
|
|
@@ -494,7 +501,8 @@ const getEntitlement = async () => {
|
|
|
494
501
|
} catch {
|
|
495
502
|
cachedClaims = null;
|
|
496
503
|
}
|
|
497
|
-
|
|
504
|
+
const isTrialExpired = cachedClaims?.plan === "trial" && cachedClaims.trial_ends_at && now > Math.floor(new Date(cachedClaims.trial_ends_at).getTime() / 1e3);
|
|
505
|
+
if (cached && cachedClaims && !isTrialExpired && now < cached.refresh_after) return {
|
|
498
506
|
jwt: cached.entitlement,
|
|
499
507
|
claims: cachedClaims,
|
|
500
508
|
fromCache: true
|
|
@@ -503,7 +511,7 @@ const getEntitlement = async () => {
|
|
|
503
511
|
if (!activeToken) {
|
|
504
512
|
oidcToken = await fetchGitHubOidcToken();
|
|
505
513
|
if (!oidcToken) {
|
|
506
|
-
if (cached && cachedClaims) return {
|
|
514
|
+
if (cached && cachedClaims && !isTrialExpired) return {
|
|
507
515
|
jwt: cached.entitlement,
|
|
508
516
|
claims: cachedClaims,
|
|
509
517
|
fromCache: true
|
|
@@ -512,7 +520,8 @@ const getEntitlement = async () => {
|
|
|
512
520
|
}
|
|
513
521
|
} else oidcToken = await fetchGitHubOidcToken();
|
|
514
522
|
try {
|
|
515
|
-
const
|
|
523
|
+
const tokenToPass = activeToken?.token ?? "";
|
|
524
|
+
const response = await fetchEntitlementFromApi(tokenToPass, oidcToken);
|
|
516
525
|
const claims = verifyEntitlementJwt(response.entitlement, { nowSeconds: now });
|
|
517
526
|
saveCachedEntitlement(response.entitlement, response.refresh_after);
|
|
518
527
|
return {
|
|
@@ -526,7 +535,7 @@ const getEntitlement = async () => {
|
|
|
526
535
|
};
|
|
527
536
|
//#endregion
|
|
528
537
|
//#region src/cloud/owner.ts
|
|
529
|
-
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*$/;
|
|
530
539
|
const parseGitRemote = (rawUrl) => {
|
|
531
540
|
if (!rawUrl) return {
|
|
532
541
|
owner: null,
|
|
@@ -664,6 +673,23 @@ const requireEntitlement = async (options = {}) => {
|
|
|
664
673
|
try {
|
|
665
674
|
const entitlement = await getEntitlement();
|
|
666
675
|
const tokenKind = getActiveToken()?.kind ?? "session";
|
|
676
|
+
if (entitlement.claims.plan === "trial" && entitlement.claims.trial_ends_at) {
|
|
677
|
+
const trialEnd = new Date(entitlement.claims.trial_ends_at).getTime();
|
|
678
|
+
if (Date.now() > trialEnd) {
|
|
679
|
+
const message = "Raigal license denied: Organization trial has expired";
|
|
680
|
+
if (mode === "throw") throw new LicenceDeniedError(message, "trial_expired");
|
|
681
|
+
if (mode === "fatal") {
|
|
682
|
+
process.stderr.write(`${message}\n`);
|
|
683
|
+
process.exit(30);
|
|
684
|
+
}
|
|
685
|
+
process.stderr.write(`[raigal] ${message}\n`);
|
|
686
|
+
return {
|
|
687
|
+
ok: false,
|
|
688
|
+
exitCode: 30,
|
|
689
|
+
message
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
667
693
|
const detectedOwner = detectLocalRepoOwner(directory);
|
|
668
694
|
if (options.repoOwner) detectedOwner.owner = options.repoOwner.toLowerCase();
|
|
669
695
|
const ownerCheck = checkOwnerAgainstAllowlist(detectedOwner, entitlement.claims.allowed_owners, tokenKind);
|
|
@@ -784,12 +810,13 @@ const DEFAULT_CONFIG = {
|
|
|
784
810
|
format: "json"
|
|
785
811
|
},
|
|
786
812
|
telemetry: { enabled: true },
|
|
787
|
-
rules: {}
|
|
813
|
+
rules: {},
|
|
814
|
+
overrides: []
|
|
788
815
|
};
|
|
789
816
|
//#endregion
|
|
790
817
|
//#region src/config/extends.ts
|
|
791
818
|
const MAX_DEPTH$1 = 5;
|
|
792
|
-
const MAX_CONFIG_BYTES =
|
|
819
|
+
const MAX_CONFIG_BYTES = 1048576;
|
|
793
820
|
const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
794
821
|
const deepMerge = (...sources) => {
|
|
795
822
|
const result = {};
|
|
@@ -970,12 +997,25 @@ const CiSchema = z.object({
|
|
|
970
997
|
format: z.enum(["json"]).default("json")
|
|
971
998
|
}).strict();
|
|
972
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();
|
|
973
1003
|
const RuleSeverityOverride = z.enum([
|
|
974
1004
|
"error",
|
|
975
1005
|
"warning",
|
|
976
1006
|
"off"
|
|
977
1007
|
]);
|
|
978
1008
|
const RulesSchema = z.record(z.string(), RuleSeverityOverride).default(() => ({}));
|
|
1009
|
+
const FileOverrideSchema = z.object({
|
|
1010
|
+
files: z.array(z.string().min(1)).min(1),
|
|
1011
|
+
quality: z.object({
|
|
1012
|
+
maxFunctionLoc: QualitySchema.shape.maxFunctionLoc.unwrap().optional(),
|
|
1013
|
+
maxFileLoc: QualitySchema.shape.maxFileLoc.unwrap().optional(),
|
|
1014
|
+
maxNesting: QualitySchema.shape.maxNesting.unwrap().optional(),
|
|
1015
|
+
maxParams: QualitySchema.shape.maxParams.unwrap().optional()
|
|
1016
|
+
}).optional(),
|
|
1017
|
+
rules: z.record(z.string(), RuleSeverityOverride).optional()
|
|
1018
|
+
});
|
|
979
1019
|
const AislopConfigSchema = z.object({
|
|
980
1020
|
version: z.number().default(1),
|
|
981
1021
|
extends: z.union([z.string(), z.array(z.string())]).optional(),
|
|
@@ -1030,7 +1070,11 @@ const AislopConfigSchema = z.object({
|
|
|
1030
1070
|
format: "json"
|
|
1031
1071
|
})),
|
|
1032
1072
|
telemetry: TelemetrySchema.default(() => ({ enabled: true })),
|
|
1073
|
+
baseline: BaselineConfigSchema.optional(),
|
|
1074
|
+
licensing: LicensingConfigSchema.optional(),
|
|
1075
|
+
workspace: WorkspaceConfigSchema.optional(),
|
|
1033
1076
|
rules: RulesSchema,
|
|
1077
|
+
overrides: z.array(FileOverrideSchema).default(() => []),
|
|
1034
1078
|
exclude: z.array(z.string()).default(() => [
|
|
1035
1079
|
"node_modules",
|
|
1036
1080
|
".git",
|
|
@@ -1062,6 +1106,9 @@ const KNOWN_KEYS_BY_PATH = {
|
|
|
1062
1106
|
"scoring",
|
|
1063
1107
|
"ci",
|
|
1064
1108
|
"telemetry",
|
|
1109
|
+
"baseline",
|
|
1110
|
+
"licensing",
|
|
1111
|
+
"workspace",
|
|
1065
1112
|
"rules",
|
|
1066
1113
|
"exclude",
|
|
1067
1114
|
"include"
|
|
@@ -1112,7 +1159,10 @@ const KNOWN_KEYS_BY_PATH = {
|
|
|
1112
1159
|
],
|
|
1113
1160
|
"scoring.thresholds": ["good", "ok"],
|
|
1114
1161
|
ci: ["failBelow", "format"],
|
|
1115
|
-
telemetry: ["enabled"]
|
|
1162
|
+
telemetry: ["enabled"],
|
|
1163
|
+
baseline: ["mode"],
|
|
1164
|
+
licensing: ["onLapse"],
|
|
1165
|
+
workspace: ["minimumFloor"]
|
|
1116
1166
|
};
|
|
1117
1167
|
const levenshtein = (a, b) => {
|
|
1118
1168
|
const m = a.length;
|
|
@@ -1242,13 +1292,140 @@ const loadConfig = (directory) => {
|
|
|
1242
1292
|
const configPath = findConfigFile(directory);
|
|
1243
1293
|
if (!configPath) return DEFAULT_CONFIG;
|
|
1244
1294
|
try {
|
|
1245
|
-
|
|
1295
|
+
const merged = loadConfigChain(configPath);
|
|
1296
|
+
return validateConfig(merged, configPath);
|
|
1246
1297
|
} catch (error) {
|
|
1247
1298
|
if (error instanceof ConfigError) throw error;
|
|
1248
1299
|
throw new ConfigError(configPath, `Failed to parse ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1249
1300
|
}
|
|
1250
1301
|
};
|
|
1251
1302
|
//#endregion
|
|
1303
|
+
//#region src/utils/globs.ts
|
|
1304
|
+
const MAX_BRACE_EXPANSIONS = 256;
|
|
1305
|
+
const braceListAt = (pattern, start) => {
|
|
1306
|
+
let depth = 0;
|
|
1307
|
+
const commas = [];
|
|
1308
|
+
for (let i = start; i < pattern.length; i += 1) {
|
|
1309
|
+
const char = pattern[i];
|
|
1310
|
+
if (char === "\\") i += 1;
|
|
1311
|
+
else if (char === "{") depth += 1;
|
|
1312
|
+
else if (char === ",") {
|
|
1313
|
+
if (depth === 1) commas.push(i);
|
|
1314
|
+
} else if (char === "}") {
|
|
1315
|
+
depth -= 1;
|
|
1316
|
+
if (depth === 0) return commas.length > 0 ? {
|
|
1317
|
+
start,
|
|
1318
|
+
end: i,
|
|
1319
|
+
commas
|
|
1320
|
+
} : null;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
return null;
|
|
1324
|
+
};
|
|
1325
|
+
const findBraceList = (pattern) => {
|
|
1326
|
+
for (let start = pattern.indexOf("{"); start !== -1; start = pattern.indexOf("{", start + 1)) {
|
|
1327
|
+
if (start > 0 && pattern[start - 1] === "\\") continue;
|
|
1328
|
+
const list = braceListAt(pattern, start);
|
|
1329
|
+
if (list) return list;
|
|
1330
|
+
}
|
|
1331
|
+
return null;
|
|
1332
|
+
};
|
|
1333
|
+
const expandInto = (pattern, results) => {
|
|
1334
|
+
const list = findBraceList(pattern);
|
|
1335
|
+
if (!list) {
|
|
1336
|
+
results.push(pattern);
|
|
1337
|
+
return results.length <= MAX_BRACE_EXPANSIONS;
|
|
1338
|
+
}
|
|
1339
|
+
const bounds = [
|
|
1340
|
+
list.start,
|
|
1341
|
+
...list.commas,
|
|
1342
|
+
list.end
|
|
1343
|
+
];
|
|
1344
|
+
const prefix = pattern.slice(0, list.start);
|
|
1345
|
+
const suffix = pattern.slice(list.end + 1);
|
|
1346
|
+
for (let i = 0; i < bounds.length - 1; i += 1) {
|
|
1347
|
+
const option = pattern.slice(bounds[i] + 1, bounds[i + 1]);
|
|
1348
|
+
if (!expandInto(`${prefix}${option}${suffix}`, results)) return false;
|
|
1349
|
+
}
|
|
1350
|
+
return true;
|
|
1351
|
+
};
|
|
1352
|
+
const expandBraceLists = (pattern) => {
|
|
1353
|
+
const results = [];
|
|
1354
|
+
return expandInto(pattern, results) ? results : [pattern];
|
|
1355
|
+
};
|
|
1356
|
+
const isNegated = (pattern) => picomatch.scan(pattern).negated;
|
|
1357
|
+
const matchesGlobList = (value, patterns, options = {}) => {
|
|
1358
|
+
const expanded = patterns.flatMap(expandBraceLists);
|
|
1359
|
+
const positives = expanded.filter((pattern) => !isNegated(pattern));
|
|
1360
|
+
const negatives = expanded.filter(isNegated).map((pattern) => pattern.slice(1));
|
|
1361
|
+
const included = positives.length === 0 || picomatch.isMatch(value, positives, options);
|
|
1362
|
+
const excluded = negatives.length > 0 && picomatch.isMatch(value, negatives, options);
|
|
1363
|
+
return included && !excluded;
|
|
1364
|
+
};
|
|
1365
|
+
//#endregion
|
|
1366
|
+
//#region src/scoring/rule-severity.ts
|
|
1367
|
+
/**
|
|
1368
|
+
* Apply per-rule severity overrides from config: "off" drops the diagnostic,
|
|
1369
|
+
* "error"/"warning" rewrite its severity before scoring and rendering.
|
|
1370
|
+
*/
|
|
1371
|
+
const applyRuleSeverities = (diagnostics, overrides) => {
|
|
1372
|
+
if (Object.keys(overrides).length === 0) return diagnostics;
|
|
1373
|
+
const result = [];
|
|
1374
|
+
for (const diagnostic of diagnostics) {
|
|
1375
|
+
const override = overrides[diagnostic.rule];
|
|
1376
|
+
if (!override) {
|
|
1377
|
+
result.push(diagnostic);
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
if (override === "off") continue;
|
|
1381
|
+
result.push(override === diagnostic.severity ? diagnostic : {
|
|
1382
|
+
...diagnostic,
|
|
1383
|
+
severity: override
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
return result;
|
|
1387
|
+
};
|
|
1388
|
+
//#endregion
|
|
1389
|
+
//#region src/config/overrides.ts
|
|
1390
|
+
const overrideRelativePath = (rootDirectory, filePath) => {
|
|
1391
|
+
const paths = path.win32.isAbsolute(rootDirectory) && !path.posix.isAbsolute(rootDirectory) ? path.win32 : path.posix;
|
|
1392
|
+
const root = rootDirectory.replaceAll("\\", "/");
|
|
1393
|
+
const file = filePath.replaceAll("\\", "/");
|
|
1394
|
+
return paths.normalize(paths.isAbsolute(file) ? paths.relative(root, file) : file).replaceAll("\\", "/");
|
|
1395
|
+
};
|
|
1396
|
+
const matchesEntry = (relativePath, patterns) => matchesGlobList(relativePath, patterns, { dot: true });
|
|
1397
|
+
const resolveFilePolicy = (config, relativePath) => {
|
|
1398
|
+
let quality = config.quality;
|
|
1399
|
+
let rules = config.rules ?? {};
|
|
1400
|
+
if (relativePath === ".." || relativePath.startsWith("../") || path.win32.isAbsolute(relativePath)) return {
|
|
1401
|
+
quality,
|
|
1402
|
+
rules
|
|
1403
|
+
};
|
|
1404
|
+
for (const entry of config.overrides ?? []) {
|
|
1405
|
+
if (!matchesEntry(relativePath, entry.files)) continue;
|
|
1406
|
+
if (entry.quality) quality = {
|
|
1407
|
+
...quality,
|
|
1408
|
+
...entry.quality
|
|
1409
|
+
};
|
|
1410
|
+
if (entry.rules) rules = {
|
|
1411
|
+
...rules,
|
|
1412
|
+
...entry.rules
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
return {
|
|
1416
|
+
quality,
|
|
1417
|
+
rules
|
|
1418
|
+
};
|
|
1419
|
+
};
|
|
1420
|
+
const applyFileOverrides = (diagnostics, context) => {
|
|
1421
|
+
if (!context.config.overrides?.length) return diagnostics;
|
|
1422
|
+
return diagnostics.flatMap((diagnostic) => {
|
|
1423
|
+
const relativePath = overrideRelativePath(context.rootDirectory, diagnostic.filePath);
|
|
1424
|
+
const { rules } = resolveFilePolicy(context.config, relativePath);
|
|
1425
|
+
return applyRuleSeverities([diagnostic], rules);
|
|
1426
|
+
});
|
|
1427
|
+
};
|
|
1428
|
+
//#endregion
|
|
1252
1429
|
//#region src/utils/paths.ts
|
|
1253
1430
|
/** Normalize an OS path to forward-slash (POSIX) separators. */
|
|
1254
1431
|
const toPosix = (p) => p.split(path.sep).join("/");
|
|
@@ -1275,13 +1452,13 @@ const normalizeExcludePatterns = (patterns) => patterns.flatMap((pattern) => {
|
|
|
1275
1452
|
const normalized = withoutProjectPrefix.slice(0, end);
|
|
1276
1453
|
if (normalized.length === 0) return [];
|
|
1277
1454
|
if (normalized.length > 256) return [];
|
|
1278
|
-
if (
|
|
1455
|
+
if (picomatch.scan(normalized).isGlob) return [normalized];
|
|
1279
1456
|
if (normalized.startsWith(".") && !normalized.includes("/")) return supportedGlobPatterns([`**/${normalized}`, `**/${normalized}/**`]);
|
|
1280
1457
|
return supportedGlobPatterns([normalized, `${normalized}/**`]);
|
|
1281
1458
|
});
|
|
1282
1459
|
const isPathExcluded = (relativePath, normalizedPatterns) => {
|
|
1283
1460
|
if (normalizedPatterns.length === 0) return false;
|
|
1284
|
-
return
|
|
1461
|
+
return picomatch.isMatch(relativePath, normalizedPatterns, { dot: true });
|
|
1285
1462
|
};
|
|
1286
1463
|
const filterExcludedDiagnostics = (diagnostics, rootDirectory, excludePatterns) => {
|
|
1287
1464
|
if (!excludePatterns || excludePatterns.length === 0) return diagnostics;
|
|
@@ -1294,7 +1471,7 @@ const filterExcludedDiagnostics = (diagnostics, rootDirectory, excludePatterns)
|
|
|
1294
1471
|
};
|
|
1295
1472
|
//#endregion
|
|
1296
1473
|
//#region src/utils/project-path-safety.ts
|
|
1297
|
-
const MAX_PROJECT_CONFIG_BYTES =
|
|
1474
|
+
const MAX_PROJECT_CONFIG_BYTES = 1048576;
|
|
1298
1475
|
const isWithinDirectory$2 = (rootDirectory, candidate) => {
|
|
1299
1476
|
const relative = path.relative(rootDirectory, candidate);
|
|
1300
1477
|
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
@@ -1381,7 +1558,7 @@ const isAutoGenerated = (filePath) => {
|
|
|
1381
1558
|
};
|
|
1382
1559
|
//#endregion
|
|
1383
1560
|
//#region src/utils/git-ignore.ts
|
|
1384
|
-
const MAX_BUFFER$1 =
|
|
1561
|
+
const MAX_BUFFER$1 = 52428800;
|
|
1385
1562
|
const NOT_IGNORED_ARGUMENTS = [
|
|
1386
1563
|
"ls-files",
|
|
1387
1564
|
"--cached",
|
|
@@ -1492,7 +1669,7 @@ const dropGitIgnoredPaths = (rootDirectory, absolutePaths) => {
|
|
|
1492
1669
|
};
|
|
1493
1670
|
//#endregion
|
|
1494
1671
|
//#region src/utils/project-file-list.ts
|
|
1495
|
-
const MAX_BUFFER =
|
|
1672
|
+
const MAX_BUFFER = 52428800;
|
|
1496
1673
|
const normalizePruneDirectories = (directories) => new Set([...directories].map((directory) => directory.toLowerCase()));
|
|
1497
1674
|
const isInPrunedDirectory = (filePath, pruneDirectories) => {
|
|
1498
1675
|
const pathSegments = filePath.split("/");
|
|
@@ -1703,12 +1880,13 @@ const normalizeIncludePatterns = (patterns) => patterns.flatMap((pattern) => {
|
|
|
1703
1880
|
const normalized = pattern.trim().replace(/^\.\//, "").replace(/\/$/, "");
|
|
1704
1881
|
if (normalized === "" || normalized === ".") return ["**"];
|
|
1705
1882
|
if (normalized.length > 256) return [];
|
|
1706
|
-
if (
|
|
1883
|
+
if (picomatch.scan(normalized).isGlob) return [normalized];
|
|
1707
1884
|
return supportedGlobPatterns([normalized, `${normalized}/**`]);
|
|
1708
1885
|
});
|
|
1709
1886
|
const createPathMatcher = (patterns) => {
|
|
1710
|
-
|
|
1711
|
-
|
|
1887
|
+
if (patterns.length === 0) return () => false;
|
|
1888
|
+
const isMatch = picomatch(patterns, { dot: true });
|
|
1889
|
+
return (filePath) => isMatch(filePath);
|
|
1712
1890
|
};
|
|
1713
1891
|
const filterFiles = (rootDirectory, files, options) => {
|
|
1714
1892
|
const extraExtensions = new Set(options.extraExtensions);
|
|
@@ -2405,7 +2583,7 @@ const tokenizeJs = (content, ext = ".ts") => {
|
|
|
2405
2583
|
};
|
|
2406
2584
|
//#endregion
|
|
2407
2585
|
//#region src/utils/source-masker.ts
|
|
2408
|
-
const JS_EXTS$
|
|
2586
|
+
const JS_EXTS$3 = /* @__PURE__ */ new Set([
|
|
2409
2587
|
".ts",
|
|
2410
2588
|
".tsx",
|
|
2411
2589
|
".js",
|
|
@@ -2413,7 +2591,7 @@ const JS_EXTS$2 = /* @__PURE__ */ new Set([
|
|
|
2413
2591
|
".mjs",
|
|
2414
2592
|
".cjs"
|
|
2415
2593
|
]);
|
|
2416
|
-
const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
|
|
2594
|
+
const PY_EXTS$1 = /* @__PURE__ */ new Set([".py"]);
|
|
2417
2595
|
const RB_EXTS = /* @__PURE__ */ new Set([".rb"]);
|
|
2418
2596
|
const PHP_EXTS = /* @__PURE__ */ new Set([".php"]);
|
|
2419
2597
|
const CSHARP_EXTS = /* @__PURE__ */ new Set([".cs"]);
|
|
@@ -2437,8 +2615,8 @@ const C_STYLE_COMMENT_EXTS = /* @__PURE__ */ new Set([
|
|
|
2437
2615
|
".zig"
|
|
2438
2616
|
]);
|
|
2439
2617
|
const familyForExt = (ext) => {
|
|
2440
|
-
if (JS_EXTS$
|
|
2441
|
-
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";
|
|
2442
2620
|
if (RB_EXTS.has(ext)) return "rb";
|
|
2443
2621
|
if (PHP_EXTS.has(ext)) return "php";
|
|
2444
2622
|
if (CSHARP_EXTS.has(ext)) return "csharp";
|
|
@@ -2670,7 +2848,7 @@ const getCommentLines = (content, ext) => {
|
|
|
2670
2848
|
};
|
|
2671
2849
|
//#endregion
|
|
2672
2850
|
//#region src/engines/ai-slop/abstractions.ts
|
|
2673
|
-
const JS_EXTS$
|
|
2851
|
+
const JS_EXTS$2 = /* @__PURE__ */ new Set([
|
|
2674
2852
|
".ts",
|
|
2675
2853
|
".tsx",
|
|
2676
2854
|
".js",
|
|
@@ -2681,11 +2859,11 @@ const JS_EXTS$1 = /* @__PURE__ */ new Set([
|
|
|
2681
2859
|
const THIN_WRAPPER_PATTERNS = [
|
|
2682
2860
|
{
|
|
2683
2861
|
pattern: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*(?::\s*\w[^{]*)?\{\s*\n?\s*return\s+\w+\([^)]*\);\s*\n?\s*\}/g,
|
|
2684
|
-
extensions: JS_EXTS$
|
|
2862
|
+
extensions: JS_EXTS$2
|
|
2685
2863
|
},
|
|
2686
2864
|
{
|
|
2687
2865
|
pattern: /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w[^=]*)?\s*=>\s*\w+\([^)]*\);/g,
|
|
2688
|
-
extensions: JS_EXTS$
|
|
2866
|
+
extensions: JS_EXTS$2
|
|
2689
2867
|
},
|
|
2690
2868
|
{
|
|
2691
2869
|
pattern: /def\s+(\w+)\s*\([^)]*\)(?:\s*->[^:]*)?:\s*\n\s+return\s+\w+\([^)]*\)\s*$/gm,
|
|
@@ -3933,7 +4111,7 @@ const scriptKindFor$1 = (ext) => {
|
|
|
3933
4111
|
}
|
|
3934
4112
|
};
|
|
3935
4113
|
const lineFor$1 = (sourceFile, node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
3936
|
-
const makeDiagnostic = (filePath, line, rule, message, help) => ({
|
|
4114
|
+
const makeDiagnostic$1 = (filePath, line, rule, message, help) => ({
|
|
3937
4115
|
filePath,
|
|
3938
4116
|
engine: "ai-slop",
|
|
3939
4117
|
rule,
|
|
@@ -3983,7 +4161,7 @@ const detectRedundantTryCatch = (sourceFile, relativePath) => {
|
|
|
3983
4161
|
if (ts.isTryStatement(node) && node.catchClause && !node.finallyBlock) {
|
|
3984
4162
|
const catchNameNode = node.catchClause.variableDeclaration?.name;
|
|
3985
4163
|
const [onlyStatement] = node.catchClause.block.statements;
|
|
3986
|
-
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."));
|
|
3987
4165
|
}
|
|
3988
4166
|
ts.forEachChild(node, visit);
|
|
3989
4167
|
};
|
|
@@ -4001,7 +4179,7 @@ const detectPrimitiveCoercions = (sourceFile, relativePath) => {
|
|
|
4001
4179
|
const [arg] = child.arguments;
|
|
4002
4180
|
if (arg && ts.isIdentifier(arg)) {
|
|
4003
4181
|
const primitive = params.get(arg.text);
|
|
4004
|
-
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."));
|
|
4005
4183
|
}
|
|
4006
4184
|
}
|
|
4007
4185
|
ts.forEachChild(child, visitBody);
|
|
@@ -4044,7 +4222,7 @@ const detectDuplicateExportedTypes = (parsedSources) => {
|
|
|
4044
4222
|
continue;
|
|
4045
4223
|
}
|
|
4046
4224
|
if (previous.filePath === declaration.filePath) continue;
|
|
4047
|
-
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.`));
|
|
4048
4226
|
}
|
|
4049
4227
|
}
|
|
4050
4228
|
return diagnostics;
|
|
@@ -4146,7 +4324,7 @@ const detectDuplicateImports = async (context) => {
|
|
|
4146
4324
|
rule: "ai-slop/duplicate-import",
|
|
4147
4325
|
severity: "warning",
|
|
4148
4326
|
message: `"${spec}" is also imported on line ${firstLine}. Merge into a single import statement.`,
|
|
4149
|
-
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.",
|
|
4150
4328
|
line: dup.line,
|
|
4151
4329
|
column: 1,
|
|
4152
4330
|
category: "AI Slop",
|
|
@@ -5854,7 +6032,8 @@ const isJsRelativeOrAbsolute = (spec) => spec.startsWith(".") || spec.startsWith
|
|
|
5854
6032
|
const RUNTIME_BUILTINS = /* @__PURE__ */ new Set(["bun"]);
|
|
5855
6033
|
const isJsBuiltin = (spec) => {
|
|
5856
6034
|
if (RUNTIME_BUILTINS.has(spec)) return true;
|
|
5857
|
-
|
|
6035
|
+
const stripped = spec.startsWith("node:") ? spec.slice(5) : spec;
|
|
6036
|
+
return isBuiltin(stripped) || isBuiltin(spec);
|
|
5858
6037
|
};
|
|
5859
6038
|
const VIRTUAL_MODULE_PREFIXES = [
|
|
5860
6039
|
"astro:",
|
|
@@ -6066,7 +6245,8 @@ const isLocalAliasReplacement = (rawValue, configDirectory, rootDirectory) => {
|
|
|
6066
6245
|
if (!target) return false;
|
|
6067
6246
|
if (!target.startsWith(".") && !target.startsWith("/") && !target.startsWith("~/") && !target.startsWith("@/")) return false;
|
|
6068
6247
|
const normalized = target.startsWith("~/") || target.startsWith("@/") ? target.slice(2) : target;
|
|
6069
|
-
|
|
6248
|
+
const candidate = path.isAbsolute(normalized) ? normalized : path.resolve(configDirectory, normalized);
|
|
6249
|
+
return isRootBoundedTarget(candidate, rootDirectory);
|
|
6070
6250
|
};
|
|
6071
6251
|
const hasValidPackageImportTarget = (value, packageDirectory, rootDirectory) => {
|
|
6072
6252
|
if (!isRootBoundedTarget(packageDirectory, rootDirectory)) return false;
|
|
@@ -7186,7 +7366,9 @@ const isRustDocCommentLine = (line) => {
|
|
|
7186
7366
|
};
|
|
7187
7367
|
const collectBlocks = (sourceLines, syntax, content, ext) => {
|
|
7188
7368
|
const blocks = [];
|
|
7189
|
-
const
|
|
7369
|
+
const actualContent = content ?? sourceLines.join("\n");
|
|
7370
|
+
const actualExt = ext ?? (syntax.linePrefixes.includes("//") ? ".ts" : ".py");
|
|
7371
|
+
const commentLines = getCommentLines(actualContent, actualExt);
|
|
7190
7372
|
let i = 0;
|
|
7191
7373
|
while (i < sourceLines.length) {
|
|
7192
7374
|
const line = sourceLines[i];
|
|
@@ -7514,7 +7696,8 @@ const detectNarrativeComments = async (context) => {
|
|
|
7514
7696
|
} catch {
|
|
7515
7697
|
continue;
|
|
7516
7698
|
}
|
|
7517
|
-
const
|
|
7699
|
+
const lines = content.split("\n");
|
|
7700
|
+
const blocks = collectBlocks(lines, syntax, content, ext);
|
|
7518
7701
|
for (const block of blocks) {
|
|
7519
7702
|
const { matched, reason } = detectNarrativeInBlock(block, ext);
|
|
7520
7703
|
if (!matched) continue;
|
|
@@ -7986,7 +8169,7 @@ const detectRustPatterns = async (context) => {
|
|
|
7986
8169
|
};
|
|
7987
8170
|
//#endregion
|
|
7988
8171
|
//#region src/engines/ai-slop/silent-recovery.ts
|
|
7989
|
-
const JS_EXTS = /* @__PURE__ */ new Set([
|
|
8172
|
+
const JS_EXTS$1 = /* @__PURE__ */ new Set([
|
|
7990
8173
|
".ts",
|
|
7991
8174
|
".tsx",
|
|
7992
8175
|
".js",
|
|
@@ -8181,7 +8364,7 @@ const detectSilentRecovery = async (context) => {
|
|
|
8181
8364
|
for (const filePath of files) {
|
|
8182
8365
|
if (isAutoGenerated(filePath)) continue;
|
|
8183
8366
|
const ext = path.extname(filePath);
|
|
8184
|
-
const isJs = JS_EXTS.has(ext);
|
|
8367
|
+
const isJs = JS_EXTS$1.has(ext);
|
|
8185
8368
|
if (!isJs && !(ext === ".py")) continue;
|
|
8186
8369
|
const relPath = relativePosix(context.rootDirectory, filePath);
|
|
8187
8370
|
if (isNonProductionPath(relPath)) continue;
|
|
@@ -8466,7 +8649,7 @@ const detectUnusedCss = async (context) => {
|
|
|
8466
8649
|
rule: "ai-slop/unused-css",
|
|
8467
8650
|
severity: "warning",
|
|
8468
8651
|
message: `CSS class '.${def.name}' is defined but never referenced in the codebase`,
|
|
8469
|
-
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)",
|
|
8470
8653
|
line: def.line,
|
|
8471
8654
|
column: 0,
|
|
8472
8655
|
category: "AI Slop",
|
|
@@ -9466,7 +9649,8 @@ const checkFunctionDiagnostics = (relativePath, fn, limits, ext) => {
|
|
|
9466
9649
|
const results = [];
|
|
9467
9650
|
const multiplier = functionLocMultiplier(fn, ext);
|
|
9468
9651
|
const fnMax = functionLocBudget(fn, ext, limits.maxFunctionLoc);
|
|
9469
|
-
|
|
9652
|
+
const effectiveLineCount = fn.lineCount - fn.templateLines;
|
|
9653
|
+
if (exceedsLimit(effectiveLineCount, limits.maxFunctionLoc, multiplier)) results.push({
|
|
9470
9654
|
filePath: relativePath,
|
|
9471
9655
|
engine: "code-quality",
|
|
9472
9656
|
rule: "complexity/function-too-long",
|
|
@@ -9523,10 +9707,10 @@ const checkFileComplexity = (filePath, rootDirectory, limits) => {
|
|
|
9523
9707
|
};
|
|
9524
9708
|
const checkComplexity = async (context) => {
|
|
9525
9709
|
const files = getSourceFiles(context);
|
|
9526
|
-
const limits = context.config.quality;
|
|
9527
9710
|
const diagnostics = [];
|
|
9528
9711
|
for (const filePath of files) {
|
|
9529
9712
|
if (isAutoGenerated(filePath)) continue;
|
|
9713
|
+
const { quality: limits } = resolveFilePolicy(context.config, overrideRelativePath(context.rootDirectory, filePath));
|
|
9530
9714
|
diagnostics.push(...checkFileComplexity(filePath, context.rootDirectory, limits));
|
|
9531
9715
|
}
|
|
9532
9716
|
return diagnostics;
|
|
@@ -9556,14 +9740,14 @@ const isTrivialLine = (line) => {
|
|
|
9556
9740
|
};
|
|
9557
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)=/;
|
|
9558
9742
|
const DATA_LITERAL_RE = /^\s*(?:[A-Za-z_$][\w$-]*:\s*(?:["'`[{]|\d|true\b|false\b|null\b)|["'`][^"'`]*["'`],?\s*$|[{}\]],?\s*$|\),?\s*$)/;
|
|
9559
|
-
const SUPPRESS_RE = /aislop[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
9560
|
-
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/;
|
|
9561
9745
|
const fileHasSuppression = (content) => FILE_SUPPRESS_RE.test(content);
|
|
9562
9746
|
const isLowSignalMarkupWindow = (lines) => {
|
|
9563
9747
|
return lines.filter((line) => SVG_MARKUP_RE.test(line)).length >= Math.ceil(WINDOW_SIZE / 2);
|
|
9564
9748
|
};
|
|
9565
9749
|
const isLowSignalDataWindow = (lines) => {
|
|
9566
|
-
return lines.filter((line) => DATA_LITERAL_RE.test(line)).length >=
|
|
9750
|
+
return lines.filter((line) => DATA_LITERAL_RE.test(line)).length >= 9;
|
|
9567
9751
|
};
|
|
9568
9752
|
const findSuppressedLines = (lines) => {
|
|
9569
9753
|
const suppressed = /* @__PURE__ */ new Set();
|
|
@@ -9595,7 +9779,7 @@ const collectMeaningfulLines = (content) => {
|
|
|
9595
9779
|
if (isLowSignalDataWindow(window)) continue;
|
|
9596
9780
|
if (window.every(isTrivialLine)) continue;
|
|
9597
9781
|
const normalised = window.map(normaliseLine);
|
|
9598
|
-
if (normalised.filter((n) => n.length > 0 && n !== "}" && n !== "{").length <
|
|
9782
|
+
if (normalised.filter((n) => n.length > 0 && n !== "}" && n !== "{").length < 9) continue;
|
|
9599
9783
|
if (new Set(normalised).size < MIN_DISTINCT_LINES) continue;
|
|
9600
9784
|
hits.push({
|
|
9601
9785
|
startLine: i + 1,
|
|
@@ -9883,8 +10067,8 @@ const shouldIncludeIssue = (issueType, filePath) => {
|
|
|
9883
10067
|
return !filePath.replace(/\\/g, "/").includes(".github/workflows/");
|
|
9884
10068
|
};
|
|
9885
10069
|
const DEPENDENCY_HELP = {
|
|
9886
|
-
dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `
|
|
9887
|
-
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`.",
|
|
9888
10072
|
unlisted: "This package is imported in code but not declared in package.json. Run `npm install` to add it.",
|
|
9889
10073
|
unresolved: "This import cannot be resolved. Check for typos or missing packages.",
|
|
9890
10074
|
binaries: "This binary is used but its package is not in package.json."
|
|
@@ -10516,13 +10700,23 @@ const withReportPath = async (run) => {
|
|
|
10516
10700
|
};
|
|
10517
10701
|
const UNSUPPORTED_GLOB_SYNTAX = /[?[\]{}!|()\\]/;
|
|
10518
10702
|
const isDotnetFormatPath = (pattern) => !pattern.startsWith("-") && !UNSUPPORTED_GLOB_SYNTAX.test(pattern);
|
|
10703
|
+
const expandBraces = (pattern, depth = 0) => {
|
|
10704
|
+
if (depth > 10) return [pattern];
|
|
10705
|
+
const match = /\{([^{}]+)\}/.exec(pattern);
|
|
10706
|
+
if (!match) return [pattern];
|
|
10707
|
+
const [fullMatch, options] = match;
|
|
10708
|
+
const parts = options.split(",");
|
|
10709
|
+
const prefix = pattern.slice(0, match.index);
|
|
10710
|
+
const suffix = pattern.slice(match.index + fullMatch.length);
|
|
10711
|
+
return parts.flatMap((part) => expandBraces(`${prefix}${part}${suffix}`, depth + 1));
|
|
10712
|
+
};
|
|
10519
10713
|
const buildDotnetFormatExcludeScope = (excludePatterns) => {
|
|
10520
10714
|
const scope = {
|
|
10521
10715
|
excludeArguments: [],
|
|
10522
10716
|
unsupportedPatterns: []
|
|
10523
10717
|
};
|
|
10524
10718
|
for (const pattern of normalizeExcludePatterns(excludePatterns ?? [])) {
|
|
10525
|
-
const expanded =
|
|
10719
|
+
const expanded = expandBraces(pattern);
|
|
10526
10720
|
if (expanded.every(isDotnetFormatPath)) scope.excludeArguments.push(...expanded);
|
|
10527
10721
|
else scope.unsupportedPatterns.push(pattern);
|
|
10528
10722
|
}
|
|
@@ -10716,7 +10910,7 @@ const runGofmt = async (context) => {
|
|
|
10716
10910
|
rule: "go-formatting",
|
|
10717
10911
|
severity: "warning",
|
|
10718
10912
|
message: "Go file is not formatted correctly",
|
|
10719
|
-
help: "Run `
|
|
10913
|
+
help: "Run `raigal fix` to auto-format with gofmt",
|
|
10720
10914
|
line: 0,
|
|
10721
10915
|
column: 0,
|
|
10722
10916
|
category: "Format",
|
|
@@ -10735,7 +10929,8 @@ const resolvePackageRoot = (startFile) => {
|
|
|
10735
10929
|
while (true) {
|
|
10736
10930
|
const packageJsonPath = path.join(current, "package.json");
|
|
10737
10931
|
if (fs.existsSync(packageJsonPath)) try {
|
|
10738
|
-
|
|
10932
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
10933
|
+
if (packageJson.name === "aislop" || packageJson.name === "@methiu/raigal" || packageJson.name === "raigal") return current;
|
|
10739
10934
|
} catch {}
|
|
10740
10935
|
const parent = path.dirname(current);
|
|
10741
10936
|
if (parent === current) break;
|
|
@@ -10846,7 +11041,7 @@ const parseRuffFormatOutput = (output, rootDir) => {
|
|
|
10846
11041
|
rule: "python-formatting",
|
|
10847
11042
|
severity: "warning",
|
|
10848
11043
|
message: "Python file is not formatted correctly",
|
|
10849
|
-
help: "Run `
|
|
11044
|
+
help: "Run `raigal fix` to auto-format with ruff",
|
|
10850
11045
|
line: 0,
|
|
10851
11046
|
column: 0,
|
|
10852
11047
|
category: "Format",
|
|
@@ -10919,7 +11114,8 @@ const parseClangTidyOutput = (output, rootDirectory) => {
|
|
|
10919
11114
|
const runClangTidy = async (context) => {
|
|
10920
11115
|
const compileCommandsDir = findCompileCommandsDir(context);
|
|
10921
11116
|
if (!compileCommandsDir) return [];
|
|
10922
|
-
const
|
|
11117
|
+
const implSources = findCppSources(context).filter((f) => CPP_IMPL_EXTENSIONS.has(path.extname(f).toLowerCase()));
|
|
11118
|
+
const sources = filterSourcesInDatabase(implSources, readCompileCommandsFiles(compileCommandsDir));
|
|
10923
11119
|
if (sources.length === 0) return [];
|
|
10924
11120
|
const diagnostics = [];
|
|
10925
11121
|
for (const chunk of chunkFilePaths(sources, CLANG_TIDY_CHUNK_MAX_FILES)) try {
|
|
@@ -11289,7 +11485,7 @@ const runExpoDoctor = async (context) => {
|
|
|
11289
11485
|
rule: "expo-doctor/config-error",
|
|
11290
11486
|
severity: "warning",
|
|
11291
11487
|
message: configError,
|
|
11292
|
-
help: "Install project dependencies, then re-run `
|
|
11488
|
+
help: "Install project dependencies, then re-run `raigal scan`.",
|
|
11293
11489
|
line: 0,
|
|
11294
11490
|
column: 0,
|
|
11295
11491
|
category: "Expo",
|
|
@@ -12107,7 +12303,7 @@ const normalizeEslintIgnorePatterns = (patterns) => patterns.map((pattern) => pa
|
|
|
12107
12303
|
if (!pattern.includes("*") && !path.extname(pattern)) return [`${pattern}/**`];
|
|
12108
12304
|
return [pattern];
|
|
12109
12305
|
});
|
|
12110
|
-
const isEslintIgnored = (relativePath, patterns) => patterns.length > 0 &&
|
|
12306
|
+
const isEslintIgnored = (relativePath, patterns) => patterns.length > 0 && picomatch.isMatch(relativePath, patterns, { dot: true });
|
|
12111
12307
|
const getOxlintTargets = (context) => {
|
|
12112
12308
|
const eslintIgnorePatterns = normalizeEslintIgnorePatterns(collectEslintIgnorePatterns(context.rootDirectory));
|
|
12113
12309
|
return getSourceFiles(context).filter((filePath) => OXLINT_EXTENSIONS.has(path.extname(filePath).toLowerCase())).filter((filePath) => !isAutoGenerated(filePath)).map((filePath) => path.relative(context.rootDirectory, filePath).split(path.sep).join("/")).filter((filePath) => !isEslintIgnored(filePath, eslintIgnorePatterns));
|
|
@@ -12253,7 +12449,8 @@ const resolveTrustedTscPath = () => {
|
|
|
12253
12449
|
};
|
|
12254
12450
|
const isReferenceOnlyConfig = (tsconfigPath) => {
|
|
12255
12451
|
try {
|
|
12256
|
-
const
|
|
12452
|
+
const raw = fs.readFileSync(tsconfigPath, "utf-8");
|
|
12453
|
+
const parsed = parseJsonc(raw);
|
|
12257
12454
|
if (!parsed) return false;
|
|
12258
12455
|
return Array.isArray(parsed.references) && !parsed.files && !parsed.include && !parsed.extends;
|
|
12259
12456
|
} catch {
|
|
@@ -12396,6 +12593,174 @@ const lintEngine = {
|
|
|
12396
12593
|
}
|
|
12397
12594
|
};
|
|
12398
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
|
|
12399
12764
|
//#region src/ui/invocation.ts
|
|
12400
12765
|
const detectInvocation = () => "raigal";
|
|
12401
12766
|
//#endregion
|
|
@@ -12778,7 +13143,7 @@ const parseJsAudit = (output, source, manifest) => {
|
|
|
12778
13143
|
rule: "security/dependency-audit-skipped",
|
|
12779
13144
|
severity: "info",
|
|
12780
13145
|
message: `Dependency audit skipped (${source}): lockfile is missing`,
|
|
12781
|
-
help: errorDetail ?? "Generate a lockfile, then re-run `
|
|
13146
|
+
help: errorDetail ?? "Generate a lockfile, then re-run `raigal scan` for dependency vulnerability checks.",
|
|
12782
13147
|
line: 0,
|
|
12783
13148
|
column: 0,
|
|
12784
13149
|
category: "Security",
|
|
@@ -12889,7 +13254,8 @@ const readRuntimeDependencies = (rootDir) => {
|
|
|
12889
13254
|
collectRuntimeNames(rootManifest, names);
|
|
12890
13255
|
const globs = [...npmWorkspaceGlobs(rootManifest), ...pnpmWorkspaceGlobs(rootDir)];
|
|
12891
13256
|
if (globs.length === 0) return { runtimeDependencies: names };
|
|
12892
|
-
|
|
13257
|
+
const isMatch = picomatch(globs, { dot: true });
|
|
13258
|
+
for (const relativeDir of listPackageDirectories(rootDir).filter((dir) => isMatch(dir))) {
|
|
12893
13259
|
const memberManifest = readJsonFile(path.join(rootDir, relativeDir, "package.json"));
|
|
12894
13260
|
if (memberManifest) collectRuntimeNames(memberManifest, names);
|
|
12895
13261
|
}
|
|
@@ -12951,10 +13317,11 @@ const auditSkippedDiagnostic = (source, help) => ({
|
|
|
12951
13317
|
});
|
|
12952
13318
|
const runNpmAudit = async (rootDir, timeout) => {
|
|
12953
13319
|
try {
|
|
12954
|
-
|
|
13320
|
+
const result = await runSubprocess("npm", ["audit", "--json"], {
|
|
12955
13321
|
cwd: rootDir,
|
|
12956
13322
|
timeout
|
|
12957
|
-
})
|
|
13323
|
+
});
|
|
13324
|
+
return parseJsAudit(result.stdout, "npm audit", readRuntimeDependencies(rootDir));
|
|
12958
13325
|
} catch (error) {
|
|
12959
13326
|
return [auditSkippedDiagnostic("npm audit", `Failed to run npm audit: ${errorMessageOf(error)}`)];
|
|
12960
13327
|
}
|
|
@@ -12975,10 +13342,11 @@ const runBunAudit = async (rootDir, timeout) => {
|
|
|
12975
13342
|
const runPnpmAuditWithFallback = async (rootDir, timeout) => {
|
|
12976
13343
|
const canFallbackToNpm = fs.existsSync(path.join(rootDir, "package-lock.json"));
|
|
12977
13344
|
try {
|
|
12978
|
-
const
|
|
13345
|
+
const result = await runSubprocess("pnpm", ["audit", "--json"], {
|
|
12979
13346
|
cwd: rootDir,
|
|
12980
13347
|
timeout
|
|
12981
|
-
})
|
|
13348
|
+
});
|
|
13349
|
+
const diagnostics = parseJsAudit(result.stdout, "pnpm audit", readRuntimeDependencies(rootDir));
|
|
12982
13350
|
if (diagnostics.some((d) => d.rule === "security/dependency-audit-skipped")) {
|
|
12983
13351
|
if (canFallbackToNpm) return runNpmAudit(rootDir, timeout);
|
|
12984
13352
|
return diagnostics;
|
|
@@ -13335,7 +13703,7 @@ const RISKY_PATTERNS = [
|
|
|
13335
13703
|
//#region src/engines/security/risky.ts
|
|
13336
13704
|
const hasDangerouslySetInnerHtmlIgnore = (lines, lineIndex) => {
|
|
13337
13705
|
const start = Math.max(0, lineIndex - 2);
|
|
13338
|
-
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));
|
|
13339
13707
|
};
|
|
13340
13708
|
const isStructuredDataScript = (content, matchIndex) => {
|
|
13341
13709
|
const before = content.slice(Math.max(0, matchIndex - 300), matchIndex);
|
|
@@ -13657,7 +14025,11 @@ const securityEngine = {
|
|
|
13657
14025
|
name: "security",
|
|
13658
14026
|
async run(context) {
|
|
13659
14027
|
const diagnostics = [];
|
|
13660
|
-
const promises = [
|
|
14028
|
+
const promises = [
|
|
14029
|
+
scanSecrets(context),
|
|
14030
|
+
detectRiskyConstructs(context),
|
|
14031
|
+
scanAiSecurity(context)
|
|
14032
|
+
];
|
|
13661
14033
|
if (context.config.security.audit) promises.push(runDependencyAudit(context));
|
|
13662
14034
|
const results = await Promise.allSettled(promises);
|
|
13663
14035
|
for (const result of results) if (result.status === "fulfilled") diagnostics.push(...result.value);
|
|
@@ -13758,6 +14130,7 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
|
|
|
13758
14130
|
const result = await engine.run(context);
|
|
13759
14131
|
result.diagnostics = filterExcludedDiagnostics(result.diagnostics, context.rootDirectory, context.excludePatterns);
|
|
13760
14132
|
result.elapsed = performance.now() - start;
|
|
14133
|
+
result.diagnostics = applyFileOverrides(result.diagnostics, context);
|
|
13761
14134
|
onComplete?.(result);
|
|
13762
14135
|
return result;
|
|
13763
14136
|
} catch (error) {
|
|
@@ -13766,6 +14139,7 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
|
|
|
13766
14139
|
diagnostics: [],
|
|
13767
14140
|
elapsed: performance.now() - start,
|
|
13768
14141
|
skipped: true,
|
|
14142
|
+
failed: true,
|
|
13769
14143
|
skipReason: error instanceof Error ? error.message : String(error)
|
|
13770
14144
|
};
|
|
13771
14145
|
onComplete?.(result);
|
|
@@ -13776,9 +14150,13 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
|
|
|
13776
14150
|
diagnostics: [],
|
|
13777
14151
|
elapsed: 0,
|
|
13778
14152
|
skipped: true,
|
|
14153
|
+
failed: true,
|
|
13779
14154
|
skipReason: r.reason instanceof Error ? r.reason.message : String(r.reason)
|
|
13780
14155
|
});
|
|
13781
|
-
|
|
14156
|
+
const stripped = stripSecretDiagnostics(finalResults);
|
|
14157
|
+
const csharp = dedupeCSharpAsync(stripped);
|
|
14158
|
+
const comments = dedupeOverlappingComments(csharp);
|
|
14159
|
+
return dedupeEquivalenceDiagnostics(comments);
|
|
13782
14160
|
};
|
|
13783
14161
|
const stripSecretDiagnostics = (results) => {
|
|
13784
14162
|
for (const result of results) for (const d of result.diagnostics) if (d.redactSource || isSecretClassRule(d.rule)) {
|
|
@@ -13924,6 +14302,9 @@ const RULE_SCORE_IMPACTS = {
|
|
|
13924
14302
|
"security/shell-injection": strict("Interpolated shell commands can become command execution."),
|
|
13925
14303
|
"security/unsafe-deserialization": strict("Deserializing untrusted data with a legacy .NET formatter can become remote code execution."),
|
|
13926
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."),
|
|
13927
14308
|
"security/dependency-audit-skipped": advisory("An unavailable audit is visibility loss, not evidence of a vulnerability."),
|
|
13928
14309
|
"dotnet/projects-skipped": advisory("Skipping unrestored C# projects is visibility loss, not evidence of a defect."),
|
|
13929
14310
|
"cppcheck/chunks-skipped": advisory("A skipped cppcheck chunk is visibility loss, not evidence that the skipped files have a defect."),
|
|
@@ -14000,7 +14381,8 @@ const calculateScore = (diagnostics, weights, thresholds, sourceFileCount, smoot
|
|
|
14000
14381
|
const capScale = Math.max(1, fileCount.effectiveSourceFileCount / CAP_REFERENCE_FILES);
|
|
14001
14382
|
const defaultRuleCap = typeof maxPerRule === "number" && maxPerRule > 0 ? maxPerRule : null;
|
|
14002
14383
|
const capForRule = (key) => {
|
|
14003
|
-
const
|
|
14384
|
+
const rule = key.slice(key.indexOf(":") + 1);
|
|
14385
|
+
const ruleCap = scoreImpactForRule(rule).cap;
|
|
14004
14386
|
if (typeof ruleCap === "number") return defaultRuleCap ? Math.min(defaultRuleCap, ruleCap) : ruleCap;
|
|
14005
14387
|
return defaultRuleCap;
|
|
14006
14388
|
};
|
|
@@ -14113,8 +14495,10 @@ const detectSourceLanguages = (sourceFiles) => {
|
|
|
14113
14495
|
const detectLanguages = (directory, sourceFiles) => {
|
|
14114
14496
|
const languages = new Set(detectSourceLanguages(sourceFiles));
|
|
14115
14497
|
for (const [file, lang] of Object.entries(LANGUAGE_SIGNALS)) if (fs.existsSync(path.join(directory, file))) languages.add(lang);
|
|
14116
|
-
if (readPackageJson(path.join(directory, "package.json")))
|
|
14117
|
-
|
|
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
|
+
}
|
|
14118
14502
|
for (const signal of PYTHON_SIGNALS) if (fs.existsSync(path.join(directory, signal))) {
|
|
14119
14503
|
languages.add("python");
|
|
14120
14504
|
break;
|
|
@@ -14305,14 +14689,19 @@ const hasExistingLiteralBackslashPath = (cwd, fingerprint) => {
|
|
|
14305
14689
|
}
|
|
14306
14690
|
};
|
|
14307
14691
|
const migrateMarkerlessFingerprint = (cwd, fingerprint) => hasExistingLiteralBackslashPath(cwd, fingerprint) ? fingerprint : normalizeLegacyFingerprint(fingerprint);
|
|
14308
|
-
const
|
|
14309
|
-
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
|
+
};
|
|
14310
14699
|
const readBaseline = (cwd) => {
|
|
14311
14700
|
const raw = readIfExists(baselinePath(cwd));
|
|
14312
14701
|
if (!raw) return null;
|
|
14313
14702
|
try {
|
|
14314
14703
|
const parsed = JSON.parse(raw);
|
|
14315
|
-
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;
|
|
14316
14705
|
const findingFingerprints = parsed.findingFingerprints ?? [];
|
|
14317
14706
|
const needsWindowsMigration = parsed.pathFormat !== "posix";
|
|
14318
14707
|
return {
|
|
@@ -14449,6 +14838,8 @@ const buildEngineContext = (rootDirectory, project, config) => {
|
|
|
14449
14838
|
frameworks: project.frameworks,
|
|
14450
14839
|
installedTools: project.installedTools,
|
|
14451
14840
|
config: {
|
|
14841
|
+
overrides: config.overrides,
|
|
14842
|
+
rules: config.rules,
|
|
14452
14843
|
quality: config.quality,
|
|
14453
14844
|
security: config.security,
|
|
14454
14845
|
lint: config.lint,
|
|
@@ -14495,7 +14886,9 @@ const summariseDiagnostics = (diagnostics, rootDirectory) => {
|
|
|
14495
14886
|
const runScan = async (cwd) => {
|
|
14496
14887
|
const project = await discoverProject(cwd);
|
|
14497
14888
|
const config = loadConfig(cwd);
|
|
14498
|
-
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);
|
|
14499
14892
|
const { score } = calculateScore(diagnostics, config.scoring.weights, config.scoring.thresholds, project.sourceFileCount, config.scoring.smoothing, config.scoring.maxPerRule);
|
|
14500
14893
|
const errorCount = diagnostics.filter((d) => d.severity === "error").length;
|
|
14501
14894
|
const failBelow = config.ci.failBelow;
|
|
@@ -14510,12 +14903,11 @@ const runScan = async (cwd) => {
|
|
|
14510
14903
|
}
|
|
14511
14904
|
};
|
|
14512
14905
|
};
|
|
14513
|
-
const
|
|
14514
|
-
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 = {
|
|
14515
14908
|
name: "raigal_scan",
|
|
14516
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.",
|
|
14517
|
-
inputSchema:
|
|
14518
|
-
name: "aislop_scan"
|
|
14910
|
+
inputSchema: raigalScanInputSchema
|
|
14519
14911
|
};
|
|
14520
14912
|
const handleRaigalScan = async (input) => {
|
|
14521
14913
|
const cwd = resolveCwd(input.path);
|
|
@@ -14530,8 +14922,7 @@ const handleRaigalScan = async (input) => {
|
|
|
14530
14922
|
...summary
|
|
14531
14923
|
};
|
|
14532
14924
|
};
|
|
14533
|
-
const
|
|
14534
|
-
const aislopFixInputSchema = z$1.object({
|
|
14925
|
+
const raigalFixInputSchema = z$1.object({
|
|
14535
14926
|
path: z$1.string().optional().describe("Project directory to fix, confined to the MCP server's cwd. Defaults to that cwd."),
|
|
14536
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.")
|
|
14537
14928
|
});
|
|
@@ -14561,11 +14952,10 @@ const runRaigalFix = (cwd, force) => {
|
|
|
14561
14952
|
}));
|
|
14562
14953
|
});
|
|
14563
14954
|
};
|
|
14564
|
-
const
|
|
14955
|
+
const raigalFixTool = {
|
|
14565
14956
|
name: "raigal_fix",
|
|
14566
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.",
|
|
14567
|
-
inputSchema:
|
|
14568
|
-
name: "aislop_fix"
|
|
14958
|
+
inputSchema: raigalFixInputSchema
|
|
14569
14959
|
};
|
|
14570
14960
|
const handleRaigalFix = async (input) => {
|
|
14571
14961
|
const cwd = resolveCwd(input.path);
|
|
@@ -14587,13 +14977,11 @@ const handleRaigalFix = async (input) => {
|
|
|
14587
14977
|
elided: summary.elided
|
|
14588
14978
|
};
|
|
14589
14979
|
};
|
|
14590
|
-
const
|
|
14591
|
-
const
|
|
14592
|
-
const aislopWhyTool = {
|
|
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 = {
|
|
14593
14982
|
name: "raigal_why",
|
|
14594
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.",
|
|
14595
|
-
inputSchema:
|
|
14596
|
-
name: "aislop_why"
|
|
14984
|
+
inputSchema: raigalWhyInputSchema
|
|
14597
14985
|
};
|
|
14598
14986
|
const handleRaigalWhy = (input) => {
|
|
14599
14987
|
const ruleId = input.rule_id.trim();
|
|
@@ -14606,13 +14994,11 @@ const handleRaigalWhy = (input) => {
|
|
|
14606
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."
|
|
14607
14995
|
};
|
|
14608
14996
|
};
|
|
14609
|
-
const
|
|
14610
|
-
const
|
|
14611
|
-
const aislopBaselineTool = {
|
|
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 = {
|
|
14612
14999
|
name: "raigal_baseline",
|
|
14613
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).",
|
|
14614
|
-
inputSchema:
|
|
14615
|
-
name: "aislop_baseline"
|
|
15001
|
+
inputSchema: raigalBaselineInputSchema
|
|
14616
15002
|
};
|
|
14617
15003
|
const handleRaigalBaseline = (input) => {
|
|
14618
15004
|
const baseline = readBaseline(resolveCwd(input.path));
|
|
@@ -14627,7 +15013,6 @@ const handleRaigalBaseline = (input) => {
|
|
|
14627
15013
|
hint: "Run `raigal hook baseline` to capture a baseline."
|
|
14628
15014
|
};
|
|
14629
15015
|
};
|
|
14630
|
-
const handleAislopBaseline = handleRaigalBaseline;
|
|
14631
15016
|
//#endregion
|
|
14632
15017
|
//#region src/telemetry/env.ts
|
|
14633
15018
|
const ALLOWED_OVERRIDE = /* @__PURE__ */ new Set([
|
|
@@ -14766,6 +15151,10 @@ const SAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set([
|
|
|
14766
15151
|
"exit_code",
|
|
14767
15152
|
"duration_ms",
|
|
14768
15153
|
"error_kind",
|
|
15154
|
+
"error_name",
|
|
15155
|
+
"error_code",
|
|
15156
|
+
"failed_stage",
|
|
15157
|
+
"engines_failed",
|
|
14769
15158
|
"score",
|
|
14770
15159
|
"score_bucket",
|
|
14771
15160
|
"finding_count",
|
|
@@ -14879,10 +15268,11 @@ const track = (input) => {
|
|
|
14879
15268
|
cachedInstallId = ensured.installId;
|
|
14880
15269
|
installCreated = ensured.created;
|
|
14881
15270
|
}
|
|
14882
|
-
const
|
|
15271
|
+
const merged = {
|
|
14883
15272
|
...baseProperties(cachedInstallId),
|
|
14884
15273
|
...input.properties
|
|
14885
|
-
}
|
|
15274
|
+
};
|
|
15275
|
+
const { clean, dropped } = redactProperties(merged);
|
|
14886
15276
|
if (isDebug()) {
|
|
14887
15277
|
const compact = JSON.stringify({
|
|
14888
15278
|
event: input.event,
|
|
@@ -14986,38 +15376,22 @@ const buildServer = () => {
|
|
|
14986
15376
|
name: "raigal",
|
|
14987
15377
|
version: APP_VERSION
|
|
14988
15378
|
});
|
|
14989
|
-
server.registerTool(
|
|
14990
|
-
description:
|
|
14991
|
-
inputSchema:
|
|
14992
|
-
}, (input) => instrument("
|
|
14993
|
-
server.registerTool(
|
|
14994
|
-
description:
|
|
14995
|
-
inputSchema:
|
|
14996
|
-
}, (input) => instrument("
|
|
14997
|
-
server.registerTool(
|
|
14998
|
-
description:
|
|
14999
|
-
inputSchema:
|
|
15000
|
-
}, (input) => instrument("
|
|
15001
|
-
server.registerTool(
|
|
15002
|
-
description:
|
|
15003
|
-
inputSchema:
|
|
15004
|
-
}, (input) => instrument("
|
|
15005
|
-
server.registerTool("raigal_scan", {
|
|
15006
|
-
description: aislopScanTool.description,
|
|
15007
|
-
inputSchema: aislopScanInputSchema.shape
|
|
15008
|
-
}, (input) => instrument("raigal_scan", () => handleAislopScan(input)));
|
|
15009
|
-
server.registerTool("raigal_fix", {
|
|
15010
|
-
description: aislopFixTool.description,
|
|
15011
|
-
inputSchema: aislopFixInputSchema.shape
|
|
15012
|
-
}, (input) => instrument("raigal_fix", () => handleAislopFix(input)));
|
|
15013
|
-
server.registerTool("raigal_why", {
|
|
15014
|
-
description: aislopWhyTool.description,
|
|
15015
|
-
inputSchema: aislopWhyInputSchema.shape
|
|
15016
|
-
}, (input) => instrument("raigal_why", () => handleAislopWhy(input)));
|
|
15017
|
-
server.registerTool("raigal_baseline", {
|
|
15018
|
-
description: aislopBaselineTool.description,
|
|
15019
|
-
inputSchema: aislopBaselineInputSchema.shape
|
|
15020
|
-
}, (input) => instrument("raigal_baseline", () => handleAislopBaseline(input)));
|
|
15379
|
+
server.registerTool(raigalScanTool.name, {
|
|
15380
|
+
description: raigalScanTool.description,
|
|
15381
|
+
inputSchema: raigalScanInputSchema.shape
|
|
15382
|
+
}, (input) => instrument("raigal_scan", () => handleRaigalScan(input)));
|
|
15383
|
+
server.registerTool(raigalFixTool.name, {
|
|
15384
|
+
description: raigalFixTool.description,
|
|
15385
|
+
inputSchema: raigalFixInputSchema.shape
|
|
15386
|
+
}, (input) => instrument("raigal_fix", () => handleRaigalFix(input)));
|
|
15387
|
+
server.registerTool(raigalWhyTool.name, {
|
|
15388
|
+
description: raigalWhyTool.description,
|
|
15389
|
+
inputSchema: raigalWhyInputSchema.shape
|
|
15390
|
+
}, (input) => instrument("raigal_why", () => handleRaigalWhy(input)));
|
|
15391
|
+
server.registerTool(raigalBaselineTool.name, {
|
|
15392
|
+
description: raigalBaselineTool.description,
|
|
15393
|
+
inputSchema: raigalBaselineInputSchema.shape
|
|
15394
|
+
}, (input) => instrument("raigal_baseline", () => handleRaigalBaseline(input)));
|
|
15021
15395
|
return server;
|
|
15022
15396
|
};
|
|
15023
15397
|
const main = async () => {
|