@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/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { t as APP_VERSION } from "./version-9Cnt1TiK.js";
2
- import { a as getEngineLabel, i as ENGINE_INFO, r as scoreImpactForRule, t as summarizeFindingAssessments } from "./finding-assessment-M5fUTE-C.js";
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 = 1024 * 1024;
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
- return validateConfig(loadConfigChain(configPath), configPath);
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 w = wcwidth(ch.codePointAt(0) ?? 0);
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 cw = wcwidth(ch.codePointAt(0) ?? 0);
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
- return style(deps.theme ?? theme, "section", label);
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"))) if (fs.existsSync(path.join(directory, "tsconfig.json"))) languages.add("typescript");
872
- else languages.add("javascript");
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 = 50 * 1024 * 1024;
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 = 50 * 1024 * 1024;
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
- if (JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")).name === "aislop") return current;
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 = 1024 * 1024;
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 parsed = parseJsonc(fs.readFileSync(tsconfigPath, "utf-8"));
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 = 50 * 1024 * 1024;
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$2 = /* @__PURE__ */ new Set([
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$2.has(ext)) return "js";
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$1 = /* @__PURE__ */ new Set([
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$1
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$1
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 aislop fix to merge them automatically.",
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
- return isBuiltin(spec.startsWith("node:") ? spec.slice(5) : spec) || isBuiltin(spec);
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
- return isRootBoundedTarget(path.isAbsolute(normalized) ? normalized : path.resolve(configDirectory, normalized), rootDirectory);
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 commentLines = getCommentLines(content ?? sourceLines.join("\n"), ext ?? (syntax.linePrefixes.includes("//") ? ".ts" : ".py"));
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 blocks = collectBlocks(content.split("\n"), syntax, content, ext);
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 an aislop-ignore comment if so)",
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
- if (exceedsLimit(fn.lineCount - fn.templateLines, limits.maxFunctionLoc, multiplier)) results.push({
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 >= WINDOW_SIZE - 1;
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 < WINDOW_SIZE - 1) continue;
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 `aislop fix`.",
10699
- devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `aislop fix`.",
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 `aislop fix` to auto-format with gofmt",
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 `aislop fix` to auto-format with ruff",
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 result = await runSubprocess(resolveToolBinary("ruff"), ["format", ...targets], {
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 sources = filterSourcesInDatabase(findCppSources(context).filter((f) => CPP_IMPL_EXTENSIONS.has(path.extname(f).toLowerCase())), readCompileCommandsFiles(compileCommandsDir));
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 `aislop scan`.",
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 { edit, skipReason } = computeEdit(sourceFile, classifyIdentifier(best));
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 result = await runSubprocess(resolveToolBinary("ruff"), ruffLintFixArgs(context, false), {
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 result = await runSubprocess(resolveToolBinary("ruff"), ruffLintFixArgs(context, true), {
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 `aislop scan` for dependency vulnerability checks.",
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
- return parseJsAudit((await runSubprocess("npm", ["audit", "--json"], {
14310
+ const result = await runSubprocess("npm", ["audit", "--json"], {
14112
14311
  cwd: rootDir,
14113
14312
  timeout
14114
- })).stdout, "npm audit", readRuntimeDependencies(rootDir));
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 diagnostics = parseJsAudit((await runSubprocess("pnpm", ["audit", "--json"], {
14335
+ const result = await runSubprocess("pnpm", ["audit", "--json"], {
14136
14336
  cwd: rootDir,
14137
14337
  timeout
14138
- })).stdout, "pnpm audit", readRuntimeDependencies(rootDir));
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 = [scanSecrets(context), detectRiskyConstructs(context)];
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
- return dedupeEquivalenceDiagnostics(dedupeOverlappingComments(dedupeCSharpAsync(stripSecretDiagnostics(finalResults))));
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 ruleCap = scoreImpactForRule(key.slice(key.indexOf(":") + 1)).cap;
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 { clean, dropped } = redactProperties({
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
- return ` ${style(deps.theme ?? theme, "accentDim", (deps.symbols ?? symbols).rail)}\n`;
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
- return ` ${style(t, "accentDim", (deps.symbols ?? symbols).railEnd)} ${highlightAislop(footer, t)}\n`;
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
- return ` ${style(t, "accent", (deps.symbols ?? symbols).hint)} ${highlightAislop(maskSecrets(hint), t)}\n`;
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
- return spawnSync(process.platform === "win32" ? "where" : "which", [bin], { encoding: "utf-8" }).status === 0;
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
- if (!verify(null, Buffer.from(`${headerB64}.${payloadB64}`, "utf-8"), publicKey, Buffer.from(signatureB64, "base64url"))) throw new JwtVerificationError("Invalid JWT cryptographic signature", "invalid_signature");
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.2", 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");
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 = 4320 * 60;
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 response = await fetchEntitlementFromApi(activeToken?.token ?? "", oidcToken);
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\/)([^/]+)\/([^/.\s]+?)(?:\.git)?\s*$/;
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
- if (!areLiteralMultisetsEqual(beforeLiterals, extractStringAndTemplateLiterals(newContent, ext))) continue;
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; aislop will remove it.",
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; aislop will remove it.",
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 = 1800 * 1e3;
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 = 1800 * 1e3;
17509
- const AUDIT_TIMEOUT = 60 * 1e3;
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
- return scanTargetError(resolvedDir, {
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) if (isScopedFix(deps.context)) deps.skipStep?.("Formatting (rust)", CANNOT_SCOPE_REASON);
17894
- else await deps.runStep("Formatting (rust)", () => runGenericFormatter(deps.context, "rust"), () => fixGenericFormatter(deps.context, "rust"));
17895
- else if (deps.projectInfo.languages.includes("rust")) log.warn("Rust detected but rustfmt is not installed; skipping Rust formatting fixes.");
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 declarations = diagnosticsToDeclarations(await detectUnusedDeclarations(deps.context));
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) if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Dependency audit fixes", MISSING_MANIFEST_REASON);
17976
- else await deps.runStep("Dependency audit fixes", () => runDependencyAudit(deps.context), () => fixDependencyAudit(deps.context, railUpdate));
17977
- if (deps.projectInfo.frameworks.includes("expo") && deps.config.lint.expoDoctor) if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Expo dependency alignment", MISSING_MANIFEST_REASON);
17978
- else await deps.runStep("Expo dependency alignment", () => runExpoDoctor(deps.context), () => fixExpoDependencies(deps.context, railUpdate));
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 data = await requestApi(`/v1/policy?repo=${encodeURIComponent(repoSlug.toLowerCase())}`, {
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 next = upsertMarkdownFence(readIfExists(paths.rules), AISLOP_MD_BODY, sentinelHash(AISLOP_MD_BODY)).nextContent;
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: "aislop hook claude",
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: "aislop hook claude",
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: "aislop hook claude --stop" });
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: "aislop hook claude --stop",
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: "aislop hook claude --on-file-changed",
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: "aislop hook claude --on-file-changed",
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 fenced = upsertMarkdownFence(readIfExists(paths.aislopMd), AISLOP_MD_BODY, mdHash);
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: "aislop hook cursor",
19121
+ command: "raigal hook cursor",
18890
19122
  timeout: 5e3
18891
19123
  });
18892
19124
  return {
18893
- command: "aislop hook cursor",
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 rules = upsertMarkdownFence(readIfExists(paths.rules), AISLOP_MD_BODY, sentinelHash(AISLOP_MD_BODY)).nextContent;
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: "aislop hook gemini",
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: "aislop",
19200
+ name: "raigal",
18967
19201
  type: "command",
18968
- command: "aislop hook gemini",
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 fenced = upsertMarkdownFence(readIfExists(paths.aislopMd), AISLOP_MD_BODY, sentinelHash(AISLOP_MD_BODY)).nextContent;
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 = `// aislop — auto-generated pi extension. Do not edit by hand.
19060
- // Reinstall with: aislop hook install --pi
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 || "aislop";
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 aislop extension");
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
- return ` ${style(t, step.emphasis === "primary" ? "accent" : "muted", glyph)} ${highlightAislop(step.text ?? "", t)}`;
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
- return `\n ${style(deps.theme ?? theme, "muted", `★ Found this useful? Consider purchasing this software at ${terminalLink("https://raigal.dev")}`)}\n`;
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
- return `\n ${style(deps.theme ?? theme, "muted", `→ Using raigal with a team? Get a 14-day team baseline at ${terminalLink("https://raigal.dev/contact?intent=team-baseline")}`)}\n`;
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 { results: unannotated, suppressedCount } = applySuppressions((await runEnginesWithProgress({
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
- })), resolvedDir);
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-Ba5D8TPH.js");
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-DBoMe4xB.js");
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 scope = collectFixFileScope(resolvedDir, [...config.exclude, ...readAislopIgnorePatterns(resolvedDir)], config.include, options);
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")) if (!fs.existsSync(rulesPath)) {
20813
- fs.writeFileSync(rulesPath, DEFAULT_RULES_YAML);
20814
- steps.push({
20815
- status: "done",
20816
- label: `Wrote ${CONFIG_DIR}/${RULES_FILE}`
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
- } else steps.push({
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) if (selected.has(item.value)) selected.delete(item.value);
21016
- else selected.add(item.value);
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 archRules = loadArchitectureRules(path.join(configDir, RULES_FILE));
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",