@methiu/raigal 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as APP_VERSION } from "./version-B4eYJn3o.js";
1
+ import { t as APP_VERSION } from "./version-CukuIVX4.js";
2
2
  import { a as getEngineLabel, i as ENGINE_INFO, r as scoreImpactForRule, t as summarizeFindingAssessments } from "./finding-assessment-CGpt5YUR.js";
3
3
  import { n as isSecretClassRule, r as maskSecrets, t as FIXED_SECRET_DIAGNOSTIC_MESSAGE } from "./mask-secrets-BXn-ZSEh.js";
4
4
  import { createRequire, isBuiltin } from "node:module";
@@ -132,7 +132,7 @@ const DEFAULT_RULES_YAML = `# Architecture rules (BYO)
132
132
  //#endregion
133
133
  //#region src/config/extends.ts
134
134
  const MAX_DEPTH$1 = 5;
135
- const MAX_CONFIG_BYTES = 1048576;
135
+ const MAX_CONFIG_BYTES = 1024 * 1024;
136
136
  const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
137
137
  const deepMerge = (...sources) => {
138
138
  const result = {};
@@ -611,8 +611,7 @@ const loadConfig = (directory) => {
611
611
  const configPath = findConfigFile(directory);
612
612
  if (!configPath) return DEFAULT_CONFIG;
613
613
  try {
614
- const merged = loadConfigChain(configPath);
615
- return validateConfig(merged, configPath);
614
+ return validateConfig(loadConfigChain(configPath), configPath);
616
615
  } catch (error) {
617
616
  if (error instanceof ConfigError) throw error;
618
617
  throw new ConfigError(configPath, `Failed to parse ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
