@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/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as APP_VERSION } from "./version-
|
|
2
|
-
import { a as getEngineLabel, i as ENGINE_INFO, r as scoreImpactForRule, t as summarizeFindingAssessments } from "./finding-assessment-
|
|
1
|
+
import { t as APP_VERSION } from "./version-B4eYJn3o.js";
|
|
2
|
+
import { a as getEngineLabel, i as ENGINE_INFO, r as scoreImpactForRule, t as summarizeFindingAssessments } from "./finding-assessment-CGpt5YUR.js";
|
|
3
3
|
import { n as isSecretClassRule, r as maskSecrets, t as FIXED_SECRET_DIAGNOSTIC_MESSAGE } from "./mask-secrets-BXn-ZSEh.js";
|
|
4
4
|
import { createRequire, isBuiltin } from "node:module";
|
|
5
5
|
import path from "node:path";
|
|
@@ -132,7 +132,7 @@ const DEFAULT_RULES_YAML = `# Architecture rules (BYO)
|
|
|
132
132
|
//#endregion
|
|
133
133
|
//#region src/config/extends.ts
|
|
134
134
|
const MAX_DEPTH$1 = 5;
|
|
135
|
-
const MAX_CONFIG_BYTES =
|
|
135
|
+
const MAX_CONFIG_BYTES = 1048576;
|
|
136
136
|
const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
137
137
|
const deepMerge = (...sources) => {
|
|
138
138
|
const result = {};
|
|
@@ -313,6 +313,9 @@ const CiSchema = z.object({
|
|
|
313
313
|
format: z.enum(["json"]).default("json")
|
|
314
314
|
}).strict();
|
|
315
315
|
const TelemetrySchema = z.object({ enabled: z.boolean().default(true) }).strict();
|
|
316
|
+
const BaselineConfigSchema = z.object({ mode: z.enum(["strict", "relaxed"]).default("relaxed") }).strict();
|
|
317
|
+
const LicensingConfigSchema = z.object({ onLapse: z.enum(["fail-open", "fail-closed"]).default("fail-open") }).strict();
|
|
318
|
+
const WorkspaceConfigSchema = z.object({ minimumFloor: z.number().min(0).max(100).default(50) }).strict();
|
|
316
319
|
const RuleSeverityOverride = z.enum([
|
|
317
320
|
"error",
|
|
318
321
|
"warning",
|
|
@@ -383,6 +386,9 @@ const AislopConfigSchema = z.object({
|
|
|
383
386
|
format: "json"
|
|
384
387
|
})),
|
|
385
388
|
telemetry: TelemetrySchema.default(() => ({ enabled: true })),
|
|
389
|
+
baseline: BaselineConfigSchema.optional(),
|
|
390
|
+
licensing: LicensingConfigSchema.optional(),
|
|
391
|
+
workspace: WorkspaceConfigSchema.optional(),
|
|
386
392
|
rules: RulesSchema,
|
|
387
393
|
overrides: z.array(FileOverrideSchema).default(() => []),
|
|
388
394
|
exclude: z.array(z.string()).default(() => [
|
|
@@ -417,6 +423,9 @@ const KNOWN_KEYS_BY_PATH = {
|
|
|
417
423
|
"scoring",
|
|
418
424
|
"ci",
|
|
419
425
|
"telemetry",
|
|
426
|
+
"baseline",
|
|
427
|
+
"licensing",
|
|
428
|
+
"workspace",
|
|
420
429
|
"rules",
|
|
421
430
|
"exclude",
|
|
422
431
|
"include"
|
|
@@ -467,7 +476,10 @@ const KNOWN_KEYS_BY_PATH = {
|
|
|
467
476
|
],
|
|
468
477
|
"scoring.thresholds": ["good", "ok"],
|
|
469
478
|
ci: ["failBelow", "format"],
|
|
470
|
-
telemetry: ["enabled"]
|
|
479
|
+
telemetry: ["enabled"],
|
|
480
|
+
baseline: ["mode"],
|
|
481
|
+
licensing: ["onLapse"],
|
|
482
|
+
workspace: ["minimumFloor"]
|
|
471
483
|
};
|
|
472
484
|
const levenshtein = (a, b) => {
|
|
473
485
|
const m = a.length;
|
|
@@ -599,7 +611,8 @@ const loadConfig = (directory) => {
|
|
|
599
611
|
const configPath = findConfigFile(directory);
|
|
600
612
|
if (!configPath) return DEFAULT_CONFIG;
|
|
601
613
|
try {
|
|
602
|
-
|
|
614
|
+
const merged = loadConfigChain(configPath);
|
|
615
|
+
return validateConfig(merged, configPath);
|
|
603
616
|
} catch (error) {
|
|
604
617
|
if (error instanceof ConfigError) throw error;
|
|
605
618
|
throw new ConfigError(configPath, `Failed to parse ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -695,7 +708,8 @@ const stringWidth = (s) => {
|
|
|
695
708
|
const bare = stripAnsi$1(s);
|
|
696
709
|
let total = 0;
|
|
697
710
|
for (const ch of bare) {
|
|
698
|
-
const
|
|
711
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
712
|
+
const w = wcwidth(cp);
|
|
699
713
|
total += w > 0 ? w : 1;
|
|
700
714
|
}
|
|
701
715
|
return total;
|
|
@@ -711,7 +725,8 @@ const truncate = (s, max, ellipsis = "…") => {
|
|
|
711
725
|
let out = "";
|
|
712
726
|
let w = 0;
|
|
713
727
|
for (const ch of s) {
|
|
714
|
-
const
|
|
728
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
729
|
+
const cw = wcwidth(cp);
|
|
715
730
|
if (w + cw > limit) break;
|
|
716
731
|
out += ch;
|
|
717
732
|
w += cw;
|
|
@@ -721,7 +736,8 @@ const truncate = (s, max, ellipsis = "…") => {
|
|
|
721
736
|
//#endregion
|
|
722
737
|
//#region src/ui/display.ts
|
|
723
738
|
const renderDisplaySection = (label, deps = {}) => {
|
|
724
|
-
|
|
739
|
+
const t = deps.theme ?? theme;
|
|
740
|
+
return style(t, "section", label);
|
|
725
741
|
};
|
|
726
742
|
const renderDisplayRows = (rows, options = {}) => {
|
|
727
743
|
if (rows.length === 0) return [];
|
|
@@ -868,8 +884,10 @@ const detectSourceLanguages = (sourceFiles) => {
|
|
|
868
884
|
const detectLanguages = (directory, sourceFiles) => {
|
|
869
885
|
const languages = new Set(detectSourceLanguages(sourceFiles));
|
|
870
886
|
for (const [file, lang] of Object.entries(LANGUAGE_SIGNALS)) if (fs.existsSync(path.join(directory, file))) languages.add(lang);
|
|
871
|
-
if (readPackageJson(path.join(directory, "package.json")))
|
|
872
|
-
|
|
887
|
+
if (readPackageJson(path.join(directory, "package.json"))) {
|
|
888
|
+
if (fs.existsSync(path.join(directory, "tsconfig.json"))) languages.add("typescript");
|
|
889
|
+
else languages.add("javascript");
|
|
890
|
+
}
|
|
873
891
|
for (const signal of PYTHON_SIGNALS) if (fs.existsSync(path.join(directory, signal))) {
|
|
874
892
|
languages.add("python");
|
|
875
893
|
break;
|
|
@@ -1002,7 +1020,7 @@ const isAutoGenerated = (filePath) => {
|
|
|
1002
1020
|
};
|
|
1003
1021
|
//#endregion
|
|
1004
1022
|
//#region src/utils/git-ignore.ts
|
|
1005
|
-
const MAX_BUFFER$2 =
|
|
1023
|
+
const MAX_BUFFER$2 = 52428800;
|
|
1006
1024
|
const NOT_IGNORED_ARGUMENTS = [
|
|
1007
1025
|
"ls-files",
|
|
1008
1026
|
"--cached",
|
|
@@ -1113,7 +1131,7 @@ const dropGitIgnoredPaths = (rootDirectory, absolutePaths) => {
|
|
|
1113
1131
|
};
|
|
1114
1132
|
//#endregion
|
|
1115
1133
|
//#region src/utils/project-file-list.ts
|
|
1116
|
-
const MAX_BUFFER$1 =
|
|
1134
|
+
const MAX_BUFFER$1 = 52428800;
|
|
1117
1135
|
const normalizePruneDirectories = (directories) => new Set([...directories].map((directory) => directory.toLowerCase()));
|
|
1118
1136
|
const isInPrunedDirectory = (filePath, pruneDirectories) => {
|
|
1119
1137
|
const pathSegments = filePath.split("/");
|
|
@@ -1664,7 +1682,8 @@ const resolvePackageRoot = (startFile) => {
|
|
|
1664
1682
|
while (true) {
|
|
1665
1683
|
const packageJsonPath = path.join(current, "package.json");
|
|
1666
1684
|
if (fs.existsSync(packageJsonPath)) try {
|
|
1667
|
-
|
|
1685
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
1686
|
+
if (packageJson.name === "aislop" || packageJson.name === "@methiu/raigal" || packageJson.name === "raigal") return current;
|
|
1668
1687
|
} catch {}
|
|
1669
1688
|
const parent = path.dirname(current);
|
|
1670
1689
|
if (parent === current) break;
|
|
@@ -1759,7 +1778,7 @@ const checkInstalledTools = async () => {
|
|
|
1759
1778
|
};
|
|
1760
1779
|
//#endregion
|
|
1761
1780
|
//#region src/utils/project-path-safety.ts
|
|
1762
|
-
const MAX_PROJECT_CONFIG_BYTES =
|
|
1781
|
+
const MAX_PROJECT_CONFIG_BYTES = 1048576;
|
|
1763
1782
|
const isWithinDirectory$2 = (rootDirectory, candidate) => {
|
|
1764
1783
|
const relative = path.relative(rootDirectory, candidate);
|
|
1765
1784
|
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
@@ -2084,7 +2103,8 @@ const resolveTrustedTscPath = () => {
|
|
|
2084
2103
|
};
|
|
2085
2104
|
const isReferenceOnlyConfig = (tsconfigPath) => {
|
|
2086
2105
|
try {
|
|
2087
|
-
const
|
|
2106
|
+
const raw = fs.readFileSync(tsconfigPath, "utf-8");
|
|
2107
|
+
const parsed = parseJsonc(raw);
|
|
2088
2108
|
if (!parsed) return false;
|
|
2089
2109
|
return Array.isArray(parsed.references) && !parsed.files && !parsed.include && !parsed.extends;
|
|
2090
2110
|
} catch {
|
|
@@ -2403,7 +2423,7 @@ const changeContextOrder = (context) => {
|
|
|
2403
2423
|
};
|
|
2404
2424
|
//#endregion
|
|
2405
2425
|
//#region src/utils/git.ts
|
|
2406
|
-
const MAX_BUFFER =
|
|
2426
|
+
const MAX_BUFFER = 52428800;
|
|
2407
2427
|
const baseRefExists = (cwd, ref) => {
|
|
2408
2428
|
const result = spawnSync("git", [
|
|
2409
2429
|
"rev-parse",
|
|
@@ -3385,7 +3405,7 @@ const tokenizeJs = (content, ext = ".ts") => {
|
|
|
3385
3405
|
};
|
|
3386
3406
|
//#endregion
|
|
3387
3407
|
//#region src/utils/source-masker.ts
|
|
3388
|
-
const JS_EXTS$
|
|
3408
|
+
const JS_EXTS$3 = /* @__PURE__ */ new Set([
|
|
3389
3409
|
".ts",
|
|
3390
3410
|
".tsx",
|
|
3391
3411
|
".js",
|
|
@@ -3393,7 +3413,7 @@ const JS_EXTS$2 = /* @__PURE__ */ new Set([
|
|
|
3393
3413
|
".mjs",
|
|
3394
3414
|
".cjs"
|
|
3395
3415
|
]);
|
|
3396
|
-
const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
|
|
3416
|
+
const PY_EXTS$1 = /* @__PURE__ */ new Set([".py"]);
|
|
3397
3417
|
const RB_EXTS = /* @__PURE__ */ new Set([".rb"]);
|
|
3398
3418
|
const PHP_EXTS = /* @__PURE__ */ new Set([".php"]);
|
|
3399
3419
|
const CSHARP_EXTS = /* @__PURE__ */ new Set([".cs"]);
|
|
@@ -3417,8 +3437,8 @@ const C_STYLE_COMMENT_EXTS = /* @__PURE__ */ new Set([
|
|
|
3417
3437
|
".zig"
|
|
3418
3438
|
]);
|
|
3419
3439
|
const familyForExt = (ext) => {
|
|
3420
|
-
if (JS_EXTS$
|
|
3421
|
-
if (PY_EXTS.has(ext)) return "py";
|
|
3440
|
+
if (JS_EXTS$3.has(ext)) return "js";
|
|
3441
|
+
if (PY_EXTS$1.has(ext)) return "py";
|
|
3422
3442
|
if (RB_EXTS.has(ext)) return "rb";
|
|
3423
3443
|
if (PHP_EXTS.has(ext)) return "php";
|
|
3424
3444
|
if (CSHARP_EXTS.has(ext)) return "csharp";
|
|
@@ -3688,7 +3708,7 @@ const areLiteralMultisetsEqual = (a, b) => {
|
|
|
3688
3708
|
};
|
|
3689
3709
|
//#endregion
|
|
3690
3710
|
//#region src/engines/ai-slop/abstractions.ts
|
|
3691
|
-
const JS_EXTS$
|
|
3711
|
+
const JS_EXTS$2 = /* @__PURE__ */ new Set([
|
|
3692
3712
|
".ts",
|
|
3693
3713
|
".tsx",
|
|
3694
3714
|
".js",
|
|
@@ -3699,11 +3719,11 @@ const JS_EXTS$1 = /* @__PURE__ */ new Set([
|
|
|
3699
3719
|
const THIN_WRAPPER_PATTERNS = [
|
|
3700
3720
|
{
|
|
3701
3721
|
pattern: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*(?::\s*\w[^{]*)?\{\s*\n?\s*return\s+\w+\([^)]*\);\s*\n?\s*\}/g,
|
|
3702
|
-
extensions: JS_EXTS$
|
|
3722
|
+
extensions: JS_EXTS$2
|
|
3703
3723
|
},
|
|
3704
3724
|
{
|
|
3705
3725
|
pattern: /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w[^=]*)?\s*=>\s*\w+\([^)]*\);/g,
|
|
3706
|
-
extensions: JS_EXTS$
|
|
3726
|
+
extensions: JS_EXTS$2
|
|
3707
3727
|
},
|
|
3708
3728
|
{
|
|
3709
3729
|
pattern: /def\s+(\w+)\s*\([^)]*\)(?:\s*->[^:]*)?:\s*\n\s+return\s+\w+\([^)]*\)\s*$/gm,
|
|
@@ -4951,7 +4971,7 @@ const scriptKindFor$1 = (ext) => {
|
|
|
4951
4971
|
}
|
|
4952
4972
|
};
|
|
4953
4973
|
const lineFor$1 = (sourceFile, node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
4954
|
-
const makeDiagnostic = (filePath, line, rule, message, help) => ({
|
|
4974
|
+
const makeDiagnostic$1 = (filePath, line, rule, message, help) => ({
|
|
4955
4975
|
filePath,
|
|
4956
4976
|
engine: "ai-slop",
|
|
4957
4977
|
rule,
|
|
@@ -5001,7 +5021,7 @@ const detectRedundantTryCatch = (sourceFile, relativePath) => {
|
|
|
5001
5021
|
if (ts.isTryStatement(node) && node.catchClause && !node.finallyBlock) {
|
|
5002
5022
|
const catchNameNode = node.catchClause.variableDeclaration?.name;
|
|
5003
5023
|
const [onlyStatement] = node.catchClause.block.statements;
|
|
5004
|
-
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."));
|
|
5024
|
+
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."));
|
|
5005
5025
|
}
|
|
5006
5026
|
ts.forEachChild(node, visit);
|
|
5007
5027
|
};
|
|
@@ -5019,7 +5039,7 @@ const detectPrimitiveCoercions = (sourceFile, relativePath) => {
|
|
|
5019
5039
|
const [arg] = child.arguments;
|
|
5020
5040
|
if (arg && ts.isIdentifier(arg)) {
|
|
5021
5041
|
const primitive = params.get(arg.text);
|
|
5022
|
-
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."));
|
|
5042
|
+
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."));
|
|
5023
5043
|
}
|
|
5024
5044
|
}
|
|
5025
5045
|
ts.forEachChild(child, visitBody);
|
|
@@ -5062,7 +5082,7 @@ const detectDuplicateExportedTypes = (parsedSources) => {
|
|
|
5062
5082
|
continue;
|
|
5063
5083
|
}
|
|
5064
5084
|
if (previous.filePath === declaration.filePath) continue;
|
|
5065
|
-
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.`));
|
|
5085
|
+
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.`));
|
|
5066
5086
|
}
|
|
5067
5087
|
}
|
|
5068
5088
|
return diagnostics;
|
|
@@ -5164,7 +5184,7 @@ const detectDuplicateImports = async (context) => {
|
|
|
5164
5184
|
rule: "ai-slop/duplicate-import",
|
|
5165
5185
|
severity: "warning",
|
|
5166
5186
|
message: `"${spec}" is also imported on line ${firstLine}. Merge into a single import statement.`,
|
|
5167
|
-
help: "Two imports from the same module split readers' attention and grow the import block. Run
|
|
5187
|
+
help: "Two imports from the same module split readers' attention and grow the import block. Run raigal fix to merge them automatically.",
|
|
5168
5188
|
line: dup.line,
|
|
5169
5189
|
column: 1,
|
|
5170
5190
|
category: "AI Slop",
|
|
@@ -6786,7 +6806,8 @@ const isJsRelativeOrAbsolute = (spec) => spec.startsWith(".") || spec.startsWith
|
|
|
6786
6806
|
const RUNTIME_BUILTINS = /* @__PURE__ */ new Set(["bun"]);
|
|
6787
6807
|
const isJsBuiltin = (spec) => {
|
|
6788
6808
|
if (RUNTIME_BUILTINS.has(spec)) return true;
|
|
6789
|
-
|
|
6809
|
+
const stripped = spec.startsWith("node:") ? spec.slice(5) : spec;
|
|
6810
|
+
return isBuiltin(stripped) || isBuiltin(spec);
|
|
6790
6811
|
};
|
|
6791
6812
|
const VIRTUAL_MODULE_PREFIXES = [
|
|
6792
6813
|
"astro:",
|
|
@@ -6998,7 +7019,8 @@ const isLocalAliasReplacement = (rawValue, configDirectory, rootDirectory) => {
|
|
|
6998
7019
|
if (!target) return false;
|
|
6999
7020
|
if (!target.startsWith(".") && !target.startsWith("/") && !target.startsWith("~/") && !target.startsWith("@/")) return false;
|
|
7000
7021
|
const normalized = target.startsWith("~/") || target.startsWith("@/") ? target.slice(2) : target;
|
|
7001
|
-
|
|
7022
|
+
const candidate = path.isAbsolute(normalized) ? normalized : path.resolve(configDirectory, normalized);
|
|
7023
|
+
return isRootBoundedTarget(candidate, rootDirectory);
|
|
7002
7024
|
};
|
|
7003
7025
|
const hasValidPackageImportTarget = (value, packageDirectory, rootDirectory) => {
|
|
7004
7026
|
if (!isRootBoundedTarget(packageDirectory, rootDirectory)) return false;
|
|
@@ -8192,7 +8214,9 @@ const isRustDocCommentLine = (line) => {
|
|
|
8192
8214
|
};
|
|
8193
8215
|
const collectBlocks = (sourceLines, syntax, content, ext) => {
|
|
8194
8216
|
const blocks = [];
|
|
8195
|
-
const
|
|
8217
|
+
const actualContent = content ?? sourceLines.join("\n");
|
|
8218
|
+
const actualExt = ext ?? (syntax.linePrefixes.includes("//") ? ".ts" : ".py");
|
|
8219
|
+
const commentLines = getCommentLines(actualContent, actualExt);
|
|
8196
8220
|
let i = 0;
|
|
8197
8221
|
while (i < sourceLines.length) {
|
|
8198
8222
|
const line = sourceLines[i];
|
|
@@ -8520,7 +8544,8 @@ const detectNarrativeComments = async (context) => {
|
|
|
8520
8544
|
} catch {
|
|
8521
8545
|
continue;
|
|
8522
8546
|
}
|
|
8523
|
-
const
|
|
8547
|
+
const lines = content.split("\n");
|
|
8548
|
+
const blocks = collectBlocks(lines, syntax, content, ext);
|
|
8524
8549
|
for (const block of blocks) {
|
|
8525
8550
|
const { matched, reason } = detectNarrativeInBlock(block, ext);
|
|
8526
8551
|
if (!matched) continue;
|
|
@@ -8992,7 +9017,7 @@ const detectRustPatterns = async (context) => {
|
|
|
8992
9017
|
};
|
|
8993
9018
|
//#endregion
|
|
8994
9019
|
//#region src/engines/ai-slop/silent-recovery.ts
|
|
8995
|
-
const JS_EXTS = /* @__PURE__ */ new Set([
|
|
9020
|
+
const JS_EXTS$1 = /* @__PURE__ */ new Set([
|
|
8996
9021
|
".ts",
|
|
8997
9022
|
".tsx",
|
|
8998
9023
|
".js",
|
|
@@ -9187,7 +9212,7 @@ const detectSilentRecovery = async (context) => {
|
|
|
9187
9212
|
for (const filePath of files) {
|
|
9188
9213
|
if (isAutoGenerated(filePath)) continue;
|
|
9189
9214
|
const ext = path.extname(filePath);
|
|
9190
|
-
const isJs = JS_EXTS.has(ext);
|
|
9215
|
+
const isJs = JS_EXTS$1.has(ext);
|
|
9191
9216
|
if (!isJs && !(ext === ".py")) continue;
|
|
9192
9217
|
const relPath = relativePosix(context.rootDirectory, filePath);
|
|
9193
9218
|
if (isNonProductionPath(relPath)) continue;
|
|
@@ -9472,7 +9497,7 @@ const detectUnusedCss = async (context) => {
|
|
|
9472
9497
|
rule: "ai-slop/unused-css",
|
|
9473
9498
|
severity: "warning",
|
|
9474
9499
|
message: `CSS class '.${def.name}' is defined but never referenced in the codebase`,
|
|
9475
|
-
help: "Remove the dead CSS rule, or confirm the class is used dynamically (and add
|
|
9500
|
+
help: "Remove the dead CSS rule, or confirm the class is used dynamically (and add a raigal-ignore comment if so)",
|
|
9476
9501
|
line: def.line,
|
|
9477
9502
|
column: 0,
|
|
9478
9503
|
category: "AI Slop",
|
|
@@ -10462,7 +10487,8 @@ const checkFunctionDiagnostics = (relativePath, fn, limits, ext) => {
|
|
|
10462
10487
|
const results = [];
|
|
10463
10488
|
const multiplier = functionLocMultiplier(fn, ext);
|
|
10464
10489
|
const fnMax = functionLocBudget(fn, ext, limits.maxFunctionLoc);
|
|
10465
|
-
|
|
10490
|
+
const effectiveLineCount = fn.lineCount - fn.templateLines;
|
|
10491
|
+
if (exceedsLimit(effectiveLineCount, limits.maxFunctionLoc, multiplier)) results.push({
|
|
10466
10492
|
filePath: relativePath,
|
|
10467
10493
|
engine: "code-quality",
|
|
10468
10494
|
rule: "complexity/function-too-long",
|
|
@@ -10552,14 +10578,14 @@ const isTrivialLine = (line) => {
|
|
|
10552
10578
|
};
|
|
10553
10579
|
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)=/;
|
|
10554
10580
|
const DATA_LITERAL_RE = /^\s*(?:[A-Za-z_$][\w$-]*:\s*(?:["'`[{]|\d|true\b|false\b|null\b)|["'`][^"'`]*["'`],?\s*$|[{}\]],?\s*$|\),?\s*$)/;
|
|
10555
|
-
const SUPPRESS_RE = /aislop[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
10556
|
-
const FILE_SUPPRESS_RE = /aislop[- ]ignore-file\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
10581
|
+
const SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
10582
|
+
const FILE_SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore-file\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
|
|
10557
10583
|
const fileHasSuppression = (content) => FILE_SUPPRESS_RE.test(content);
|
|
10558
10584
|
const isLowSignalMarkupWindow = (lines) => {
|
|
10559
10585
|
return lines.filter((line) => SVG_MARKUP_RE.test(line)).length >= Math.ceil(WINDOW_SIZE / 2);
|
|
10560
10586
|
};
|
|
10561
10587
|
const isLowSignalDataWindow = (lines) => {
|
|
10562
|
-
return lines.filter((line) => DATA_LITERAL_RE.test(line)).length >=
|
|
10588
|
+
return lines.filter((line) => DATA_LITERAL_RE.test(line)).length >= 9;
|
|
10563
10589
|
};
|
|
10564
10590
|
const findSuppressedLines = (lines) => {
|
|
10565
10591
|
const suppressed = /* @__PURE__ */ new Set();
|
|
@@ -10591,7 +10617,7 @@ const collectMeaningfulLines = (content) => {
|
|
|
10591
10617
|
if (isLowSignalDataWindow(window)) continue;
|
|
10592
10618
|
if (window.every(isTrivialLine)) continue;
|
|
10593
10619
|
const normalised = window.map(normaliseLine);
|
|
10594
|
-
if (normalised.filter((n) => n.length > 0 && n !== "}" && n !== "{").length <
|
|
10620
|
+
if (normalised.filter((n) => n.length > 0 && n !== "}" && n !== "{").length < 9) continue;
|
|
10595
10621
|
if (new Set(normalised).size < MIN_DISTINCT_LINES) continue;
|
|
10596
10622
|
hits.push({
|
|
10597
10623
|
startLine: i + 1,
|
|
@@ -10695,8 +10721,8 @@ const shouldIncludeIssue = (issueType, filePath) => {
|
|
|
10695
10721
|
return !filePath.replace(/\\/g, "/").includes(".github/workflows/");
|
|
10696
10722
|
};
|
|
10697
10723
|
const DEPENDENCY_HELP = {
|
|
10698
|
-
dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `
|
|
10699
|
-
devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `
|
|
10724
|
+
dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
|
|
10725
|
+
devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
|
|
10700
10726
|
unlisted: "This package is imported in code but not declared in package.json. Run `npm install` to add it.",
|
|
10701
10727
|
unresolved: "This import cannot be resolved. Check for typos or missing packages.",
|
|
10702
10728
|
binaries: "This binary is used but its package is not in package.json."
|
|
@@ -11655,7 +11681,7 @@ const runGofmt = async (context) => {
|
|
|
11655
11681
|
rule: "go-formatting",
|
|
11656
11682
|
severity: "warning",
|
|
11657
11683
|
message: "Go file is not formatted correctly",
|
|
11658
|
-
help: "Run `
|
|
11684
|
+
help: "Run `raigal fix` to auto-format with gofmt",
|
|
11659
11685
|
line: 0,
|
|
11660
11686
|
column: 0,
|
|
11661
11687
|
category: "Format",
|
|
@@ -11725,7 +11751,7 @@ const parseRuffFormatOutput = (output, rootDir) => {
|
|
|
11725
11751
|
rule: "python-formatting",
|
|
11726
11752
|
severity: "warning",
|
|
11727
11753
|
message: "Python file is not formatted correctly",
|
|
11728
|
-
help: "Run `
|
|
11754
|
+
help: "Run `raigal fix` to auto-format with ruff",
|
|
11729
11755
|
line: 0,
|
|
11730
11756
|
column: 0,
|
|
11731
11757
|
category: "Format",
|
|
@@ -11737,7 +11763,8 @@ const parseRuffFormatOutput = (output, rootDir) => {
|
|
|
11737
11763
|
const fixRuffFormat = async (context) => {
|
|
11738
11764
|
const targets = context.files ? getPythonTargets(context) : [context.rootDirectory];
|
|
11739
11765
|
if (context.files && targets.length === 0) return;
|
|
11740
|
-
const
|
|
11766
|
+
const ruffBinary = resolveToolBinary("ruff");
|
|
11767
|
+
const result = await runSubprocess(ruffBinary, ["format", ...targets], {
|
|
11741
11768
|
cwd: context.rootDirectory,
|
|
11742
11769
|
timeout: 6e4
|
|
11743
11770
|
});
|
|
@@ -11807,7 +11834,8 @@ const parseClangTidyOutput = (output, rootDirectory) => {
|
|
|
11807
11834
|
const runClangTidy = async (context) => {
|
|
11808
11835
|
const compileCommandsDir = findCompileCommandsDir(context);
|
|
11809
11836
|
if (!compileCommandsDir) return [];
|
|
11810
|
-
const
|
|
11837
|
+
const implSources = findCppSources(context).filter((f) => CPP_IMPL_EXTENSIONS.has(path.extname(f).toLowerCase()));
|
|
11838
|
+
const sources = filterSourcesInDatabase(implSources, readCompileCommandsFiles(compileCommandsDir));
|
|
11811
11839
|
if (sources.length === 0) return [];
|
|
11812
11840
|
const diagnostics = [];
|
|
11813
11841
|
for (const chunk of chunkFilePaths(sources, CLANG_TIDY_CHUNK_MAX_FILES)) try {
|
|
@@ -12177,7 +12205,7 @@ const runExpoDoctor = async (context) => {
|
|
|
12177
12205
|
rule: "expo-doctor/config-error",
|
|
12178
12206
|
severity: "warning",
|
|
12179
12207
|
message: configError,
|
|
12180
|
-
help: "Install project dependencies, then re-run `
|
|
12208
|
+
help: "Install project dependencies, then re-run `raigal scan`.",
|
|
12181
12209
|
line: 0,
|
|
12182
12210
|
column: 0,
|
|
12183
12211
|
category: "Expo",
|
|
@@ -12643,7 +12671,8 @@ const processFile = (filePath, fileTargets, result) => {
|
|
|
12643
12671
|
});
|
|
12644
12672
|
continue;
|
|
12645
12673
|
}
|
|
12646
|
-
const
|
|
12674
|
+
const shape = classifyIdentifier(best);
|
|
12675
|
+
const { edit, skipReason } = computeEdit(sourceFile, shape);
|
|
12647
12676
|
if (!edit) {
|
|
12648
12677
|
pendingSkips.push({
|
|
12649
12678
|
target,
|
|
@@ -13458,7 +13487,8 @@ const ruffLintFixArgs = (context, unsafe) => {
|
|
|
13458
13487
|
};
|
|
13459
13488
|
const fixRuffLint = async (context) => {
|
|
13460
13489
|
if (context.files && getPythonTargets(context).length === 0) return;
|
|
13461
|
-
const
|
|
13490
|
+
const ruffBinary = resolveToolBinary("ruff");
|
|
13491
|
+
const result = await runSubprocess(ruffBinary, ruffLintFixArgs(context, false), {
|
|
13462
13492
|
cwd: context.rootDirectory,
|
|
13463
13493
|
timeout: 6e4
|
|
13464
13494
|
});
|
|
@@ -13466,7 +13496,8 @@ const fixRuffLint = async (context) => {
|
|
|
13466
13496
|
};
|
|
13467
13497
|
const fixRuffLintForce = async (context) => {
|
|
13468
13498
|
if (context.files && getPythonTargets(context).length === 0) return;
|
|
13469
|
-
const
|
|
13499
|
+
const ruffBinary = resolveToolBinary("ruff");
|
|
13500
|
+
const result = await runSubprocess(ruffBinary, ruffLintFixArgs(context, true), {
|
|
13470
13501
|
cwd: context.rootDirectory,
|
|
13471
13502
|
timeout: 6e4
|
|
13472
13503
|
});
|
|
@@ -13555,6 +13586,174 @@ const lintEngine = {
|
|
|
13555
13586
|
}
|
|
13556
13587
|
};
|
|
13557
13588
|
//#endregion
|
|
13589
|
+
//#region src/engines/security/ai-security.ts
|
|
13590
|
+
const JS_EXTS = /* @__PURE__ */ new Set([
|
|
13591
|
+
".ts",
|
|
13592
|
+
".tsx",
|
|
13593
|
+
".js",
|
|
13594
|
+
".jsx",
|
|
13595
|
+
".mjs",
|
|
13596
|
+
".cjs"
|
|
13597
|
+
]);
|
|
13598
|
+
const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
|
|
13599
|
+
const UNICODE_TAG_RE = /[\u{E0000}-\u{E007F}]/gu;
|
|
13600
|
+
const hasRuleIgnore = (lines, lineIndex, ruleSuffix) => {
|
|
13601
|
+
const start = Math.max(0, lineIndex - 2);
|
|
13602
|
+
const re = new RegExp(`(?:raigal|aislop)[- ]ignore(?:-file)?\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
|
|
13603
|
+
return lines.slice(start, lineIndex + 1).some((line) => re.test(line));
|
|
13604
|
+
};
|
|
13605
|
+
const hasFileIgnore = (lines, ruleSuffix) => {
|
|
13606
|
+
const re = new RegExp(`(?:raigal|aislop)[- ]ignore-file\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
|
|
13607
|
+
return lines.slice(0, 10).some((line) => re.test(line));
|
|
13608
|
+
};
|
|
13609
|
+
const makeDiagnostic = (filePath, rule, message, help, line, column) => ({
|
|
13610
|
+
filePath,
|
|
13611
|
+
engine: "security",
|
|
13612
|
+
rule,
|
|
13613
|
+
severity: "error",
|
|
13614
|
+
message,
|
|
13615
|
+
help,
|
|
13616
|
+
line,
|
|
13617
|
+
column,
|
|
13618
|
+
category: "Security",
|
|
13619
|
+
fixable: false
|
|
13620
|
+
});
|
|
13621
|
+
const P_BYPASS = "bypassPermissions";
|
|
13622
|
+
const P_EXEC_TYPE = "executor_type";
|
|
13623
|
+
const P_LOCAL = "local";
|
|
13624
|
+
const P_USE_DOCKER = "use_docker";
|
|
13625
|
+
const P_AUTO_RUN = "auto_run";
|
|
13626
|
+
const P_UNSAFE_MODE = "unsafe_mode";
|
|
13627
|
+
const P_PYTHON_REPL = "PythonREPLTool";
|
|
13628
|
+
const UNSANDBOXED_TOOL_PATTERNS = [
|
|
13629
|
+
{
|
|
13630
|
+
pattern: new RegExp(`\\bpermission_?mode["']?\\s*[:=]\\s*["']${P_BYPASS}["']`, "i"),
|
|
13631
|
+
message: `permission_mode '${P_BYPASS}' bypasses agent sandboxing and human confirmation`,
|
|
13632
|
+
help: `Do not use ${P_BYPASS} mode in production; use standard interactive approval`
|
|
13633
|
+
},
|
|
13634
|
+
{
|
|
13635
|
+
pattern: new RegExp(`\\b${P_EXEC_TYPE}["']?\\s*[:=]\\s*["']${P_LOCAL}["']`, "i"),
|
|
13636
|
+
message: `${P_EXEC_TYPE}='${P_LOCAL}' executes LLM-generated code directly on host machine without container isolation`,
|
|
13637
|
+
help: "Configure agent to use DockerCommandLineCodeExecutor or a containerized sandbox environment"
|
|
13638
|
+
},
|
|
13639
|
+
{
|
|
13640
|
+
pattern: new RegExp(`\\b(?:LocalCommandLineCodeExecutor|LocalCodeExecutor)\\s*\\(`),
|
|
13641
|
+
message: "Local code executor executes LLM-generated code on host machine without container isolation",
|
|
13642
|
+
help: "Use DockerCommandLineCodeExecutor or an isolated execution sandbox"
|
|
13643
|
+
},
|
|
13644
|
+
{
|
|
13645
|
+
pattern: new RegExp(`\\b${P_USE_DOCKER}\\s*[:=]\\s*(?:False|false)\\b|["']${P_USE_DOCKER}["']\\s*:\\s*(?:False|false)\\b`),
|
|
13646
|
+
message: `${P_USE_DOCKER}=False disables container sandboxing for agent code execution`,
|
|
13647
|
+
help: `Set ${P_USE_DOCKER}=True to isolate agent execution inside Docker containers`
|
|
13648
|
+
},
|
|
13649
|
+
{
|
|
13650
|
+
pattern: new RegExp(`\\b(?:interpreter\\.)?${P_AUTO_RUN}\\s*[:=]\\s*(?:True|true)\\b`),
|
|
13651
|
+
message: `${P_AUTO_RUN}=True allows autonomous shell execution without human confirmation`,
|
|
13652
|
+
help: `Keep ${P_AUTO_RUN}=False so dangerous commands require explicit human review`
|
|
13653
|
+
},
|
|
13654
|
+
{
|
|
13655
|
+
pattern: new RegExp(`\\bCodeInterpreterTool\\s*\\([^)]*${P_UNSAFE_MODE}\\s*=\\s*(?:True|true)\\b|\\b${P_UNSAFE_MODE}\\s*[:=]\\s*(?:True|true)\\b`),
|
|
13656
|
+
message: `${P_UNSAFE_MODE}=True allows code interpreter tool to execute without Docker isolation`,
|
|
13657
|
+
help: `Disable ${P_UNSAFE_MODE} and isolate code execution in containerized environments`
|
|
13658
|
+
},
|
|
13659
|
+
{
|
|
13660
|
+
pattern: new RegExp(`\\b(?:${P_PYTHON_REPL}|PythonAstREPLTool)\\s*\\(`),
|
|
13661
|
+
message: `${P_PYTHON_REPL} executes arbitrary Python code in host process without container isolation`,
|
|
13662
|
+
help: `Use containerized sandbox execution rather than unconfined ${P_PYTHON_REPL} in production`
|
|
13663
|
+
}
|
|
13664
|
+
];
|
|
13665
|
+
const P_SYS = "system";
|
|
13666
|
+
const P_DEV = "developer";
|
|
13667
|
+
const P_SM = "SystemMessage";
|
|
13668
|
+
const PROMPT_INJECTION_JS_PATTERNS = [
|
|
13669
|
+
new RegExp(`(?:role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`)`, "g"),
|
|
13670
|
+
new RegExp(`(?:content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`[^}{;]{0,120}?role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
|
|
13671
|
+
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"),
|
|
13672
|
+
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"),
|
|
13673
|
+
new RegExp(`\\b(?:${P_SYS}|systemPrompt|system_prompt|system_instruction)\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\``, "g"),
|
|
13674
|
+
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"),
|
|
13675
|
+
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"),
|
|
13676
|
+
new RegExp(`\\[\\s*["']${P_SYS}["']\\s*,\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`\\s*\\]`, "g")
|
|
13677
|
+
];
|
|
13678
|
+
const PROMPT_INJECTION_PY_PATTERNS = [
|
|
13679
|
+
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"),
|
|
13680
|
+
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"),
|
|
13681
|
+
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"),
|
|
13682
|
+
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"),
|
|
13683
|
+
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"),
|
|
13684
|
+
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"),
|
|
13685
|
+
new RegExp(`\\b${P_SM}\\s*\\(\\s*(?:content\\s*=\\s*)?f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])`, "g"),
|
|
13686
|
+
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"),
|
|
13687
|
+
new RegExp(`\\(\\s*["']${P_SYS}["']\\s*,\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])\\s*\\)`, "g")
|
|
13688
|
+
];
|
|
13689
|
+
const checkInvisibleUnicode = (relPath, content, lines) => {
|
|
13690
|
+
if (hasFileIgnore(lines, "invisible-unicode-tags")) return [];
|
|
13691
|
+
const results = [];
|
|
13692
|
+
const tagRegex = new RegExp(UNICODE_TAG_RE.source, UNICODE_TAG_RE.flags);
|
|
13693
|
+
for (const match of content.matchAll(tagRegex)) {
|
|
13694
|
+
const matchIndex = match.index ?? 0;
|
|
13695
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
13696
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
13697
|
+
if (hasRuleIgnore(lines, line - 1, "invisible-unicode-tags")) continue;
|
|
13698
|
+
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));
|
|
13699
|
+
}
|
|
13700
|
+
return results;
|
|
13701
|
+
};
|
|
13702
|
+
const checkUnsandboxedTools = (relPath, content, lines) => {
|
|
13703
|
+
if (hasFileIgnore(lines, "unsandboxed-agent-tool")) return [];
|
|
13704
|
+
const results = [];
|
|
13705
|
+
for (const { pattern, message, help } of UNSANDBOXED_TOOL_PATTERNS) {
|
|
13706
|
+
const regex = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
13707
|
+
for (const match of content.matchAll(regex)) {
|
|
13708
|
+
const matchIndex = match.index ?? 0;
|
|
13709
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
13710
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
13711
|
+
if (hasRuleIgnore(lines, line - 1, "unsandboxed-agent-tool")) continue;
|
|
13712
|
+
results.push(makeDiagnostic(relPath, "security/unsandboxed-agent-tool", message, help, line, col));
|
|
13713
|
+
}
|
|
13714
|
+
}
|
|
13715
|
+
return results;
|
|
13716
|
+
};
|
|
13717
|
+
const checkPromptInjection = (relPath, content, lines, isJs) => {
|
|
13718
|
+
if (hasFileIgnore(lines, "prompt-injection")) return [];
|
|
13719
|
+
const results = [];
|
|
13720
|
+
const patterns = isJs ? PROMPT_INJECTION_JS_PATTERNS : PROMPT_INJECTION_PY_PATTERNS;
|
|
13721
|
+
for (const pattern of patterns) {
|
|
13722
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
13723
|
+
for (const match of content.matchAll(regex)) {
|
|
13724
|
+
const matchIndex = match.index ?? 0;
|
|
13725
|
+
const line = content.slice(0, matchIndex).split("\n").length;
|
|
13726
|
+
const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
|
|
13727
|
+
if (hasRuleIgnore(lines, line - 1, "prompt-injection")) continue;
|
|
13728
|
+
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));
|
|
13729
|
+
}
|
|
13730
|
+
}
|
|
13731
|
+
return results;
|
|
13732
|
+
};
|
|
13733
|
+
const scanAiSecurity = async (context) => {
|
|
13734
|
+
const files = getSourceFiles(context);
|
|
13735
|
+
const diagnostics = [];
|
|
13736
|
+
for (const filePath of files) {
|
|
13737
|
+
const ext = path.extname(filePath);
|
|
13738
|
+
let content;
|
|
13739
|
+
try {
|
|
13740
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
13741
|
+
} catch {
|
|
13742
|
+
continue;
|
|
13743
|
+
}
|
|
13744
|
+
const relativePath = relativePosix(context.rootDirectory, filePath);
|
|
13745
|
+
const lines = content.split("\n");
|
|
13746
|
+
diagnostics.push(...checkInvisibleUnicode(relativePath, content, lines));
|
|
13747
|
+
const isJs = JS_EXTS.has(ext);
|
|
13748
|
+
const isPy = PY_EXTS.has(ext);
|
|
13749
|
+
if (isJs || isPy) {
|
|
13750
|
+
diagnostics.push(...checkUnsandboxedTools(relativePath, content, lines));
|
|
13751
|
+
diagnostics.push(...checkPromptInjection(relativePath, content, lines, isJs));
|
|
13752
|
+
}
|
|
13753
|
+
}
|
|
13754
|
+
return diagnostics;
|
|
13755
|
+
};
|
|
13756
|
+
//#endregion
|
|
13558
13757
|
//#region src/engines/security/audit-value.ts
|
|
13559
13758
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13560
13759
|
const readString = (record, key) => {
|
|
@@ -13934,7 +14133,7 @@ const parseJsAudit = (output, source, manifest) => {
|
|
|
13934
14133
|
rule: "security/dependency-audit-skipped",
|
|
13935
14134
|
severity: "info",
|
|
13936
14135
|
message: `Dependency audit skipped (${source}): lockfile is missing`,
|
|
13937
|
-
help: errorDetail ?? "Generate a lockfile, then re-run `
|
|
14136
|
+
help: errorDetail ?? "Generate a lockfile, then re-run `raigal scan` for dependency vulnerability checks.",
|
|
13938
14137
|
line: 0,
|
|
13939
14138
|
column: 0,
|
|
13940
14139
|
category: "Security",
|
|
@@ -14108,10 +14307,11 @@ const auditSkippedDiagnostic = (source, help) => ({
|
|
|
14108
14307
|
});
|
|
14109
14308
|
const runNpmAudit = async (rootDir, timeout) => {
|
|
14110
14309
|
try {
|
|
14111
|
-
|
|
14310
|
+
const result = await runSubprocess("npm", ["audit", "--json"], {
|
|
14112
14311
|
cwd: rootDir,
|
|
14113
14312
|
timeout
|
|
14114
|
-
})
|
|
14313
|
+
});
|
|
14314
|
+
return parseJsAudit(result.stdout, "npm audit", readRuntimeDependencies(rootDir));
|
|
14115
14315
|
} catch (error) {
|
|
14116
14316
|
return [auditSkippedDiagnostic("npm audit", `Failed to run npm audit: ${errorMessageOf(error)}`)];
|
|
14117
14317
|
}
|
|
@@ -14132,10 +14332,11 @@ const runBunAudit = async (rootDir, timeout) => {
|
|
|
14132
14332
|
const runPnpmAuditWithFallback = async (rootDir, timeout) => {
|
|
14133
14333
|
const canFallbackToNpm = fs.existsSync(path.join(rootDir, "package-lock.json"));
|
|
14134
14334
|
try {
|
|
14135
|
-
const
|
|
14335
|
+
const result = await runSubprocess("pnpm", ["audit", "--json"], {
|
|
14136
14336
|
cwd: rootDir,
|
|
14137
14337
|
timeout
|
|
14138
|
-
})
|
|
14338
|
+
});
|
|
14339
|
+
const diagnostics = parseJsAudit(result.stdout, "pnpm audit", readRuntimeDependencies(rootDir));
|
|
14139
14340
|
if (diagnostics.some((d) => d.rule === "security/dependency-audit-skipped")) {
|
|
14140
14341
|
if (canFallbackToNpm) return runNpmAudit(rootDir, timeout);
|
|
14141
14342
|
return diagnostics;
|
|
@@ -14492,7 +14693,7 @@ const RISKY_PATTERNS = [
|
|
|
14492
14693
|
//#region src/engines/security/risky.ts
|
|
14493
14694
|
const hasDangerouslySetInnerHtmlIgnore = (lines, lineIndex) => {
|
|
14494
14695
|
const start = Math.max(0, lineIndex - 2);
|
|
14495
|
-
return lines.slice(start, lineIndex + 1).some((line) => /(?:biome-ignore|eslint-disable|aislop-ignore).*(?:noDangerouslySetInnerHtml|dangerouslySetInnerHTML|dangerously-set-innerhtml)/i.test(line));
|
|
14696
|
+
return lines.slice(start, lineIndex + 1).some((line) => /(?:biome-ignore|eslint-disable|raigal-ignore|aislop-ignore).*(?:noDangerouslySetInnerHtml|dangerouslySetInnerHTML|dangerously-set-innerhtml)/i.test(line));
|
|
14496
14697
|
};
|
|
14497
14698
|
const isStructuredDataScript = (content, matchIndex) => {
|
|
14498
14699
|
const before = content.slice(Math.max(0, matchIndex - 300), matchIndex);
|
|
@@ -14820,7 +15021,11 @@ const ALL_ENGINES = [
|
|
|
14820
15021
|
name: "security",
|
|
14821
15022
|
async run(context) {
|
|
14822
15023
|
const diagnostics = [];
|
|
14823
|
-
const promises = [
|
|
15024
|
+
const promises = [
|
|
15025
|
+
scanSecrets(context),
|
|
15026
|
+
detectRiskyConstructs(context),
|
|
15027
|
+
scanAiSecurity(context)
|
|
15028
|
+
];
|
|
14824
15029
|
if (context.config.security.audit) promises.push(runDependencyAudit(context));
|
|
14825
15030
|
const results = await Promise.allSettled(promises);
|
|
14826
15031
|
for (const result of results) if (result.status === "fulfilled") diagnostics.push(...result.value);
|
|
@@ -14865,7 +15070,10 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
|
|
|
14865
15070
|
failed: true,
|
|
14866
15071
|
skipReason: r.reason instanceof Error ? r.reason.message : String(r.reason)
|
|
14867
15072
|
});
|
|
14868
|
-
|
|
15073
|
+
const stripped = stripSecretDiagnostics(finalResults);
|
|
15074
|
+
const csharp = dedupeCSharpAsync(stripped);
|
|
15075
|
+
const comments = dedupeOverlappingComments(csharp);
|
|
15076
|
+
return dedupeEquivalenceDiagnostics(comments);
|
|
14869
15077
|
};
|
|
14870
15078
|
const stripSecretDiagnostics = (results) => {
|
|
14871
15079
|
for (const result of results) for (const d of result.diagnostics) if (d.redactSource || isSecretClassRule(d.rule)) {
|
|
@@ -14914,7 +15122,8 @@ const calculateScore = (diagnostics, weights, thresholds, sourceFileCount, smoot
|
|
|
14914
15122
|
const capScale = Math.max(1, fileCount.effectiveSourceFileCount / CAP_REFERENCE_FILES);
|
|
14915
15123
|
const defaultRuleCap = typeof maxPerRule === "number" && maxPerRule > 0 ? maxPerRule : null;
|
|
14916
15124
|
const capForRule = (key) => {
|
|
14917
|
-
const
|
|
15125
|
+
const rule = key.slice(key.indexOf(":") + 1);
|
|
15126
|
+
const ruleCap = scoreImpactForRule(rule).cap;
|
|
14918
15127
|
if (typeof ruleCap === "number") return defaultRuleCap ? Math.min(defaultRuleCap, ruleCap) : ruleCap;
|
|
14919
15128
|
return defaultRuleCap;
|
|
14920
15129
|
};
|
|
@@ -15198,10 +15407,11 @@ const track = (input) => {
|
|
|
15198
15407
|
cachedInstallId = ensured.installId;
|
|
15199
15408
|
installCreated = ensured.created;
|
|
15200
15409
|
}
|
|
15201
|
-
const
|
|
15410
|
+
const merged = {
|
|
15202
15411
|
...baseProperties(cachedInstallId),
|
|
15203
15412
|
...input.properties
|
|
15204
|
-
}
|
|
15413
|
+
};
|
|
15414
|
+
const { clean, dropped } = redactProperties(merged);
|
|
15205
15415
|
if (isDebug()) {
|
|
15206
15416
|
const compact = JSON.stringify({
|
|
15207
15417
|
event: input.event,
|
|
@@ -15481,14 +15691,17 @@ const renderRailStep = (step, deps = {}) => {
|
|
|
15481
15691
|
* between the last step and the footer.
|
|
15482
15692
|
*/
|
|
15483
15693
|
const renderRailConnector = (deps = {}) => {
|
|
15484
|
-
|
|
15694
|
+
const t = deps.theme ?? theme;
|
|
15695
|
+
const s = deps.symbols ?? symbols;
|
|
15696
|
+
return ` ${style(t, "accentDim", s.rail)}\n`;
|
|
15485
15697
|
};
|
|
15486
15698
|
/**
|
|
15487
15699
|
* Render the rail-end footer line ("└ <footer text>").
|
|
15488
15700
|
*/
|
|
15489
15701
|
const renderRailFooter = (footer, deps = {}) => {
|
|
15490
15702
|
const t = deps.theme ?? theme;
|
|
15491
|
-
|
|
15703
|
+
const s = deps.symbols ?? symbols;
|
|
15704
|
+
return ` ${style(t, "accentDim", s.railEnd)} ${highlightAislop(footer, t)}\n`;
|
|
15492
15705
|
};
|
|
15493
15706
|
const renderRail = (input, deps = {}) => {
|
|
15494
15707
|
const t = deps.theme ?? theme;
|
|
@@ -15615,7 +15828,8 @@ var LiveRail = class {
|
|
|
15615
15828
|
*/
|
|
15616
15829
|
const renderHintLine = (hint, deps = {}) => {
|
|
15617
15830
|
const t = deps.theme ?? theme;
|
|
15618
|
-
|
|
15831
|
+
const s = deps.symbols ?? symbols;
|
|
15832
|
+
return ` ${style(t, "accent", s.hint)} ${highlightAislop(maskSecrets(hint), t)}\n`;
|
|
15619
15833
|
};
|
|
15620
15834
|
const createLogger = (deps = {}) => {
|
|
15621
15835
|
const t = deps.theme ?? theme;
|
|
@@ -15760,7 +15974,8 @@ const groupByFile = (diagnostics) => {
|
|
|
15760
15974
|
});
|
|
15761
15975
|
};
|
|
15762
15976
|
const isInstalled = (bin) => {
|
|
15763
|
-
|
|
15977
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
15978
|
+
return spawnSync(cmd, [bin], { encoding: "utf-8" }).status === 0;
|
|
15764
15979
|
};
|
|
15765
15980
|
const copyToClipboard = (text) => {
|
|
15766
15981
|
const args = {
|
|
@@ -16243,7 +16458,9 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
|
|
|
16243
16458
|
if (!header.kid) throw new JwtVerificationError("JWT header missing 'kid'", "unknown_kid");
|
|
16244
16459
|
const publicKey = getPublicKey(header.kid);
|
|
16245
16460
|
if (!publicKey) throw new JwtVerificationError(`Unknown or untrusted signing key id (kid: ${header.kid})`, "unknown_kid");
|
|
16246
|
-
|
|
16461
|
+
const signedData = Buffer.from(`${headerB64}.${payloadB64}`, "utf-8");
|
|
16462
|
+
const signature = Buffer.from(signatureB64, "base64url");
|
|
16463
|
+
if (!verify(null, signedData, publicKey, signature)) throw new JwtVerificationError("Invalid JWT cryptographic signature", "invalid_signature");
|
|
16247
16464
|
let rawPayload;
|
|
16248
16465
|
try {
|
|
16249
16466
|
rawPayload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf-8"));
|
|
@@ -16255,7 +16472,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
|
|
|
16255
16472
|
const claims = parsed.data;
|
|
16256
16473
|
const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
|
|
16257
16474
|
if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
|
|
16258
|
-
if (!isVersionAtLeast("0.2.
|
|
16475
|
+
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");
|
|
16259
16476
|
const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
16260
16477
|
const grace = options.allowGraceSeconds ?? 0;
|
|
16261
16478
|
if (now > claims.exp + grace) {
|
|
@@ -16289,7 +16506,7 @@ const fetchGitHubOidcToken = async () => {
|
|
|
16289
16506
|
};
|
|
16290
16507
|
//#endregion
|
|
16291
16508
|
//#region src/cloud/entitlement.ts
|
|
16292
|
-
const GRACE_PERIOD_SECONDS =
|
|
16509
|
+
const GRACE_PERIOD_SECONDS = 259200;
|
|
16293
16510
|
var MissingCredentialError = class extends Error {
|
|
16294
16511
|
constructor(message = "No organization credential found") {
|
|
16295
16512
|
super(message);
|
|
@@ -16414,7 +16631,8 @@ const getEntitlement = async () => {
|
|
|
16414
16631
|
}
|
|
16415
16632
|
} else oidcToken = await fetchGitHubOidcToken();
|
|
16416
16633
|
try {
|
|
16417
|
-
const
|
|
16634
|
+
const tokenToPass = activeToken?.token ?? "";
|
|
16635
|
+
const response = await fetchEntitlementFromApi(tokenToPass, oidcToken);
|
|
16418
16636
|
const claims = verifyEntitlementJwt(response.entitlement, { nowSeconds: now });
|
|
16419
16637
|
saveCachedEntitlement(response.entitlement, response.refresh_after);
|
|
16420
16638
|
return {
|
|
@@ -16428,7 +16646,7 @@ const getEntitlement = async () => {
|
|
|
16428
16646
|
};
|
|
16429
16647
|
//#endregion
|
|
16430
16648
|
//#region src/cloud/owner.ts
|
|
16431
|
-
const GITHUB_REMOTE_RE = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([
|
|
16649
|
+
const GITHUB_REMOTE_RE = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
|
|
16432
16650
|
const parseGitRemote = (rawUrl) => {
|
|
16433
16651
|
if (!rawUrl) return {
|
|
16434
16652
|
owner: null,
|
|
@@ -16957,7 +17175,8 @@ const fixNarrativeComments = async (context) => {
|
|
|
16957
17175
|
for (let i = 0; i < lines.length; i += 1) if (!toRemove.has(i + 1)) kept.push(lines[i]);
|
|
16958
17176
|
const newContent = kept.join("\n");
|
|
16959
17177
|
if (newContent !== content) {
|
|
16960
|
-
|
|
17178
|
+
const afterLiterals = extractStringAndTemplateLiterals(newContent, ext);
|
|
17179
|
+
if (!areLiteralMultisetsEqual(beforeLiterals, afterLiterals)) continue;
|
|
16961
17180
|
fs.writeFileSync(filePath, newContent);
|
|
16962
17181
|
}
|
|
16963
17182
|
}
|
|
@@ -17327,7 +17546,7 @@ const detectUnusedDeclarations = async (context) => {
|
|
|
17327
17546
|
rule: "code-quality/unused-declaration",
|
|
17328
17547
|
severity: "warning",
|
|
17329
17548
|
message: `Unused ${extracted.kind}: ${extracted.name}`,
|
|
17330
|
-
help: "This top-level declaration is never used;
|
|
17549
|
+
help: "This top-level declaration is never used; raigal will remove it.",
|
|
17331
17550
|
line: d.line,
|
|
17332
17551
|
column: d.column,
|
|
17333
17552
|
category: "Dead Code",
|
|
@@ -17345,7 +17564,7 @@ const detectUnusedDeclarations = async (context) => {
|
|
|
17345
17564
|
rule: "code-quality/unused-declaration",
|
|
17346
17565
|
severity: "warning",
|
|
17347
17566
|
message: `Unused ${kind}: ${name}`,
|
|
17348
|
-
help: "This top-level declaration is never imported;
|
|
17567
|
+
help: "This top-level declaration is never imported; raigal will remove it.",
|
|
17349
17568
|
line: d.line,
|
|
17350
17569
|
column: d.column,
|
|
17351
17570
|
category: "Dead Code",
|
|
@@ -17451,7 +17670,7 @@ const removeUnusedDeclarations = (rootDirectory, declarations) => {
|
|
|
17451
17670
|
};
|
|
17452
17671
|
//#endregion
|
|
17453
17672
|
//#region src/commands/fix-expo.ts
|
|
17454
|
-
const INSTALL_TIMEOUT$1 =
|
|
17673
|
+
const INSTALL_TIMEOUT$1 = 18e5;
|
|
17455
17674
|
const fixExpoDependencies = async (context, onProgress) => {
|
|
17456
17675
|
await removeDisallowedExpoPackages(context.rootDirectory, onProgress);
|
|
17457
17676
|
onProgress?.("Expo dependency alignment · running expo install --fix (can take a few minutes)");
|
|
@@ -17505,8 +17724,8 @@ const removeDisallowedExpoPackages = async (rootDir, onProgress) => {
|
|
|
17505
17724
|
};
|
|
17506
17725
|
//#endregion
|
|
17507
17726
|
//#region src/commands/fix-force.ts
|
|
17508
|
-
const INSTALL_TIMEOUT =
|
|
17509
|
-
const AUDIT_TIMEOUT =
|
|
17727
|
+
const INSTALL_TIMEOUT = 18e5;
|
|
17728
|
+
const AUDIT_TIMEOUT = 6e4;
|
|
17510
17729
|
const detectPackageManager = (rootDirectory) => {
|
|
17511
17730
|
if (fs.existsSync(path.join(rootDirectory, "pnpm-lock.yaml"))) return "pnpm";
|
|
17512
17731
|
if (fs.existsSync(path.join(rootDirectory, "package-lock.json")) || fs.existsSync(path.join(rootDirectory, "package.json"))) return "npm";
|
|
@@ -17825,13 +18044,14 @@ const CANNOT_SCOPE_REASON = "cannot honour an explicit file list under --changes
|
|
|
17825
18044
|
const MISSING_MANIFEST_REASON = "no manifest or lockfile in the selected scope";
|
|
17826
18045
|
const fixScopeError = (resolvedDir, flags) => {
|
|
17827
18046
|
if (flags.changes && flags.staged) return "--changes and --staged cannot be used together.";
|
|
17828
|
-
|
|
18047
|
+
const options = {
|
|
17829
18048
|
changes: Boolean(flags.changes),
|
|
17830
18049
|
staged: Boolean(flags.staged),
|
|
17831
18050
|
base: flags.base,
|
|
17832
18051
|
verbose: false,
|
|
17833
18052
|
json: false
|
|
17834
|
-
}
|
|
18053
|
+
};
|
|
18054
|
+
return scanTargetError(resolvedDir, options);
|
|
17835
18055
|
};
|
|
17836
18056
|
const collectFixFileScope = (rootDirectory, excludePatterns, includePatterns, flags) => {
|
|
17837
18057
|
const mode = resolveScanScopeMode({
|
|
@@ -17890,9 +18110,10 @@ const runFormattingStep = async (deps) => {
|
|
|
17890
18110
|
else if (deps.projectInfo.languages.includes("python")) log.warn("Python detected but ruff is not installed; skipping Python formatting fixes.");
|
|
17891
18111
|
if (deps.projectInfo.languages.includes("go") && deps.projectInfo.installedTools.gofmt) await deps.runStep("Formatting (go)", () => runGofmt(deps.context), () => fixGofmt(deps.context));
|
|
17892
18112
|
else if (deps.projectInfo.languages.includes("go")) log.warn("Go detected but gofmt is not installed; skipping Go formatting fixes.");
|
|
17893
|
-
if (deps.projectInfo.languages.includes("rust") && deps.projectInfo.installedTools.rustfmt)
|
|
17894
|
-
|
|
17895
|
-
|
|
18113
|
+
if (deps.projectInfo.languages.includes("rust") && deps.projectInfo.installedTools.rustfmt) {
|
|
18114
|
+
if (isScopedFix(deps.context)) deps.skipStep?.("Formatting (rust)", CANNOT_SCOPE_REASON);
|
|
18115
|
+
else await deps.runStep("Formatting (rust)", () => runGenericFormatter(deps.context, "rust"), () => fixGenericFormatter(deps.context, "rust"));
|
|
18116
|
+
} else if (deps.projectInfo.languages.includes("rust")) log.warn("Rust detected but rustfmt is not installed; skipping Rust formatting fixes.");
|
|
17896
18117
|
if (deps.projectInfo.languages.includes("ruby") && deps.projectInfo.installedTools.rubocop) {
|
|
17897
18118
|
if (!skipUnsafeSafeFormatter(deps, "ruby")) await deps.runStep("Formatting (ruby)", () => runGenericFormatter(deps.context, "ruby"), () => fixGenericFormatter(deps.context, "ruby"));
|
|
17898
18119
|
} else if (deps.projectInfo.languages.includes("ruby")) log.warn("Ruby detected but rubocop is not installed; skipping Ruby formatting fixes.");
|
|
@@ -17941,7 +18162,8 @@ const runDeclarationStep = async (deps) => {
|
|
|
17941
18162
|
if (!deps.config.engines["code-quality"]) return;
|
|
17942
18163
|
if (!hasJsOrTs(deps.projectInfo)) return;
|
|
17943
18164
|
await deps.runStep("Unused declarations", () => detectUnusedDeclarations(deps.context), async () => {
|
|
17944
|
-
const
|
|
18165
|
+
const diagnostics = await detectUnusedDeclarations(deps.context);
|
|
18166
|
+
const declarations = diagnosticsToDeclarations(diagnostics);
|
|
17945
18167
|
removeUnusedDeclarations(deps.resolvedDir, declarations);
|
|
17946
18168
|
});
|
|
17947
18169
|
};
|
|
@@ -17972,10 +18194,14 @@ const runForceSteps = async (deps) => {
|
|
|
17972
18194
|
return (await runKnipUnusedFiles(deps.resolvedDir)).filter((diagnostic) => isPathInFixScope(deps.context, diagnostic.filePath));
|
|
17973
18195
|
}, () => fixUnusedFiles(deps.resolvedDir, (filePath) => isPathInFixScope(deps.context, filePath)));
|
|
17974
18196
|
const railUpdate = (label) => deps.rail.setActiveLabel(label);
|
|
17975
|
-
if (deps.config.engines.security)
|
|
17976
|
-
|
|
17977
|
-
|
|
17978
|
-
|
|
18197
|
+
if (deps.config.engines.security) {
|
|
18198
|
+
if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Dependency audit fixes", MISSING_MANIFEST_REASON);
|
|
18199
|
+
else await deps.runStep("Dependency audit fixes", () => runDependencyAudit(deps.context), () => fixDependencyAudit(deps.context, railUpdate));
|
|
18200
|
+
}
|
|
18201
|
+
if (deps.projectInfo.frameworks.includes("expo") && deps.config.lint.expoDoctor) {
|
|
18202
|
+
if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Expo dependency alignment", MISSING_MANIFEST_REASON);
|
|
18203
|
+
else await deps.runStep("Expo dependency alignment", () => runExpoDoctor(deps.context), () => fixExpoDependencies(deps.context, railUpdate));
|
|
18204
|
+
}
|
|
17979
18205
|
};
|
|
17980
18206
|
//#endregion
|
|
17981
18207
|
//#region src/commands/fix-plan.ts
|
|
@@ -18297,7 +18523,8 @@ const saveCachedPolicy = (repoSlug, policy) => {
|
|
|
18297
18523
|
};
|
|
18298
18524
|
const fetchRemotePolicy = async (repoSlug, token) => {
|
|
18299
18525
|
try {
|
|
18300
|
-
const
|
|
18526
|
+
const endpoint = `/v1/policy?repo=${encodeURIComponent(repoSlug.toLowerCase())}`;
|
|
18527
|
+
const data = await requestApi(endpoint, {
|
|
18301
18528
|
method: "GET",
|
|
18302
18529
|
token,
|
|
18303
18530
|
timeoutMs: 4e3
|
|
@@ -18357,6 +18584,8 @@ const resolveHistoryDir = (directory) => {
|
|
|
18357
18584
|
const resolved = path.resolve(directory);
|
|
18358
18585
|
const primary = path.join(resolved, CONFIG_DIR);
|
|
18359
18586
|
if (fs.existsSync(primary)) return primary;
|
|
18587
|
+
const legacy = path.join(resolved, ".aislop");
|
|
18588
|
+
if (fs.existsSync(legacy)) return legacy;
|
|
18360
18589
|
return null;
|
|
18361
18590
|
};
|
|
18362
18591
|
const historyPath = (directory) => {
|
|
@@ -18595,7 +18824,9 @@ const applyRemoval = (result, opts, target, nextContent) => {
|
|
|
18595
18824
|
//#region src/hooks/install/rules-only.ts
|
|
18596
18825
|
const installRulesOnly = (opts, paths, summary) => {
|
|
18597
18826
|
const result = emptyResult();
|
|
18598
|
-
const
|
|
18827
|
+
const existing = readIfExists(paths.rules);
|
|
18828
|
+
const hash = sentinelHash(AISLOP_MD_BODY);
|
|
18829
|
+
const next = upsertMarkdownFence(existing, AISLOP_MD_BODY, hash).nextContent;
|
|
18599
18830
|
applyContent(result, opts, paths.rules, next, summary);
|
|
18600
18831
|
if (paths.host && paths.marker) {
|
|
18601
18832
|
const host = readIfExists(paths.host) ?? "";
|
|
@@ -18647,7 +18878,7 @@ const uninstallAntigravity = (opts) => {
|
|
|
18647
18878
|
//#endregion
|
|
18648
18879
|
//#region src/hooks/io/json-patch.ts
|
|
18649
18880
|
const AISLOP_SENTINEL_KEY = "__aislop";
|
|
18650
|
-
const isAislopManaged = (x) => typeof x === "object" && x !== null && "__aislop" in x && x["__aislop"] != null;
|
|
18881
|
+
const isAislopManaged = (x) => typeof x === "object" && x !== null && ("__aislop" in x && x["__aislop"] != null || "__raigal" in x && x["__raigal"] != null);
|
|
18651
18882
|
const groupIsAislop = (group) => {
|
|
18652
18883
|
if (typeof group !== "object" || group === null) return false;
|
|
18653
18884
|
const hooks = group.hooks;
|
|
@@ -18702,14 +18933,14 @@ const resolveClaudePaths = (opts) => {
|
|
|
18702
18933
|
};
|
|
18703
18934
|
const buildHookGroup$1 = () => {
|
|
18704
18935
|
const hashBody = JSON.stringify({
|
|
18705
|
-
command: "
|
|
18936
|
+
command: "raigal hook claude",
|
|
18706
18937
|
matcher: "Edit|Write|MultiEdit"
|
|
18707
18938
|
});
|
|
18708
18939
|
return {
|
|
18709
18940
|
matcher: "Edit|Write|MultiEdit",
|
|
18710
18941
|
hooks: [{
|
|
18711
18942
|
type: "command",
|
|
18712
|
-
command: "
|
|
18943
|
+
command: "raigal hook claude",
|
|
18713
18944
|
[AISLOP_SENTINEL_KEY]: {
|
|
18714
18945
|
v: 1,
|
|
18715
18946
|
managed: true,
|
|
@@ -18719,12 +18950,12 @@ const buildHookGroup$1 = () => {
|
|
|
18719
18950
|
};
|
|
18720
18951
|
};
|
|
18721
18952
|
const buildStopHookGroup = () => {
|
|
18722
|
-
const hashBody = JSON.stringify({ command: "
|
|
18953
|
+
const hashBody = JSON.stringify({ command: "raigal hook claude --stop" });
|
|
18723
18954
|
return {
|
|
18724
18955
|
matcher: "",
|
|
18725
18956
|
hooks: [{
|
|
18726
18957
|
type: "command",
|
|
18727
|
-
command: "
|
|
18958
|
+
command: "raigal hook claude --stop",
|
|
18728
18959
|
[AISLOP_SENTINEL_KEY]: {
|
|
18729
18960
|
v: 1,
|
|
18730
18961
|
managed: true,
|
|
@@ -18736,14 +18967,14 @@ const buildStopHookGroup = () => {
|
|
|
18736
18967
|
const FILE_CHANGED_MATCHER = ".raigal/config.yml|.raigal/rules.yml|.aislop/config.yml|.aislop/rules.yml|package.json";
|
|
18737
18968
|
const buildFileChangedHookGroup = () => {
|
|
18738
18969
|
const hashBody = JSON.stringify({
|
|
18739
|
-
command: "
|
|
18970
|
+
command: "raigal hook claude --on-file-changed",
|
|
18740
18971
|
matcher: FILE_CHANGED_MATCHER
|
|
18741
18972
|
});
|
|
18742
18973
|
return {
|
|
18743
18974
|
matcher: FILE_CHANGED_MATCHER,
|
|
18744
18975
|
hooks: [{
|
|
18745
18976
|
type: "command",
|
|
18746
|
-
command: "
|
|
18977
|
+
command: "raigal hook claude --on-file-changed",
|
|
18747
18978
|
[AISLOP_SENTINEL_KEY]: {
|
|
18748
18979
|
v: 1,
|
|
18749
18980
|
managed: true,
|
|
@@ -18771,7 +19002,8 @@ const installClaude = (opts) => {
|
|
|
18771
19002
|
const nextSettings = renderSettings$1(readIfExists(paths.settings), Boolean(opts.qualityGate));
|
|
18772
19003
|
applyContent(result, opts, paths.settings, nextSettings, "register PostToolUse + FileChanged hooks");
|
|
18773
19004
|
const mdHash = sentinelHash(AISLOP_MD_BODY);
|
|
18774
|
-
const
|
|
19005
|
+
const existingMd = readIfExists(paths.aislopMd);
|
|
19006
|
+
const fenced = upsertMarkdownFence(existingMd, AISLOP_MD_BODY, mdHash);
|
|
18775
19007
|
applyContent(result, opts, paths.aislopMd, fenced.nextContent, "write AISLOP.md rules");
|
|
18776
19008
|
const existingClaudeMd = readIfExists(paths.claudeMd) ?? "";
|
|
18777
19009
|
const marker = "@AISLOP.md";
|
|
@@ -18886,11 +19118,11 @@ const resolveCursorPaths = (opts) => {
|
|
|
18886
19118
|
};
|
|
18887
19119
|
const buildHookEntry = () => {
|
|
18888
19120
|
const hashBody = JSON.stringify({
|
|
18889
|
-
command: "
|
|
19121
|
+
command: "raigal hook cursor",
|
|
18890
19122
|
timeout: 5e3
|
|
18891
19123
|
});
|
|
18892
19124
|
return {
|
|
18893
|
-
command: "
|
|
19125
|
+
command: "raigal hook cursor",
|
|
18894
19126
|
type: "command",
|
|
18895
19127
|
timeout: 5e3,
|
|
18896
19128
|
[AISLOP_SENTINEL_KEY]: {
|
|
@@ -18917,7 +19149,9 @@ const installCursor = (opts) => {
|
|
|
18917
19149
|
const nextHooks = renderHooksJson(readIfExists(paths.hooks));
|
|
18918
19150
|
applyContent(result, opts, paths.hooks, nextHooks, "register afterFileEdit hook");
|
|
18919
19151
|
if (opts.scope === "project") {
|
|
18920
|
-
const
|
|
19152
|
+
const existingRules = readIfExists(paths.rules);
|
|
19153
|
+
const hash = sentinelHash(AISLOP_MD_BODY);
|
|
19154
|
+
const rules = upsertMarkdownFence(existingRules, AISLOP_MD_BODY, hash).nextContent;
|
|
18921
19155
|
applyContent(result, opts, paths.rules, rules, "write .cursor/rules/aislop.mdc");
|
|
18922
19156
|
}
|
|
18923
19157
|
return result;
|
|
@@ -18957,15 +19191,15 @@ const resolveGeminiPaths = (opts) => {
|
|
|
18957
19191
|
};
|
|
18958
19192
|
const buildHookGroup = () => {
|
|
18959
19193
|
const hashBody = JSON.stringify({
|
|
18960
|
-
command: "
|
|
19194
|
+
command: "raigal hook gemini",
|
|
18961
19195
|
matcher: "write_file|replace"
|
|
18962
19196
|
});
|
|
18963
19197
|
return {
|
|
18964
19198
|
matcher: "write_file|replace",
|
|
18965
19199
|
hooks: [{
|
|
18966
|
-
name: "
|
|
19200
|
+
name: "raigal",
|
|
18967
19201
|
type: "command",
|
|
18968
|
-
command: "
|
|
19202
|
+
command: "raigal hook gemini",
|
|
18969
19203
|
timeout: 5e3,
|
|
18970
19204
|
[AISLOP_SENTINEL_KEY]: {
|
|
18971
19205
|
v: 1,
|
|
@@ -18990,7 +19224,9 @@ const installGemini = (opts) => {
|
|
|
18990
19224
|
const result = emptyResult();
|
|
18991
19225
|
const next = renderSettings(readIfExists(paths.settings));
|
|
18992
19226
|
applyContent(result, opts, paths.settings, next, "register AfterTool hook");
|
|
18993
|
-
const
|
|
19227
|
+
const existingMd = readIfExists(paths.aislopMd);
|
|
19228
|
+
const hash = sentinelHash(AISLOP_MD_BODY);
|
|
19229
|
+
const fenced = upsertMarkdownFence(existingMd, AISLOP_MD_BODY, hash).nextContent;
|
|
18994
19230
|
applyContent(result, opts, paths.aislopMd, fenced, "write AISLOP.md rules");
|
|
18995
19231
|
const existingGeminiMd = readIfExists(paths.geminiMd) ?? "";
|
|
18996
19232
|
const marker = "@AISLOP.md";
|
|
@@ -19056,8 +19292,8 @@ const uninstallKilocode = (opts) => {
|
|
|
19056
19292
|
const resolvePiPaths = (opts) => {
|
|
19057
19293
|
return { extension: opts.scope === "project" ? path.join(opts.cwd, ".pi", "extensions", "aislop.js") : path.join(opts.home, ".pi", "agent", "extensions", "aislop.js") };
|
|
19058
19294
|
};
|
|
19059
|
-
const PI_EXTENSION_SOURCE = `//
|
|
19060
|
-
// Reinstall with:
|
|
19295
|
+
const PI_EXTENSION_SOURCE = `// raigal — auto-generated pi extension. Do not edit by hand.
|
|
19296
|
+
// Reinstall with: raigal hook install --pi
|
|
19061
19297
|
import { spawn } from "node:child_process";
|
|
19062
19298
|
|
|
19063
19299
|
// Runs the CLI async so a slow scan never blocks pi's event loop, and forwards
|
|
@@ -19102,7 +19338,7 @@ export default function (pi) {
|
|
|
19102
19338
|
const filePath = event.input && event.input.path;
|
|
19103
19339
|
if (typeof filePath !== "string" || filePath.length === 0) return;
|
|
19104
19340
|
|
|
19105
|
-
const bin = process.env.AISLOP_BIN || "
|
|
19341
|
+
const bin = process.env.RAIGAL_BIN || process.env.AISLOP_BIN || "raigal";
|
|
19106
19342
|
const payload = JSON.stringify({
|
|
19107
19343
|
cwd: ctx.cwd,
|
|
19108
19344
|
file_path: filePath,
|
|
@@ -19129,7 +19365,7 @@ export default function (pi) {
|
|
|
19129
19365
|
const installPi = (opts) => {
|
|
19130
19366
|
const paths = resolvePiPaths(opts);
|
|
19131
19367
|
const result = emptyResult();
|
|
19132
|
-
applyContent(result, opts, paths.extension, PI_EXTENSION_SOURCE, "write pi
|
|
19368
|
+
applyContent(result, opts, paths.extension, PI_EXTENSION_SOURCE, "write pi raigal extension");
|
|
19133
19369
|
return result;
|
|
19134
19370
|
};
|
|
19135
19371
|
const uninstallPi = (opts) => {
|
|
@@ -19768,6 +20004,9 @@ const RULE_LABELS = {
|
|
|
19768
20004
|
"security/shell-injection": "Possible shell injection",
|
|
19769
20005
|
"security/unsafe-deserialization": "Unsafe deserialization",
|
|
19770
20006
|
"security/unsafe-c-call": "Memory-unsafe C call",
|
|
20007
|
+
"security/prompt-injection": "Prompt injection (untrusted system prompt)",
|
|
20008
|
+
"security/unsandboxed-agent-tool": "Unsandboxed agent code execution tool",
|
|
20009
|
+
"security/invisible-unicode-tags": "Invisible Unicode tag character (stealth injection)",
|
|
19771
20010
|
"dotnet/projects-skipped": "C# projects skipped by build-backed lint",
|
|
19772
20011
|
"cppcheck/chunks-skipped": "C/C++ files skipped by cppcheck",
|
|
19773
20012
|
"eslint/no-undef": "Undefined identifier",
|
|
@@ -19856,6 +20095,9 @@ const RULE_DESCRIPTIONS = {
|
|
|
19856
20095
|
"security/shell-injection": "Shell command is built from unsanitized input.",
|
|
19857
20096
|
"security/unsafe-deserialization": "Legacy .NET formatter can deserialize arbitrary types (RCE).",
|
|
19858
20097
|
"security/unsafe-c-call": "Unbounded C string/exec function with buffer-overflow or shell risk.",
|
|
20098
|
+
"security/prompt-injection": "Untrusted dynamic variable interpolation into system or developer prompt.",
|
|
20099
|
+
"security/unsandboxed-agent-tool": "Agent executes code or commands on host without container or interactive gate.",
|
|
20100
|
+
"security/invisible-unicode-tags": "Invisible Unicode Tag block (U+E0000-U+E007F) used for prompt injection or ASCII smuggling.",
|
|
19859
20101
|
"dotnet/projects-skipped": "Build-backed C# lint only ran on restored projects; the rest were skipped.",
|
|
19860
20102
|
"cppcheck/chunks-skipped": "A cppcheck invocation timed out or failed on one or more chunks; their files were not analyzed.",
|
|
19861
20103
|
"oxlint/*": "JavaScript/TypeScript lint finding from oxlint.",
|
|
@@ -19930,7 +20172,8 @@ const renderActionRows = (steps, t) => {
|
|
|
19930
20172
|
const renderTextSteps = (steps, t, s) => {
|
|
19931
20173
|
return steps.filter((step) => step.text).map((step) => {
|
|
19932
20174
|
const glyph = step.emphasis === "primary" ? s.hint : s.bullet;
|
|
19933
|
-
|
|
20175
|
+
const tokenFor = step.emphasis === "primary" ? "accent" : "muted";
|
|
20176
|
+
return ` ${style(t, tokenFor, glyph)} ${highlightAislop(step.text ?? "", t)}`;
|
|
19934
20177
|
});
|
|
19935
20178
|
};
|
|
19936
20179
|
const renderSummary = (input, deps = {}) => {
|
|
@@ -19987,10 +20230,14 @@ const renderSummary = (input, deps = {}) => {
|
|
|
19987
20230
|
return lines.join("\n");
|
|
19988
20231
|
};
|
|
19989
20232
|
const renderStarCta = (deps = {}) => {
|
|
19990
|
-
|
|
20233
|
+
const t = deps.theme ?? theme;
|
|
20234
|
+
const repository = terminalLink("https://raigal.dev");
|
|
20235
|
+
return `\n ${style(t, "muted", `★ Found this useful? Consider purchasing this software at ${repository}`)}\n`;
|
|
19991
20236
|
};
|
|
19992
20237
|
const renderTeamCta = (deps = {}) => {
|
|
19993
|
-
|
|
20238
|
+
const t = deps.theme ?? theme;
|
|
20239
|
+
const href = terminalLink("https://raigal.dev/contact?intent=team-baseline");
|
|
20240
|
+
return `\n ${style(t, "muted", `→ Using raigal with a team? Get a 14-day team baseline at ${href}`)}\n`;
|
|
19994
20241
|
};
|
|
19995
20242
|
const renderCleanRun = (input, deps = {}) => {
|
|
19996
20243
|
const t = deps.theme ?? theme;
|
|
@@ -20216,7 +20463,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
20216
20463
|
architectureRulesPath: config.engines.architecture ? rulesPath : void 0,
|
|
20217
20464
|
engines: config.engines
|
|
20218
20465
|
};
|
|
20219
|
-
const
|
|
20466
|
+
const severityAdjusted = (await runEnginesWithProgress({
|
|
20220
20467
|
rootDirectory: resolvedDir,
|
|
20221
20468
|
languages: projectInfo.languages,
|
|
20222
20469
|
frameworks: projectInfo.frameworks,
|
|
@@ -20232,7 +20479,8 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
20232
20479
|
}, config.engines, machineOutput)).map((result) => ({
|
|
20233
20480
|
...result,
|
|
20234
20481
|
diagnostics: config.overrides.length ? result.diagnostics : applyRuleSeverities(result.diagnostics, config.rules)
|
|
20235
|
-
}))
|
|
20482
|
+
}));
|
|
20483
|
+
const { results: unannotated, suppressedCount } = applySuppressions(severityAdjusted, resolvedDir);
|
|
20236
20484
|
if (suppressedCount > 0 && !machineOutput) log.muted(`Suppressed ${suppressedCount} finding(s) via raigal-ignore directives`);
|
|
20237
20485
|
const changeMap = options.changes && !options.staged ? getChangedLineMap(resolvedDir, options.base) : null;
|
|
20238
20486
|
const results = changeMap ? unannotated.map((result) => ({
|
|
@@ -20293,12 +20541,12 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
20293
20541
|
scoreFileCount
|
|
20294
20542
|
});
|
|
20295
20543
|
if (options.sarif) {
|
|
20296
|
-
const { buildSarifLog } = await import("./sarif-
|
|
20544
|
+
const { buildSarifLog } = await import("./sarif-CpuYM3aK.js");
|
|
20297
20545
|
console.log(JSON.stringify(buildSarifLog(results), null, 2));
|
|
20298
20546
|
return completion;
|
|
20299
20547
|
}
|
|
20300
20548
|
if (options.json) {
|
|
20301
|
-
const { buildJsonOutput } = await import("./json-
|
|
20549
|
+
const { buildJsonOutput } = await import("./json-BflWt4dX.js");
|
|
20302
20550
|
const jsonOut = buildJsonOutput(results, scoreResult, scoreFileCount, elapsedMs, scanCoverage);
|
|
20303
20551
|
console.log(JSON.stringify(jsonOut, null, 2));
|
|
20304
20552
|
return completion;
|
|
@@ -20452,7 +20700,8 @@ const runFixBody = async (resolvedDir, config, options, projectInfo) => {
|
|
|
20452
20700
|
brand: options.printBrand !== false
|
|
20453
20701
|
}));
|
|
20454
20702
|
const safe = Boolean(options.safe);
|
|
20455
|
-
const
|
|
20703
|
+
const excludePatterns = [...config.exclude, ...readAislopIgnorePatterns(resolvedDir)];
|
|
20704
|
+
const scope = collectFixFileScope(resolvedDir, excludePatterns, config.include, options);
|
|
20456
20705
|
const scopedProjectInfo = scope ? {
|
|
20457
20706
|
...projectInfo,
|
|
20458
20707
|
languages: detectSourceLanguages([...scope.files, ...scope.testFiles])
|
|
@@ -20678,7 +20927,7 @@ const promptForConfigChoices = async () => {
|
|
|
20678
20927
|
initialValues: DEFAULT_ENGINE_SELECTION,
|
|
20679
20928
|
required: false
|
|
20680
20929
|
}));
|
|
20681
|
-
if (enginesSelection === void 0) return null;
|
|
20930
|
+
if (enginesSelection === void 0 || isCancel(enginesSelection)) return null;
|
|
20682
20931
|
const failBelowRaw = await text({
|
|
20683
20932
|
message: "CI quality gate — fail the build when the score drops below (0-100)",
|
|
20684
20933
|
initialValue: "70",
|
|
@@ -20809,16 +21058,18 @@ const initCommand = async (directory, options = {}) => {
|
|
|
20809
21058
|
status: "done",
|
|
20810
21059
|
label: `Wrote ${CONFIG_DIR}/${CONFIG_FILE}`
|
|
20811
21060
|
}];
|
|
20812
|
-
if (choices.engines.includes("architecture"))
|
|
20813
|
-
fs.
|
|
20814
|
-
|
|
20815
|
-
|
|
20816
|
-
|
|
21061
|
+
if (choices.engines.includes("architecture")) {
|
|
21062
|
+
if (!fs.existsSync(rulesPath)) {
|
|
21063
|
+
fs.writeFileSync(rulesPath, DEFAULT_RULES_YAML);
|
|
21064
|
+
steps.push({
|
|
21065
|
+
status: "done",
|
|
21066
|
+
label: `Wrote ${CONFIG_DIR}/${RULES_FILE}`
|
|
21067
|
+
});
|
|
21068
|
+
} else steps.push({
|
|
21069
|
+
status: "skipped",
|
|
21070
|
+
label: `${CONFIG_DIR}/${RULES_FILE} already exists`
|
|
20817
21071
|
});
|
|
20818
|
-
}
|
|
20819
|
-
status: "skipped",
|
|
20820
|
-
label: `${CONFIG_DIR}/${RULES_FILE} already exists`
|
|
20821
|
-
});
|
|
21072
|
+
}
|
|
20822
21073
|
const workflowResult = writeGithubWorkflow(resolvedDir, choices.writeGithubWorkflow);
|
|
20823
21074
|
if (workflowResult.status === "written") steps.push({
|
|
20824
21075
|
status: "done",
|
|
@@ -21012,8 +21263,10 @@ const runSearchPrompt = async (options) => {
|
|
|
21012
21263
|
}
|
|
21013
21264
|
if (key.name === "space" && options.mode === "multi") {
|
|
21014
21265
|
const item = filtered[cursor];
|
|
21015
|
-
if (item)
|
|
21016
|
-
|
|
21266
|
+
if (item) {
|
|
21267
|
+
if (selected.has(item.value)) selected.delete(item.value);
|
|
21268
|
+
else selected.add(item.value);
|
|
21269
|
+
}
|
|
21017
21270
|
render();
|
|
21018
21271
|
return;
|
|
21019
21272
|
}
|
|
@@ -21209,7 +21462,8 @@ const BUILTIN_RULES = [
|
|
|
21209
21462
|
"typescript/*",
|
|
21210
21463
|
"dotnet/*",
|
|
21211
21464
|
"cppcheck/*",
|
|
21212
|
-
"clang-tidy/*"
|
|
21465
|
+
"clang-tidy/*",
|
|
21466
|
+
"expo-doctor/*"
|
|
21213
21467
|
]
|
|
21214
21468
|
},
|
|
21215
21469
|
{
|
|
@@ -21308,7 +21562,10 @@ const BUILTIN_RULES = [
|
|
|
21308
21562
|
"security/shell-injection",
|
|
21309
21563
|
"security/unsafe-deserialization",
|
|
21310
21564
|
"security/unsafe-c-call",
|
|
21311
|
-
"security/dependency-audit-skipped"
|
|
21565
|
+
"security/dependency-audit-skipped",
|
|
21566
|
+
"security/prompt-injection",
|
|
21567
|
+
"security/unsandboxed-agent-tool",
|
|
21568
|
+
"security/invisible-unicode-tags"
|
|
21312
21569
|
]
|
|
21313
21570
|
}
|
|
21314
21571
|
];
|
|
@@ -21318,7 +21575,8 @@ const collectRuleEntries = (directory) => {
|
|
|
21318
21575
|
for (const { engine, rules } of BUILTIN_RULES) for (const rule of rules) entries.push(toRuleEntry(engine, rule));
|
|
21319
21576
|
const configDir = findConfigDir(resolvedDir);
|
|
21320
21577
|
if (configDir) {
|
|
21321
|
-
const
|
|
21578
|
+
const rulesPath = path.join(configDir, RULES_FILE);
|
|
21579
|
+
const archRules = loadArchitectureRules(rulesPath);
|
|
21322
21580
|
for (const rule of archRules) entries.push({
|
|
21323
21581
|
id: `arch/${rule.name}`,
|
|
21324
21582
|
engine: "architecture",
|