@methiu/raigal 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -923,7 +923,7 @@ const createLogger = (deps = {}) => {
923
923
  const log = createLogger();
924
924
  //#endregion
925
925
  //#region src/version.ts
926
- const APP_VERSION = "0.2.2";
926
+ const APP_VERSION = "0.2.4";
927
927
  //#endregion
928
928
  //#region src/commands/agent-connect.ts
929
929
  const commandText = (status) => [status.provider.loginCommand.command, ...status.provider.loginCommand.args].join(" ");
@@ -1422,7 +1422,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
1422
1422
  const claims = parsed.data;
1423
1423
  const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
1424
1424
  if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
1425
- 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");
1425
+ if (!isVersionAtLeast("0.2.4", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
1426
1426
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
1427
1427
  const grace = options.allowGraceSeconds ?? 0;
1428
1428
  if (now > claims.exp + grace) {
@@ -1595,7 +1595,7 @@ const getEntitlement = async () => {
1595
1595
  };
1596
1596
  //#endregion
1597
1597
  //#region src/cloud/owner.ts
1598
- const GITHUB_REMOTE_RE$1 = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/.\s]+?)(?:\.git)?\s*$/;
1598
+ const GITHUB_REMOTE_RE$1 = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
1599
1599
  const parseGitRemote = (rawUrl) => {
1600
1600
  if (!rawUrl) return {
1601
1601
  owner: null,
@@ -3164,7 +3164,7 @@ const recordAgentSessionTelemetry = ({ worktreePath, published, branchOption, pr
3164
3164
  if (!recorder) return;
3165
3165
  let patchDiff;
3166
3166
  if (worktreePath) try {
3167
- patchDiff = (spawnSync("git", [
3167
+ const rawDiff = spawnSync("git", [
3168
3168
  "diff",
3169
3169
  "-U2",
3170
3170
  "--no-color",
@@ -3172,7 +3172,8 @@ const recordAgentSessionTelemetry = ({ worktreePath, published, branchOption, pr
3172
3172
  ], {
3173
3173
  cwd: worktreePath,
3174
3174
  encoding: "utf-8"
3175
- }).stdout || "").slice(0, 2e4) || void 0;
3175
+ }).stdout || "";
3176
+ patchDiff = rawDiff ? maskSecrets(rawDiff).slice(0, 2e4) : void 0;
3176
3177
  } catch {}
3177
3178
  if (published) {
3178
3179
  const prMatch = published.prUrl?.match(/\/pull\/(\d+)/);
@@ -4994,6 +4995,9 @@ const CiSchema = z.object({
4994
4995
  format: z.enum(["json"]).default("json")
4995
4996
  }).strict();
4996
4997
  const TelemetrySchema = z.object({ enabled: z.boolean().default(true) }).strict();
4998
+ const BaselineConfigSchema = z.object({ mode: z.enum(["strict", "relaxed"]).default("relaxed") }).strict();
4999
+ const LicensingConfigSchema = z.object({ onLapse: z.enum(["fail-open", "fail-closed"]).default("fail-open") }).strict();
5000
+ const WorkspaceConfigSchema = z.object({ minimumFloor: z.number().min(0).max(100).default(50) }).strict();
4997
5001
  const RuleSeverityOverride = z.enum([
4998
5002
  "error",
4999
5003
  "warning",
@@ -5064,6 +5068,9 @@ const AislopConfigSchema = z.object({
5064
5068
  format: "json"
5065
5069
  })),
5066
5070
  telemetry: TelemetrySchema.default(() => ({ enabled: true })),
5071
+ baseline: BaselineConfigSchema.optional(),
5072
+ licensing: LicensingConfigSchema.optional(),
5073
+ workspace: WorkspaceConfigSchema.optional(),
5067
5074
  rules: RulesSchema,
5068
5075
  overrides: z.array(FileOverrideSchema).default(() => []),
5069
5076
  exclude: z.array(z.string()).default(() => [
@@ -5097,6 +5104,9 @@ const KNOWN_KEYS_BY_PATH = {
5097
5104
  "scoring",
5098
5105
  "ci",
5099
5106
  "telemetry",
5107
+ "baseline",
5108
+ "licensing",
5109
+ "workspace",
5100
5110
  "rules",
5101
5111
  "exclude",
5102
5112
  "include"
@@ -5147,7 +5157,10 @@ const KNOWN_KEYS_BY_PATH = {
5147
5157
  ],
5148
5158
  "scoring.thresholds": ["good", "ok"],
5149
5159
  ci: ["failBelow", "format"],
5150
- telemetry: ["enabled"]
5160
+ telemetry: ["enabled"],
5161
+ baseline: ["mode"],
5162
+ licensing: ["onLapse"],
5163
+ workspace: ["minimumFloor"]
5151
5164
  };
5152
5165
  const levenshtein = (a, b) => {
5153
5166
  const m = a.length;
@@ -8130,9 +8143,9 @@ const buildSuggestedActions = (diagnostics, findings, regressed, delta) => {
8130
8143
  if (fixableDiags.length > 0) {
8131
8144
  const ruleIds = Array.from(new Set(fixableDiags.map((d) => d.rule)));
8132
8145
  actions.push({
8133
- id: "run_aislop_fix",
8134
- label: `Run aislop fix to clear ${fixableDiags.length} mechanical finding${fixableDiags.length === 1 ? "" : "s"}.`,
8135
- command: "aislop fix",
8146
+ id: "run_raigal_fix",
8147
+ label: `Run raigal fix to clear ${fixableDiags.length} mechanical finding${fixableDiags.length === 1 ? "" : "s"}.`,
8148
+ command: "raigal fix",
8136
8149
  rationale: "These findings have deterministic fixes (formatting, unused imports, trivial comments). Running this before any manual work avoids burning agent tokens on what the CLI handles for free.",
8137
8150
  ruleIds
8138
8151
  });
@@ -8193,8 +8206,40 @@ const buildFeedback = (diagnostics, score, rootDirectory, baseline, meta) => {
8193
8206
  const regressed = typeof delta === "number" ? delta < 0 : false;
8194
8207
  let newSinceBaseline;
8195
8208
  if (baselineSnapshot && baselineSnapshot.findingFingerprints.length > 0) {
8196
- const known = new Set(baselineSnapshot.findingFingerprints);
8197
- newSinceBaseline = all.filter((f) => !known.has(fingerprintFinding(f))).slice(0, MAX_NEW_SINCE_BASELINE);
8209
+ const remainingExact = new Set(baselineSnapshot.findingFingerprints);
8210
+ const unmatchedFindings = [];
8211
+ for (const f of all) {
8212
+ const exactFp = fingerprintFinding(f);
8213
+ if (remainingExact.has(exactFp)) remainingExact.delete(exactFp);
8214
+ else unmatchedFindings.push(f);
8215
+ }
8216
+ const remainingPool = [];
8217
+ for (const fp of remainingExact) {
8218
+ const parts = fp.split(":");
8219
+ if (parts.length >= 3) {
8220
+ const file = parts[0];
8221
+ const line = Number.parseInt(parts[1], 10);
8222
+ const rule = parts.slice(2).join(":");
8223
+ remainingPool.push({
8224
+ file,
8225
+ line: Number.isNaN(line) ? 0 : line,
8226
+ rule
8227
+ });
8228
+ }
8229
+ }
8230
+ const stillUnmatched = [];
8231
+ for (const f of unmatchedFindings) {
8232
+ const candidateIdx = remainingPool.findIndex((b) => b.file === f.file && b.rule === f.ruleId && Math.abs(b.line - f.line) <= 5);
8233
+ if (candidateIdx >= 0) remainingPool.splice(candidateIdx, 1);
8234
+ else stillUnmatched.push(f);
8235
+ }
8236
+ const fresh = [];
8237
+ for (const f of stillUnmatched) {
8238
+ const candidateIdx = remainingPool.findIndex((b) => b.file === f.file && b.rule === f.ruleId);
8239
+ if (candidateIdx >= 0) remainingPool.splice(candidateIdx, 1);
8240
+ else fresh.push(f);
8241
+ }
8242
+ newSinceBaseline = fresh.slice(0, MAX_NEW_SINCE_BASELINE);
8198
8243
  }
8199
8244
  return {
8200
8245
  schema: "aislop.hook.v2",
@@ -9549,7 +9594,7 @@ const tokenizeJs = (content, ext = ".ts") => {
9549
9594
  };
9550
9595
  //#endregion
9551
9596
  //#region src/utils/source-masker.ts
9552
- const JS_EXTS$2 = /* @__PURE__ */ new Set([
9597
+ const JS_EXTS$3 = /* @__PURE__ */ new Set([
9553
9598
  ".ts",
9554
9599
  ".tsx",
9555
9600
  ".js",
@@ -9557,7 +9602,7 @@ const JS_EXTS$2 = /* @__PURE__ */ new Set([
9557
9602
  ".mjs",
9558
9603
  ".cjs"
9559
9604
  ]);
9560
- const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
9605
+ const PY_EXTS$1 = /* @__PURE__ */ new Set([".py"]);
9561
9606
  const RB_EXTS = /* @__PURE__ */ new Set([".rb"]);
9562
9607
  const PHP_EXTS = /* @__PURE__ */ new Set([".php"]);
9563
9608
  const CSHARP_EXTS = /* @__PURE__ */ new Set([".cs"]);
@@ -9581,8 +9626,8 @@ const C_STYLE_COMMENT_EXTS = /* @__PURE__ */ new Set([
9581
9626
  ".zig"
9582
9627
  ]);
9583
9628
  const familyForExt = (ext) => {
9584
- if (JS_EXTS$2.has(ext)) return "js";
9585
- if (PY_EXTS.has(ext)) return "py";
9629
+ if (JS_EXTS$3.has(ext)) return "js";
9630
+ if (PY_EXTS$1.has(ext)) return "py";
9586
9631
  if (RB_EXTS.has(ext)) return "rb";
9587
9632
  if (PHP_EXTS.has(ext)) return "php";
9588
9633
  if (CSHARP_EXTS.has(ext)) return "csharp";
@@ -9852,7 +9897,7 @@ const areLiteralMultisetsEqual = (a, b) => {
9852
9897
  };
9853
9898
  //#endregion
9854
9899
  //#region src/engines/ai-slop/abstractions.ts
9855
- const JS_EXTS$1 = /* @__PURE__ */ new Set([
9900
+ const JS_EXTS$2 = /* @__PURE__ */ new Set([
9856
9901
  ".ts",
9857
9902
  ".tsx",
9858
9903
  ".js",
@@ -9863,11 +9908,11 @@ const JS_EXTS$1 = /* @__PURE__ */ new Set([
9863
9908
  const THIN_WRAPPER_PATTERNS = [
9864
9909
  {
9865
9910
  pattern: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*(?::\s*\w[^{]*)?\{\s*\n?\s*return\s+\w+\([^)]*\);\s*\n?\s*\}/g,
9866
- extensions: JS_EXTS$1
9911
+ extensions: JS_EXTS$2
9867
9912
  },
9868
9913
  {
9869
9914
  pattern: /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w[^=]*)?\s*=>\s*\w+\([^)]*\);/g,
9870
- extensions: JS_EXTS$1
9915
+ extensions: JS_EXTS$2
9871
9916
  },
9872
9917
  {
9873
9918
  pattern: /def\s+(\w+)\s*\([^)]*\)(?:\s*->[^:]*)?:\s*\n\s+return\s+\w+\([^)]*\)\s*$/gm,
@@ -11115,7 +11160,7 @@ const scriptKindFor$1 = (ext) => {
11115
11160
  }
11116
11161
  };
11117
11162
  const lineFor$1 = (sourceFile, node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
11118
- const makeDiagnostic = (filePath, line, rule, message, help) => ({
11163
+ const makeDiagnostic$1 = (filePath, line, rule, message, help) => ({
11119
11164
  filePath,
11120
11165
  engine: "ai-slop",
11121
11166
  rule,
@@ -11165,7 +11210,7 @@ const detectRedundantTryCatch = (sourceFile, relativePath) => {
11165
11210
  if (ts.isTryStatement(node) && node.catchClause && !node.finallyBlock) {
11166
11211
  const catchNameNode = node.catchClause.variableDeclaration?.name;
11167
11212
  const [onlyStatement] = node.catchClause.block.statements;
11168
- 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."));
11213
+ 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."));
11169
11214
  }
11170
11215
  ts.forEachChild(node, visit);
11171
11216
  };
@@ -11183,7 +11228,7 @@ const detectPrimitiveCoercions = (sourceFile, relativePath) => {
11183
11228
  const [arg] = child.arguments;
11184
11229
  if (arg && ts.isIdentifier(arg)) {
11185
11230
  const primitive = params.get(arg.text);
11186
- 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."));
11231
+ 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."));
11187
11232
  }
11188
11233
  }
11189
11234
  ts.forEachChild(child, visitBody);
@@ -11226,7 +11271,7 @@ const detectDuplicateExportedTypes = (parsedSources) => {
11226
11271
  continue;
11227
11272
  }
11228
11273
  if (previous.filePath === declaration.filePath) continue;
11229
- 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.`));
11274
+ 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.`));
11230
11275
  }
11231
11276
  }
11232
11277
  return diagnostics;
@@ -11328,7 +11373,7 @@ const detectDuplicateImports = async (context) => {
11328
11373
  rule: "ai-slop/duplicate-import",
11329
11374
  severity: "warning",
11330
11375
  message: `"${spec}" is also imported on line ${firstLine}. Merge into a single import statement.`,
11331
- help: "Two imports from the same module split readers' attention and grow the import block. Run aislop fix to merge them automatically.",
11376
+ help: "Two imports from the same module split readers' attention and grow the import block. Run raigal fix to merge them automatically.",
11332
11377
  line: dup.line,
11333
11378
  column: 1,
11334
11379
  category: "AI Slop",
@@ -15242,7 +15287,7 @@ const detectRustPatterns = async (context) => {
15242
15287
  };
15243
15288
  //#endregion
15244
15289
  //#region src/engines/ai-slop/silent-recovery.ts
15245
- const JS_EXTS = /* @__PURE__ */ new Set([
15290
+ const JS_EXTS$1 = /* @__PURE__ */ new Set([
15246
15291
  ".ts",
15247
15292
  ".tsx",
15248
15293
  ".js",
@@ -15437,7 +15482,7 @@ const detectSilentRecovery = async (context) => {
15437
15482
  for (const filePath of files) {
15438
15483
  if (isAutoGenerated(filePath)) continue;
15439
15484
  const ext = path.extname(filePath);
15440
- const isJs = JS_EXTS.has(ext);
15485
+ const isJs = JS_EXTS$1.has(ext);
15441
15486
  if (!isJs && !(ext === ".py")) continue;
15442
15487
  const relPath = relativePosix(context.rootDirectory, filePath);
15443
15488
  if (isNonProductionPath(relPath)) continue;
@@ -15722,7 +15767,7 @@ const detectUnusedCss = async (context) => {
15722
15767
  rule: "ai-slop/unused-css",
15723
15768
  severity: "warning",
15724
15769
  message: `CSS class '.${def.name}' is defined but never referenced in the codebase`,
15725
- help: "Remove the dead CSS rule, or confirm the class is used dynamically (and add an aislop-ignore comment if so)",
15770
+ help: "Remove the dead CSS rule, or confirm the class is used dynamically (and add a raigal-ignore comment if so)",
15726
15771
  line: def.line,
15727
15772
  column: 0,
15728
15773
  category: "AI Slop",
@@ -16813,8 +16858,8 @@ const isTrivialLine = (line) => {
16813
16858
  };
16814
16859
  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)=/;
16815
16860
  const DATA_LITERAL_RE = /^\s*(?:[A-Za-z_$][\w$-]*:\s*(?:["'`[{]|\d|true\b|false\b|null\b)|["'`][^"'`]*["'`],?\s*$|[{}\]],?\s*$|\),?\s*$)/;
16816
- const SUPPRESS_RE = /aislop[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
16817
- const FILE_SUPPRESS_RE = /aislop[- ]ignore-file\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
16861
+ const SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
16862
+ const FILE_SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore-file\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
16818
16863
  const fileHasSuppression = (content) => FILE_SUPPRESS_RE.test(content);
16819
16864
  const isLowSignalMarkupWindow = (lines) => {
16820
16865
  return lines.filter((line) => SVG_MARKUP_RE.test(line)).length >= Math.ceil(WINDOW_SIZE / 2);
@@ -16956,8 +17001,8 @@ const shouldIncludeIssue = (issueType, filePath) => {
16956
17001
  return !filePath.replace(/\\/g, "/").includes(".github/workflows/");
16957
17002
  };
16958
17003
  const DEPENDENCY_HELP = {
16959
- dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `aislop fix`.",
16960
- devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `aislop fix`.",
17004
+ dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
17005
+ devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
16961
17006
  unlisted: "This package is imported in code but not declared in package.json. Run `npm install` to add it.",
16962
17007
  unresolved: "This import cannot be resolved. Check for typos or missing packages.",
16963
17008
  binaries: "This binary is used but its package is not in package.json."
@@ -17627,7 +17672,7 @@ const fixClangFormat = async (context) => {
17627
17672
  };
17628
17673
  //#endregion
17629
17674
  //#region src/engines/format/dotnet-format.ts
17630
- const REPORT_FILENAME = ".aislop-dotnet-format.json";
17675
+ const REPORT_FILENAME = ".raigal-dotnet-format.json";
17631
17676
  const parseDotnetFormatReport = (json, rootDirectory) => {
17632
17677
  if (!json) return [];
17633
17678
  let report;
@@ -17916,7 +17961,7 @@ const runGofmt = async (context) => {
17916
17961
  rule: "go-formatting",
17917
17962
  severity: "warning",
17918
17963
  message: "Go file is not formatted correctly",
17919
- help: "Run `aislop fix` to auto-format with gofmt",
17964
+ help: "Run `raigal fix` to auto-format with gofmt",
17920
17965
  line: 0,
17921
17966
  column: 0,
17922
17967
  category: "Format",
@@ -17944,7 +17989,8 @@ const resolvePackageRoot = (startFile) => {
17944
17989
  while (true) {
17945
17990
  const packageJsonPath = path.join(current, "package.json");
17946
17991
  if (fs.existsSync(packageJsonPath)) try {
17947
- if (JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")).name === "aislop") return current;
17992
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
17993
+ if (packageJson.name === "aislop" || packageJson.name === "@methiu/raigal" || packageJson.name === "raigal") return current;
17948
17994
  } catch {}
17949
17995
  const parent = path.dirname(current);
17950
17996
  if (parent === current) break;
@@ -18055,7 +18101,7 @@ const parseRuffFormatOutput = (output, rootDir) => {
18055
18101
  rule: "python-formatting",
18056
18102
  severity: "warning",
18057
18103
  message: "Python file is not formatted correctly",
18058
- help: "Run `aislop fix` to auto-format with ruff",
18104
+ help: "Run `raigal fix` to auto-format with ruff",
18059
18105
  line: 0,
18060
18106
  column: 0,
18061
18107
  category: "Format",
@@ -18507,7 +18553,7 @@ const runExpoDoctor = async (context) => {
18507
18553
  rule: "expo-doctor/config-error",
18508
18554
  severity: "warning",
18509
18555
  message: configError,
18510
- help: "Install project dependencies, then re-run `aislop scan`.",
18556
+ help: "Install project dependencies, then re-run `raigal scan`.",
18511
18557
  line: 0,
18512
18558
  column: 0,
18513
18559
  category: "Expo",
@@ -19980,6 +20026,174 @@ const lintEngine = {
19980
20026
  }
19981
20027
  };
19982
20028
  //#endregion
20029
+ //#region src/engines/security/ai-security.ts
20030
+ const JS_EXTS = /* @__PURE__ */ new Set([
20031
+ ".ts",
20032
+ ".tsx",
20033
+ ".js",
20034
+ ".jsx",
20035
+ ".mjs",
20036
+ ".cjs"
20037
+ ]);
20038
+ const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
20039
+ const UNICODE_TAG_RE = /[\u{E0000}-\u{E007F}]/gu;
20040
+ const hasRuleIgnore = (lines, lineIndex, ruleSuffix) => {
20041
+ const start = Math.max(0, lineIndex - 2);
20042
+ const re = new RegExp(`(?:raigal|aislop)[- ]ignore(?:-file)?\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
20043
+ return lines.slice(start, lineIndex + 1).some((line) => re.test(line));
20044
+ };
20045
+ const hasFileIgnore = (lines, ruleSuffix) => {
20046
+ const re = new RegExp(`(?:raigal|aislop)[- ]ignore-file\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
20047
+ return lines.slice(0, 10).some((line) => re.test(line));
20048
+ };
20049
+ const makeDiagnostic = (filePath, rule, message, help, line, column) => ({
20050
+ filePath,
20051
+ engine: "security",
20052
+ rule,
20053
+ severity: "error",
20054
+ message,
20055
+ help,
20056
+ line,
20057
+ column,
20058
+ category: "Security",
20059
+ fixable: false
20060
+ });
20061
+ const P_BYPASS = "bypassPermissions";
20062
+ const P_EXEC_TYPE = "executor_type";
20063
+ const P_LOCAL = "local";
20064
+ const P_USE_DOCKER = "use_docker";
20065
+ const P_AUTO_RUN = "auto_run";
20066
+ const P_UNSAFE_MODE = "unsafe_mode";
20067
+ const P_PYTHON_REPL = "PythonREPLTool";
20068
+ const UNSANDBOXED_TOOL_PATTERNS = [
20069
+ {
20070
+ pattern: new RegExp(`\\bpermission_?mode["']?\\s*[:=]\\s*["']${P_BYPASS}["']`, "i"),
20071
+ message: `permission_mode '${P_BYPASS}' bypasses agent sandboxing and human confirmation`,
20072
+ help: `Do not use ${P_BYPASS} mode in production; use standard interactive approval`
20073
+ },
20074
+ {
20075
+ pattern: new RegExp(`\\b${P_EXEC_TYPE}["']?\\s*[:=]\\s*["']${P_LOCAL}["']`, "i"),
20076
+ message: `${P_EXEC_TYPE}='${P_LOCAL}' executes LLM-generated code directly on host machine without container isolation`,
20077
+ help: "Configure agent to use DockerCommandLineCodeExecutor or a containerized sandbox environment"
20078
+ },
20079
+ {
20080
+ pattern: new RegExp(`\\b(?:LocalCommandLineCodeExecutor|LocalCodeExecutor)\\s*\\(`),
20081
+ message: "Local code executor executes LLM-generated code on host machine without container isolation",
20082
+ help: "Use DockerCommandLineCodeExecutor or an isolated execution sandbox"
20083
+ },
20084
+ {
20085
+ pattern: new RegExp(`\\b${P_USE_DOCKER}\\s*[:=]\\s*(?:False|false)\\b|["']${P_USE_DOCKER}["']\\s*:\\s*(?:False|false)\\b`),
20086
+ message: `${P_USE_DOCKER}=False disables container sandboxing for agent code execution`,
20087
+ help: `Set ${P_USE_DOCKER}=True to isolate agent execution inside Docker containers`
20088
+ },
20089
+ {
20090
+ pattern: new RegExp(`\\b(?:interpreter\\.)?${P_AUTO_RUN}\\s*[:=]\\s*(?:True|true)\\b`),
20091
+ message: `${P_AUTO_RUN}=True allows autonomous shell execution without human confirmation`,
20092
+ help: `Keep ${P_AUTO_RUN}=False so dangerous commands require explicit human review`
20093
+ },
20094
+ {
20095
+ pattern: new RegExp(`\\bCodeInterpreterTool\\s*\\([^)]*${P_UNSAFE_MODE}\\s*=\\s*(?:True|true)\\b|\\b${P_UNSAFE_MODE}\\s*[:=]\\s*(?:True|true)\\b`),
20096
+ message: `${P_UNSAFE_MODE}=True allows code interpreter tool to execute without Docker isolation`,
20097
+ help: `Disable ${P_UNSAFE_MODE} and isolate code execution in containerized environments`
20098
+ },
20099
+ {
20100
+ pattern: new RegExp(`\\b(?:${P_PYTHON_REPL}|PythonAstREPLTool)\\s*\\(`),
20101
+ message: `${P_PYTHON_REPL} executes arbitrary Python code in host process without container isolation`,
20102
+ help: `Use containerized sandbox execution rather than unconfined ${P_PYTHON_REPL} in production`
20103
+ }
20104
+ ];
20105
+ const P_SYS = "system";
20106
+ const P_DEV = "developer";
20107
+ const P_SM = "SystemMessage";
20108
+ const PROMPT_INJECTION_JS_PATTERNS = [
20109
+ new RegExp(`(?:role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`)`, "g"),
20110
+ new RegExp(`(?:content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`[^}{;]{0,120}?role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
20111
+ 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"),
20112
+ 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"),
20113
+ new RegExp(`\\b(?:${P_SYS}|systemPrompt|system_prompt|system_instruction)\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\``, "g"),
20114
+ 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"),
20115
+ 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"),
20116
+ new RegExp(`\\[\\s*["']${P_SYS}["']\\s*,\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`\\s*\\]`, "g")
20117
+ ];
20118
+ const PROMPT_INJECTION_PY_PATTERNS = [
20119
+ 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"),
20120
+ 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"),
20121
+ 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"),
20122
+ 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"),
20123
+ 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"),
20124
+ 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"),
20125
+ new RegExp(`\\b${P_SM}\\s*\\(\\s*(?:content\\s*=\\s*)?f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])`, "g"),
20126
+ 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"),
20127
+ new RegExp(`\\(\\s*["']${P_SYS}["']\\s*,\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])\\s*\\)`, "g")
20128
+ ];
20129
+ const checkInvisibleUnicode = (relPath, content, lines) => {
20130
+ if (hasFileIgnore(lines, "invisible-unicode-tags")) return [];
20131
+ const results = [];
20132
+ const tagRegex = new RegExp(UNICODE_TAG_RE.source, UNICODE_TAG_RE.flags);
20133
+ for (const match of content.matchAll(tagRegex)) {
20134
+ const matchIndex = match.index ?? 0;
20135
+ const line = content.slice(0, matchIndex).split("\n").length;
20136
+ const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
20137
+ if (hasRuleIgnore(lines, line - 1, "invisible-unicode-tags")) continue;
20138
+ 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));
20139
+ }
20140
+ return results;
20141
+ };
20142
+ const checkUnsandboxedTools = (relPath, content, lines) => {
20143
+ if (hasFileIgnore(lines, "unsandboxed-agent-tool")) return [];
20144
+ const results = [];
20145
+ for (const { pattern, message, help } of UNSANDBOXED_TOOL_PATTERNS) {
20146
+ const regex = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
20147
+ for (const match of content.matchAll(regex)) {
20148
+ const matchIndex = match.index ?? 0;
20149
+ const line = content.slice(0, matchIndex).split("\n").length;
20150
+ const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
20151
+ if (hasRuleIgnore(lines, line - 1, "unsandboxed-agent-tool")) continue;
20152
+ results.push(makeDiagnostic(relPath, "security/unsandboxed-agent-tool", message, help, line, col));
20153
+ }
20154
+ }
20155
+ return results;
20156
+ };
20157
+ const checkPromptInjection = (relPath, content, lines, isJs) => {
20158
+ if (hasFileIgnore(lines, "prompt-injection")) return [];
20159
+ const results = [];
20160
+ const patterns = isJs ? PROMPT_INJECTION_JS_PATTERNS : PROMPT_INJECTION_PY_PATTERNS;
20161
+ for (const pattern of patterns) {
20162
+ const regex = new RegExp(pattern.source, pattern.flags);
20163
+ for (const match of content.matchAll(regex)) {
20164
+ const matchIndex = match.index ?? 0;
20165
+ const line = content.slice(0, matchIndex).split("\n").length;
20166
+ const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
20167
+ if (hasRuleIgnore(lines, line - 1, "prompt-injection")) continue;
20168
+ 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));
20169
+ }
20170
+ }
20171
+ return results;
20172
+ };
20173
+ const scanAiSecurity = async (context) => {
20174
+ const files = getSourceFiles(context);
20175
+ const diagnostics = [];
20176
+ for (const filePath of files) {
20177
+ const ext = path.extname(filePath);
20178
+ let content;
20179
+ try {
20180
+ content = fs.readFileSync(filePath, "utf-8");
20181
+ } catch {
20182
+ continue;
20183
+ }
20184
+ const relativePath = relativePosix(context.rootDirectory, filePath);
20185
+ const lines = content.split("\n");
20186
+ diagnostics.push(...checkInvisibleUnicode(relativePath, content, lines));
20187
+ const isJs = JS_EXTS.has(ext);
20188
+ const isPy = PY_EXTS.has(ext);
20189
+ if (isJs || isPy) {
20190
+ diagnostics.push(...checkUnsandboxedTools(relativePath, content, lines));
20191
+ diagnostics.push(...checkPromptInjection(relativePath, content, lines, isJs));
20192
+ }
20193
+ }
20194
+ return diagnostics;
20195
+ };
20196
+ //#endregion
19983
20197
  //#region src/ui/invocation.ts
19984
20198
  const detectInvocation = () => "raigal";
19985
20199
  //#endregion
@@ -20362,7 +20576,7 @@ const parseJsAudit = (output, source, manifest) => {
20362
20576
  rule: "security/dependency-audit-skipped",
20363
20577
  severity: "info",
20364
20578
  message: `Dependency audit skipped (${source}): lockfile is missing`,
20365
- help: errorDetail ?? "Generate a lockfile, then re-run `aislop scan` for dependency vulnerability checks.",
20579
+ help: errorDetail ?? "Generate a lockfile, then re-run `raigal scan` for dependency vulnerability checks.",
20366
20580
  line: 0,
20367
20581
  column: 0,
20368
20582
  category: "Security",
@@ -20920,7 +21134,7 @@ const RISKY_PATTERNS = [
20920
21134
  //#region src/engines/security/risky.ts
20921
21135
  const hasDangerouslySetInnerHtmlIgnore = (lines, lineIndex) => {
20922
21136
  const start = Math.max(0, lineIndex - 2);
20923
- return lines.slice(start, lineIndex + 1).some((line) => /(?:biome-ignore|eslint-disable|aislop-ignore).*(?:noDangerouslySetInnerHtml|dangerouslySetInnerHTML|dangerously-set-innerhtml)/i.test(line));
21137
+ return lines.slice(start, lineIndex + 1).some((line) => /(?:biome-ignore|eslint-disable|raigal-ignore|aislop-ignore).*(?:noDangerouslySetInnerHtml|dangerouslySetInnerHTML|dangerously-set-innerhtml)/i.test(line));
20924
21138
  };
20925
21139
  const isStructuredDataScript = (content, matchIndex) => {
20926
21140
  const before = content.slice(Math.max(0, matchIndex - 300), matchIndex);
@@ -21248,7 +21462,11 @@ const ALL_ENGINES = [
21248
21462
  name: "security",
21249
21463
  async run(context) {
21250
21464
  const diagnostics = [];
21251
- const promises = [scanSecrets(context), detectRiskyConstructs(context)];
21465
+ const promises = [
21466
+ scanSecrets(context),
21467
+ detectRiskyConstructs(context),
21468
+ scanAiSecurity(context)
21469
+ ];
21252
21470
  if (context.config.security.audit) promises.push(runDependencyAudit(context));
21253
21471
  const results = await Promise.allSettled(promises);
21254
21472
  for (const result of results) if (result.status === "fulfilled") diagnostics.push(...result.value);
@@ -21439,6 +21657,9 @@ const RULE_SCORE_IMPACTS = {
21439
21657
  "security/shell-injection": strict("Interpolated shell commands can become command execution."),
21440
21658
  "security/unsafe-deserialization": strict("Deserializing untrusted data with a legacy .NET formatter can become remote code execution."),
21441
21659
  "security/unsafe-c-call": strict("Unbounded C string and process calls can become buffer overflows or command execution."),
21660
+ "security/prompt-injection": strict("Dynamic user interpolation in system/developer prompts enables prompt injection attacks."),
21661
+ "security/unsandboxed-agent-tool": strict("Unsandboxed agent code execution or tool permission bypass allows arbitrary host commands."),
21662
+ "security/invisible-unicode-tags": strict("Invisible Unicode Tag characters (U+E0000-U+E007F) hide stealth prompt injection payloads."),
21442
21663
  "security/dependency-audit-skipped": advisory("An unavailable audit is visibility loss, not evidence of a vulnerability."),
21443
21664
  "dotnet/projects-skipped": advisory("Skipping unrestored C# projects is visibility loss, not evidence of a defect."),
21444
21665
  "cppcheck/chunks-skipped": advisory("A skipped cppcheck chunk is visibility loss, not evidence that the skipped files have a defect."),
@@ -21900,7 +22121,7 @@ const atomicWrite = (targetPath, content) => {
21900
22121
  const dir = path.dirname(targetPath);
21901
22122
  fs.mkdirSync(dir, { recursive: true });
21902
22123
  const rand = Math.random().toString(36).slice(2, 10);
21903
- const tmp = path.join(dir, `.aislop-tmp-${process.pid}-${rand}`);
22124
+ const tmp = path.join(dir, `.raigal-tmp-${process.pid}-${rand}`);
21904
22125
  fs.writeFileSync(tmp, content, "utf-8");
21905
22126
  try {
21906
22127
  fs.renameSync(tmp, targetPath);
@@ -21944,18 +22165,34 @@ const hasExistingLiteralBackslashPath = (cwd, fingerprint) => {
21944
22165
  }
21945
22166
  };
21946
22167
  const migrateMarkerlessFingerprint = (cwd, fingerprint) => hasExistingLiteralBackslashPath(cwd, fingerprint) ? fingerprint : normalizeLegacyFingerprint(fingerprint);
21947
- const BASELINE_REL = path.join(".aislop", "baseline.json");
21948
- const baselinePath = (cwd) => path.join(cwd, BASELINE_REL);
22168
+ const baselinePath = (cwd) => {
22169
+ const raigalCandidate = path.join(cwd, ".raigal", "baseline.json");
22170
+ if (fs.existsSync(raigalCandidate)) return raigalCandidate;
22171
+ const raigalDir = path.join(cwd, ".raigal");
22172
+ if (fs.existsSync(raigalDir)) return raigalCandidate;
22173
+ const legacyCandidate = path.join(cwd, ".aislop", "baseline.json");
22174
+ if (fs.existsSync(legacyCandidate)) return legacyCandidate;
22175
+ return raigalCandidate;
22176
+ };
22177
+ const sessionPath = (cwd) => {
22178
+ const raigalCandidate = path.join(cwd, ".raigal", "session.jsonl");
22179
+ if (fs.existsSync(raigalCandidate)) return raigalCandidate;
22180
+ const raigalDir = path.join(cwd, ".raigal");
22181
+ if (fs.existsSync(raigalDir)) return raigalCandidate;
22182
+ const legacyCandidate = path.join(cwd, ".aislop", "session.jsonl");
22183
+ if (fs.existsSync(legacyCandidate)) return legacyCandidate;
22184
+ return raigalCandidate;
22185
+ };
21949
22186
  const readBaseline = (cwd) => {
21950
22187
  const raw = readIfExists(baselinePath(cwd));
21951
22188
  if (!raw) return null;
21952
22189
  try {
21953
22190
  const parsed = JSON.parse(raw);
21954
- if (parsed.schema !== "aislop.baseline.v2" && parsed.schema !== "aislop.baseline.v1") return null;
22191
+ if (parsed.schema !== "aislop.baseline.v2" && parsed.schema !== "aislop.baseline.v1" && parsed.schema !== "raigal.baseline.v2" && parsed.schema !== "raigal.baseline.v1") return null;
21955
22192
  const findingFingerprints = parsed.findingFingerprints ?? [];
21956
22193
  const needsWindowsMigration = parsed.pathFormat !== "posix";
21957
22194
  return {
21958
- schema: "aislop.baseline.v2",
22195
+ schema: "raigal.baseline.v2",
21959
22196
  pathFormat: "posix",
21960
22197
  updatedAt: parsed.updatedAt ?? "",
21961
22198
  score: parsed.score ?? 0,
@@ -21969,7 +22206,7 @@ const readBaseline = (cwd) => {
21969
22206
  }
21970
22207
  };
21971
22208
  const writeBaseline = (cwd, baseline) => {
21972
- const target = baselinePath(cwd);
22209
+ const target = path.join(cwd, ".raigal", "baseline.json");
21973
22210
  atomicWrite(target, `${JSON.stringify({
21974
22211
  ...baseline,
21975
22212
  pathFormat: "posix"
@@ -21987,7 +22224,6 @@ const captureBaseline = async (cwd) => {
21987
22224
  rootDirectory: project.rootDirectory,
21988
22225
  languages: project.languages,
21989
22226
  frameworks: project.frameworks,
21990
- files: [],
21991
22227
  installedTools: project.installedTools,
21992
22228
  config: {
21993
22229
  overrides: config.overrides,
@@ -22011,16 +22247,16 @@ const captureBaseline = async (cwd) => {
22011
22247
  architecture: config.engines.architecture,
22012
22248
  security: false
22013
22249
  });
22014
- const diagnostics = results.flatMap((r) => r.diagnostics);
22250
+ const diagnostics = applyRuleSeverities(results.flatMap((r) => r.diagnostics), config.overrides.length ? {} : config.rules);
22015
22251
  const { score } = calculateScore(diagnostics, config.scoring.weights, config.scoring.thresholds, project.sourceFileCount, config.scoring.smoothing, config.scoring.maxPerRule);
22016
22252
  const byEngine = {};
22017
22253
  for (const r of results) {
22018
- const { score: engineScore } = calculateScore(diagnostics.filter((d) => r.diagnostics.includes(d)), config.scoring.weights, config.scoring.thresholds, project.sourceFileCount, config.scoring.smoothing, config.scoring.maxPerRule);
22254
+ const { score: engineScore } = calculateScore(diagnostics.filter((d) => d.engine === r.engine), config.scoring.weights, config.scoring.thresholds, project.sourceFileCount, config.scoring.smoothing, config.scoring.maxPerRule);
22019
22255
  byEngine[r.engine] = engineScore;
22020
22256
  }
22021
22257
  const findingFingerprints = diagnostics.filter((d) => d.severity === "error" || d.severity === "warning").map((d) => fingerprintDiagnostic(d, project.rootDirectory));
22022
22258
  const baseline = {
22023
- schema: "aislop.baseline.v2",
22259
+ schema: "raigal.baseline.v2",
22024
22260
  pathFormat: "posix",
22025
22261
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
22026
22262
  score,
@@ -22037,7 +22273,7 @@ const captureBaseline = async (cwd) => {
22037
22273
  };
22038
22274
  const appendSessionFiles = (cwd, files) => {
22039
22275
  if (files.length === 0) return;
22040
- const target = path.join(cwd, ".aislop", "session.jsonl");
22276
+ const target = sessionPath(cwd);
22041
22277
  try {
22042
22278
  fs.mkdirSync(path.dirname(target), { recursive: true });
22043
22279
  const line = `${JSON.stringify({
@@ -22048,7 +22284,7 @@ const appendSessionFiles = (cwd, files) => {
22048
22284
  } catch {}
22049
22285
  };
22050
22286
  const readSessionFiles = (cwd) => {
22051
- const raw = readIfExists(path.join(cwd, ".aislop", "session.jsonl"));
22287
+ const raw = readIfExists(sessionPath(cwd));
22052
22288
  if (!raw) return [];
22053
22289
  const files = /* @__PURE__ */ new Set();
22054
22290
  for (const line of raw.split("\n")) {
@@ -22061,7 +22297,7 @@ const readSessionFiles = (cwd) => {
22061
22297
  return Array.from(files);
22062
22298
  };
22063
22299
  const clearSessionFiles = (cwd) => {
22064
- const target = path.join(cwd, ".aislop", "session.jsonl");
22300
+ const target = sessionPath(cwd);
22065
22301
  try {
22066
22302
  fs.unlinkSync(target);
22067
22303
  } catch {}
@@ -22165,7 +22401,7 @@ const runClaudeFileChangedHook = async (deps = {}) => {
22165
22401
  schema: "aislop.hook.v2",
22166
22402
  event: "file_changed",
22167
22403
  file: changed,
22168
- message: `Watched file changed (${changed}). aislop refreshed the baseline — score: ${result.score}.`,
22404
+ message: `Watched file changed (${changed}). raigal refreshed the baseline - score: ${result.score}.`,
22169
22405
  baseline: {
22170
22406
  score: result.score,
22171
22407
  fileCount: result.fileCount
@@ -22438,8 +22674,8 @@ A PostToolUse hook runs \`raigal hook claude\` after every Edit, Write, or Multi
22438
22674
 
22439
22675
  ## Rules
22440
22676
 
22441
- - \`.raigal/config.yaml\` (or \`.aislop/config.yaml\`) - thresholds and engine toggles. Treat as authoritative; don't edit without user consent.
22442
- - \`.raigal/rules.yaml\` (or \`.aislop/rules.yaml\`) - project-specific architecture rules (may be absent). When a finding cites \`architecture/*\`, open this file and follow it.
22677
+ - \`.raigal/config.yaml\` - thresholds and engine toggles. Treat as authoritative; don't edit without user consent.
22678
+ - \`.raigal/rules.yaml\` - project-specific architecture rules (may be absent). When a finding cites \`architecture/*\`, open this file and follow it.
22443
22679
  - Custom rules can change between sessions. Trust what the scan returns, not a cached understanding of what the rules are.
22444
22680
 
22445
22681
  ## Principles
@@ -22573,7 +22809,7 @@ const uninstallAntigravity = (opts) => {
22573
22809
  //#endregion
22574
22810
  //#region src/hooks/io/json-patch.ts
22575
22811
  const AISLOP_SENTINEL_KEY = "__aislop";
22576
- const isAislopManaged = (x) => typeof x === "object" && x !== null && "__aislop" in x && x["__aislop"] != null;
22812
+ const isAislopManaged = (x) => typeof x === "object" && x !== null && ("__aislop" in x && x["__aislop"] != null || "__raigal" in x && x["__raigal"] != null);
22577
22813
  const groupIsAislop = (group) => {
22578
22814
  if (typeof group !== "object" || group === null) return false;
22579
22815
  const hooks = group.hooks;
@@ -22628,14 +22864,14 @@ const resolveClaudePaths = (opts) => {
22628
22864
  };
22629
22865
  const buildHookGroup$1 = () => {
22630
22866
  const hashBody = JSON.stringify({
22631
- command: "aislop hook claude",
22867
+ command: "raigal hook claude",
22632
22868
  matcher: "Edit|Write|MultiEdit"
22633
22869
  });
22634
22870
  return {
22635
22871
  matcher: "Edit|Write|MultiEdit",
22636
22872
  hooks: [{
22637
22873
  type: "command",
22638
- command: "aislop hook claude",
22874
+ command: "raigal hook claude",
22639
22875
  [AISLOP_SENTINEL_KEY]: {
22640
22876
  v: 1,
22641
22877
  managed: true,
@@ -22645,12 +22881,12 @@ const buildHookGroup$1 = () => {
22645
22881
  };
22646
22882
  };
22647
22883
  const buildStopHookGroup = () => {
22648
- const hashBody = JSON.stringify({ command: "aislop hook claude --stop" });
22884
+ const hashBody = JSON.stringify({ command: "raigal hook claude --stop" });
22649
22885
  return {
22650
22886
  matcher: "",
22651
22887
  hooks: [{
22652
22888
  type: "command",
22653
- command: "aislop hook claude --stop",
22889
+ command: "raigal hook claude --stop",
22654
22890
  [AISLOP_SENTINEL_KEY]: {
22655
22891
  v: 1,
22656
22892
  managed: true,
@@ -22662,14 +22898,14 @@ const buildStopHookGroup = () => {
22662
22898
  const FILE_CHANGED_MATCHER = ".raigal/config.yml|.raigal/rules.yml|.aislop/config.yml|.aislop/rules.yml|package.json";
22663
22899
  const buildFileChangedHookGroup = () => {
22664
22900
  const hashBody = JSON.stringify({
22665
- command: "aislop hook claude --on-file-changed",
22901
+ command: "raigal hook claude --on-file-changed",
22666
22902
  matcher: FILE_CHANGED_MATCHER
22667
22903
  });
22668
22904
  return {
22669
22905
  matcher: FILE_CHANGED_MATCHER,
22670
22906
  hooks: [{
22671
22907
  type: "command",
22672
- command: "aislop hook claude --on-file-changed",
22908
+ command: "raigal hook claude --on-file-changed",
22673
22909
  [AISLOP_SENTINEL_KEY]: {
22674
22910
  v: 1,
22675
22911
  managed: true,
@@ -22812,11 +23048,11 @@ const resolveCursorPaths = (opts) => {
22812
23048
  };
22813
23049
  const buildHookEntry = () => {
22814
23050
  const hashBody = JSON.stringify({
22815
- command: "aislop hook cursor",
23051
+ command: "raigal hook cursor",
22816
23052
  timeout: 5e3
22817
23053
  });
22818
23054
  return {
22819
- command: "aislop hook cursor",
23055
+ command: "raigal hook cursor",
22820
23056
  type: "command",
22821
23057
  timeout: 5e3,
22822
23058
  [AISLOP_SENTINEL_KEY]: {
@@ -22883,15 +23119,15 @@ const resolveGeminiPaths = (opts) => {
22883
23119
  };
22884
23120
  const buildHookGroup = () => {
22885
23121
  const hashBody = JSON.stringify({
22886
- command: "aislop hook gemini",
23122
+ command: "raigal hook gemini",
22887
23123
  matcher: "write_file|replace"
22888
23124
  });
22889
23125
  return {
22890
23126
  matcher: "write_file|replace",
22891
23127
  hooks: [{
22892
- name: "aislop",
23128
+ name: "raigal",
22893
23129
  type: "command",
22894
- command: "aislop hook gemini",
23130
+ command: "raigal hook gemini",
22895
23131
  timeout: 5e3,
22896
23132
  [AISLOP_SENTINEL_KEY]: {
22897
23133
  v: 1,
@@ -22982,8 +23218,8 @@ const uninstallKilocode = (opts) => {
22982
23218
  const resolvePiPaths = (opts) => {
22983
23219
  return { extension: opts.scope === "project" ? path.join(opts.cwd, ".pi", "extensions", "aislop.js") : path.join(opts.home, ".pi", "agent", "extensions", "aislop.js") };
22984
23220
  };
22985
- const PI_EXTENSION_SOURCE = `// aislop — auto-generated pi extension. Do not edit by hand.
22986
- // Reinstall with: aislop hook install --pi
23221
+ const PI_EXTENSION_SOURCE = `// raigal — auto-generated pi extension. Do not edit by hand.
23222
+ // Reinstall with: raigal hook install --pi
22987
23223
  import { spawn } from "node:child_process";
22988
23224
 
22989
23225
  // Runs the CLI async so a slow scan never blocks pi's event loop, and forwards
@@ -23028,7 +23264,7 @@ export default function (pi) {
23028
23264
  const filePath = event.input && event.input.path;
23029
23265
  if (typeof filePath !== "string" || filePath.length === 0) return;
23030
23266
 
23031
- const bin = process.env.AISLOP_BIN || "aislop";
23267
+ const bin = process.env.RAIGAL_BIN || process.env.AISLOP_BIN || "raigal";
23032
23268
  const payload = JSON.stringify({
23033
23269
  cwd: ctx.cwd,
23034
23270
  file_path: filePath,
@@ -23055,7 +23291,7 @@ export default function (pi) {
23055
23291
  const installPi = (opts) => {
23056
23292
  const paths = resolvePiPaths(opts);
23057
23293
  const result = emptyResult();
23058
- applyContent(result, opts, paths.extension, PI_EXTENSION_SOURCE, "write pi aislop extension");
23294
+ applyContent(result, opts, paths.extension, PI_EXTENSION_SOURCE, "write pi raigal extension");
23059
23295
  return result;
23060
23296
  };
23061
23297
  const uninstallPi = (opts) => {
@@ -23924,7 +24160,7 @@ const registerHookAliases = (program) => {
23924
24160
  };
23925
24161
  //#endregion
23926
24162
  //#region src/utils/github-slug.ts
23927
- const GITHUB_REMOTE_RE = /^(?:git@github\.com:|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
24163
+ const GITHUB_REMOTE_RE = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
23928
24164
  const parseGithubSlug = (remoteUrl) => {
23929
24165
  const match = remoteUrl.trim().match(GITHUB_REMOTE_RE);
23930
24166
  if (!match) return null;
@@ -24122,6 +24358,9 @@ const RULE_LABELS = {
24122
24358
  "security/shell-injection": "Possible shell injection",
24123
24359
  "security/unsafe-deserialization": "Unsafe deserialization",
24124
24360
  "security/unsafe-c-call": "Memory-unsafe C call",
24361
+ "security/prompt-injection": "Prompt injection (untrusted system prompt)",
24362
+ "security/unsandboxed-agent-tool": "Unsandboxed agent code execution tool",
24363
+ "security/invisible-unicode-tags": "Invisible Unicode tag character (stealth injection)",
24125
24364
  "dotnet/projects-skipped": "C# projects skipped by build-backed lint",
24126
24365
  "cppcheck/chunks-skipped": "C/C++ files skipped by cppcheck",
24127
24366
  "eslint/no-undef": "Undefined identifier",
@@ -24210,6 +24449,9 @@ const RULE_DESCRIPTIONS = {
24210
24449
  "security/shell-injection": "Shell command is built from unsanitized input.",
24211
24450
  "security/unsafe-deserialization": "Legacy .NET formatter can deserialize arbitrary types (RCE).",
24212
24451
  "security/unsafe-c-call": "Unbounded C string/exec function with buffer-overflow or shell risk.",
24452
+ "security/prompt-injection": "Untrusted dynamic variable interpolation into system or developer prompt.",
24453
+ "security/unsandboxed-agent-tool": "Agent executes code or commands on host without container or interactive gate.",
24454
+ "security/invisible-unicode-tags": "Invisible Unicode Tag block (U+E0000-U+E007F) used for prompt injection or ASCII smuggling.",
24213
24455
  "dotnet/projects-skipped": "Build-backed C# lint only ran on restored projects; the rest were skipped.",
24214
24456
  "cppcheck/chunks-skipped": "A cppcheck invocation timed out or failed on one or more chunks; their files were not analyzed.",
24215
24457
  "oxlint/*": "JavaScript/TypeScript lint finding from oxlint.",
@@ -24404,7 +24646,8 @@ const BUILTIN_RULES = [
24404
24646
  "typescript/*",
24405
24647
  "dotnet/*",
24406
24648
  "cppcheck/*",
24407
- "clang-tidy/*"
24649
+ "clang-tidy/*",
24650
+ "expo-doctor/*"
24408
24651
  ]
24409
24652
  },
24410
24653
  {
@@ -24503,7 +24746,10 @@ const BUILTIN_RULES = [
24503
24746
  "security/shell-injection",
24504
24747
  "security/unsafe-deserialization",
24505
24748
  "security/unsafe-c-call",
24506
- "security/dependency-audit-skipped"
24749
+ "security/dependency-audit-skipped",
24750
+ "security/prompt-injection",
24751
+ "security/unsandboxed-agent-tool",
24752
+ "security/invisible-unicode-tags"
24507
24753
  ]
24508
24754
  }
24509
24755
  ];
@@ -24570,6 +24816,8 @@ const resolveHistoryDir = (directory) => {
24570
24816
  const resolved = path.resolve(directory);
24571
24817
  const primary = path.join(resolved, CONFIG_DIR);
24572
24818
  if (fs.existsSync(primary)) return primary;
24819
+ const legacy = path.join(resolved, ".aislop");
24820
+ if (fs.existsSync(legacy)) return legacy;
24573
24821
  return null;
24574
24822
  };
24575
24823
  const historyPath = (directory) => {
@@ -24782,7 +25030,7 @@ const maybeNotifyUpdate = async (now = Date.now()) => {
24782
25030
  if (!process.stderr.isTTY) return;
24783
25031
  const cachePath = resolveUpdateCachePath();
24784
25032
  const cache = readCache(cachePath);
24785
- if (cache && isOutdated("0.2.2", cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
25033
+ if (cache && isOutdated("0.2.4", cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
24786
25034
  if (!cache || now - cache.checkedAt > CHECK_INTERVAL_MS) {
24787
25035
  const latest = await fetchLatestVersion$1();
24788
25036
  if (latest) writeCache(cachePath, {
@@ -25302,7 +25550,7 @@ const renderCommandGroup = (group, commandWidth) => {
25302
25550
  return lines;
25303
25551
  };
25304
25552
  const renderCommandReference = (input = {}) => {
25305
- const version = input.version ?? "0.2.2";
25553
+ const version = input.version ?? "0.2.4";
25306
25554
  const commandWidth = Math.max(...COMMAND_REFERENCE.map((item) => item.command.length));
25307
25555
  const lines = [renderHeader({
25308
25556
  version,
@@ -25525,7 +25773,7 @@ const renderHelpDetails = () => [
25525
25773
  ].map((line) => highlightRaigal(line, theme)).join("\n");
25526
25774
  const renderHome = (input = {}) => {
25527
25775
  let out = renderHeader({
25528
- version: input.version ?? "0.2.2",
25776
+ version: input.version ?? "0.2.4",
25529
25777
  command: "--bare",
25530
25778
  context: []
25531
25779
  });
@@ -25585,10 +25833,10 @@ const registerExtraCommands = (program) => {
25585
25833
  return { exitCode: 0 };
25586
25834
  });
25587
25835
  });
25588
- program.command("update").alias("upgrade").description("Check npm for the latest aislop version").action(async () => {
25836
+ program.command("update").alias("upgrade").description("Check npm for the latest raigal version").action(async () => {
25589
25837
  await updateCommand();
25590
25838
  });
25591
- program.command("version").description("Print the installed aislop version").action(() => {
25839
+ program.command("version").description("Print the installed raigal version").action(() => {
25592
25840
  process.stdout.write(`${APP_VERSION}\n`);
25593
25841
  });
25594
25842
  program.command("commands").description("List all commands and major flags").action(() => {
@@ -27075,7 +27323,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
27075
27323
  scoreFileCount
27076
27324
  });
27077
27325
  if (options.sarif) {
27078
- const { buildSarifLog } = await import("./sarif--DI_2p2r.js");
27326
+ const { buildSarifLog } = await import("./sarif-CdtsAfJ1.js");
27079
27327
  console.log(JSON.stringify(buildSarifLog(results), null, 2));
27080
27328
  return completion;
27081
27329
  }
@@ -28571,7 +28819,7 @@ const detectUnusedDeclarations = async (context) => {
28571
28819
  rule: "code-quality/unused-declaration",
28572
28820
  severity: "warning",
28573
28821
  message: `Unused ${extracted.kind}: ${extracted.name}`,
28574
- help: "This top-level declaration is never used; aislop will remove it.",
28822
+ help: "This top-level declaration is never used; raigal will remove it.",
28575
28823
  line: d.line,
28576
28824
  column: d.column,
28577
28825
  category: "Dead Code",
@@ -28589,7 +28837,7 @@ const detectUnusedDeclarations = async (context) => {
28589
28837
  rule: "code-quality/unused-declaration",
28590
28838
  severity: "warning",
28591
28839
  message: `Unused ${kind}: ${name}`,
28592
- help: "This top-level declaration is never imported; aislop will remove it.",
28840
+ help: "This top-level declaration is never imported; raigal will remove it.",
28593
28841
  line: d.line,
28594
28842
  column: d.column,
28595
28843
  category: "Dead Code",
@@ -29713,7 +29961,7 @@ const promptForConfigChoices = async () => {
29713
29961
  initialValues: DEFAULT_ENGINE_SELECTION,
29714
29962
  required: false
29715
29963
  }));
29716
- if (enginesSelection === void 0) return null;
29964
+ if (enginesSelection === void 0 || isCancel(enginesSelection)) return null;
29717
29965
  const failBelowRaw = await text({
29718
29966
  message: "CI quality gate — fail the build when the score drops below (0-100)",
29719
29967
  initialValue: "70",