@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/cli.js CHANGED
@@ -13,8 +13,7 @@ import { z } from "zod/v4";
13
13
  import crypto, { createHash, createPublicKey, randomUUID, verify } from "node:crypto";
14
14
  import YAML, { parse } from "yaml";
15
15
  import { confirm, isCancel, multiselect, select, text } from "@clack/prompts";
16
- import * as readline$1 from "node:readline";
17
- import readline from "node:readline";
16
+ import readline, * as readline$1 from "node:readline";
18
17
  import picomatch from "picomatch";
19
18
  import ts from "typescript";
20
19
  import { parse as parse$1 } from "smol-toml";
@@ -113,7 +112,8 @@ const commandExists = (bin) => {
113
112
  }
114
113
  }
115
114
  }
116
- const result = spawnSync(process.platform === "win32" ? "where.exe" : "which", [bin], {
115
+ const command = process.platform === "win32" ? "where.exe" : "which";
116
+ const result = spawnSync(command, [bin], {
117
117
  encoding: "utf-8",
118
118
  timeout: PROVIDER_PROBE_TIMEOUT_MS
119
119
  });
@@ -711,7 +711,8 @@ const stringWidth = (s) => {
711
711
  const bare = stripAnsi$1(s);
712
712
  let total = 0;
713
713
  for (const ch of bare) {
714
- const w = wcwidth(ch.codePointAt(0) ?? 0);
714
+ const cp = ch.codePointAt(0) ?? 0;
715
+ const w = wcwidth(cp);
715
716
  total += w > 0 ? w : 1;
716
717
  }
717
718
  return total;
@@ -727,7 +728,8 @@ const truncate = (s, max, ellipsis = "…") => {
727
728
  let out = "";
728
729
  let w = 0;
729
730
  for (const ch of s) {
730
- const cw = wcwidth(ch.codePointAt(0) ?? 0);
731
+ const cp = ch.codePointAt(0) ?? 0;
732
+ const cw = wcwidth(cp);
731
733
  if (w + cw > limit) break;
732
734
  out += ch;
733
735
  w += cw;
@@ -737,7 +739,8 @@ const truncate = (s, max, ellipsis = "…") => {
737
739
  //#endregion
738
740
  //#region src/ui/display.ts
739
741
  const renderDisplaySection = (label, deps = {}) => {
740
- return style$1(deps.theme ?? theme, "section", label);
742
+ const t = deps.theme ?? theme;
743
+ return style$1(t, "section", label);
741
744
  };
742
745
  const renderDisplayRows = (rows, options = {}) => {
743
746
  if (rows.length === 0) return [];
@@ -898,7 +901,8 @@ const symbols = createSymbols({ plain: isPlain() });
898
901
  */
899
902
  const renderHintLine = (hint, deps = {}) => {
900
903
  const t = deps.theme ?? theme;
901
- return ` ${style$1(t, "accent", (deps.symbols ?? symbols).hint)} ${highlightAislop(maskSecrets(hint), t)}\n`;
904
+ const s = deps.symbols ?? symbols;
905
+ return ` ${style$1(t, "accent", s.hint)} ${highlightAislop(maskSecrets(hint), t)}\n`;
902
906
  };
903
907
  const createLogger = (deps = {}) => {
904
908
  const t = deps.theme ?? theme;
@@ -923,7 +927,7 @@ const createLogger = (deps = {}) => {
923
927
  const log = createLogger();
924
928
  //#endregion
925
929
  //#region src/version.ts
926
- const APP_VERSION = "0.2.2";
930
+ const APP_VERSION = "0.2.3";
927
931
  //#endregion
928
932
  //#region src/commands/agent-connect.ts
929
933
  const commandText = (status) => [status.provider.loginCommand.command, ...status.provider.loginCommand.args].join(" ");
@@ -1410,7 +1414,9 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
1410
1414
  if (!header.kid) throw new JwtVerificationError("JWT header missing 'kid'", "unknown_kid");
1411
1415
  const publicKey = getPublicKey(header.kid);
1412
1416
  if (!publicKey) throw new JwtVerificationError(`Unknown or untrusted signing key id (kid: ${header.kid})`, "unknown_kid");
1413
- if (!verify(null, Buffer.from(`${headerB64}.${payloadB64}`, "utf-8"), publicKey, Buffer.from(signatureB64, "base64url"))) throw new JwtVerificationError("Invalid JWT cryptographic signature", "invalid_signature");
1417
+ const signedData = Buffer.from(`${headerB64}.${payloadB64}`, "utf-8");
1418
+ const signature = Buffer.from(signatureB64, "base64url");
1419
+ if (!verify(null, signedData, publicKey, signature)) throw new JwtVerificationError("Invalid JWT cryptographic signature", "invalid_signature");
1414
1420
  let rawPayload;
1415
1421
  try {
1416
1422
  rawPayload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf-8"));
@@ -1422,7 +1428,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
1422
1428
  const claims = parsed.data;
1423
1429
  const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
1424
1430
  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");
1431
+ 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");
1426
1432
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
1427
1433
  const grace = options.allowGraceSeconds ?? 0;
1428
1434
  if (now > claims.exp + grace) {
@@ -1456,7 +1462,7 @@ const fetchGitHubOidcToken = async () => {
1456
1462
  };
1457
1463
  //#endregion
1458
1464
  //#region src/cloud/entitlement.ts
1459
- const GRACE_PERIOD_SECONDS = 4320 * 60;
1465
+ const GRACE_PERIOD_SECONDS = 259200;
1460
1466
  var MissingCredentialError = class extends Error {
1461
1467
  constructor(message = "No organization credential found") {
1462
1468
  super(message);
@@ -1581,7 +1587,8 @@ const getEntitlement = async () => {
1581
1587
  }
1582
1588
  } else oidcToken = await fetchGitHubOidcToken();
1583
1589
  try {
1584
- const response = await fetchEntitlementFromApi(activeToken?.token ?? "", oidcToken);
1590
+ const tokenToPass = activeToken?.token ?? "";
1591
+ const response = await fetchEntitlementFromApi(tokenToPass, oidcToken);
1585
1592
  const claims = verifyEntitlementJwt(response.entitlement, { nowSeconds: now });
1586
1593
  saveCachedEntitlement(response.entitlement, response.refresh_after);
1587
1594
  return {
@@ -1595,7 +1602,7 @@ const getEntitlement = async () => {
1595
1602
  };
1596
1603
  //#endregion
1597
1604
  //#region src/cloud/owner.ts
1598
- const GITHUB_REMOTE_RE$1 = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/.\s]+?)(?:\.git)?\s*$/;
1605
+ const GITHUB_REMOTE_RE$1 = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
1599
1606
  const parseGitRemote = (rawUrl) => {
1600
1607
  if (!rawUrl) return {
1601
1608
  owner: null,
@@ -2376,7 +2383,7 @@ var AgentTui = class {
2376
2383
  }
2377
2384
  ensureMounted() {
2378
2385
  if (!this.tty || this.handle) return;
2379
- this.handle = import("./mount-DTc8WaIt.js").then((m) => m.mountAgentTui(this.store));
2386
+ this.handle = import("./mount-CHGc3gB6.js").then((m) => m.mountAgentTui(this.store));
2380
2387
  }
2381
2388
  setActions(actions) {
2382
2389
  this.store.update({ actions: actions.filter((action) => action.trim().length > 0) });
@@ -2471,7 +2478,7 @@ const runSelf = (args, cwd) => {
2471
2478
  AISLOP_NO_UPDATE_NOTIFIER: "1",
2472
2479
  NO_COLOR: "1"
2473
2480
  },
2474
- maxBuffer: 50 * 1024 * 1024
2481
+ maxBuffer: 52428800
2475
2482
  });
2476
2483
  return {
2477
2484
  exitCode: result.status,
@@ -2510,7 +2517,7 @@ const applyDiff = async (root, patch) => {
2510
2517
  cwd: root,
2511
2518
  input: patch,
2512
2519
  encoding: "utf-8",
2513
- maxBuffer: 50 * 1024 * 1024
2520
+ maxBuffer: 52428800
2514
2521
  });
2515
2522
  if (child.status !== 0) throw new Error(child.stderr || child.stdout || "Failed to apply agent diff.");
2516
2523
  };
@@ -2883,7 +2890,7 @@ const detectCiContext = () => {
2883
2890
  };
2884
2891
  //#endregion
2885
2892
  //#region src/cloud/outbox.ts
2886
- const MAX_OUTBOX_BYTES = 5 * 1024 * 1024;
2893
+ const MAX_OUTBOX_BYTES = 5242880;
2887
2894
  const getMaxOutboxBytes = () => {
2888
2895
  const override = process.env.RAIGAL_MAX_OUTBOX_BYTES;
2889
2896
  if (override) {
@@ -3078,7 +3085,8 @@ var CloudRunRecorder = class {
3078
3085
  if (this.finished) return;
3079
3086
  this.finished = true;
3080
3087
  try {
3081
- enqueueRun(this.buildRunPayload(exitCode));
3088
+ const run = this.buildRunPayload(exitCode);
3089
+ enqueueRun(run);
3082
3090
  const activeToken = getActiveToken();
3083
3091
  let oidcToken;
3084
3092
  if (!activeToken) oidcToken = await fetchGitHubOidcToken();
@@ -3164,7 +3172,7 @@ const recordAgentSessionTelemetry = ({ worktreePath, published, branchOption, pr
3164
3172
  if (!recorder) return;
3165
3173
  let patchDiff;
3166
3174
  if (worktreePath) try {
3167
- patchDiff = (spawnSync("git", [
3175
+ const rawDiff = spawnSync("git", [
3168
3176
  "diff",
3169
3177
  "-U2",
3170
3178
  "--no-color",
@@ -3172,7 +3180,8 @@ const recordAgentSessionTelemetry = ({ worktreePath, published, branchOption, pr
3172
3180
  ], {
3173
3181
  cwd: worktreePath,
3174
3182
  encoding: "utf-8"
3175
- }).stdout || "").slice(0, 2e4) || void 0;
3183
+ }).stdout || "";
3184
+ patchDiff = rawDiff ? maskSecrets(rawDiff).slice(0, 2e4) : void 0;
3176
3185
  } catch {}
3177
3186
  if (published) {
3178
3187
  const prMatch = published.prUrl?.match(/\/pull\/(\d+)/);
@@ -3780,7 +3789,8 @@ const runSafeFixStep = (tui, cwd, options) => {
3780
3789
  };
3781
3790
  const selectFindings = (tui, cwd, limit, pass) => {
3782
3791
  tui.start(`Pass ${pass}: selecting findings`);
3783
- const findings = selectAgentFindings(scanJson(cwd).diagnostics, limit);
3792
+ const scan = scanJson(cwd);
3793
+ const findings = selectAgentFindings(scan.diagnostics, limit);
3784
3794
  tui.setMetric("Selected", findings.length);
3785
3795
  tui.complete({
3786
3796
  status: findings.length > 0 ? "done" : "skipped",
@@ -4813,7 +4823,7 @@ const DEFAULT_RULES_YAML = `# Architecture rules (BYO)
4813
4823
  //#endregion
4814
4824
  //#region src/config/extends.ts
4815
4825
  const MAX_DEPTH$1 = 5;
4816
- const MAX_CONFIG_BYTES = 1024 * 1024;
4826
+ const MAX_CONFIG_BYTES = 1048576;
4817
4827
  const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
4818
4828
  const deepMerge = (...sources) => {
4819
4829
  const result = {};
@@ -4994,6 +5004,9 @@ const CiSchema = z.object({
4994
5004
  format: z.enum(["json"]).default("json")
4995
5005
  }).strict();
4996
5006
  const TelemetrySchema = z.object({ enabled: z.boolean().default(true) }).strict();
5007
+ const BaselineConfigSchema = z.object({ mode: z.enum(["strict", "relaxed"]).default("relaxed") }).strict();
5008
+ const LicensingConfigSchema = z.object({ onLapse: z.enum(["fail-open", "fail-closed"]).default("fail-open") }).strict();
5009
+ const WorkspaceConfigSchema = z.object({ minimumFloor: z.number().min(0).max(100).default(50) }).strict();
4997
5010
  const RuleSeverityOverride = z.enum([
4998
5011
  "error",
4999
5012
  "warning",
@@ -5064,6 +5077,9 @@ const AislopConfigSchema = z.object({
5064
5077
  format: "json"
5065
5078
  })),
5066
5079
  telemetry: TelemetrySchema.default(() => ({ enabled: true })),
5080
+ baseline: BaselineConfigSchema.optional(),
5081
+ licensing: LicensingConfigSchema.optional(),
5082
+ workspace: WorkspaceConfigSchema.optional(),
5067
5083
  rules: RulesSchema,
5068
5084
  overrides: z.array(FileOverrideSchema).default(() => []),
5069
5085
  exclude: z.array(z.string()).default(() => [
@@ -5097,6 +5113,9 @@ const KNOWN_KEYS_BY_PATH = {
5097
5113
  "scoring",
5098
5114
  "ci",
5099
5115
  "telemetry",
5116
+ "baseline",
5117
+ "licensing",
5118
+ "workspace",
5100
5119
  "rules",
5101
5120
  "exclude",
5102
5121
  "include"
@@ -5147,7 +5166,10 @@ const KNOWN_KEYS_BY_PATH = {
5147
5166
  ],
5148
5167
  "scoring.thresholds": ["good", "ok"],
5149
5168
  ci: ["failBelow", "format"],
5150
- telemetry: ["enabled"]
5169
+ telemetry: ["enabled"],
5170
+ baseline: ["mode"],
5171
+ licensing: ["onLapse"],
5172
+ workspace: ["minimumFloor"]
5151
5173
  };
5152
5174
  const levenshtein = (a, b) => {
5153
5175
  const m = a.length;
@@ -5279,7 +5301,8 @@ const loadConfig = (directory) => {
5279
5301
  const configPath = findConfigFile(directory);
5280
5302
  if (!configPath) return DEFAULT_CONFIG;
5281
5303
  try {
5282
- return validateConfig(loadConfigChain(configPath), configPath);
5304
+ const merged = loadConfigChain(configPath);
5305
+ return validateConfig(merged, configPath);
5283
5306
  } catch (error) {
5284
5307
  if (error instanceof ConfigError) throw error;
5285
5308
  throw new ConfigError(configPath, `Failed to parse ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
@@ -5554,10 +5577,11 @@ const track = (input) => {
5554
5577
  cachedInstallId = ensured.installId;
5555
5578
  installCreated = ensured.created;
5556
5579
  }
5557
- const { clean, dropped } = redactProperties({
5580
+ const merged = {
5558
5581
  ...baseProperties(cachedInstallId),
5559
5582
  ...input.properties
5560
- });
5583
+ };
5584
+ const { clean, dropped } = redactProperties(merged);
5561
5585
  if (isDebug()) {
5562
5586
  const compact = JSON.stringify({
5563
5587
  event: input.event,
@@ -6397,14 +6421,17 @@ const renderRailStep = (step, deps = {}) => {
6397
6421
  * between the last step and the footer.
6398
6422
  */
6399
6423
  const renderRailConnector = (deps = {}) => {
6400
- return ` ${style$1(deps.theme ?? theme, "accentDim", (deps.symbols ?? symbols).rail)}\n`;
6424
+ const t = deps.theme ?? theme;
6425
+ const s = deps.symbols ?? symbols;
6426
+ return ` ${style$1(t, "accentDim", s.rail)}\n`;
6401
6427
  };
6402
6428
  /**
6403
6429
  * Render the rail-end footer line ("└ <footer text>").
6404
6430
  */
6405
6431
  const renderRailFooter = (footer, deps = {}) => {
6406
6432
  const t = deps.theme ?? theme;
6407
- return ` ${style$1(t, "accentDim", (deps.symbols ?? symbols).railEnd)} ${highlightAislop(footer, t)}\n`;
6433
+ const s = deps.symbols ?? symbols;
6434
+ return ` ${style$1(t, "accentDim", s.railEnd)} ${highlightAislop(footer, t)}\n`;
6408
6435
  };
6409
6436
  const renderRail = (input, deps = {}) => {
6410
6437
  const t = deps.theme ?? theme;
@@ -6561,7 +6588,7 @@ const launchMonitorInBackground = async (directory, options) => {
6561
6588
  fs.mkdirSync(logDir, { recursive: true });
6562
6589
  const logPath = path.join(logDir, `${monitorId}.log`);
6563
6590
  const logFd = fs.openSync(logPath, "a");
6564
- writeAgentMonitorRecord(root, {
6591
+ const record = {
6565
6592
  id: monitorId,
6566
6593
  root,
6567
6594
  requestedDirectory: path.resolve(directory),
@@ -6579,7 +6606,8 @@ const launchMonitorInBackground = async (directory, options) => {
6579
6606
  noFix: options.noFix,
6580
6607
  logPath,
6581
6608
  recentCycles: []
6582
- });
6609
+ };
6610
+ writeAgentMonitorRecord(root, record);
6583
6611
  const child = spawn(process.execPath, [cliPath(), ...buildBackgroundMonitorArgs(directory, options)], {
6584
6612
  cwd: root,
6585
6613
  detached: true,
@@ -7828,7 +7856,8 @@ const registerPlanSubcommand = (agent) => {
7828
7856
  const plan = agent.command("plan [directory]").description("Preview provider, worktree, findings, and publish actions without editing");
7829
7857
  addAgentOptions(plan);
7830
7858
  plan.action(async (directory = ".", _flags, command) => {
7831
- const options = agentOptionsFromFlags(command.optsWithGlobals(), command);
7859
+ const flags = command.optsWithGlobals();
7860
+ const options = agentOptionsFromFlags(flags, command);
7832
7861
  await withAgentLifecycle("agent_plan", directory, agentTelemetryProperties(options), async () => {
7833
7862
  await agentPlanCommand(directory, options);
7834
7863
  });
@@ -7838,7 +7867,8 @@ const registerAgentCommand = (program) => {
7838
7867
  const agent = program.command("agent [directory]").description("Run a local AI slop repair session with Codex, Claude Code, or OpenCode");
7839
7868
  addAgentOptions(agent);
7840
7869
  agent.action(async (directory = ".", _flags, command) => {
7841
- const options = agentOptionsFromFlags(command.optsWithGlobals(), command);
7870
+ const flags = command.optsWithGlobals();
7871
+ const options = agentOptionsFromFlags(flags, command);
7842
7872
  await withAgentLifecycle("agent", directory, agentTelemetryProperties(options), async () => {
7843
7873
  return await agentCommand(directory, options);
7844
7874
  });
@@ -8130,9 +8160,9 @@ const buildSuggestedActions = (diagnostics, findings, regressed, delta) => {
8130
8160
  if (fixableDiags.length > 0) {
8131
8161
  const ruleIds = Array.from(new Set(fixableDiags.map((d) => d.rule)));
8132
8162
  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",
8163
+ id: "run_raigal_fix",
8164
+ label: `Run raigal fix to clear ${fixableDiags.length} mechanical finding${fixableDiags.length === 1 ? "" : "s"}.`,
8165
+ command: "raigal fix",
8136
8166
  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
8167
  ruleIds
8138
8168
  });
@@ -8193,8 +8223,40 @@ const buildFeedback = (diagnostics, score, rootDirectory, baseline, meta) => {
8193
8223
  const regressed = typeof delta === "number" ? delta < 0 : false;
8194
8224
  let newSinceBaseline;
8195
8225
  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);
8226
+ const remainingExact = new Set(baselineSnapshot.findingFingerprints);
8227
+ const unmatchedFindings = [];
8228
+ for (const f of all) {
8229
+ const exactFp = fingerprintFinding(f);
8230
+ if (remainingExact.has(exactFp)) remainingExact.delete(exactFp);
8231
+ else unmatchedFindings.push(f);
8232
+ }
8233
+ const remainingPool = [];
8234
+ for (const fp of remainingExact) {
8235
+ const parts = fp.split(":");
8236
+ if (parts.length >= 3) {
8237
+ const file = parts[0];
8238
+ const line = Number.parseInt(parts[1], 10);
8239
+ const rule = parts.slice(2).join(":");
8240
+ remainingPool.push({
8241
+ file,
8242
+ line: Number.isNaN(line) ? 0 : line,
8243
+ rule
8244
+ });
8245
+ }
8246
+ }
8247
+ const stillUnmatched = [];
8248
+ for (const f of unmatchedFindings) {
8249
+ const candidateIdx = remainingPool.findIndex((b) => b.file === f.file && b.rule === f.ruleId && Math.abs(b.line - f.line) <= 5);
8250
+ if (candidateIdx >= 0) remainingPool.splice(candidateIdx, 1);
8251
+ else stillUnmatched.push(f);
8252
+ }
8253
+ const fresh = [];
8254
+ for (const f of stillUnmatched) {
8255
+ const candidateIdx = remainingPool.findIndex((b) => b.file === f.file && b.rule === f.ruleId);
8256
+ if (candidateIdx >= 0) remainingPool.splice(candidateIdx, 1);
8257
+ else fresh.push(f);
8258
+ }
8259
+ newSinceBaseline = fresh.slice(0, MAX_NEW_SINCE_BASELINE);
8198
8260
  }
8199
8261
  return {
8200
8262
  schema: "aislop.hook.v2",
@@ -8406,7 +8468,7 @@ const filterExcludedDiagnostics = (diagnostics, rootDirectory, excludePatterns)
8406
8468
  };
8407
8469
  //#endregion
8408
8470
  //#region src/utils/project-path-safety.ts
8409
- const MAX_PROJECT_CONFIG_BYTES = 1024 * 1024;
8471
+ const MAX_PROJECT_CONFIG_BYTES = 1048576;
8410
8472
  const isWithinDirectory$3 = (rootDirectory, candidate) => {
8411
8473
  const relative = path.relative(rootDirectory, candidate);
8412
8474
  return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
@@ -8506,7 +8568,7 @@ const isAutoGenerated = (filePath) => {
8506
8568
  };
8507
8569
  //#endregion
8508
8570
  //#region src/utils/git-ignore.ts
8509
- const MAX_BUFFER$2 = 50 * 1024 * 1024;
8571
+ const MAX_BUFFER$2 = 52428800;
8510
8572
  const NOT_IGNORED_ARGUMENTS = [
8511
8573
  "ls-files",
8512
8574
  "--cached",
@@ -8617,7 +8679,7 @@ const dropGitIgnoredPaths = (rootDirectory, absolutePaths) => {
8617
8679
  };
8618
8680
  //#endregion
8619
8681
  //#region src/utils/project-file-list.ts
8620
- const MAX_BUFFER$1 = 50 * 1024 * 1024;
8682
+ const MAX_BUFFER$1 = 52428800;
8621
8683
  const normalizePruneDirectories = (directories) => new Set([...directories].map((directory) => directory.toLowerCase()));
8622
8684
  const isInPrunedDirectory = (filePath, pruneDirectories) => {
8623
8685
  const pathSegments = filePath.split("/");
@@ -9549,7 +9611,7 @@ const tokenizeJs = (content, ext = ".ts") => {
9549
9611
  };
9550
9612
  //#endregion
9551
9613
  //#region src/utils/source-masker.ts
9552
- const JS_EXTS$2 = /* @__PURE__ */ new Set([
9614
+ const JS_EXTS$3 = /* @__PURE__ */ new Set([
9553
9615
  ".ts",
9554
9616
  ".tsx",
9555
9617
  ".js",
@@ -9557,7 +9619,7 @@ const JS_EXTS$2 = /* @__PURE__ */ new Set([
9557
9619
  ".mjs",
9558
9620
  ".cjs"
9559
9621
  ]);
9560
- const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
9622
+ const PY_EXTS$1 = /* @__PURE__ */ new Set([".py"]);
9561
9623
  const RB_EXTS = /* @__PURE__ */ new Set([".rb"]);
9562
9624
  const PHP_EXTS = /* @__PURE__ */ new Set([".php"]);
9563
9625
  const CSHARP_EXTS = /* @__PURE__ */ new Set([".cs"]);
@@ -9581,8 +9643,8 @@ const C_STYLE_COMMENT_EXTS = /* @__PURE__ */ new Set([
9581
9643
  ".zig"
9582
9644
  ]);
9583
9645
  const familyForExt = (ext) => {
9584
- if (JS_EXTS$2.has(ext)) return "js";
9585
- if (PY_EXTS.has(ext)) return "py";
9646
+ if (JS_EXTS$3.has(ext)) return "js";
9647
+ if (PY_EXTS$1.has(ext)) return "py";
9586
9648
  if (RB_EXTS.has(ext)) return "rb";
9587
9649
  if (PHP_EXTS.has(ext)) return "php";
9588
9650
  if (CSHARP_EXTS.has(ext)) return "csharp";
@@ -9852,7 +9914,7 @@ const areLiteralMultisetsEqual = (a, b) => {
9852
9914
  };
9853
9915
  //#endregion
9854
9916
  //#region src/engines/ai-slop/abstractions.ts
9855
- const JS_EXTS$1 = /* @__PURE__ */ new Set([
9917
+ const JS_EXTS$2 = /* @__PURE__ */ new Set([
9856
9918
  ".ts",
9857
9919
  ".tsx",
9858
9920
  ".js",
@@ -9863,11 +9925,11 @@ const JS_EXTS$1 = /* @__PURE__ */ new Set([
9863
9925
  const THIN_WRAPPER_PATTERNS = [
9864
9926
  {
9865
9927
  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
9928
+ extensions: JS_EXTS$2
9867
9929
  },
9868
9930
  {
9869
9931
  pattern: /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w[^=]*)?\s*=>\s*\w+\([^)]*\);/g,
9870
- extensions: JS_EXTS$1
9932
+ extensions: JS_EXTS$2
9871
9933
  },
9872
9934
  {
9873
9935
  pattern: /def\s+(\w+)\s*\([^)]*\)(?:\s*->[^:]*)?:\s*\n\s+return\s+\w+\([^)]*\)\s*$/gm,
@@ -11115,7 +11177,7 @@ const scriptKindFor$1 = (ext) => {
11115
11177
  }
11116
11178
  };
11117
11179
  const lineFor$1 = (sourceFile, node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
11118
- const makeDiagnostic = (filePath, line, rule, message, help) => ({
11180
+ const makeDiagnostic$1 = (filePath, line, rule, message, help) => ({
11119
11181
  filePath,
11120
11182
  engine: "ai-slop",
11121
11183
  rule,
@@ -11165,7 +11227,7 @@ const detectRedundantTryCatch = (sourceFile, relativePath) => {
11165
11227
  if (ts.isTryStatement(node) && node.catchClause && !node.finallyBlock) {
11166
11228
  const catchNameNode = node.catchClause.variableDeclaration?.name;
11167
11229
  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."));
11230
+ 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
11231
  }
11170
11232
  ts.forEachChild(node, visit);
11171
11233
  };
@@ -11183,7 +11245,7 @@ const detectPrimitiveCoercions = (sourceFile, relativePath) => {
11183
11245
  const [arg] = child.arguments;
11184
11246
  if (arg && ts.isIdentifier(arg)) {
11185
11247
  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."));
11248
+ 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
11249
  }
11188
11250
  }
11189
11251
  ts.forEachChild(child, visitBody);
@@ -11226,7 +11288,7 @@ const detectDuplicateExportedTypes = (parsedSources) => {
11226
11288
  continue;
11227
11289
  }
11228
11290
  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.`));
11291
+ 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
11292
  }
11231
11293
  }
11232
11294
  return diagnostics;
@@ -11328,7 +11390,7 @@ const detectDuplicateImports = async (context) => {
11328
11390
  rule: "ai-slop/duplicate-import",
11329
11391
  severity: "warning",
11330
11392
  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.",
11393
+ help: "Two imports from the same module split readers' attention and grow the import block. Run raigal fix to merge them automatically.",
11332
11394
  line: dup.line,
11333
11395
  column: 1,
11334
11396
  category: "AI Slop",
@@ -13036,7 +13098,8 @@ const isJsRelativeOrAbsolute = (spec) => spec.startsWith(".") || spec.startsWith
13036
13098
  const RUNTIME_BUILTINS = /* @__PURE__ */ new Set(["bun"]);
13037
13099
  const isJsBuiltin = (spec) => {
13038
13100
  if (RUNTIME_BUILTINS.has(spec)) return true;
13039
- return isBuiltin(spec.startsWith("node:") ? spec.slice(5) : spec) || isBuiltin(spec);
13101
+ const stripped = spec.startsWith("node:") ? spec.slice(5) : spec;
13102
+ return isBuiltin(stripped) || isBuiltin(spec);
13040
13103
  };
13041
13104
  const VIRTUAL_MODULE_PREFIXES = [
13042
13105
  "astro:",
@@ -13248,7 +13311,8 @@ const isLocalAliasReplacement = (rawValue, configDirectory, rootDirectory) => {
13248
13311
  if (!target) return false;
13249
13312
  if (!target.startsWith(".") && !target.startsWith("/") && !target.startsWith("~/") && !target.startsWith("@/")) return false;
13250
13313
  const normalized = target.startsWith("~/") || target.startsWith("@/") ? target.slice(2) : target;
13251
- return isRootBoundedTarget(path.isAbsolute(normalized) ? normalized : path.resolve(configDirectory, normalized), rootDirectory);
13314
+ const candidate = path.isAbsolute(normalized) ? normalized : path.resolve(configDirectory, normalized);
13315
+ return isRootBoundedTarget(candidate, rootDirectory);
13252
13316
  };
13253
13317
  const hasValidPackageImportTarget = (value, packageDirectory, rootDirectory) => {
13254
13318
  if (!isRootBoundedTarget(packageDirectory, rootDirectory)) return false;
@@ -14442,7 +14506,9 @@ const isRustDocCommentLine = (line) => {
14442
14506
  };
14443
14507
  const collectBlocks = (sourceLines, syntax, content, ext) => {
14444
14508
  const blocks = [];
14445
- const commentLines = getCommentLines(content ?? sourceLines.join("\n"), ext ?? (syntax.linePrefixes.includes("//") ? ".ts" : ".py"));
14509
+ const actualContent = content ?? sourceLines.join("\n");
14510
+ const actualExt = ext ?? (syntax.linePrefixes.includes("//") ? ".ts" : ".py");
14511
+ const commentLines = getCommentLines(actualContent, actualExt);
14446
14512
  let i = 0;
14447
14513
  while (i < sourceLines.length) {
14448
14514
  const line = sourceLines[i];
@@ -14770,7 +14836,8 @@ const detectNarrativeComments = async (context) => {
14770
14836
  } catch {
14771
14837
  continue;
14772
14838
  }
14773
- const blocks = collectBlocks(content.split("\n"), syntax, content, ext);
14839
+ const lines = content.split("\n");
14840
+ const blocks = collectBlocks(lines, syntax, content, ext);
14774
14841
  for (const block of blocks) {
14775
14842
  const { matched, reason } = detectNarrativeInBlock(block, ext);
14776
14843
  if (!matched) continue;
@@ -15242,7 +15309,7 @@ const detectRustPatterns = async (context) => {
15242
15309
  };
15243
15310
  //#endregion
15244
15311
  //#region src/engines/ai-slop/silent-recovery.ts
15245
- const JS_EXTS = /* @__PURE__ */ new Set([
15312
+ const JS_EXTS$1 = /* @__PURE__ */ new Set([
15246
15313
  ".ts",
15247
15314
  ".tsx",
15248
15315
  ".js",
@@ -15437,7 +15504,7 @@ const detectSilentRecovery = async (context) => {
15437
15504
  for (const filePath of files) {
15438
15505
  if (isAutoGenerated(filePath)) continue;
15439
15506
  const ext = path.extname(filePath);
15440
- const isJs = JS_EXTS.has(ext);
15507
+ const isJs = JS_EXTS$1.has(ext);
15441
15508
  if (!isJs && !(ext === ".py")) continue;
15442
15509
  const relPath = relativePosix(context.rootDirectory, filePath);
15443
15510
  if (isNonProductionPath(relPath)) continue;
@@ -15722,7 +15789,7 @@ const detectUnusedCss = async (context) => {
15722
15789
  rule: "ai-slop/unused-css",
15723
15790
  severity: "warning",
15724
15791
  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)",
15792
+ help: "Remove the dead CSS rule, or confirm the class is used dynamically (and add a raigal-ignore comment if so)",
15726
15793
  line: def.line,
15727
15794
  column: 0,
15728
15795
  category: "AI Slop",
@@ -16723,7 +16790,8 @@ const checkFunctionDiagnostics = (relativePath, fn, limits, ext) => {
16723
16790
  const results = [];
16724
16791
  const multiplier = functionLocMultiplier(fn, ext);
16725
16792
  const fnMax = functionLocBudget(fn, ext, limits.maxFunctionLoc);
16726
- if (exceedsLimit(fn.lineCount - fn.templateLines, limits.maxFunctionLoc, multiplier)) results.push({
16793
+ const effectiveLineCount = fn.lineCount - fn.templateLines;
16794
+ if (exceedsLimit(effectiveLineCount, limits.maxFunctionLoc, multiplier)) results.push({
16727
16795
  filePath: relativePath,
16728
16796
  engine: "code-quality",
16729
16797
  rule: "complexity/function-too-long",
@@ -16813,14 +16881,14 @@ const isTrivialLine = (line) => {
16813
16881
  };
16814
16882
  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
16883
  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/;
16884
+ const SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore(?:-next-block|-file)?\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
16885
+ const FILE_SUPPRESS_RE = /(?:raigal|aislop)[- ]ignore-file\b.*\b(?:duplicate-block|code-quality\/duplicate-block)\b/;
16818
16886
  const fileHasSuppression = (content) => FILE_SUPPRESS_RE.test(content);
16819
16887
  const isLowSignalMarkupWindow = (lines) => {
16820
16888
  return lines.filter((line) => SVG_MARKUP_RE.test(line)).length >= Math.ceil(WINDOW_SIZE / 2);
16821
16889
  };
16822
16890
  const isLowSignalDataWindow = (lines) => {
16823
- return lines.filter((line) => DATA_LITERAL_RE.test(line)).length >= WINDOW_SIZE - 1;
16891
+ return lines.filter((line) => DATA_LITERAL_RE.test(line)).length >= 9;
16824
16892
  };
16825
16893
  const findSuppressedLines = (lines) => {
16826
16894
  const suppressed = /* @__PURE__ */ new Set();
@@ -16852,7 +16920,7 @@ const collectMeaningfulLines = (content) => {
16852
16920
  if (isLowSignalDataWindow(window)) continue;
16853
16921
  if (window.every(isTrivialLine)) continue;
16854
16922
  const normalised = window.map(normaliseLine);
16855
- if (normalised.filter((n) => n.length > 0 && n !== "}" && n !== "{").length < WINDOW_SIZE - 1) continue;
16923
+ if (normalised.filter((n) => n.length > 0 && n !== "}" && n !== "{").length < 9) continue;
16856
16924
  if (new Set(normalised).size < MIN_DISTINCT_LINES) continue;
16857
16925
  hits.push({
16858
16926
  startLine: i + 1,
@@ -16956,8 +17024,8 @@ const shouldIncludeIssue = (issueType, filePath) => {
16956
17024
  return !filePath.replace(/\\/g, "/").includes(".github/workflows/");
16957
17025
  };
16958
17026
  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`.",
17027
+ dependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
17028
+ devDependencies: "This package is listed in package.json but not imported anywhere. Remove it with `npm uninstall` or `raigal fix`.",
16961
17029
  unlisted: "This package is imported in code but not declared in package.json. Run `npm install` to add it.",
16962
17030
  unresolved: "This import cannot be resolved. Check for typos or missing packages.",
16963
17031
  binaries: "This binary is used but its package is not in package.json."
@@ -17916,7 +17984,7 @@ const runGofmt = async (context) => {
17916
17984
  rule: "go-formatting",
17917
17985
  severity: "warning",
17918
17986
  message: "Go file is not formatted correctly",
17919
- help: "Run `aislop fix` to auto-format with gofmt",
17987
+ help: "Run `raigal fix` to auto-format with gofmt",
17920
17988
  line: 0,
17921
17989
  column: 0,
17922
17990
  category: "Format",
@@ -17944,7 +18012,8 @@ const resolvePackageRoot = (startFile) => {
17944
18012
  while (true) {
17945
18013
  const packageJsonPath = path.join(current, "package.json");
17946
18014
  if (fs.existsSync(packageJsonPath)) try {
17947
- if (JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")).name === "aislop") return current;
18015
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
18016
+ if (packageJson.name === "aislop" || packageJson.name === "@methiu/raigal" || packageJson.name === "raigal") return current;
17948
18017
  } catch {}
17949
18018
  const parent = path.dirname(current);
17950
18019
  if (parent === current) break;
@@ -18055,7 +18124,7 @@ const parseRuffFormatOutput = (output, rootDir) => {
18055
18124
  rule: "python-formatting",
18056
18125
  severity: "warning",
18057
18126
  message: "Python file is not formatted correctly",
18058
- help: "Run `aislop fix` to auto-format with ruff",
18127
+ help: "Run `raigal fix` to auto-format with ruff",
18059
18128
  line: 0,
18060
18129
  column: 0,
18061
18130
  category: "Format",
@@ -18067,7 +18136,8 @@ const parseRuffFormatOutput = (output, rootDir) => {
18067
18136
  const fixRuffFormat = async (context) => {
18068
18137
  const targets = context.files ? getPythonTargets(context) : [context.rootDirectory];
18069
18138
  if (context.files && targets.length === 0) return;
18070
- const result = await runSubprocess(resolveToolBinary("ruff"), ["format", ...targets], {
18139
+ const ruffBinary = resolveToolBinary("ruff");
18140
+ const result = await runSubprocess(ruffBinary, ["format", ...targets], {
18071
18141
  cwd: context.rootDirectory,
18072
18142
  timeout: 6e4
18073
18143
  });
@@ -18137,7 +18207,8 @@ const parseClangTidyOutput = (output, rootDirectory) => {
18137
18207
  const runClangTidy = async (context) => {
18138
18208
  const compileCommandsDir = findCompileCommandsDir(context);
18139
18209
  if (!compileCommandsDir) return [];
18140
- const sources = filterSourcesInDatabase(findCppSources(context).filter((f) => CPP_IMPL_EXTENSIONS.has(path.extname(f).toLowerCase())), readCompileCommandsFiles(compileCommandsDir));
18210
+ const implSources = findCppSources(context).filter((f) => CPP_IMPL_EXTENSIONS.has(path.extname(f).toLowerCase()));
18211
+ const sources = filterSourcesInDatabase(implSources, readCompileCommandsFiles(compileCommandsDir));
18141
18212
  if (sources.length === 0) return [];
18142
18213
  const diagnostics = [];
18143
18214
  for (const chunk of chunkFilePaths(sources, CLANG_TIDY_CHUNK_MAX_FILES)) try {
@@ -18507,7 +18578,7 @@ const runExpoDoctor = async (context) => {
18507
18578
  rule: "expo-doctor/config-error",
18508
18579
  severity: "warning",
18509
18580
  message: configError,
18510
- help: "Install project dependencies, then re-run `aislop scan`.",
18581
+ help: "Install project dependencies, then re-run `raigal scan`.",
18511
18582
  line: 0,
18512
18583
  column: 0,
18513
18584
  category: "Expo",
@@ -18973,7 +19044,8 @@ const processFile = (filePath, fileTargets, result) => {
18973
19044
  });
18974
19045
  continue;
18975
19046
  }
18976
- const { edit, skipReason } = computeEdit(sourceFile, classifyIdentifier(best));
19047
+ const shape = classifyIdentifier(best);
19048
+ const { edit, skipReason } = computeEdit(sourceFile, shape);
18977
19049
  if (!edit) {
18978
19050
  pendingSkips.push({
18979
19051
  target,
@@ -19788,7 +19860,8 @@ const ruffLintFixArgs = (context, unsafe) => {
19788
19860
  };
19789
19861
  const fixRuffLint = async (context) => {
19790
19862
  if (context.files && getPythonTargets(context).length === 0) return;
19791
- const result = await runSubprocess(resolveToolBinary("ruff"), ruffLintFixArgs(context, false), {
19863
+ const ruffBinary = resolveToolBinary("ruff");
19864
+ const result = await runSubprocess(ruffBinary, ruffLintFixArgs(context, false), {
19792
19865
  cwd: context.rootDirectory,
19793
19866
  timeout: 6e4
19794
19867
  });
@@ -19796,7 +19869,8 @@ const fixRuffLint = async (context) => {
19796
19869
  };
19797
19870
  const fixRuffLintForce = async (context) => {
19798
19871
  if (context.files && getPythonTargets(context).length === 0) return;
19799
- const result = await runSubprocess(resolveToolBinary("ruff"), ruffLintFixArgs(context, true), {
19872
+ const ruffBinary = resolveToolBinary("ruff");
19873
+ const result = await runSubprocess(ruffBinary, ruffLintFixArgs(context, true), {
19800
19874
  cwd: context.rootDirectory,
19801
19875
  timeout: 6e4
19802
19876
  });
@@ -19837,7 +19911,8 @@ const resolveTrustedTscPath = () => {
19837
19911
  };
19838
19912
  const isReferenceOnlyConfig = (tsconfigPath) => {
19839
19913
  try {
19840
- const parsed = parseJsonc(fs.readFileSync(tsconfigPath, "utf-8"));
19914
+ const raw = fs.readFileSync(tsconfigPath, "utf-8");
19915
+ const parsed = parseJsonc(raw);
19841
19916
  if (!parsed) return false;
19842
19917
  return Array.isArray(parsed.references) && !parsed.files && !parsed.include && !parsed.extends;
19843
19918
  } catch {
@@ -19980,6 +20055,174 @@ const lintEngine = {
19980
20055
  }
19981
20056
  };
19982
20057
  //#endregion
20058
+ //#region src/engines/security/ai-security.ts
20059
+ const JS_EXTS = /* @__PURE__ */ new Set([
20060
+ ".ts",
20061
+ ".tsx",
20062
+ ".js",
20063
+ ".jsx",
20064
+ ".mjs",
20065
+ ".cjs"
20066
+ ]);
20067
+ const PY_EXTS = /* @__PURE__ */ new Set([".py"]);
20068
+ const UNICODE_TAG_RE = /[\u{E0000}-\u{E007F}]/gu;
20069
+ const hasRuleIgnore = (lines, lineIndex, ruleSuffix) => {
20070
+ const start = Math.max(0, lineIndex - 2);
20071
+ const re = new RegExp(`(?:raigal|aislop)[- ]ignore(?:-file)?\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
20072
+ return lines.slice(start, lineIndex + 1).some((line) => re.test(line));
20073
+ };
20074
+ const hasFileIgnore = (lines, ruleSuffix) => {
20075
+ const re = new RegExp(`(?:raigal|aislop)[- ]ignore-file\\b.*(?:${ruleSuffix}|security\\/${ruleSuffix})`, "i");
20076
+ return lines.slice(0, 10).some((line) => re.test(line));
20077
+ };
20078
+ const makeDiagnostic = (filePath, rule, message, help, line, column) => ({
20079
+ filePath,
20080
+ engine: "security",
20081
+ rule,
20082
+ severity: "error",
20083
+ message,
20084
+ help,
20085
+ line,
20086
+ column,
20087
+ category: "Security",
20088
+ fixable: false
20089
+ });
20090
+ const P_BYPASS = "bypassPermissions";
20091
+ const P_EXEC_TYPE = "executor_type";
20092
+ const P_LOCAL = "local";
20093
+ const P_USE_DOCKER = "use_docker";
20094
+ const P_AUTO_RUN = "auto_run";
20095
+ const P_UNSAFE_MODE = "unsafe_mode";
20096
+ const P_PYTHON_REPL = "PythonREPLTool";
20097
+ const UNSANDBOXED_TOOL_PATTERNS = [
20098
+ {
20099
+ pattern: new RegExp(`\\bpermission_?mode["']?\\s*[:=]\\s*["']${P_BYPASS}["']`, "i"),
20100
+ message: `permission_mode '${P_BYPASS}' bypasses agent sandboxing and human confirmation`,
20101
+ help: `Do not use ${P_BYPASS} mode in production; use standard interactive approval`
20102
+ },
20103
+ {
20104
+ pattern: new RegExp(`\\b${P_EXEC_TYPE}["']?\\s*[:=]\\s*["']${P_LOCAL}["']`, "i"),
20105
+ message: `${P_EXEC_TYPE}='${P_LOCAL}' executes LLM-generated code directly on host machine without container isolation`,
20106
+ help: "Configure agent to use DockerCommandLineCodeExecutor or a containerized sandbox environment"
20107
+ },
20108
+ {
20109
+ pattern: new RegExp(`\\b(?:LocalCommandLineCodeExecutor|LocalCodeExecutor)\\s*\\(`),
20110
+ message: "Local code executor executes LLM-generated code on host machine without container isolation",
20111
+ help: "Use DockerCommandLineCodeExecutor or an isolated execution sandbox"
20112
+ },
20113
+ {
20114
+ pattern: new RegExp(`\\b${P_USE_DOCKER}\\s*[:=]\\s*(?:False|false)\\b|["']${P_USE_DOCKER}["']\\s*:\\s*(?:False|false)\\b`),
20115
+ message: `${P_USE_DOCKER}=False disables container sandboxing for agent code execution`,
20116
+ help: `Set ${P_USE_DOCKER}=True to isolate agent execution inside Docker containers`
20117
+ },
20118
+ {
20119
+ pattern: new RegExp(`\\b(?:interpreter\\.)?${P_AUTO_RUN}\\s*[:=]\\s*(?:True|true)\\b`),
20120
+ message: `${P_AUTO_RUN}=True allows autonomous shell execution without human confirmation`,
20121
+ help: `Keep ${P_AUTO_RUN}=False so dangerous commands require explicit human review`
20122
+ },
20123
+ {
20124
+ pattern: new RegExp(`\\bCodeInterpreterTool\\s*\\([^)]*${P_UNSAFE_MODE}\\s*=\\s*(?:True|true)\\b|\\b${P_UNSAFE_MODE}\\s*[:=]\\s*(?:True|true)\\b`),
20125
+ message: `${P_UNSAFE_MODE}=True allows code interpreter tool to execute without Docker isolation`,
20126
+ help: `Disable ${P_UNSAFE_MODE} and isolate code execution in containerized environments`
20127
+ },
20128
+ {
20129
+ pattern: new RegExp(`\\b(?:${P_PYTHON_REPL}|PythonAstREPLTool)\\s*\\(`),
20130
+ message: `${P_PYTHON_REPL} executes arbitrary Python code in host process without container isolation`,
20131
+ help: `Use containerized sandbox execution rather than unconfined ${P_PYTHON_REPL} in production`
20132
+ }
20133
+ ];
20134
+ const P_SYS = "system";
20135
+ const P_DEV = "developer";
20136
+ const P_SM = "SystemMessage";
20137
+ const PROMPT_INJECTION_JS_PATTERNS = [
20138
+ new RegExp(`(?:role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'][^}{;]{0,120}?content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`)`, "g"),
20139
+ new RegExp(`(?:content\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`[^}{;]{0,120}?role\\s*:\\s*["'](?:${P_SYS}|${P_DEV})["'])`, "g"),
20140
+ 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"),
20141
+ 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"),
20142
+ new RegExp(`\\b(?:${P_SYS}|systemPrompt|system_prompt|system_instruction)\\s*:\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\``, "g"),
20143
+ 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"),
20144
+ 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"),
20145
+ new RegExp(`\\[\\s*["']${P_SYS}["']\\s*,\\s*\`[^\`]*\\$\\{[^}]+\\}[^\`]*\`\\s*\\]`, "g")
20146
+ ];
20147
+ const PROMPT_INJECTION_PY_PATTERNS = [
20148
+ 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"),
20149
+ 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"),
20150
+ 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"),
20151
+ 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"),
20152
+ 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"),
20153
+ 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"),
20154
+ new RegExp(`\\b${P_SM}\\s*\\(\\s*(?:content\\s*=\\s*)?f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])`, "g"),
20155
+ 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"),
20156
+ new RegExp(`\\(\\s*["']${P_SYS}["']\\s*,\\s*f(?:"""[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?"""|\'\'\'[\\s\\S]*?\\{[^}]+\\}[\\s\\S]*?\'\'\'|["'][^"'\\n]*?\\{[^}]+\\}[^"'\\n]*?["'])\\s*\\)`, "g")
20157
+ ];
20158
+ const checkInvisibleUnicode = (relPath, content, lines) => {
20159
+ if (hasFileIgnore(lines, "invisible-unicode-tags")) return [];
20160
+ const results = [];
20161
+ const tagRegex = new RegExp(UNICODE_TAG_RE.source, UNICODE_TAG_RE.flags);
20162
+ for (const match of content.matchAll(tagRegex)) {
20163
+ const matchIndex = match.index ?? 0;
20164
+ const line = content.slice(0, matchIndex).split("\n").length;
20165
+ const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
20166
+ if (hasRuleIgnore(lines, line - 1, "invisible-unicode-tags")) continue;
20167
+ 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));
20168
+ }
20169
+ return results;
20170
+ };
20171
+ const checkUnsandboxedTools = (relPath, content, lines) => {
20172
+ if (hasFileIgnore(lines, "unsandboxed-agent-tool")) return [];
20173
+ const results = [];
20174
+ for (const { pattern, message, help } of UNSANDBOXED_TOOL_PATTERNS) {
20175
+ const regex = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
20176
+ for (const match of content.matchAll(regex)) {
20177
+ const matchIndex = match.index ?? 0;
20178
+ const line = content.slice(0, matchIndex).split("\n").length;
20179
+ const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
20180
+ if (hasRuleIgnore(lines, line - 1, "unsandboxed-agent-tool")) continue;
20181
+ results.push(makeDiagnostic(relPath, "security/unsandboxed-agent-tool", message, help, line, col));
20182
+ }
20183
+ }
20184
+ return results;
20185
+ };
20186
+ const checkPromptInjection = (relPath, content, lines, isJs) => {
20187
+ if (hasFileIgnore(lines, "prompt-injection")) return [];
20188
+ const results = [];
20189
+ const patterns = isJs ? PROMPT_INJECTION_JS_PATTERNS : PROMPT_INJECTION_PY_PATTERNS;
20190
+ for (const pattern of patterns) {
20191
+ const regex = new RegExp(pattern.source, pattern.flags);
20192
+ for (const match of content.matchAll(regex)) {
20193
+ const matchIndex = match.index ?? 0;
20194
+ const line = content.slice(0, matchIndex).split("\n").length;
20195
+ const col = matchIndex - content.lastIndexOf("\n", matchIndex - 1);
20196
+ if (hasRuleIgnore(lines, line - 1, "prompt-injection")) continue;
20197
+ 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));
20198
+ }
20199
+ }
20200
+ return results;
20201
+ };
20202
+ const scanAiSecurity = async (context) => {
20203
+ const files = getSourceFiles(context);
20204
+ const diagnostics = [];
20205
+ for (const filePath of files) {
20206
+ const ext = path.extname(filePath);
20207
+ let content;
20208
+ try {
20209
+ content = fs.readFileSync(filePath, "utf-8");
20210
+ } catch {
20211
+ continue;
20212
+ }
20213
+ const relativePath = relativePosix(context.rootDirectory, filePath);
20214
+ const lines = content.split("\n");
20215
+ diagnostics.push(...checkInvisibleUnicode(relativePath, content, lines));
20216
+ const isJs = JS_EXTS.has(ext);
20217
+ const isPy = PY_EXTS.has(ext);
20218
+ if (isJs || isPy) {
20219
+ diagnostics.push(...checkUnsandboxedTools(relativePath, content, lines));
20220
+ diagnostics.push(...checkPromptInjection(relativePath, content, lines, isJs));
20221
+ }
20222
+ }
20223
+ return diagnostics;
20224
+ };
20225
+ //#endregion
19983
20226
  //#region src/ui/invocation.ts
19984
20227
  const detectInvocation = () => "raigal";
19985
20228
  //#endregion
@@ -20362,7 +20605,7 @@ const parseJsAudit = (output, source, manifest) => {
20362
20605
  rule: "security/dependency-audit-skipped",
20363
20606
  severity: "info",
20364
20607
  message: `Dependency audit skipped (${source}): lockfile is missing`,
20365
- help: errorDetail ?? "Generate a lockfile, then re-run `aislop scan` for dependency vulnerability checks.",
20608
+ help: errorDetail ?? "Generate a lockfile, then re-run `raigal scan` for dependency vulnerability checks.",
20366
20609
  line: 0,
20367
20610
  column: 0,
20368
20611
  category: "Security",
@@ -20536,10 +20779,11 @@ const auditSkippedDiagnostic = (source, help) => ({
20536
20779
  });
20537
20780
  const runNpmAudit = async (rootDir, timeout) => {
20538
20781
  try {
20539
- return parseJsAudit((await runSubprocess("npm", ["audit", "--json"], {
20782
+ const result = await runSubprocess("npm", ["audit", "--json"], {
20540
20783
  cwd: rootDir,
20541
20784
  timeout
20542
- })).stdout, "npm audit", readRuntimeDependencies(rootDir));
20785
+ });
20786
+ return parseJsAudit(result.stdout, "npm audit", readRuntimeDependencies(rootDir));
20543
20787
  } catch (error) {
20544
20788
  return [auditSkippedDiagnostic("npm audit", `Failed to run npm audit: ${errorMessageOf(error)}`)];
20545
20789
  }
@@ -20560,10 +20804,11 @@ const runBunAudit = async (rootDir, timeout) => {
20560
20804
  const runPnpmAuditWithFallback = async (rootDir, timeout) => {
20561
20805
  const canFallbackToNpm = fs.existsSync(path.join(rootDir, "package-lock.json"));
20562
20806
  try {
20563
- const diagnostics = parseJsAudit((await runSubprocess("pnpm", ["audit", "--json"], {
20807
+ const result = await runSubprocess("pnpm", ["audit", "--json"], {
20564
20808
  cwd: rootDir,
20565
20809
  timeout
20566
- })).stdout, "pnpm audit", readRuntimeDependencies(rootDir));
20810
+ });
20811
+ const diagnostics = parseJsAudit(result.stdout, "pnpm audit", readRuntimeDependencies(rootDir));
20567
20812
  if (diagnostics.some((d) => d.rule === "security/dependency-audit-skipped")) {
20568
20813
  if (canFallbackToNpm) return runNpmAudit(rootDir, timeout);
20569
20814
  return diagnostics;
@@ -20920,7 +21165,7 @@ const RISKY_PATTERNS = [
20920
21165
  //#region src/engines/security/risky.ts
20921
21166
  const hasDangerouslySetInnerHtmlIgnore = (lines, lineIndex) => {
20922
21167
  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));
21168
+ 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
21169
  };
20925
21170
  const isStructuredDataScript = (content, matchIndex) => {
20926
21171
  const before = content.slice(Math.max(0, matchIndex - 300), matchIndex);
@@ -21248,7 +21493,11 @@ const ALL_ENGINES = [
21248
21493
  name: "security",
21249
21494
  async run(context) {
21250
21495
  const diagnostics = [];
21251
- const promises = [scanSecrets(context), detectRiskyConstructs(context)];
21496
+ const promises = [
21497
+ scanSecrets(context),
21498
+ detectRiskyConstructs(context),
21499
+ scanAiSecurity(context)
21500
+ ];
21252
21501
  if (context.config.security.audit) promises.push(runDependencyAudit(context));
21253
21502
  const results = await Promise.allSettled(promises);
21254
21503
  for (const result of results) if (result.status === "fulfilled") diagnostics.push(...result.value);
@@ -21293,7 +21542,10 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
21293
21542
  failed: true,
21294
21543
  skipReason: r.reason instanceof Error ? r.reason.message : String(r.reason)
21295
21544
  });
21296
- return dedupeEquivalenceDiagnostics(dedupeOverlappingComments(dedupeCSharpAsync(stripSecretDiagnostics(finalResults))));
21545
+ const stripped = stripSecretDiagnostics(finalResults);
21546
+ const csharp = dedupeCSharpAsync(stripped);
21547
+ const comments = dedupeOverlappingComments(csharp);
21548
+ return dedupeEquivalenceDiagnostics(comments);
21297
21549
  };
21298
21550
  const stripSecretDiagnostics = (results) => {
21299
21551
  for (const result of results) for (const d of result.diagnostics) if (d.redactSource || isSecretClassRule(d.rule)) {
@@ -21439,6 +21691,9 @@ const RULE_SCORE_IMPACTS = {
21439
21691
  "security/shell-injection": strict("Interpolated shell commands can become command execution."),
21440
21692
  "security/unsafe-deserialization": strict("Deserializing untrusted data with a legacy .NET formatter can become remote code execution."),
21441
21693
  "security/unsafe-c-call": strict("Unbounded C string and process calls can become buffer overflows or command execution."),
21694
+ "security/prompt-injection": strict("Dynamic user interpolation in system/developer prompts enables prompt injection attacks."),
21695
+ "security/unsandboxed-agent-tool": strict("Unsandboxed agent code execution or tool permission bypass allows arbitrary host commands."),
21696
+ "security/invisible-unicode-tags": strict("Invisible Unicode Tag characters (U+E0000-U+E007F) hide stealth prompt injection payloads."),
21442
21697
  "security/dependency-audit-skipped": advisory("An unavailable audit is visibility loss, not evidence of a vulnerability."),
21443
21698
  "dotnet/projects-skipped": advisory("Skipping unrestored C# projects is visibility loss, not evidence of a defect."),
21444
21699
  "cppcheck/chunks-skipped": advisory("A skipped cppcheck chunk is visibility loss, not evidence that the skipped files have a defect."),
@@ -21515,7 +21770,8 @@ const calculateScore = (diagnostics, weights, thresholds, sourceFileCount, smoot
21515
21770
  const capScale = Math.max(1, fileCount.effectiveSourceFileCount / CAP_REFERENCE_FILES);
21516
21771
  const defaultRuleCap = typeof maxPerRule === "number" && maxPerRule > 0 ? maxPerRule : null;
21517
21772
  const capForRule = (key) => {
21518
- const ruleCap = scoreImpactForRule(key.slice(key.indexOf(":") + 1)).cap;
21773
+ const rule = key.slice(key.indexOf(":") + 1);
21774
+ const ruleCap = scoreImpactForRule(rule).cap;
21519
21775
  if (typeof ruleCap === "number") return defaultRuleCap ? Math.min(defaultRuleCap, ruleCap) : ruleCap;
21520
21776
  return defaultRuleCap;
21521
21777
  };
@@ -21628,8 +21884,10 @@ const detectSourceLanguages = (sourceFiles) => {
21628
21884
  const detectLanguages = (directory, sourceFiles) => {
21629
21885
  const languages = new Set(detectSourceLanguages(sourceFiles));
21630
21886
  for (const [file, lang] of Object.entries(LANGUAGE_SIGNALS)) if (fs.existsSync(path.join(directory, file))) languages.add(lang);
21631
- if (readPackageJson(path.join(directory, "package.json"))) if (fs.existsSync(path.join(directory, "tsconfig.json"))) languages.add("typescript");
21632
- else languages.add("javascript");
21887
+ if (readPackageJson(path.join(directory, "package.json"))) {
21888
+ if (fs.existsSync(path.join(directory, "tsconfig.json"))) languages.add("typescript");
21889
+ else languages.add("javascript");
21890
+ }
21633
21891
  for (const signal of PYTHON_SIGNALS) if (fs.existsSync(path.join(directory, signal))) {
21634
21892
  languages.add("python");
21635
21893
  break;
@@ -21856,7 +22114,7 @@ const runScopedScan = async (cwd, filePaths) => {
21856
22114
  const rulesPath = configDir ? path.join(configDir, RULES_FILE) : void 0;
21857
22115
  const enumeratedPaths = new Set(projectCandidates.map((filePath) => path.resolve(rootDirectory, filePath)));
21858
22116
  const scopedCandidates = filePaths.filter((filePath) => enumeratedPaths.has(path.resolve(rootDirectory, filePath)));
21859
- const diagnostics = applyRuleSeverities((await runEngines({
22117
+ const context = {
21860
22118
  rootDirectory: project.rootDirectory,
21861
22119
  languages: project.languages,
21862
22120
  frameworks: project.frameworks,
@@ -21879,14 +22137,17 @@ const runScopedScan = async (cwd, filePaths) => {
21879
22137
  allowProjectLocalTools: false,
21880
22138
  architectureRulesPath: config.engines.architecture ? rulesPath : void 0
21881
22139
  }
21882
- }, {
22140
+ };
22141
+ const enabled = {
21883
22142
  format: false,
21884
22143
  lint: false,
21885
22144
  "code-quality": config.engines["code-quality"],
21886
22145
  "ai-slop": config.engines["ai-slop"],
21887
22146
  architecture: config.engines.architecture,
21888
22147
  security: false
21889
- })).flatMap((result) => result.diagnostics), config.overrides.length ? {} : config.rules);
22148
+ };
22149
+ const results = await runEngines(context, enabled);
22150
+ const diagnostics = applyRuleSeverities(results.flatMap((result) => result.diagnostics), config.overrides.length ? {} : config.rules);
21890
22151
  const { score } = calculateScore(diagnostics, config.scoring.weights, config.scoring.thresholds, projectSourceFiles.length + projectTestFiles.length, config.scoring.smoothing, config.scoring.maxPerRule);
21891
22152
  return {
21892
22153
  diagnostics,
@@ -21944,14 +22205,26 @@ const hasExistingLiteralBackslashPath = (cwd, fingerprint) => {
21944
22205
  }
21945
22206
  };
21946
22207
  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);
22208
+ const baselinePath = (cwd) => {
22209
+ const raigalCandidate = path.join(cwd, ".raigal", "baseline.json");
22210
+ if (fs.existsSync(raigalCandidate)) return raigalCandidate;
22211
+ const raigalDir = path.join(cwd, ".raigal");
22212
+ if (fs.existsSync(raigalDir)) return raigalCandidate;
22213
+ return path.join(cwd, ".aislop", "baseline.json");
22214
+ };
22215
+ const sessionPath = (cwd) => {
22216
+ const raigalCandidate = path.join(cwd, ".raigal", "session.jsonl");
22217
+ if (fs.existsSync(raigalCandidate)) return raigalCandidate;
22218
+ const raigalDir = path.join(cwd, ".raigal");
22219
+ if (fs.existsSync(raigalDir)) return raigalCandidate;
22220
+ return path.join(cwd, ".aislop", "session.jsonl");
22221
+ };
21949
22222
  const readBaseline = (cwd) => {
21950
22223
  const raw = readIfExists(baselinePath(cwd));
21951
22224
  if (!raw) return null;
21952
22225
  try {
21953
22226
  const parsed = JSON.parse(raw);
21954
- if (parsed.schema !== "aislop.baseline.v2" && parsed.schema !== "aislop.baseline.v1") return null;
22227
+ if (parsed.schema !== "aislop.baseline.v2" && parsed.schema !== "aislop.baseline.v1" && parsed.schema !== "raigal.baseline.v2" && parsed.schema !== "raigal.baseline.v1") return null;
21955
22228
  const findingFingerprints = parsed.findingFingerprints ?? [];
21956
22229
  const needsWindowsMigration = parsed.pathFormat !== "posix";
21957
22230
  return {
@@ -21983,11 +22256,10 @@ const captureBaseline = async (cwd) => {
21983
22256
  });
21984
22257
  const project = await discoverProject(cwd);
21985
22258
  const config = loadConfig(cwd);
21986
- const results = await runEngines({
22259
+ const context = {
21987
22260
  rootDirectory: project.rootDirectory,
21988
22261
  languages: project.languages,
21989
22262
  frameworks: project.frameworks,
21990
- files: [],
21991
22263
  installedTools: project.installedTools,
21992
22264
  config: {
21993
22265
  overrides: config.overrides,
@@ -22003,19 +22275,22 @@ const captureBaseline = async (cwd) => {
22003
22275
  },
22004
22276
  allowProjectLocalTools: false
22005
22277
  }
22006
- }, {
22278
+ };
22279
+ const enabled = {
22007
22280
  format: false,
22008
22281
  lint: false,
22009
22282
  "code-quality": config.engines["code-quality"],
22010
22283
  "ai-slop": config.engines["ai-slop"],
22011
22284
  architecture: config.engines.architecture,
22012
22285
  security: false
22013
- });
22014
- const diagnostics = results.flatMap((r) => r.diagnostics);
22286
+ };
22287
+ const results = await runEngines(context, enabled);
22288
+ const diagnostics = applyRuleSeverities(results.flatMap((r) => r.diagnostics), config.overrides.length ? {} : config.rules);
22015
22289
  const { score } = calculateScore(diagnostics, config.scoring.weights, config.scoring.thresholds, project.sourceFileCount, config.scoring.smoothing, config.scoring.maxPerRule);
22016
22290
  const byEngine = {};
22017
22291
  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);
22292
+ const engineDiags = diagnostics.filter((d) => d.engine === r.engine);
22293
+ const { score: engineScore } = calculateScore(engineDiags, config.scoring.weights, config.scoring.thresholds, project.sourceFileCount, config.scoring.smoothing, config.scoring.maxPerRule);
22019
22294
  byEngine[r.engine] = engineScore;
22020
22295
  }
22021
22296
  const findingFingerprints = diagnostics.filter((d) => d.severity === "error" || d.severity === "warning").map((d) => fingerprintDiagnostic(d, project.rootDirectory));
@@ -22037,7 +22312,7 @@ const captureBaseline = async (cwd) => {
22037
22312
  };
22038
22313
  const appendSessionFiles = (cwd, files) => {
22039
22314
  if (files.length === 0) return;
22040
- const target = path.join(cwd, ".aislop", "session.jsonl");
22315
+ const target = sessionPath(cwd);
22041
22316
  try {
22042
22317
  fs.mkdirSync(path.dirname(target), { recursive: true });
22043
22318
  const line = `${JSON.stringify({
@@ -22048,7 +22323,8 @@ const appendSessionFiles = (cwd, files) => {
22048
22323
  } catch {}
22049
22324
  };
22050
22325
  const readSessionFiles = (cwd) => {
22051
- const raw = readIfExists(path.join(cwd, ".aislop", "session.jsonl"));
22326
+ const target = sessionPath(cwd);
22327
+ const raw = readIfExists(target);
22052
22328
  if (!raw) return [];
22053
22329
  const files = /* @__PURE__ */ new Set();
22054
22330
  for (const line of raw.split("\n")) {
@@ -22061,7 +22337,7 @@ const readSessionFiles = (cwd) => {
22061
22337
  return Array.from(files);
22062
22338
  };
22063
22339
  const clearSessionFiles = (cwd) => {
22064
- const target = path.join(cwd, ".aislop", "session.jsonl");
22340
+ const target = sessionPath(cwd);
22065
22341
  try {
22066
22342
  fs.unlinkSync(target);
22067
22343
  } catch {}
@@ -22165,7 +22441,7 @@ const runClaudeFileChangedHook = async (deps = {}) => {
22165
22441
  schema: "aislop.hook.v2",
22166
22442
  event: "file_changed",
22167
22443
  file: changed,
22168
- message: `Watched file changed (${changed}). aislop refreshed the baseline — score: ${result.score}.`,
22444
+ message: `Watched file changed (${changed}). raigal refreshed the baseline - score: ${result.score}.`,
22169
22445
  baseline: {
22170
22446
  score: result.score,
22171
22447
  fileCount: result.fileCount
@@ -22521,7 +22797,9 @@ const applyRemoval = (result, opts, target, nextContent) => {
22521
22797
  //#region src/hooks/install/rules-only.ts
22522
22798
  const installRulesOnly = (opts, paths, summary) => {
22523
22799
  const result = emptyResult();
22524
- const next = upsertMarkdownFence(readIfExists(paths.rules), AISLOP_MD_BODY, sentinelHash(AISLOP_MD_BODY)).nextContent;
22800
+ const existing = readIfExists(paths.rules);
22801
+ const hash = sentinelHash(AISLOP_MD_BODY);
22802
+ const next = upsertMarkdownFence(existing, AISLOP_MD_BODY, hash).nextContent;
22525
22803
  applyContent(result, opts, paths.rules, next, summary);
22526
22804
  if (paths.host && paths.marker) {
22527
22805
  const host = readIfExists(paths.host) ?? "";
@@ -22573,7 +22851,7 @@ const uninstallAntigravity = (opts) => {
22573
22851
  //#endregion
22574
22852
  //#region src/hooks/io/json-patch.ts
22575
22853
  const AISLOP_SENTINEL_KEY = "__aislop";
22576
- const isAislopManaged = (x) => typeof x === "object" && x !== null && "__aislop" in x && x["__aislop"] != null;
22854
+ const isAislopManaged = (x) => typeof x === "object" && x !== null && ("__aislop" in x && x["__aislop"] != null || "__raigal" in x && x["__raigal"] != null);
22577
22855
  const groupIsAislop = (group) => {
22578
22856
  if (typeof group !== "object" || group === null) return false;
22579
22857
  const hooks = group.hooks;
@@ -22628,14 +22906,14 @@ const resolveClaudePaths = (opts) => {
22628
22906
  };
22629
22907
  const buildHookGroup$1 = () => {
22630
22908
  const hashBody = JSON.stringify({
22631
- command: "aislop hook claude",
22909
+ command: "raigal hook claude",
22632
22910
  matcher: "Edit|Write|MultiEdit"
22633
22911
  });
22634
22912
  return {
22635
22913
  matcher: "Edit|Write|MultiEdit",
22636
22914
  hooks: [{
22637
22915
  type: "command",
22638
- command: "aislop hook claude",
22916
+ command: "raigal hook claude",
22639
22917
  [AISLOP_SENTINEL_KEY]: {
22640
22918
  v: 1,
22641
22919
  managed: true,
@@ -22645,12 +22923,12 @@ const buildHookGroup$1 = () => {
22645
22923
  };
22646
22924
  };
22647
22925
  const buildStopHookGroup = () => {
22648
- const hashBody = JSON.stringify({ command: "aislop hook claude --stop" });
22926
+ const hashBody = JSON.stringify({ command: "raigal hook claude --stop" });
22649
22927
  return {
22650
22928
  matcher: "",
22651
22929
  hooks: [{
22652
22930
  type: "command",
22653
- command: "aislop hook claude --stop",
22931
+ command: "raigal hook claude --stop",
22654
22932
  [AISLOP_SENTINEL_KEY]: {
22655
22933
  v: 1,
22656
22934
  managed: true,
@@ -22662,14 +22940,14 @@ const buildStopHookGroup = () => {
22662
22940
  const FILE_CHANGED_MATCHER = ".raigal/config.yml|.raigal/rules.yml|.aislop/config.yml|.aislop/rules.yml|package.json";
22663
22941
  const buildFileChangedHookGroup = () => {
22664
22942
  const hashBody = JSON.stringify({
22665
- command: "aislop hook claude --on-file-changed",
22943
+ command: "raigal hook claude --on-file-changed",
22666
22944
  matcher: FILE_CHANGED_MATCHER
22667
22945
  });
22668
22946
  return {
22669
22947
  matcher: FILE_CHANGED_MATCHER,
22670
22948
  hooks: [{
22671
22949
  type: "command",
22672
- command: "aislop hook claude --on-file-changed",
22950
+ command: "raigal hook claude --on-file-changed",
22673
22951
  [AISLOP_SENTINEL_KEY]: {
22674
22952
  v: 1,
22675
22953
  managed: true,
@@ -22697,7 +22975,8 @@ const installClaude = (opts) => {
22697
22975
  const nextSettings = renderSettings$1(readIfExists(paths.settings), Boolean(opts.qualityGate));
22698
22976
  applyContent(result, opts, paths.settings, nextSettings, "register PostToolUse + FileChanged hooks");
22699
22977
  const mdHash = sentinelHash(AISLOP_MD_BODY);
22700
- const fenced = upsertMarkdownFence(readIfExists(paths.aislopMd), AISLOP_MD_BODY, mdHash);
22978
+ const existingMd = readIfExists(paths.aislopMd);
22979
+ const fenced = upsertMarkdownFence(existingMd, AISLOP_MD_BODY, mdHash);
22701
22980
  applyContent(result, opts, paths.aislopMd, fenced.nextContent, "write AISLOP.md rules");
22702
22981
  const existingClaudeMd = readIfExists(paths.claudeMd) ?? "";
22703
22982
  const marker = "@AISLOP.md";
@@ -22812,11 +23091,11 @@ const resolveCursorPaths = (opts) => {
22812
23091
  };
22813
23092
  const buildHookEntry = () => {
22814
23093
  const hashBody = JSON.stringify({
22815
- command: "aislop hook cursor",
23094
+ command: "raigal hook cursor",
22816
23095
  timeout: 5e3
22817
23096
  });
22818
23097
  return {
22819
- command: "aislop hook cursor",
23098
+ command: "raigal hook cursor",
22820
23099
  type: "command",
22821
23100
  timeout: 5e3,
22822
23101
  [AISLOP_SENTINEL_KEY]: {
@@ -22843,7 +23122,9 @@ const installCursor = (opts) => {
22843
23122
  const nextHooks = renderHooksJson(readIfExists(paths.hooks));
22844
23123
  applyContent(result, opts, paths.hooks, nextHooks, "register afterFileEdit hook");
22845
23124
  if (opts.scope === "project") {
22846
- const rules = upsertMarkdownFence(readIfExists(paths.rules), AISLOP_MD_BODY, sentinelHash(AISLOP_MD_BODY)).nextContent;
23125
+ const existingRules = readIfExists(paths.rules);
23126
+ const hash = sentinelHash(AISLOP_MD_BODY);
23127
+ const rules = upsertMarkdownFence(existingRules, AISLOP_MD_BODY, hash).nextContent;
22847
23128
  applyContent(result, opts, paths.rules, rules, "write .cursor/rules/aislop.mdc");
22848
23129
  }
22849
23130
  return result;
@@ -22883,15 +23164,15 @@ const resolveGeminiPaths = (opts) => {
22883
23164
  };
22884
23165
  const buildHookGroup = () => {
22885
23166
  const hashBody = JSON.stringify({
22886
- command: "aislop hook gemini",
23167
+ command: "raigal hook gemini",
22887
23168
  matcher: "write_file|replace"
22888
23169
  });
22889
23170
  return {
22890
23171
  matcher: "write_file|replace",
22891
23172
  hooks: [{
22892
- name: "aislop",
23173
+ name: "raigal",
22893
23174
  type: "command",
22894
- command: "aislop hook gemini",
23175
+ command: "raigal hook gemini",
22895
23176
  timeout: 5e3,
22896
23177
  [AISLOP_SENTINEL_KEY]: {
22897
23178
  v: 1,
@@ -22916,7 +23197,9 @@ const installGemini = (opts) => {
22916
23197
  const result = emptyResult();
22917
23198
  const next = renderSettings(readIfExists(paths.settings));
22918
23199
  applyContent(result, opts, paths.settings, next, "register AfterTool hook");
22919
- const fenced = upsertMarkdownFence(readIfExists(paths.aislopMd), AISLOP_MD_BODY, sentinelHash(AISLOP_MD_BODY)).nextContent;
23200
+ const existingMd = readIfExists(paths.aislopMd);
23201
+ const hash = sentinelHash(AISLOP_MD_BODY);
23202
+ const fenced = upsertMarkdownFence(existingMd, AISLOP_MD_BODY, hash).nextContent;
22920
23203
  applyContent(result, opts, paths.aislopMd, fenced, "write AISLOP.md rules");
22921
23204
  const existingGeminiMd = readIfExists(paths.geminiMd) ?? "";
22922
23205
  const marker = "@AISLOP.md";
@@ -22982,8 +23265,8 @@ const uninstallKilocode = (opts) => {
22982
23265
  const resolvePiPaths = (opts) => {
22983
23266
  return { extension: opts.scope === "project" ? path.join(opts.cwd, ".pi", "extensions", "aislop.js") : path.join(opts.home, ".pi", "agent", "extensions", "aislop.js") };
22984
23267
  };
22985
- const PI_EXTENSION_SOURCE = `// aislop — auto-generated pi extension. Do not edit by hand.
22986
- // Reinstall with: aislop hook install --pi
23268
+ const PI_EXTENSION_SOURCE = `// raigal — auto-generated pi extension. Do not edit by hand.
23269
+ // Reinstall with: raigal hook install --pi
22987
23270
  import { spawn } from "node:child_process";
22988
23271
 
22989
23272
  // Runs the CLI async so a slow scan never blocks pi's event loop, and forwards
@@ -23028,7 +23311,7 @@ export default function (pi) {
23028
23311
  const filePath = event.input && event.input.path;
23029
23312
  if (typeof filePath !== "string" || filePath.length === 0) return;
23030
23313
 
23031
- const bin = process.env.AISLOP_BIN || "aislop";
23314
+ const bin = process.env.RAIGAL_BIN || process.env.AISLOP_BIN || "raigal";
23032
23315
  const payload = JSON.stringify({
23033
23316
  cwd: ctx.cwd,
23034
23317
  file_path: filePath,
@@ -23055,7 +23338,7 @@ export default function (pi) {
23055
23338
  const installPi = (opts) => {
23056
23339
  const paths = resolvePiPaths(opts);
23057
23340
  const result = emptyResult();
23058
- applyContent(result, opts, paths.extension, PI_EXTENSION_SOURCE, "write pi aislop extension");
23341
+ applyContent(result, opts, paths.extension, PI_EXTENSION_SOURCE, "write pi raigal extension");
23059
23342
  return result;
23060
23343
  };
23061
23344
  const uninstallPi = (opts) => {
@@ -23414,8 +23697,10 @@ const runSearchPrompt = async (options) => {
23414
23697
  }
23415
23698
  if (key.name === "space" && options.mode === "multi") {
23416
23699
  const item = filtered[cursor];
23417
- if (item) if (selected.has(item.value)) selected.delete(item.value);
23418
- else selected.add(item.value);
23700
+ if (item) {
23701
+ if (selected.has(item.value)) selected.delete(item.value);
23702
+ else selected.add(item.value);
23703
+ }
23419
23704
  render();
23420
23705
  return;
23421
23706
  }
@@ -23679,10 +23964,11 @@ const hookRun = async (agent, flags) => {
23679
23964
  process.exit(0);
23680
23965
  }
23681
23966
  let exitCode = 0;
23682
- if (agent === "claude") if (flags?.onFileChanged) exitCode = await runClaudeFileChangedHook();
23683
- else if (flags?.stop) exitCode = await runClaudeStopHook();
23684
- else exitCode = await runClaudeHook();
23685
- else if (agent === "cursor") exitCode = await runCursorHook();
23967
+ if (agent === "claude") {
23968
+ if (flags?.onFileChanged) exitCode = await runClaudeFileChangedHook();
23969
+ else if (flags?.stop) exitCode = await runClaudeStopHook();
23970
+ else exitCode = await runClaudeHook();
23971
+ } else if (agent === "cursor") exitCode = await runCursorHook();
23686
23972
  else if (agent === "gemini") exitCode = await runGeminiHook();
23687
23973
  else if (agent === "pi") exitCode = await runPiHook();
23688
23974
  else {
@@ -23693,7 +23979,8 @@ const hookRun = async (agent, flags) => {
23693
23979
  process.exit(exitCode);
23694
23980
  };
23695
23981
  const hookBaseline = async () => {
23696
- const result = await captureBaseline(process.cwd());
23982
+ const cwd = process.cwd();
23983
+ const result = await captureBaseline(cwd);
23697
23984
  process.stdout.write(renderHookBaseline({
23698
23985
  score: result.score,
23699
23986
  fileCount: result.fileCount,
@@ -23924,7 +24211,7 @@ const registerHookAliases = (program) => {
23924
24211
  };
23925
24212
  //#endregion
23926
24213
  //#region src/utils/github-slug.ts
23927
- const GITHUB_REMOTE_RE = /^(?:git@github\.com:|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
24214
+ const GITHUB_REMOTE_RE = /^(?:(?:git@|ssh:\/\/(?:git@)?)github\.com(?::(?:\d+\/)?|[:/])|https:\/\/(?:[^@]+@)?github\.com\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
23928
24215
  const parseGithubSlug = (remoteUrl) => {
23929
24216
  const match = remoteUrl.trim().match(GITHUB_REMOTE_RE);
23930
24217
  if (!match) return null;
@@ -24122,6 +24409,9 @@ const RULE_LABELS = {
24122
24409
  "security/shell-injection": "Possible shell injection",
24123
24410
  "security/unsafe-deserialization": "Unsafe deserialization",
24124
24411
  "security/unsafe-c-call": "Memory-unsafe C call",
24412
+ "security/prompt-injection": "Prompt injection (untrusted system prompt)",
24413
+ "security/unsandboxed-agent-tool": "Unsandboxed agent code execution tool",
24414
+ "security/invisible-unicode-tags": "Invisible Unicode tag character (stealth injection)",
24125
24415
  "dotnet/projects-skipped": "C# projects skipped by build-backed lint",
24126
24416
  "cppcheck/chunks-skipped": "C/C++ files skipped by cppcheck",
24127
24417
  "eslint/no-undef": "Undefined identifier",
@@ -24210,6 +24500,9 @@ const RULE_DESCRIPTIONS = {
24210
24500
  "security/shell-injection": "Shell command is built from unsanitized input.",
24211
24501
  "security/unsafe-deserialization": "Legacy .NET formatter can deserialize arbitrary types (RCE).",
24212
24502
  "security/unsafe-c-call": "Unbounded C string/exec function with buffer-overflow or shell risk.",
24503
+ "security/prompt-injection": "Untrusted dynamic variable interpolation into system or developer prompt.",
24504
+ "security/unsandboxed-agent-tool": "Agent executes code or commands on host without container or interactive gate.",
24505
+ "security/invisible-unicode-tags": "Invisible Unicode Tag block (U+E0000-U+E007F) used for prompt injection or ASCII smuggling.",
24213
24506
  "dotnet/projects-skipped": "Build-backed C# lint only ran on restored projects; the rest were skipped.",
24214
24507
  "cppcheck/chunks-skipped": "A cppcheck invocation timed out or failed on one or more chunks; their files were not analyzed.",
24215
24508
  "oxlint/*": "JavaScript/TypeScript lint finding from oxlint.",
@@ -24404,7 +24697,8 @@ const BUILTIN_RULES = [
24404
24697
  "typescript/*",
24405
24698
  "dotnet/*",
24406
24699
  "cppcheck/*",
24407
- "clang-tidy/*"
24700
+ "clang-tidy/*",
24701
+ "expo-doctor/*"
24408
24702
  ]
24409
24703
  },
24410
24704
  {
@@ -24503,7 +24797,10 @@ const BUILTIN_RULES = [
24503
24797
  "security/shell-injection",
24504
24798
  "security/unsafe-deserialization",
24505
24799
  "security/unsafe-c-call",
24506
- "security/dependency-audit-skipped"
24800
+ "security/dependency-audit-skipped",
24801
+ "security/prompt-injection",
24802
+ "security/unsandboxed-agent-tool",
24803
+ "security/invisible-unicode-tags"
24507
24804
  ]
24508
24805
  }
24509
24806
  ];
@@ -24513,7 +24810,8 @@ const collectRuleEntries = (directory) => {
24513
24810
  for (const { engine, rules } of BUILTIN_RULES) for (const rule of rules) entries.push(toRuleEntry(engine, rule));
24514
24811
  const configDir = findConfigDir(resolvedDir);
24515
24812
  if (configDir) {
24516
- const archRules = loadArchitectureRules(path.join(configDir, RULES_FILE));
24813
+ const rulesPath = path.join(configDir, RULES_FILE);
24814
+ const archRules = loadArchitectureRules(rulesPath);
24517
24815
  for (const rule of archRules) entries.push({
24518
24816
  id: `arch/${rule.name}`,
24519
24817
  engine: "architecture",
@@ -24570,6 +24868,8 @@ const resolveHistoryDir = (directory) => {
24570
24868
  const resolved = path.resolve(directory);
24571
24869
  const primary = path.join(resolved, CONFIG_DIR);
24572
24870
  if (fs.existsSync(primary)) return primary;
24871
+ const legacy = path.join(resolved, ".aislop");
24872
+ if (fs.existsSync(legacy)) return legacy;
24573
24873
  return null;
24574
24874
  };
24575
24875
  const historyPath = (directory) => {
@@ -24709,7 +25009,7 @@ const trendCommand = async (directory, limit) => {
24709
25009
  //#endregion
24710
25010
  //#region src/update-notifier.ts
24711
25011
  const REGISTRY_URL = "https://registry.npmjs.org/@methiu/raigal/latest";
24712
- const CHECK_INTERVAL_MS = 1440 * 60 * 1e3;
25012
+ const CHECK_INTERVAL_MS = 864e5;
24713
25013
  const REQUEST_TIMEOUT_MS = 2e3;
24714
25014
  const CACHE_BASENAME = "update_check.json";
24715
25015
  const isUpdateNotifierDisabled = (env = process.env) => {
@@ -24782,7 +25082,7 @@ const maybeNotifyUpdate = async (now = Date.now()) => {
24782
25082
  if (!process.stderr.isTTY) return;
24783
25083
  const cachePath = resolveUpdateCachePath();
24784
25084
  const cache = readCache(cachePath);
24785
- if (cache && isOutdated("0.2.2", cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
25085
+ if (cache && isOutdated("0.2.3", cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
24786
25086
  if (!cache || now - cache.checkedAt > CHECK_INTERVAL_MS) {
24787
25087
  const latest = await fetchLatestVersion$1();
24788
25088
  if (latest) writeCache(cachePath, {
@@ -25302,7 +25602,7 @@ const renderCommandGroup = (group, commandWidth) => {
25302
25602
  return lines;
25303
25603
  };
25304
25604
  const renderCommandReference = (input = {}) => {
25305
- const version = input.version ?? "0.2.2";
25605
+ const version = input.version ?? "0.2.3";
25306
25606
  const commandWidth = Math.max(...COMMAND_REFERENCE.map((item) => item.command.length));
25307
25607
  const lines = [renderHeader({
25308
25608
  version,
@@ -25524,8 +25824,9 @@ const renderHelpDetails = () => [
25524
25824
  ""
25525
25825
  ].map((line) => highlightRaigal(line, theme)).join("\n");
25526
25826
  const renderHome = (input = {}) => {
25827
+ const version = input.version ?? "0.2.3";
25527
25828
  let out = renderHeader({
25528
- version: input.version ?? "0.2.2",
25829
+ version,
25529
25830
  command: "--bare",
25530
25831
  context: []
25531
25832
  });
@@ -25585,10 +25886,10 @@ const registerExtraCommands = (program) => {
25585
25886
  return { exitCode: 0 };
25586
25887
  });
25587
25888
  });
25588
- program.command("update").alias("upgrade").description("Check npm for the latest aislop version").action(async () => {
25889
+ program.command("update").alias("upgrade").description("Check npm for the latest raigal version").action(async () => {
25589
25890
  await updateCommand();
25590
25891
  });
25591
- program.command("version").description("Print the installed aislop version").action(() => {
25892
+ program.command("version").description("Print the installed raigal version").action(() => {
25592
25893
  process.stdout.write(`${APP_VERSION}\n`);
25593
25894
  });
25594
25895
  program.command("commands").description("List all commands and major flags").action(() => {
@@ -25776,7 +26077,8 @@ const saveCachedPolicy = (repoSlug, policy) => {
25776
26077
  };
25777
26078
  const fetchRemotePolicy = async (repoSlug, token) => {
25778
26079
  try {
25779
- const data = await requestApi(`/v1/policy?repo=${encodeURIComponent(repoSlug.toLowerCase())}`, {
26080
+ const endpoint = `/v1/policy?repo=${encodeURIComponent(repoSlug.toLowerCase())}`;
26081
+ const data = await requestApi(endpoint, {
25780
26082
  method: "GET",
25781
26083
  token,
25782
26084
  timeoutMs: 4e3
@@ -26181,7 +26483,7 @@ const printEngineStatus = (result) => {
26181
26483
  };
26182
26484
  //#endregion
26183
26485
  //#region src/utils/git.ts
26184
- const MAX_BUFFER = 50 * 1024 * 1024;
26486
+ const MAX_BUFFER = 52428800;
26185
26487
  const baseRefExists = (cwd, ref) => {
26186
26488
  const result = spawnSync("git", [
26187
26489
  "rev-parse",
@@ -26704,7 +27006,8 @@ const renderActionRows = (steps, t) => {
26704
27006
  const renderTextSteps = (steps, t, s) => {
26705
27007
  return steps.filter((step) => step.text).map((step) => {
26706
27008
  const glyph = step.emphasis === "primary" ? s.hint : s.bullet;
26707
- return ` ${style$1(t, step.emphasis === "primary" ? "accent" : "muted", glyph)} ${highlightAislop(step.text ?? "", t)}`;
27009
+ const tokenFor = step.emphasis === "primary" ? "accent" : "muted";
27010
+ return ` ${style$1(t, tokenFor, glyph)} ${highlightAislop(step.text ?? "", t)}`;
26708
27011
  });
26709
27012
  };
26710
27013
  const renderSummary = (input, deps = {}) => {
@@ -26761,10 +27064,14 @@ const renderSummary = (input, deps = {}) => {
26761
27064
  return lines.join("\n");
26762
27065
  };
26763
27066
  const renderStarCta = (deps = {}) => {
26764
- return `\n ${style$1(deps.theme ?? theme, "muted", `★ Found this useful? Consider purchasing this software at ${terminalLink("https://raigal.dev")}`)}\n`;
27067
+ const t = deps.theme ?? theme;
27068
+ const repository = terminalLink("https://raigal.dev");
27069
+ return `\n ${style$1(t, "muted", `★ Found this useful? Consider purchasing this software at ${repository}`)}\n`;
26765
27070
  };
26766
27071
  const renderTeamCta = (deps = {}) => {
26767
- return `\n ${style$1(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`;
27072
+ const t = deps.theme ?? theme;
27073
+ const href = terminalLink("https://raigal.dev/contact?intent=team-baseline");
27074
+ return `\n ${style$1(t, "muted", `→ Using raigal with a team? Get a 14-day team baseline at ${href}`)}\n`;
26768
27075
  };
26769
27076
  const renderCleanRun = (input, deps = {}) => {
26770
27077
  const t = deps.theme ?? theme;
@@ -26998,7 +27305,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
26998
27305
  architectureRulesPath: config.engines.architecture ? rulesPath : void 0,
26999
27306
  engines: config.engines
27000
27307
  };
27001
- const { results: unannotated, suppressedCount } = applySuppressions((await runEnginesWithProgress({
27308
+ const severityAdjusted = (await runEnginesWithProgress({
27002
27309
  rootDirectory: resolvedDir,
27003
27310
  languages: projectInfo.languages,
27004
27311
  frameworks: projectInfo.frameworks,
@@ -27014,7 +27321,8 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
27014
27321
  }, config.engines, machineOutput)).map((result) => ({
27015
27322
  ...result,
27016
27323
  diagnostics: config.overrides.length ? result.diagnostics : applyRuleSeverities(result.diagnostics, config.rules)
27017
- })), resolvedDir);
27324
+ }));
27325
+ const { results: unannotated, suppressedCount } = applySuppressions(severityAdjusted, resolvedDir);
27018
27326
  if (suppressedCount > 0 && !machineOutput) log.muted(`Suppressed ${suppressedCount} finding(s) via raigal-ignore directives`);
27019
27327
  const changeMap = options.changes && !options.staged ? getChangedLineMap(resolvedDir, options.base) : null;
27020
27328
  const results = changeMap ? unannotated.map((result) => ({
@@ -27075,7 +27383,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
27075
27383
  scoreFileCount
27076
27384
  });
27077
27385
  if (options.sarif) {
27078
- const { buildSarifLog } = await import("./sarif--DI_2p2r.js");
27386
+ const { buildSarifLog } = await import("./sarif-CdtsAfJ1.js");
27079
27387
  console.log(JSON.stringify(buildSarifLog(results), null, 2));
27080
27388
  return completion;
27081
27389
  }
@@ -27725,7 +28033,8 @@ const groupByFile = (diagnostics) => {
27725
28033
  });
27726
28034
  };
27727
28035
  const isInstalled = (bin) => {
27728
- return spawnSync(process.platform === "win32" ? "where" : "which", [bin], { encoding: "utf-8" }).status === 0;
28036
+ const cmd = process.platform === "win32" ? "where" : "which";
28037
+ return spawnSync(cmd, [bin], { encoding: "utf-8" }).status === 0;
27729
28038
  };
27730
28039
  const copyToClipboard = (text) => {
27731
28040
  const args = {
@@ -28201,7 +28510,8 @@ const fixNarrativeComments = async (context) => {
28201
28510
  for (let i = 0; i < lines.length; i += 1) if (!toRemove.has(i + 1)) kept.push(lines[i]);
28202
28511
  const newContent = kept.join("\n");
28203
28512
  if (newContent !== content) {
28204
- if (!areLiteralMultisetsEqual(beforeLiterals, extractStringAndTemplateLiterals(newContent, ext))) continue;
28513
+ const afterLiterals = extractStringAndTemplateLiterals(newContent, ext);
28514
+ if (!areLiteralMultisetsEqual(beforeLiterals, afterLiterals)) continue;
28205
28515
  fs.writeFileSync(filePath, newContent);
28206
28516
  }
28207
28517
  }
@@ -28571,7 +28881,7 @@ const detectUnusedDeclarations = async (context) => {
28571
28881
  rule: "code-quality/unused-declaration",
28572
28882
  severity: "warning",
28573
28883
  message: `Unused ${extracted.kind}: ${extracted.name}`,
28574
- help: "This top-level declaration is never used; aislop will remove it.",
28884
+ help: "This top-level declaration is never used; raigal will remove it.",
28575
28885
  line: d.line,
28576
28886
  column: d.column,
28577
28887
  category: "Dead Code",
@@ -28589,7 +28899,7 @@ const detectUnusedDeclarations = async (context) => {
28589
28899
  rule: "code-quality/unused-declaration",
28590
28900
  severity: "warning",
28591
28901
  message: `Unused ${kind}: ${name}`,
28592
- help: "This top-level declaration is never imported; aislop will remove it.",
28902
+ help: "This top-level declaration is never imported; raigal will remove it.",
28593
28903
  line: d.line,
28594
28904
  column: d.column,
28595
28905
  category: "Dead Code",
@@ -28695,7 +29005,7 @@ const removeUnusedDeclarations = (rootDirectory, declarations) => {
28695
29005
  };
28696
29006
  //#endregion
28697
29007
  //#region src/commands/fix-expo.ts
28698
- const INSTALL_TIMEOUT$1 = 1800 * 1e3;
29008
+ const INSTALL_TIMEOUT$1 = 18e5;
28699
29009
  const fixExpoDependencies = async (context, onProgress) => {
28700
29010
  await removeDisallowedExpoPackages(context.rootDirectory, onProgress);
28701
29011
  onProgress?.("Expo dependency alignment · running expo install --fix (can take a few minutes)");
@@ -28749,8 +29059,8 @@ const removeDisallowedExpoPackages = async (rootDir, onProgress) => {
28749
29059
  };
28750
29060
  //#endregion
28751
29061
  //#region src/commands/fix-force.ts
28752
- const INSTALL_TIMEOUT = 1800 * 1e3;
28753
- const AUDIT_TIMEOUT = 60 * 1e3;
29062
+ const INSTALL_TIMEOUT = 18e5;
29063
+ const AUDIT_TIMEOUT = 6e4;
28754
29064
  const detectPackageManager = (rootDirectory) => {
28755
29065
  if (fs.existsSync(path.join(rootDirectory, "pnpm-lock.yaml"))) return "pnpm";
28756
29066
  if (fs.existsSync(path.join(rootDirectory, "package-lock.json")) || fs.existsSync(path.join(rootDirectory, "package.json"))) return "npm";
@@ -29048,13 +29358,14 @@ const CANNOT_SCOPE_REASON = "cannot honour an explicit file list under --changes
29048
29358
  const MISSING_MANIFEST_REASON = "no manifest or lockfile in the selected scope";
29049
29359
  const fixScopeError = (resolvedDir, flags) => {
29050
29360
  if (flags.changes && flags.staged) return "--changes and --staged cannot be used together.";
29051
- return scanTargetError(resolvedDir, {
29361
+ const options = {
29052
29362
  changes: Boolean(flags.changes),
29053
29363
  staged: Boolean(flags.staged),
29054
29364
  base: flags.base,
29055
29365
  verbose: false,
29056
29366
  json: false
29057
- });
29367
+ };
29368
+ return scanTargetError(resolvedDir, options);
29058
29369
  };
29059
29370
  const collectFixFileScope = (rootDirectory, excludePatterns, includePatterns, flags) => {
29060
29371
  const mode = resolveScanScopeMode({
@@ -29113,9 +29424,10 @@ const runFormattingStep = async (deps) => {
29113
29424
  else if (deps.projectInfo.languages.includes("python")) log.warn("Python detected but ruff is not installed; skipping Python formatting fixes.");
29114
29425
  if (deps.projectInfo.languages.includes("go") && deps.projectInfo.installedTools.gofmt) await deps.runStep("Formatting (go)", () => runGofmt(deps.context), () => fixGofmt(deps.context));
29115
29426
  else if (deps.projectInfo.languages.includes("go")) log.warn("Go detected but gofmt is not installed; skipping Go formatting fixes.");
29116
- if (deps.projectInfo.languages.includes("rust") && deps.projectInfo.installedTools.rustfmt) if (isScopedFix(deps.context)) deps.skipStep?.("Formatting (rust)", CANNOT_SCOPE_REASON);
29117
- else await deps.runStep("Formatting (rust)", () => runGenericFormatter(deps.context, "rust"), () => fixGenericFormatter(deps.context, "rust"));
29118
- else if (deps.projectInfo.languages.includes("rust")) log.warn("Rust detected but rustfmt is not installed; skipping Rust formatting fixes.");
29427
+ if (deps.projectInfo.languages.includes("rust") && deps.projectInfo.installedTools.rustfmt) {
29428
+ if (isScopedFix(deps.context)) deps.skipStep?.("Formatting (rust)", CANNOT_SCOPE_REASON);
29429
+ else await deps.runStep("Formatting (rust)", () => runGenericFormatter(deps.context, "rust"), () => fixGenericFormatter(deps.context, "rust"));
29430
+ } else if (deps.projectInfo.languages.includes("rust")) log.warn("Rust detected but rustfmt is not installed; skipping Rust formatting fixes.");
29119
29431
  if (deps.projectInfo.languages.includes("ruby") && deps.projectInfo.installedTools.rubocop) {
29120
29432
  if (!skipUnsafeSafeFormatter(deps, "ruby")) await deps.runStep("Formatting (ruby)", () => runGenericFormatter(deps.context, "ruby"), () => fixGenericFormatter(deps.context, "ruby"));
29121
29433
  } else if (deps.projectInfo.languages.includes("ruby")) log.warn("Ruby detected but rubocop is not installed; skipping Ruby formatting fixes.");
@@ -29164,7 +29476,8 @@ const runDeclarationStep = async (deps) => {
29164
29476
  if (!deps.config.engines["code-quality"]) return;
29165
29477
  if (!hasJsOrTs(deps.projectInfo)) return;
29166
29478
  await deps.runStep("Unused declarations", () => detectUnusedDeclarations(deps.context), async () => {
29167
- const declarations = diagnosticsToDeclarations(await detectUnusedDeclarations(deps.context));
29479
+ const diagnostics = await detectUnusedDeclarations(deps.context);
29480
+ const declarations = diagnosticsToDeclarations(diagnostics);
29168
29481
  removeUnusedDeclarations(deps.resolvedDir, declarations);
29169
29482
  });
29170
29483
  };
@@ -29195,10 +29508,14 @@ const runForceSteps = async (deps) => {
29195
29508
  return (await runKnipUnusedFiles(deps.resolvedDir)).filter((diagnostic) => isPathInFixScope(deps.context, diagnostic.filePath));
29196
29509
  }, () => fixUnusedFiles(deps.resolvedDir, (filePath) => isPathInFixScope(deps.context, filePath)));
29197
29510
  const railUpdate = (label) => deps.rail.setActiveLabel(label);
29198
- if (deps.config.engines.security) if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Dependency audit fixes", MISSING_MANIFEST_REASON);
29199
- else await deps.runStep("Dependency audit fixes", () => runDependencyAudit(deps.context), () => fixDependencyAudit(deps.context, railUpdate));
29200
- if (deps.projectInfo.frameworks.includes("expo") && deps.config.lint.expoDoctor) if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Expo dependency alignment", MISSING_MANIFEST_REASON);
29201
- else await deps.runStep("Expo dependency alignment", () => runExpoDoctor(deps.context), () => fixExpoDependencies(deps.context, railUpdate));
29511
+ if (deps.config.engines.security) {
29512
+ if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Dependency audit fixes", MISSING_MANIFEST_REASON);
29513
+ else await deps.runStep("Dependency audit fixes", () => runDependencyAudit(deps.context), () => fixDependencyAudit(deps.context, railUpdate));
29514
+ }
29515
+ if (deps.projectInfo.frameworks.includes("expo") && deps.config.lint.expoDoctor) {
29516
+ if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Expo dependency alignment", MISSING_MANIFEST_REASON);
29517
+ else await deps.runStep("Expo dependency alignment", () => runExpoDoctor(deps.context), () => fixExpoDependencies(deps.context, railUpdate));
29518
+ }
29202
29519
  };
29203
29520
  //#endregion
29204
29521
  //#region src/commands/fix-plan.ts
@@ -29494,7 +29811,8 @@ const runFixBody = async (resolvedDir, config, options, projectInfo) => {
29494
29811
  brand: options.printBrand !== false
29495
29812
  }));
29496
29813
  const safe = Boolean(options.safe);
29497
- const scope = collectFixFileScope(resolvedDir, [...config.exclude, ...readAislopIgnorePatterns(resolvedDir)], config.include, options);
29814
+ const excludePatterns = [...config.exclude, ...readAislopIgnorePatterns(resolvedDir)];
29815
+ const scope = collectFixFileScope(resolvedDir, excludePatterns, config.include, options);
29498
29816
  const scopedProjectInfo = scope ? {
29499
29817
  ...projectInfo,
29500
29818
  languages: detectSourceLanguages([...scope.files, ...scope.testFiles])
@@ -29713,7 +30031,7 @@ const promptForConfigChoices = async () => {
29713
30031
  initialValues: DEFAULT_ENGINE_SELECTION,
29714
30032
  required: false
29715
30033
  }));
29716
- if (enginesSelection === void 0) return null;
30034
+ if (enginesSelection === void 0 || isCancel(enginesSelection)) return null;
29717
30035
  const failBelowRaw = await text({
29718
30036
  message: "CI quality gate — fail the build when the score drops below (0-100)",
29719
30037
  initialValue: "70",
@@ -29844,16 +30162,18 @@ const initCommand = async (directory, options = {}) => {
29844
30162
  status: "done",
29845
30163
  label: `Wrote ${CONFIG_DIR}/${CONFIG_FILE}`
29846
30164
  }];
29847
- if (choices.engines.includes("architecture")) if (!fs.existsSync(rulesPath)) {
29848
- fs.writeFileSync(rulesPath, DEFAULT_RULES_YAML);
29849
- steps.push({
29850
- status: "done",
29851
- label: `Wrote ${CONFIG_DIR}/${RULES_FILE}`
30165
+ if (choices.engines.includes("architecture")) {
30166
+ if (!fs.existsSync(rulesPath)) {
30167
+ fs.writeFileSync(rulesPath, DEFAULT_RULES_YAML);
30168
+ steps.push({
30169
+ status: "done",
30170
+ label: `Wrote ${CONFIG_DIR}/${RULES_FILE}`
30171
+ });
30172
+ } else steps.push({
30173
+ status: "skipped",
30174
+ label: `${CONFIG_DIR}/${RULES_FILE} already exists`
29852
30175
  });
29853
- } else steps.push({
29854
- status: "skipped",
29855
- label: `${CONFIG_DIR}/${RULES_FILE} already exists`
29856
- });
30176
+ }
29857
30177
  const workflowResult = writeGithubWorkflow(resolvedDir, choices.writeGithubWorkflow);
29858
30178
  if (workflowResult.status === "written") steps.push({
29859
30179
  status: "done",
@@ -30245,11 +30565,13 @@ program.hook("preAction", (_thisCommand, actionCommand) => {
30245
30565
  "hook"
30246
30566
  ].includes(cmdName)) {
30247
30567
  const flagNames = process.argv.slice(2).filter((arg) => arg.startsWith("-")).map((arg) => arg.replace(/^--?/, "").split("=")[0] ?? "");
30248
- startRunRecorder(cmdName, actionCommand.args?.[0] || process.cwd(), flagNames);
30568
+ const dir = actionCommand.args?.[0] || process.cwd();
30569
+ startRunRecorder(cmdName, dir, flagNames);
30249
30570
  }
30250
30571
  });
30251
30572
  program.hook("postAction", async () => {
30252
- await finishActiveRecorder(typeof process.exitCode === "number" ? process.exitCode : 0);
30573
+ const code = typeof process.exitCode === "number" ? process.exitCode : 0;
30574
+ await finishActiveRecorder(code);
30253
30575
  });
30254
30576
  const main = async () => {
30255
30577
  fireInstalledOnce();