@@ -708,8 +707,7 @@ const stringWidth = (s) => {
708
707
  const bare = stripAnsi$1(s);
709
708
  let total = 0;
710
709
  for (const ch of bare) {
711
- const cp = ch.codePointAt(0) ?? 0;
712
- const w = wcwidth(cp);
710
+ const w = wcwidth(ch.codePointAt(0) ?? 0);
713
711
  total += w > 0 ? w : 1;
714
712
  }
715
713
  return total;
@@ -725,8 +723,7 @@ const truncate = (s, max, ellipsis = "…") => {
725
723
  let out = "";
726
724
  let w = 0;
727
725
  for (const ch of s) {
728
- const cp = ch.codePointAt(0) ?? 0;
729
- const cw = wcwidth(cp);
726
+ const cw = wcwidth(ch.codePointAt(0) ?? 0);
730
727
  if (w + cw > limit) break;
731
728
  out += ch;
732
729
  w += cw;
@@ -736,8 +733,7 @@ const truncate = (s, max, ellipsis = "…") => {
736
733
  //#endregion
737
734
  //#region src/ui/display.ts
738
735
  const renderDisplaySection = (label, deps = {}) => {
739
- const t = deps.theme ?? theme;
740
- return style(t, "section", label);
736
+ return style(deps.theme ?? theme, "section", label);
741
737
  };
742
738
  const renderDisplayRows = (rows, options = {}) => {
743
739
  if (rows.length === 0) return [];
@@ -884,10 +880,8 @@ const detectSourceLanguages = (sourceFiles) => {
884
880
  const detectLanguages = (directory, sourceFiles) => {
885
881
  const languages = new Set(detectSourceLanguages(sourceFiles));
886
882
  for (const [file, lang] of Object.entries(LANGUAGE_SIGNALS)) if (fs.existsSync(path.join(directory, file))) languages.add(lang);
887
- if (readPackageJson(path.join(directory, "package.json"))) {
888
- if (fs.existsSync(path.join(directory, "tsconfig.json"))) languages.add("typescript");
889
- else languages.add("javascript");
890
- }
883
+ if (readPackageJson(path.join(directory, "package.json"))) if (fs.existsSync(path.join(directory, "tsconfig.json"))) languages.add("typescript");
884
+ else languages.add("javascript");
891
885
  for (const signal of PYTHON_SIGNALS) if (fs.existsSync(path.join(directory, signal))) {
892
886
  languages.add("python");
893
887
  break;
@@ -1020,7 +1014,7 @@ const isAutoGenerated = (filePath) => {
1020
1014
  };
1021
1015
  //#endregion
1022
1016
  //#region src/utils/git-ignore.ts
1023
- const MAX_BUFFER$2 = 52428800;
1017
+ const MAX_BUFFER$2 = 50 * 1024 * 1024;
1024
1018
  const NOT_IGNORED_ARGUMENTS = [
1025
1019
  "ls-files",
1026
1020
  "--cached",
@@ -1131,7 +1125,7 @@ const dropGitIgnoredPaths = (rootDirectory, absolutePaths) => {
1131
1125
  };
1132
1126
  //#endregion
1133
1127
  //#region src/utils/project-file-list.ts
1134
- const MAX_BUFFER$1 = 52428800;
1128
+ const MAX_BUFFER$1 = 50 * 1024 * 1024;
1135
1129
  const normalizePruneDirectories = (directories) => new Set([...directories].map((directory) => directory.toLowerCase()));
1136
1130
  const isInPrunedDirectory = (filePath, pruneDirectories) => {
1137
1131
  const pathSegments = filePath.split("/");
@@ -1778,7 +1772,7 @@ const checkInstalledTools = async () => {
1778
1772
  };
1779
1773
  //#endregion
1780
1774
  //#region src/utils/project-path-safety.ts
1781
- const MAX_PROJECT_CONFIG_BYTES = 1048576;
1775
+ const MAX_PROJECT_CONFIG_BYTES = 1024 * 1024;
1782
1776
  const isWithinDirectory$2 = (rootDirectory, candidate) => {
1783
1777
  const relative = path.relative(rootDirectory, candidate);
1784
1778
  return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
@@ -2103,8 +2097,7 @@ const resolveTrustedTscPath = () => {
2103
2097
  };
2104
2098
  const isReferenceOnlyConfig = (tsconfigPath) => {
2105
2099
  try {
2106
- const raw = fs.readFileSync(tsconfigPath, "utf-8");
2107
- const parsed = parseJsonc(raw);
2100
+ const parsed = parseJsonc(fs.readFileSync(tsconfigPath, "utf-8"));
2108
2101
  if (!parsed) return false;
2109
2102
  return Array.isArray(parsed.references) && !parsed.files && !parsed.include && !parsed.extends;
2110
2103
  } catch {
@@ -2423,7 +2416,7 @@ const changeContextOrder = (context) => {
2423
2416
  };
2424
2417
  //#endregion
2425
2418
  //#region src/utils/git.ts
2426
- const MAX_BUFFER = 52428800;
2419
+ const MAX_BUFFER = 50 * 1024 * 1024;
2427
2420
  const baseRefExists = (cwd, ref) => {
2428
2421
  const result = spawnSync("git", [
2429
2422
  "rev-parse",
@@ -6806,8 +6799,7 @@ const isJsRelativeOrAbsolute = (spec) => spec.startsWith(".") || spec.startsWith
6806
6799
  const RUNTIME_BUILTINS = /* @__PURE__ */ new Set(["bun"]);
6807
6800
  const isJsBuiltin = (spec) => {
6808
6801
  if (RUNTIME_BUILTINS.has(spec)) return true;
6809
- const stripped = spec.startsWith("node:") ? spec.slice(5) : spec;
6810
- return isBuiltin(stripped) || isBuiltin(spec);
6802
+ return isBuiltin(spec.startsWith("node:") ? spec.slice(5) : spec) || isBuiltin(spec);
6811
6803
  };
6812
6804
  const VIRTUAL_MODULE_PREFIXES = [
6813
6805
  "astro:",
@@ -7019,8 +7011,7 @@ const isLocalAliasReplacement = (rawValue, configDirectory, rootDirectory) => {
7019
7011
  if (!target) return false;
7020
7012
  if (!target.startsWith(".") && !target.startsWith("/") && !target.startsWith("~/") && !target.startsWith("@/")) return false;
7021
7013
  const normalized = target.startsWith("~/") || target.startsWith("@/") ? target.slice(2) : target;
7022
- const candidate = path.isAbsolute(normalized) ? normalized : path.resolve(configDirectory, normalized);
7023
- return isRootBoundedTarget(candidate, rootDirectory);
7014
+ return isRootBoundedTarget(path.isAbsolute(normalized) ? normalized : path.resolve(configDirectory, normalized), rootDirectory);
7024
7015
  };
7025
7016
  const hasValidPackageImportTarget = (value, packageDirectory, rootDirectory) => {
7026
7017
  if (!isRootBoundedTarget(packageDirectory, rootDirectory)) return false;
@@ -8214,9 +8205,7 @@ const isRustDocCommentLine = (line) => {
8214
8205
  };
8215
8206
  const collectBlocks = (sourceLines, syntax, content, ext) => {
8216
8207
  const blocks = [];
8217
- const actualContent = content ?? sourceLines.join("\n");
8218
- const actualExt = ext ?? (syntax.linePrefixes.includes("//") ? ".ts" : ".py");
8219
- const commentLines = getCommentLines(actualContent, actualExt);
8208
+ const commentLines = getCommentLines(content ?? sourceLines.join("\n"), ext ?? (syntax.linePrefixes.includes("//") ? ".ts" : ".py"));
8220
8209
  let i = 0;
8221
8210
  while (i < sourceLines.length) {
8222
8211
  const line = sourceLines[i];
@@ -8544,8 +8533,7 @@ const detectNarrativeComments = async (context) => {
8544
8533
  } catch {
8545
8534
  continue;
8546
8535
  }
8547
- const lines = content.split("\n");
8548
- const blocks = collectBlocks(lines, syntax, content, ext);
8536
+ const blocks = collectBlocks(content.split("\n"), syntax, content, ext);
8549
8537
  for (const block of blocks) {
8550
8538
  const { matched, reason } = detectNarrativeInBlock(block, ext);
8551
8539
  if (!matched) continue;
@@ -10487,8 +10475,7 @@ const checkFunctionDiagnostics = (relativePath, fn, limits, ext) => {
10487
10475
  const results = [];
10488
10476
  const multiplier = functionLocMultiplier(fn, ext);
10489
10477
  const fnMax = functionLocBudget(fn, ext, limits.maxFunctionLoc);
10490
- const effectiveLineCount = fn.lineCount - fn.templateLines;
10491
- if (exceedsLimit(effectiveLineCount, limits.maxFunctionLoc, multiplier)) results.push({
10478
+ if (exceedsLimit(fn.lineCount - fn.templateLines, limits.maxFunctionLoc, multiplier)) results.push({
10492
10479
  filePath: relativePath,
10493
10480
  engine: "code-quality",
10494
10481
  rule: "complexity/function-too-long",
@@ -10585,7 +10572,7 @@ const isLowSignalMarkupWindow = (lines) => {
10585
10572
  return lines.filter((line) => SVG_MARKUP_RE.test(line)).length >= Math.ceil(WINDOW_SIZE / 2);
10586
10573
  };
10587
10574
  const isLowSignalDataWindow = (lines) => {
10588
- return lines.filter((line) => DATA_LITERAL_RE.test(line)).length >= 9;
10575
+ return lines.filter((line) => DATA_LITERAL_RE.test(line)).length >= WINDOW_SIZE - 1;
10589
10576
  };
10590
10577
  const findSuppressedLines = (lines) => {
10591
10578
  const suppressed = /* @__PURE__ */ new Set();
@@ -10617,7 +10604,7 @@ const collectMeaningfulLines = (content) => {
10617
10604
  if (isLowSignalDataWindow(window)) continue;
10618
10605
  if (window.every(isTrivialLine)) continue;
10619
10606
  const normalised = window.map(normaliseLine);
10620
- if (normalised.filter((n) => n.length > 0 && n !== "}" && n !== "{").length < 9) continue;
10607
+ if (normalised.filter((n) => n.length > 0 && n !== "}" && n !== "{").length < WINDOW_SIZE - 1) continue;
10621
10608
  if (new Set(normalised).size < MIN_DISTINCT_LINES) continue;
10622
10609
  hits.push({
10623
10610
  startLine: i + 1,
@@ -11392,7 +11379,7 @@ const fixClangFormat = async (context) => {
11392
11379
  };
11393
11380
  //#endregion
11394
11381
  //#region src/engines/format/dotnet-format.ts
11395
- const REPORT_FILENAME = ".aislop-dotnet-format.json";
11382
+ const REPORT_FILENAME = ".raigal-dotnet-format.json";
11396
11383
  const parseDotnetFormatReport = (json, rootDirectory) => {
11397
11384
  if (!json) return [];
11398
11385
  let report;
@@ -11763,8 +11750,7 @@ const parseRuffFormatOutput = (output, rootDir) => {
11763
11750
  const fixRuffFormat = async (context) => {
11764
11751
  const targets = context.files ? getPythonTargets(context) : [context.rootDirectory];
11765
11752
  if (context.files && targets.length === 0) return;
11766
- const ruffBinary = resolveToolBinary("ruff");
11767
- const result = await runSubprocess(ruffBinary, ["format", ...targets], {
11753
+ const result = await runSubprocess(resolveToolBinary("ruff"), ["format", ...targets], {
11768
11754
  cwd: context.rootDirectory,
11769
11755
  timeout: 6e4
11770
11756
  });
@@ -11834,8 +11820,7 @@ const parseClangTidyOutput = (output, rootDirectory) => {
11834
11820
  const runClangTidy = async (context) => {
11835
11821
  const compileCommandsDir = findCompileCommandsDir(context);
11836
11822
  if (!compileCommandsDir) return [];
11837
- const implSources = findCppSources(context).filter((f) => CPP_IMPL_EXTENSIONS.has(path.extname(f).toLowerCase()));
11838
- const sources = filterSourcesInDatabase(implSources, readCompileCommandsFiles(compileCommandsDir));
11823
+ const sources = filterSourcesInDatabase(findCppSources(context).filter((f) => CPP_IMPL_EXTENSIONS.has(path.extname(f).toLowerCase())), readCompileCommandsFiles(compileCommandsDir));
11839
11824
  if (sources.length === 0) return [];
11840
11825
  const diagnostics = [];
11841
11826
  for (const chunk of chunkFilePaths(sources, CLANG_TIDY_CHUNK_MAX_FILES)) try {
@@ -12671,8 +12656,7 @@ const processFile = (filePath, fileTargets, result) => {
12671
12656
  });
12672
12657
  continue;
12673
12658
  }
12674
- const shape = classifyIdentifier(best);
12675
- const { edit, skipReason } = computeEdit(sourceFile, shape);
12659
+ const { edit, skipReason } = computeEdit(sourceFile, classifyIdentifier(best));
12676
12660
  if (!edit) {
12677
12661
  pendingSkips.push({
12678
12662
  target,
@@ -13487,8 +13471,7 @@ const ruffLintFixArgs = (context, unsafe) => {
13487
13471
  };
13488
13472
  const fixRuffLint = async (context) => {
13489
13473
  if (context.files && getPythonTargets(context).length === 0) return;
13490
- const ruffBinary = resolveToolBinary("ruff");
13491
- const result = await runSubprocess(ruffBinary, ruffLintFixArgs(context, false), {
13474
+ const result = await runSubprocess(resolveToolBinary("ruff"), ruffLintFixArgs(context, false), {
13492
13475
  cwd: context.rootDirectory,
13493
13476
  timeout: 6e4
13494
13477
  });
@@ -13496,8 +13479,7 @@ const fixRuffLint = async (context) => {
13496
13479
  };
13497
13480
  const fixRuffLintForce = async (context) => {
13498
13481
  if (context.files && getPythonTargets(context).length === 0) return;
13499
- const ruffBinary = resolveToolBinary("ruff");
13500
- const result = await runSubprocess(ruffBinary, ruffLintFixArgs(context, true), {
13482
+ const result = await runSubprocess(resolveToolBinary("ruff"), ruffLintFixArgs(context, true), {
13501
13483
  cwd: context.rootDirectory,
13502
13484
  timeout: 6e4
13503
13485
  });
@@ -14307,11 +14289,10 @@ const auditSkippedDiagnostic = (source, help) => ({
14307
14289
  });
14308
14290
  const runNpmAudit = async (rootDir, timeout) => {
14309
14291
  try {
14310
- const result = await runSubprocess("npm", ["audit", "--json"], {
14292
+ return parseJsAudit((await runSubprocess("npm", ["audit", "--json"], {
14311
14293
  cwd: rootDir,
14312
14294
  timeout
14313
- });
14314
- return parseJsAudit(result.stdout, "npm audit", readRuntimeDependencies(rootDir));
14295
+ })).stdout, "npm audit", readRuntimeDependencies(rootDir));
14315
14296
  } catch (error) {
14316
14297
  return [auditSkippedDiagnostic("npm audit", `Failed to run npm audit: ${errorMessageOf(error)}`)];
14317
14298
  }
@@ -14332,11 +14313,10 @@ const runBunAudit = async (rootDir, timeout) => {
14332
14313
  const runPnpmAuditWithFallback = async (rootDir, timeout) => {
14333
14314
  const canFallbackToNpm = fs.existsSync(path.join(rootDir, "package-lock.json"));
14334
14315
  try {
14335
- const result = await runSubprocess("pnpm", ["audit", "--json"], {
14316
+ const diagnostics = parseJsAudit((await runSubprocess("pnpm", ["audit", "--json"], {
14336
14317
  cwd: rootDir,
14337
14318
  timeout
14338
- });
14339
- const diagnostics = parseJsAudit(result.stdout, "pnpm audit", readRuntimeDependencies(rootDir));
14319
+ })).stdout, "pnpm audit", readRuntimeDependencies(rootDir));
14340
14320
  if (diagnostics.some((d) => d.rule === "security/dependency-audit-skipped")) {
14341
14321
  if (canFallbackToNpm) return runNpmAudit(rootDir, timeout);
14342
14322
  return diagnostics;
@@ -15070,10 +15050,7 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
15070
15050
  failed: true,
15071
15051
  skipReason: r.reason instanceof Error ? r.reason.message : String(r.reason)
15072
15052
  });
15073
- const stripped = stripSecretDiagnostics(finalResults);
15074
- const csharp = dedupeCSharpAsync(stripped);
15075
- const comments = dedupeOverlappingComments(csharp);
15076
- return dedupeEquivalenceDiagnostics(comments);
15053
+ return dedupeEquivalenceDiagnostics(dedupeOverlappingComments(dedupeCSharpAsync(stripSecretDiagnostics(finalResults))));
15077
15054
  };
15078
15055
  const stripSecretDiagnostics = (results) => {
15079
15056
  for (const result of results) for (const d of result.diagnostics) if (d.redactSource || isSecretClassRule(d.rule)) {
@@ -15122,8 +15099,7 @@ const calculateScore = (diagnostics, weights, thresholds, sourceFileCount, smoot
15122
15099
  const capScale = Math.max(1, fileCount.effectiveSourceFileCount / CAP_REFERENCE_FILES);
15123
15100
  const defaultRuleCap = typeof maxPerRule === "number" && maxPerRule > 0 ? maxPerRule : null;
15124
15101
  const capForRule = (key) => {
15125
- const rule = key.slice(key.indexOf(":") + 1);
15126
- const ruleCap = scoreImpactForRule(rule).cap;
15102
+ const ruleCap = scoreImpactForRule(key.slice(key.indexOf(":") + 1)).cap;
15127
15103
  if (typeof ruleCap === "number") return defaultRuleCap ? Math.min(defaultRuleCap, ruleCap) : ruleCap;
15128
15104
  return defaultRuleCap;
15129
15105
  };
@@ -15407,11 +15383,10 @@ const track = (input) => {
15407
15383
  cachedInstallId = ensured.installId;
15408
15384
  installCreated = ensured.created;
15409
15385
  }
15410
- const merged = {
15386
+ const { clean, dropped } = redactProperties({
15411
15387
  ...baseProperties(cachedInstallId),
15412
15388
  ...input.properties
15413
- };
15414
- const { clean, dropped } = redactProperties(merged);
15389
+ });
15415
15390
  if (isDebug()) {
15416
15391
  const compact = JSON.stringify({
15417
15392
  event: input.event,
@@ -15691,17 +15666,14 @@ const renderRailStep = (step, deps = {}) => {
15691
15666
  * between the last step and the footer.
15692
15667
  */
15693
15668
  const renderRailConnector = (deps = {}) => {
15694
- const t = deps.theme ?? theme;
15695
- const s = deps.symbols ?? symbols;
15696
- return ` ${style(t, "accentDim", s.rail)}\n`;
15669
+ return ` ${style(deps.theme ?? theme, "accentDim", (deps.symbols ?? symbols).rail)}\n`;
15697
15670
  };
15698
15671
  /**
15699
15672
  * Render the rail-end footer line ("└ <footer text>").
15700
15673
  */
15701
15674
  const renderRailFooter = (footer, deps = {}) => {
15702
15675
  const t = deps.theme ?? theme;
15703
- const s = deps.symbols ?? symbols;
15704
- return ` ${style(t, "accentDim", s.railEnd)} ${highlightAislop(footer, t)}\n`;
15676
+ return ` ${style(t, "accentDim", (deps.symbols ?? symbols).railEnd)} ${highlightAislop(footer, t)}\n`;
15705
15677
  };
15706
15678
  const renderRail = (input, deps = {}) => {
15707
15679
  const t = deps.theme ?? theme;
@@ -15828,8 +15800,7 @@ var LiveRail = class {
15828
15800
  */
15829
15801
  const renderHintLine = (hint, deps = {}) => {
15830
15802
  const t = deps.theme ?? theme;
15831
- const s = deps.symbols ?? symbols;
15832
- return ` ${style(t, "accent", s.hint)} ${highlightAislop(maskSecrets(hint), t)}\n`;
15803
+ return ` ${style(t, "accent", (deps.symbols ?? symbols).hint)} ${highlightAislop(maskSecrets(hint), t)}\n`;
15833
15804
  };
15834
15805
  const createLogger = (deps = {}) => {
15835
15806
  const t = deps.theme ?? theme;
@@ -15974,8 +15945,7 @@ const groupByFile = (diagnostics) => {
15974
15945
  });
15975
15946
  };
15976
15947
  const isInstalled = (bin) => {
15977
- const cmd = process.platform === "win32" ? "where" : "which";
15978
- return spawnSync(cmd, [bin], { encoding: "utf-8" }).status === 0;
15948
+ return spawnSync(process.platform === "win32" ? "where" : "which", [bin], { encoding: "utf-8" }).status === 0;
15979
15949
  };
15980
15950
  const copyToClipboard = (text) => {
15981
15951
  const args = {
@@ -16458,9 +16428,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
16458
16428
  if (!header.kid) throw new JwtVerificationError("JWT header missing 'kid'", "unknown_kid");
16459
16429
  const publicKey = getPublicKey(header.kid);
16460
16430
  if (!publicKey) throw new JwtVerificationError(`Unknown or untrusted signing key id (kid: ${header.kid})`, "unknown_kid");
16461
- const signedData = Buffer.from(`${headerB64}.${payloadB64}`, "utf-8");
16462
- const signature = Buffer.from(signatureB64, "base64url");
16463
- if (!verify(null, signedData, publicKey, signature)) throw new JwtVerificationError("Invalid JWT cryptographic signature", "invalid_signature");
16431
+ if (!verify(null, Buffer.from(`${headerB64}.${payloadB64}`, "utf-8"), publicKey, Buffer.from(signatureB64, "base64url"))) throw new JwtVerificationError("Invalid JWT cryptographic signature", "invalid_signature");
16464
16432
  let rawPayload;
16465
16433
  try {
16466
16434
  rawPayload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf-8"));
@@ -16472,7 +16440,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
16472
16440
  const claims = parsed.data;
16473
16441
  const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
16474
16442
  if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
16475
- if (!isVersionAtLeast("0.2.3", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
16443
+ if (!isVersionAtLeast("0.2.4", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
16476
16444
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
16477
16445
  const grace = options.allowGraceSeconds ?? 0;
16478
16446
  if (now > claims.exp + grace) {
@@ -16506,7 +16474,7 @@ const fetchGitHubOidcToken = async () => {
16506
16474
  };
16507
16475
  //#endregion
16508
16476
  //#region src/cloud/entitlement.ts
16509
- const GRACE_PERIOD_SECONDS = 259200;
16477
+ const GRACE_PERIOD_SECONDS = 4320 * 60;
16510
16478
  var MissingCredentialError = class extends Error {
16511
16479
  constructor(message = "No organization credential found") {
16512
16480
  super(message);
@@ -16631,8 +16599,7 @@ const getEntitlement = async () => {
16631
16599
  }
16632
16600
  } else oidcToken = await fetchGitHubOidcToken();
16633
16601
  try {
16634
- const tokenToPass = activeToken?.token ?? "";
16635
- const response = await fetchEntitlementFromApi(tokenToPass, oidcToken);
16602
+ const response = await fetchEntitlementFromApi(activeToken?.token ?? "", oidcToken);
16636
16603
  const claims = verifyEntitlementJwt(response.entitlement, { nowSeconds: now });
16637
16604
  saveCachedEntitlement(response.entitlement, response.refresh_after);
16638
16605
  return {
@@ -17175,8 +17142,7 @@ const fixNarrativeComments = async (context) => {
17175
17142
  for (let i = 0; i < lines.length; i += 1) if (!toRemove.has(i + 1)) kept.push(lines[i]);
17176
17143
  const newContent = kept.join("\n");
17177
17144
  if (newContent !== content) {
17178
- const afterLiterals = extractStringAndTemplateLiterals(newContent, ext);
17179
- if (!areLiteralMultisetsEqual(beforeLiterals, afterLiterals)) continue;
17145
+ if (!areLiteralMultisetsEqual(beforeLiterals, extractStringAndTemplateLiterals(newContent, ext))) continue;
17180
17146
  fs.writeFileSync(filePath, newContent);
17181
17147
  }
17182
17148
  }
@@ -17670,7 +17636,7 @@ const removeUnusedDeclarations = (rootDirectory, declarations) => {
17670
17636
  };
17671
17637
  //#endregion
17672
17638
  //#region src/commands/fix-expo.ts
17673
- const INSTALL_TIMEOUT$1 = 18e5;
17639
+ const INSTALL_TIMEOUT$1 = 1800 * 1e3;
17674
17640
  const fixExpoDependencies = async (context, onProgress) => {
17675
17641
  await removeDisallowedExpoPackages(context.rootDirectory, onProgress);
17676
17642
  onProgress?.("Expo dependency alignment · running expo install --fix (can take a few minutes)");
@@ -17724,8 +17690,8 @@ const removeDisallowedExpoPackages = async (rootDir, onProgress) => {
17724
17690
  };
17725
17691
  //#endregion
17726
17692
  //#region src/commands/fix-force.ts
17727
- const INSTALL_TIMEOUT = 18e5;
17728
- const AUDIT_TIMEOUT = 6e4;
17693
+ const INSTALL_TIMEOUT = 1800 * 1e3;
17694
+ const AUDIT_TIMEOUT = 60 * 1e3;
17729
17695
  const detectPackageManager = (rootDirectory) => {
17730
17696
  if (fs.existsSync(path.join(rootDirectory, "pnpm-lock.yaml"))) return "pnpm";
17731
17697
  if (fs.existsSync(path.join(rootDirectory, "package-lock.json")) || fs.existsSync(path.join(rootDirectory, "package.json"))) return "npm";
@@ -18044,14 +18010,13 @@ const CANNOT_SCOPE_REASON = "cannot honour an explicit file list under --changes
18044
18010
  const MISSING_MANIFEST_REASON = "no manifest or lockfile in the selected scope";
18045
18011
  const fixScopeError = (resolvedDir, flags) => {
18046
18012
  if (flags.changes && flags.staged) return "--changes and --staged cannot be used together.";
18047
- const options = {
18013
+ return scanTargetError(resolvedDir, {
18048
18014
  changes: Boolean(flags.changes),
18049
18015
  staged: Boolean(flags.staged),
18050
18016
  base: flags.base,
18051
18017
  verbose: false,
18052
18018
  json: false
18053
- };
18054
- return scanTargetError(resolvedDir, options);
18019
+ });
18055
18020
  };
18056
18021
  const collectFixFileScope = (rootDirectory, excludePatterns, includePatterns, flags) => {
18057
18022
  const mode = resolveScanScopeMode({
@@ -18110,10 +18075,9 @@ const runFormattingStep = async (deps) => {
18110
18075
  else if (deps.projectInfo.languages.includes("python")) log.warn("Python detected but ruff is not installed; skipping Python formatting fixes.");
18111
18076
  if (deps.projectInfo.languages.includes("go") && deps.projectInfo.installedTools.gofmt) await deps.runStep("Formatting (go)", () => runGofmt(deps.context), () => fixGofmt(deps.context));
18112
18077
  else if (deps.projectInfo.languages.includes("go")) log.warn("Go detected but gofmt is not installed; skipping Go formatting fixes.");
18113
- if (deps.projectInfo.languages.includes("rust") && deps.projectInfo.installedTools.rustfmt) {
18114
- if (isScopedFix(deps.context)) deps.skipStep?.("Formatting (rust)", CANNOT_SCOPE_REASON);
18115
- else await deps.runStep("Formatting (rust)", () => runGenericFormatter(deps.context, "rust"), () => fixGenericFormatter(deps.context, "rust"));
18116
- } else if (deps.projectInfo.languages.includes("rust")) log.warn("Rust detected but rustfmt is not installed; skipping Rust formatting fixes.");
18078
+ if (deps.projectInfo.languages.includes("rust") && deps.projectInfo.installedTools.rustfmt) if (isScopedFix(deps.context)) deps.skipStep?.("Formatting (rust)", CANNOT_SCOPE_REASON);
18079
+ else await deps.runStep("Formatting (rust)", () => runGenericFormatter(deps.context, "rust"), () => fixGenericFormatter(deps.context, "rust"));
18080
+ else if (deps.projectInfo.languages.includes("rust")) log.warn("Rust detected but rustfmt is not installed; skipping Rust formatting fixes.");
18117
18081
  if (deps.projectInfo.languages.includes("ruby") && deps.projectInfo.installedTools.rubocop) {
18118
18082
  if (!skipUnsafeSafeFormatter(deps, "ruby")) await deps.runStep("Formatting (ruby)", () => runGenericFormatter(deps.context, "ruby"), () => fixGenericFormatter(deps.context, "ruby"));
18119
18083
  } else if (deps.projectInfo.languages.includes("ruby")) log.warn("Ruby detected but rubocop is not installed; skipping Ruby formatting fixes.");
@@ -18162,8 +18126,7 @@ const runDeclarationStep = async (deps) => {
18162
18126
  if (!deps.config.engines["code-quality"]) return;
18163
18127
  if (!hasJsOrTs(deps.projectInfo)) return;
18164
18128
  await deps.runStep("Unused declarations", () => detectUnusedDeclarations(deps.context), async () => {
18165
- const diagnostics = await detectUnusedDeclarations(deps.context);
18166
- const declarations = diagnosticsToDeclarations(diagnostics);
18129
+ const declarations = diagnosticsToDeclarations(await detectUnusedDeclarations(deps.context));
18167
18130
  removeUnusedDeclarations(deps.resolvedDir, declarations);
18168
18131
  });
18169
18132
  };
@@ -18194,14 +18157,10 @@ const runForceSteps = async (deps) => {
18194
18157
  return (await runKnipUnusedFiles(deps.resolvedDir)).filter((diagnostic) => isPathInFixScope(deps.context, diagnostic.filePath));
18195
18158
  }, () => fixUnusedFiles(deps.resolvedDir, (filePath) => isPathInFixScope(deps.context, filePath)));
18196
18159
  const railUpdate = (label) => deps.rail.setActiveLabel(label);
18197
- if (deps.config.engines.security) {
18198
- if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Dependency audit fixes", MISSING_MANIFEST_REASON);
18199
- else await deps.runStep("Dependency audit fixes", () => runDependencyAudit(deps.context), () => fixDependencyAudit(deps.context, railUpdate));
18200
- }
18201
- if (deps.projectInfo.frameworks.includes("expo") && deps.config.lint.expoDoctor) {
18202
- if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Expo dependency alignment", MISSING_MANIFEST_REASON);
18203
- else await deps.runStep("Expo dependency alignment", () => runExpoDoctor(deps.context), () => fixExpoDependencies(deps.context, railUpdate));
18204
- }
18160
+ if (deps.config.engines.security) if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Dependency audit fixes", MISSING_MANIFEST_REASON);
18161
+ else await deps.runStep("Dependency audit fixes", () => runDependencyAudit(deps.context), () => fixDependencyAudit(deps.context, railUpdate));
18162
+ if (deps.projectInfo.frameworks.includes("expo") && deps.config.lint.expoDoctor) if (!scopeIncludesManifestWrites(deps.context)) deps.skipStep?.("Expo dependency alignment", MISSING_MANIFEST_REASON);
18163
+ else await deps.runStep("Expo dependency alignment", () => runExpoDoctor(deps.context), () => fixExpoDependencies(deps.context, railUpdate));
18205
18164
  };
18206
18165
  //#endregion
18207
18166
  //#region src/commands/fix-plan.ts
@@ -18523,8 +18482,7 @@ const saveCachedPolicy = (repoSlug, policy) => {
18523
18482
  };
18524
18483
  const fetchRemotePolicy = async (repoSlug, token) => {
18525
18484
  try {
18526
- const endpoint = `/v1/policy?repo=${encodeURIComponent(repoSlug.toLowerCase())}`;
18527
- const data = await requestApi(endpoint, {
18485
+ const data = await requestApi(`/v1/policy?repo=${encodeURIComponent(repoSlug.toLowerCase())}`, {
18528
18486
  method: "GET",
18529
18487
  token,
18530
18488
  timeoutMs: 4e3
@@ -18714,8 +18672,8 @@ A PostToolUse hook runs \`raigal hook claude\` after every Edit, Write, or Multi
18714
18672
 
18715
18673
  ## Rules
18716
18674
 
18717
- - \`.raigal/config.yaml\` (or \`.aislop/config.yaml\`) - thresholds and engine toggles. Treat as authoritative; don't edit without user consent.
18718
- - \`.raigal/rules.yaml\` (or \`.aislop/rules.yaml\`) - project-specific architecture rules (may be absent). When a finding cites \`architecture/*\`, open this file and follow it.
18675
+ - \`.raigal/config.yaml\` - thresholds and engine toggles. Treat as authoritative; don't edit without user consent.
18676
+ - \`.raigal/rules.yaml\` - project-specific architecture rules (may be absent). When a finding cites \`architecture/*\`, open this file and follow it.
18719
18677
  - Custom rules can change between sessions. Trust what the scan returns, not a cached understanding of what the rules are.
18720
18678
 
18721
18679
  ## Principles
@@ -18730,7 +18688,7 @@ const atomicWrite = (targetPath, content) => {
18730
18688
  const dir = path.dirname(targetPath);
18731
18689
  fs.mkdirSync(dir, { recursive: true });
18732
18690
  const rand = Math.random().toString(36).slice(2, 10);
18733
- const tmp = path.join(dir, `.aislop-tmp-${process.pid}-${rand}`);
18691
+ const tmp = path.join(dir, `.raigal-tmp-${process.pid}-${rand}`);
18734
18692
  fs.writeFileSync(tmp, content, "utf-8");
18735
18693
  try {
18736
18694
  fs.renameSync(tmp, targetPath);
@@ -18824,9 +18782,7 @@ const applyRemoval = (result, opts, target, nextContent) => {
18824
18782
  //#region src/hooks/install/rules-only.ts
18825
18783
  const installRulesOnly = (opts, paths, summary) => {
18826
18784
  const result = emptyResult();
18827
- const existing = readIfExists(paths.rules);
18828
- const hash = sentinelHash(AISLOP_MD_BODY);
18829
- const next = upsertMarkdownFence(existing, AISLOP_MD_BODY, hash).nextContent;
18785
+ const next = upsertMarkdownFence(readIfExists(paths.rules), AISLOP_MD_BODY, sentinelHash(AISLOP_MD_BODY)).nextContent;
18830
18786
  applyContent(result, opts, paths.rules, next, summary);
18831
18787
  if (paths.host && paths.marker) {
18832
18788
  const host = readIfExists(paths.host) ?? "";
@@ -19002,8 +18958,7 @@ const installClaude = (opts) => {
19002
18958
  const nextSettings = renderSettings$1(readIfExists(paths.settings), Boolean(opts.qualityGate));
19003
18959
  applyContent(result, opts, paths.settings, nextSettings, "register PostToolUse + FileChanged hooks");
19004
18960
  const mdHash = sentinelHash(AISLOP_MD_BODY);
19005
- const existingMd = readIfExists(paths.aislopMd);
19006
- const fenced = upsertMarkdownFence(existingMd, AISLOP_MD_BODY, mdHash);
18961
+ const fenced = upsertMarkdownFence(readIfExists(paths.aislopMd), AISLOP_MD_BODY, mdHash);
19007
18962
  applyContent(result, opts, paths.aislopMd, fenced.nextContent, "write AISLOP.md rules");
19008
18963
  const existingClaudeMd = readIfExists(paths.claudeMd) ?? "";
19009
18964
  const marker = "@AISLOP.md";
@@ -19149,9 +19104,7 @@ const installCursor = (opts) => {
19149
19104
  const nextHooks = renderHooksJson(readIfExists(paths.hooks));
19150
19105
  applyContent(result, opts, paths.hooks, nextHooks, "register afterFileEdit hook");
19151
19106
  if (opts.scope === "project") {
19152
- const existingRules = readIfExists(paths.rules);
19153
- const hash = sentinelHash(AISLOP_MD_BODY);
19154
- const rules = upsertMarkdownFence(existingRules, AISLOP_MD_BODY, hash).nextContent;
19107
+ const rules = upsertMarkdownFence(readIfExists(paths.rules), AISLOP_MD_BODY, sentinelHash(AISLOP_MD_BODY)).nextContent;
19155
19108
  applyContent(result, opts, paths.rules, rules, "write .cursor/rules/aislop.mdc");
19156
19109
  }
19157
19110
  return result;
@@ -19224,9 +19177,7 @@ const installGemini = (opts) => {
19224
19177
  const result = emptyResult();
19225
19178
  const next = renderSettings(readIfExists(paths.settings));
19226
19179
  applyContent(result, opts, paths.settings, next, "register AfterTool hook");
19227
- const existingMd = readIfExists(paths.aislopMd);
19228
- const hash = sentinelHash(AISLOP_MD_BODY);
19229
- const fenced = upsertMarkdownFence(existingMd, AISLOP_MD_BODY, hash).nextContent;
19180
+ const fenced = upsertMarkdownFence(readIfExists(paths.aislopMd), AISLOP_MD_BODY, sentinelHash(AISLOP_MD_BODY)).nextContent;
19230
19181
  applyContent(result, opts, paths.aislopMd, fenced, "write AISLOP.md rules");
19231
19182
  const existingGeminiMd = readIfExists(paths.geminiMd) ?? "";
19232
19183
  const marker = "@AISLOP.md";
@@ -20172,8 +20123,7 @@ const renderActionRows = (steps, t) => {
20172
20123
  const renderTextSteps = (steps, t, s) => {
20173
20124
  return steps.filter((step) => step.text).map((step) => {
20174
20125
  const glyph = step.emphasis === "primary" ? s.hint : s.bullet;
20175
- const tokenFor = step.emphasis === "primary" ? "accent" : "muted";
20176
- return ` ${style(t, tokenFor, glyph)} ${highlightAislop(step.text ?? "", t)}`;
20126
+ return ` ${style(t, step.emphasis === "primary" ? "accent" : "muted", glyph)} ${highlightAislop(step.text ?? "", t)}`;
20177
20127
  });
20178
20128
  };
20179
20129
  const renderSummary = (input, deps = {}) => {
@@ -20230,14 +20180,10 @@ const renderSummary = (input, deps = {}) => {
20230
20180
  return lines.join("\n");
20231
20181
  };
20232
20182
  const renderStarCta = (deps = {}) => {
20233
- const t = deps.theme ?? theme;
20234
- const repository = terminalLink("https://raigal.dev");
20235
- return `\n ${style(t, "muted", `★ Found this useful? Consider purchasing this software at ${repository}`)}\n`;
20183
+ return `\n ${style(deps.theme ?? theme, "muted", `★ Found this useful? Consider purchasing this software at ${terminalLink("https://raigal.dev")}`)}\n`;
20236
20184
  };
20237
20185
  const renderTeamCta = (deps = {}) => {
20238
- const t = deps.theme ?? theme;
20239
- const href = terminalLink("https://raigal.dev/contact?intent=team-baseline");
20240
- return `\n ${style(t, "muted", `→ Using raigal with a team? Get a 14-day team baseline at ${href}`)}\n`;
20186
+ return `\n ${style(deps.theme ?? theme, "muted", `→ Using raigal with a team? Get a 14-day team baseline at ${terminalLink("https://raigal.dev/contact?intent=team-baseline")}`)}\n`;
20241
20187
  };
20242
20188
  const renderCleanRun = (input, deps = {}) => {
20243
20189
  const t = deps.theme ?? theme;
@@ -20463,7 +20409,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
20463
20409
  architectureRulesPath: config.engines.architecture ? rulesPath : void 0,
20464
20410
  engines: config.engines
20465
20411
  };
20466
- const severityAdjusted = (await runEnginesWithProgress({
20412
+ const { results: unannotated, suppressedCount } = applySuppressions((await runEnginesWithProgress({
20467
20413
  rootDirectory: resolvedDir,
20468
20414
  languages: projectInfo.languages,
20469
20415
  frameworks: projectInfo.frameworks,
@@ -20479,8 +20425,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
20479
20425
  }, config.engines, machineOutput)).map((result) => ({
20480
20426
  ...result,
20481
20427
  diagnostics: config.overrides.length ? result.diagnostics : applyRuleSeverities(result.diagnostics, config.rules)
20482
- }));
20483
- const { results: unannotated, suppressedCount } = applySuppressions(severityAdjusted, resolvedDir);
20428
+ })), resolvedDir);
20484
20429
  if (suppressedCount > 0 && !machineOutput) log.muted(`Suppressed ${suppressedCount} finding(s) via raigal-ignore directives`);
20485
20430
  const changeMap = options.changes && !options.staged ? getChangedLineMap(resolvedDir, options.base) : null;
20486
20431
  const results = changeMap ? unannotated.map((result) => ({
@@ -20541,12 +20486,12 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
20541
20486
  scoreFileCount
20542
20487
  });
20543
20488
  if (options.sarif) {
20544
- const { buildSarifLog } = await import("./sarif-CpuYM3aK.js");
20489
+ const { buildSarifLog } = await import("./sarif-Lt31Ld5t.js");
20545
20490
  console.log(JSON.stringify(buildSarifLog(results), null, 2));
20546
20491
  return completion;
20547
20492
  }
20548
20493
  if (options.json) {
20549
- const { buildJsonOutput } = await import("./json-BflWt4dX.js");
20494
+ const { buildJsonOutput } = await import("./json-BBZKvm0E.js");
20550
20495
  const jsonOut = buildJsonOutput(results, scoreResult, scoreFileCount, elapsedMs, scanCoverage);
20551
20496
  console.log(JSON.stringify(jsonOut, null, 2));
20552
20497
  return completion;
@@ -20700,8 +20645,7 @@ const runFixBody = async (resolvedDir, config, options, projectInfo) => {
20700
20645
  brand: options.printBrand !== false
20701
20646
  }));
20702
20647
  const safe = Boolean(options.safe);
20703
- const excludePatterns = [...config.exclude, ...readAislopIgnorePatterns(resolvedDir)];
20704
- const scope = collectFixFileScope(resolvedDir, excludePatterns, config.include, options);
20648
+ const scope = collectFixFileScope(resolvedDir, [...config.exclude, ...readAislopIgnorePatterns(resolvedDir)], config.include, options);
20705
20649
  const scopedProjectInfo = scope ? {
20706
20650
  ...projectInfo,
20707
20651
  languages: detectSourceLanguages([...scope.files, ...scope.testFiles])
@@ -21058,18 +21002,16 @@ const initCommand = async (directory, options = {}) => {
21058
21002
  status: "done",
21059
21003
  label: `Wrote ${CONFIG_DIR}/${CONFIG_FILE}`
21060
21004
  }];
21061
- if (choices.engines.includes("architecture")) {
21062
- if (!fs.existsSync(rulesPath)) {
21063
- fs.writeFileSync(rulesPath, DEFAULT_RULES_YAML);
21064
- steps.push({
21065
- status: "done",
21066
- label: `Wrote ${CONFIG_DIR}/${RULES_FILE}`
21067
- });
21068
- } else steps.push({
21069
- status: "skipped",
21070
- label: `${CONFIG_DIR}/${RULES_FILE} already exists`
21005
+ if (choices.engines.includes("architecture")) if (!fs.existsSync(rulesPath)) {
21006
+ fs.writeFileSync(rulesPath, DEFAULT_RULES_YAML);
21007
+ steps.push({
21008
+ status: "done",
21009
+ label: `Wrote ${CONFIG_DIR}/${RULES_FILE}`
21071
21010
  });
21072
- }
21011
+ } else steps.push({
21012
+ status: "skipped",
21013
+ label: `${CONFIG_DIR}/${RULES_FILE} already exists`
21014
+ });
21073
21015
  const workflowResult = writeGithubWorkflow(resolvedDir, choices.writeGithubWorkflow);
21074
21016
  if (workflowResult.status === "written") steps.push({
21075
21017
  status: "done",
@@ -21263,10 +21205,8 @@ const runSearchPrompt = async (options) => {
21263
21205
  }
21264
21206
  if (key.name === "space" && options.mode === "multi") {
21265
21207
  const item = filtered[cursor];
21266
- if (item) {
21267
- if (selected.has(item.value)) selected.delete(item.value);
21268
- else selected.add(item.value);
21269
- }
21208
+ if (item) if (selected.has(item.value)) selected.delete(item.value);
21209
+ else selected.add(item.value);
21270
21210
  render();
21271
21211
  return;
21272
21212
  }
@@ -21575,8 +21515,7 @@ const collectRuleEntries = (directory) => {
21575
21515
  for (const { engine, rules } of BUILTIN_RULES) for (const rule of rules) entries.push(toRuleEntry(engine, rule));
21576
21516
  const configDir = findConfigDir(resolvedDir);
21577
21517
  if (configDir) {
21578
- const rulesPath = path.join(configDir, RULES_FILE);
21579
- const archRules = loadArchitectureRules(rulesPath);
21518
+ const archRules = loadArchitectureRules(path.join(configDir, RULES_FILE));
21580
21519
  for (const rule of archRules) entries.push({
21581
21520
  id: `arch/${rule.name}`,
21582
21521
  engine: "architecture",