@methiu/raigal 0.16.3 → 1.0.1

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/README.md CHANGED
@@ -2,15 +2,14 @@
2
2
 
3
3
  **Catch the slop AI coding agents leave in your code.**
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/raigal.svg)](https://www.npmjs.com/package/raigal) [![npm downloads](https://img.shields.io/npm/dm/raigal.svg)](https://www.npmjs.com/package/raigal) [![CI](https://github.com/meethiu/raigal/actions/workflows/ci.yml/badge.svg)](https://github.com/meethiu/raigal/actions/workflows/ci.yml) [![Node >= 20](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org) [![Discord](https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white)](https://discord.gg/Mzz4A6mfj6)
6
-
5
+ [![npm version](https://img.shields.io/npm/v/@methiu/raigal.svg)](https://www.npmjs.com/package/@methiu/raigal) [![npm downloads](https://img.shields.io/npm/dm/@methiu/raigal.svg)](https://www.npmjs.com/package/@methiu/raigal) [![CI](https://github.com/meethiu/raigal/actions/workflows/ci.yml/badge.svg)](https://github.com/meethiu/raigal/actions/workflows/ci.yml) [![Node >= 20](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org)
7
6
  The patterns Claude Code, Cursor, Codex, and OpenCode leave behind: narrative comments above self-explanatory code, swallowed exceptions, hidden fallbacks, `as any` casts, hallucinated imports, duplicated helpers, dead code, todo stubs, oversized functions. Tests pass. Lint passes. The code rots anyway.
8
7
 
9
8
  Raigal catches them. 50+ rules across 10 language targets (TypeScript, JavaScript, Expo / React Native, Python, Go, Rust, Ruby, PHP, C#, C/C++). Scores every change 0-100. Sub-second. Deterministic: no LLM in the runtime path, same code in, same score out.
10
9
 
11
10
  ## Quick start
12
11
 
13
- Raigal is proprietary software licensed to organizations on an admin-managed allowlist. Runs require authentication via organization credentials (`raigal login` or `RAIGAL_TOKEN`).
12
+ Raigal is demo software licensed to organizations on an admin-managed allowlist. Runs require authentication via organization credentials (`raigal login` or `RAIGAL_TOKEN`).
14
13
 
15
14
  ```bash
16
15
  # 1. Sign in with your organization account
@@ -25,10 +24,11 @@ In CI and automated workflows, provide your organization API key via the `RAIGAL
25
24
  Also available on npm, Yarn, Bun, and Homebrew:
26
25
 
27
26
  ```bash
28
- npm install -g raigal # npm
29
- yarn dlx raigal scan # Yarn
30
- bun add -g raigal # Bun
31
- brew install meethiu/tap/raigal # Homebrew
27
+ npm install -g @methiu/raigal
28
+
29
+ or
30
+
31
+ npm i @methiu/raigal
32
32
  ```
33
33
 
34
34
  See [Installation](#installation) for every option.
@@ -53,7 +53,7 @@ raigal hook install --claude # per-edit hook
53
53
  [![raigal](https://badges.raigal.dev/score/<owner>/<repo>.svg)](https://raigal.dev)
54
54
  ```
55
55
 
56
- Run `npx raigal@latest badge` to auto-generate. Free at [raigal.dev](https://raigal.dev).
56
+ Run `npx @methiu/raigal@latest badge` to auto-generate. Free at [raigal.dev](https://raigal.dev).
57
57
 
58
58
  ## See it in action
59
59
 
@@ -71,22 +71,22 @@ The same CLI is published to npm and Homebrew. Pick whichever fits your stack.
71
71
 
72
72
  ```bash
73
73
  # Run without installing
74
- npx raigal@latest scan
74
+ npx @methiu/raigal@latest scan
75
75
 
76
76
  # npm
77
- npm install --save-dev raigal
77
+ npm install --save-dev @methiu/raigal
78
78
 
79
79
  # yarn
80
- yarn add --dev raigal
80
+ yarn add --dev @methiu/raigal
81
81
 
82
82
  # pnpm
83
- pnpm add -D raigal
83
+ pnpm add -D @methiu/raigal
84
84
 
85
85
  # bun
86
- bun add -d raigal
86
+ bun add -d @methiu/raigal
87
87
 
88
88
  # Global
89
- npm install -g raigal
89
+ npm install -g @methiu/raigal
90
90
  ```
91
91
 
92
92
  Package installation does not run dependency lifecycle scripts. After installing, run `raigal-tools` once if you want bundled Ruff and golangci-lint coverage; the core scanner works without it.
@@ -113,7 +113,7 @@ Full reference for every channel, optional bundled tooling, and external tools:
113
113
 
114
114
  ## Usage
115
115
 
116
- Examples below use the installed `raigal` binary. For a one-off latest run, prefix the command with `npx raigal@latest`, for example `npx raigal@latest scan`.
116
+ Examples below use the installed `raigal` binary. For a one-off latest run, prefix the command with `npx @methiu/raigal@latest`, for example `npx @methiu/raigal@latest scan`.
117
117
 
118
118
  ### Command reference
119
119
 
@@ -272,7 +272,7 @@ raigal install claude cursor # alias for hook install
272
272
  raigal install hooks --claude # natural alias for hook install
273
273
  ```
274
274
 
275
- **Runtime adapters** (scan + feedback): `claude`, `cursor`, `gemini`, `pi`.
275
+ **Runtime adapters** (scan + feedback): `claude`, `cursor`, `gemini`, `pi`.
276
276
  **Rules-only** (agent reads rules): `codex`, `windsurf`, `cline`, `kilocode`, `antigravity`, `copilot`.
277
277
 
278
278
  Hook install flags: `--agent <names>`, `-g, --global`, `--project`, `--dry-run`, `--yes`, `--quality-gate`, plus per-agent shortcuts `--claude`, `--cursor`, `--gemini`, `--pi`, `--codex`, `--windsurf`, `--cline`, `--kilocode`, `--antigravity`, `--copilot`.
@@ -298,7 +298,7 @@ Expose Raigal as MCP tools for Claude Desktop, Cursor, Codex:
298
298
  "mcpServers": {
299
299
  "raigal": {
300
300
  "command": "npx",
301
- "args": ["-y", "raigal-mcp"]
301
+ "args": ["-y", "@methiu/raigal-mcp"]
302
302
  }
303
303
  }
304
304
  }
@@ -389,7 +389,7 @@ jobs:
389
389
  with:
390
390
  node-version: 24
391
391
  - name: Raigal Quality Gate
392
- run: npx --yes raigal@latest ci
392
+ run: npx --yes @methiu/raigal@latest ci
393
393
  env:
394
394
  RAIGAL_TOKEN: ${{ secrets.RAIGAL_TOKEN }}
395
395
  ```
@@ -397,7 +397,7 @@ jobs:
397
397
  **GitHub code scanning (SARIF)**: emit a SARIF 2.1.0 report and upload it so findings appear in the Security tab:
398
398
 
399
399
  ```yaml
400
- - run: npx raigal@latest scan . --sarif > raigal.sarif
400
+ - run: npx @methiu/raigal@latest scan . --sarif > raigal.sarif
401
401
  - uses: github/codeql-action/upload-sarif@v3
402
402
  with:
403
403
  sarif_file: raigal.sarif
@@ -419,7 +419,7 @@ pipelines:
419
419
  depth: full # branch diffs need history
420
420
  script:
421
421
  - git fetch origin "$BITBUCKET_PR_DESTINATION_BRANCH"
422
- - npx --yes raigal@latest ci --changes --base FETCH_HEAD
422
+ - npx --yes @methiu/raigal@latest ci --changes --base FETCH_HEAD
423
423
  ```
424
424
 
425
425
  `ci` applies the score gate and exit code, so no JSON parsing or hand-rolled threshold is needed. More providers: [CI/CD](docs/ci.md).
@@ -488,7 +488,7 @@ Raigal rules are shaped by public scans and benchmark-derived failure modes, not
488
488
 
489
489
  ## Community
490
490
 
491
- [Discord](https://discord.gg/Mzz4A6mfj6) for community chat and support · [Discussions](https://github.com/meethiu/raigal/discussions) for questions, rule requests, and false-positive triage · [Issues](https://github.com/meethiu/raigal/issues) for bugs
491
+ [Discussions](https://github.com/meethiu/raigal/discussions) for questions, rule requests, and false-positive triage · [Issues](https://github.com/meethiu/raigal/issues) for bugs
492
492
 
493
493
  ## Contributing
494
494
 
@@ -508,4 +508,4 @@ Auto-updated by `.github/workflows/contributors.yml`. [Link commit email](https:
508
508
 
509
509
  ## License
510
510
 
511
- Proprietary. All rights reserved. Use is permitted only under a written agreement with Raigal. See [LICENSE](LICENSE) and [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
511
+ Proprietary. All rights reserved. Use is permitted only under a written agreement with Raigal. See [LICENSE](LICENSE) and [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
package/dist/cli.js CHANGED
@@ -863,7 +863,7 @@ const createLogger = (deps = {}) => {
863
863
  const log = createLogger();
864
864
  //#endregion
865
865
  //#region src/version.ts
866
- const APP_VERSION = "0.16.3";
866
+ const APP_VERSION = "1.0.1";
867
867
  //#endregion
868
868
  //#region src/commands/agent-connect.ts
869
869
  const commandText = (status) => [status.provider.loginCommand.command, ...status.provider.loginCommand.args].join(" ");
@@ -1132,7 +1132,7 @@ const AllowedOwner = z.object({
1132
1132
  login: z.string().max(100)
1133
1133
  });
1134
1134
  const EntitlementClaims = z.object({
1135
- iss: z.literal("https://app.raigal.dev"),
1135
+ iss: z.literal(process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev"),
1136
1136
  sub: z.string(),
1137
1137
  org_name: z.string(),
1138
1138
  plan: z.enum(["trial", "active"]),
@@ -1345,7 +1345,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
1345
1345
  const claims = parsed.data;
1346
1346
  const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
1347
1347
  if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
1348
- if (!isVersionAtLeast("0.16.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");
1348
+ if (!isVersionAtLeast("1.0.1", 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");
1349
1349
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
1350
1350
  const grace = options.allowGraceSeconds ?? 0;
1351
1351
  if (now > claims.exp + grace) {
@@ -23984,7 +23984,7 @@ const maybeNotifyUpdate = async (now = Date.now()) => {
23984
23984
  if (!process.stderr.isTTY) return;
23985
23985
  const cachePath = resolveUpdateCachePath();
23986
23986
  const cache = readCache(cachePath);
23987
- if (cache && isOutdated("0.16.3", cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
23987
+ if (cache && isOutdated("1.0.1", cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
23988
23988
  if (!cache || now - cache.checkedAt > CHECK_INTERVAL_MS) {
23989
23989
  const latest = await fetchLatestVersion$1();
23990
23990
  if (latest) writeCache(cachePath, {
@@ -24504,7 +24504,7 @@ const renderCommandGroup = (group, commandWidth) => {
24504
24504
  return lines;
24505
24505
  };
24506
24506
  const renderCommandReference = (input = {}) => {
24507
- const version = input.version ?? "0.16.3";
24507
+ const version = input.version ?? "1.0.1";
24508
24508
  const commandWidth = Math.max(...COMMAND_REFERENCE.map((item) => item.command.length));
24509
24509
  const lines = [renderHeader({
24510
24510
  version,
@@ -24727,7 +24727,7 @@ const renderHelpDetails = () => [
24727
24727
  ].map((line) => highlightRaigal(line, theme)).join("\n");
24728
24728
  const renderHome = (input = {}) => {
24729
24729
  let out = renderHeader({
24730
- version: input.version ?? "0.16.3",
24730
+ version: input.version ?? "1.0.1",
24731
24731
  command: "--bare",
24732
24732
  context: []
24733
24733
  });
@@ -29390,7 +29390,16 @@ const shouldRenderPlainVersion = () => {
29390
29390
  "version"
29391
29391
  ].includes(args[0] ?? "");
29392
29392
  };
29393
- const program = new Command().name("raigal").description("The quality gate for agentic coding.").version(APP_VERSION, "-v, --version").argument("[directory]", "directory to scan when no command is passed", ".").option("--changes", "only scan changed files (git diff)").option("--staged", "only scan staged files").option("--base <ref>", "diff base for --changes, e.g. origin/main (default HEAD)").option("-d, --verbose", "show file details per rule").option("--json", "output JSON instead of terminal UI").option("--sarif", "output SARIF 2.1.0 (for GitHub code scanning)").option("--format <format>", "output format: json or sarif");
29393
+ const program = new Command().name("raigal").description("The quality gate for agentic coding.").version(APP_VERSION, "-v, --version").argument("[directory]", "directory to scan when no command is passed", ".");
29394
+ for (const [flag, description] of [
29395
+ ["--changes", "only scan changed files (git diff)"],
29396
+ ["--staged", "only scan staged files"],
29397
+ ["--base <ref>", "diff base for --changes, e.g. origin/main (default HEAD)"],
29398
+ ["-d, --verbose", "show file details per rule"],
29399
+ ["--json", "output JSON instead of terminal UI"],
29400
+ ["--sarif", "output SARIF 2.1.0 (for GitHub code scanning)"],
29401
+ ["--format <format>", "output format: json or sarif"]
29402
+ ]) program.option(flag, description);
29394
29403
  addFilterAndFailOnOptions(program).showSuggestionAfterError().action(async (directory, flags) => {
29395
29404
  if (hasNoUserArgs() && noFlagsPassed(flags) && process.stdin.isTTY) try {
29396
29405
  await interactiveCommand(directory, loadConfig(directory));
@@ -29398,7 +29407,17 @@ addFilterAndFailOnOptions(program).showSuggestionAfterError().action(async (dire
29398
29407
  } catch {}
29399
29408
  await runScan(directory, flags);
29400
29409
  });
29401
- addFilterAndFailOnOptions(program.command("scan [directory]").description("Score a project and print findings").option("--changes", "only scan changed files").option("--staged", "only scan staged files").option("--base <ref>", "diff base for --changes, e.g. origin/main (default HEAD)").option("-d, --verbose", "show file details per rule").option("--json", "output JSON").option("--sarif", "output SARIF 2.1.0 (for GitHub code scanning)").option("--format <format>", "output format: json or sarif")).action(async (directory = ".", _flags, command) => {
29410
+ const scanCmd = program.command("scan [directory]").description("Score a project and print findings");
29411
+ for (const [flag, description] of [
29412
+ ["--changes", "only scan changed files"],
29413
+ ["--staged", "only scan staged files"],
29414
+ ["--base <ref>", "diff base for --changes, e.g. origin/main (default HEAD)"],
29415
+ ["-d, --verbose", "show file details per rule"],
29416
+ ["--json", "output JSON"],
29417
+ ["--sarif", "output SARIF 2.1.0 (for GitHub code scanning)"],
29418
+ ["--format <format>", "output format: json or sarif"]
29419
+ ]) scanCmd.option(flag, description);
29420
+ addFilterAndFailOnOptions(scanCmd).action(async (directory = ".", _flags, command) => {
29402
29421
  await runScan(directory, command.optsWithGlobals());
29403
29422
  });
29404
29423
  const fixProgram = program.command("fix [directory]").description("Auto-fix findings or hand off to a coding agent").option("-d, --verbose", "show detailed fix progress").option("-f, --force", "run aggressive fixes (audit and framework dependency alignment)").option("--safe", "only apply reversible fixes (imports, comment removal, safe formatters); skip anything that deletes code, rewrites behaviour, or runs unsafe formatter configs").option("--dry-run", "print planned fix steps without writing files").addOption(new Option("--changes", "only fix changed files (git diff)").conflicts("staged")).addOption(new Option("--staged", "only fix staged files").conflicts("changes")).option("--base <ref>", "diff base for --changes, e.g. origin/main (default HEAD)").option("-p, --prompt", "print a prompt for your coding agent to fix remaining issues");
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as APP_VERSION } from "./version-w6rU3OgI.js";
1
+ import { t as APP_VERSION } from "./version-om7oAIVO.js";
2
2
  import { a as getEngineLabel, i as ENGINE_INFO, r as scoreImpactForRule, t as summarizeFindingAssessments } from "./finding-assessment-M5fUTE-C.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";
@@ -15877,7 +15877,7 @@ const AllowedOwner = z.object({
15877
15877
  login: z.string().max(100)
15878
15878
  });
15879
15879
  const EntitlementClaims = z.object({
15880
- iss: z.literal("https://app.raigal.dev"),
15880
+ iss: z.literal(process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev"),
15881
15881
  sub: z.string(),
15882
15882
  org_name: z.string(),
15883
15883
  plan: z.enum(["trial", "active"]),
@@ -16051,7 +16051,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
16051
16051
  const claims = parsed.data;
16052
16052
  const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
16053
16053
  if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
16054
- if (!isVersionAtLeast("0.16.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");
16054
+ if (!isVersionAtLeast("1.0.1", 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");
16055
16055
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
16056
16056
  const grace = options.allowGraceSeconds ?? 0;
16057
16057
  if (now > claims.exp + grace) {
@@ -19171,12 +19171,12 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
19171
19171
  scoreFileCount
19172
19172
  });
19173
19173
  if (options.sarif) {
19174
- const { buildSarifLog } = await import("./sarif-TxkW17qu.js");
19174
+ const { buildSarifLog } = await import("./sarif-C9k9QYGi.js");
19175
19175
  console.log(JSON.stringify(buildSarifLog(results), null, 2));
19176
19176
  return completion;
19177
19177
  }
19178
19178
  if (options.json) {
19179
- const { buildJsonOutput } = await import("./json-CytVHOS7.js");
19179
+ const { buildJsonOutput } = await import("./json-Be-AUuDb.js");
19180
19180
  const jsonOut = buildJsonOutput(results, scoreResult, scoreFileCount, elapsedMs, scanCoverage);
19181
19181
  console.log(JSON.stringify(jsonOut, null, 2));
19182
19182
  return completion;
@@ -1,4 +1,4 @@
1
- import { t as APP_VERSION } from "./version-w6rU3OgI.js";
1
+ import { t as APP_VERSION } from "./version-om7oAIVO.js";
2
2
  import { i as ENGINE_INFO, n as withFindingAssessments, t as summarizeFindingAssessments } from "./finding-assessment-M5fUTE-C.js";
3
3
  //#region src/output/json.ts
4
4
  const buildJsonOutput = (results, scoreResult, fileCount, elapsedMs, coverage) => {
package/dist/mcp.js CHANGED
@@ -84,7 +84,7 @@ const getActiveToken = () => {
84
84
  };
85
85
  //#endregion
86
86
  //#region src/version.ts
87
- const APP_VERSION = "0.16.3";
87
+ const APP_VERSION = "1.0.1";
88
88
  //#endregion
89
89
  //#region src/cloud/contract.ts
90
90
  const Severity = z.enum(["error", "warning"]);
@@ -168,7 +168,7 @@ const AllowedOwner = z.object({
168
168
  login: z.string().max(100)
169
169
  });
170
170
  const EntitlementClaims = z.object({
171
- iss: z.literal("https://app.raigal.dev"),
171
+ iss: z.literal(process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev"),
172
172
  sub: z.string(),
173
173
  org_name: z.string(),
174
174
  plan: z.enum(["trial", "active"]),
@@ -342,7 +342,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
342
342
  const claims = parsed.data;
343
343
  const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
344
344
  if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
345
- if (!isVersionAtLeast("0.16.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");
345
+ if (!isVersionAtLeast("1.0.1", 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");
346
346
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
347
347
  const grace = options.allowGraceSeconds ?? 0;
348
348
  if (now > claims.exp + grace) {
@@ -1,4 +1,4 @@
1
- import { t as APP_VERSION } from "./version-w6rU3OgI.js";
1
+ import { t as APP_VERSION } from "./version-om7oAIVO.js";
2
2
  import { n as isSecretClassRule, t as FIXED_SECRET_DIAGNOSTIC_MESSAGE } from "./mask-secrets-BXn-ZSEh.js";
3
3
  import path from "node:path";
4
4
  //#region src/output/sarif.ts
@@ -1,6 +1,6 @@
1
1
  import "node:fs";
2
2
  import "node:url";
3
3
  //#region src/version.ts
4
- const APP_VERSION = "0.16.3";
4
+ const APP_VERSION = "1.0.1";
5
5
  //#endregion
6
6
  export { APP_VERSION as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@methiu/raigal",
3
- "version": "0.16.3",
3
+ "version": "1.0.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -96,11 +96,15 @@ try {
96
96
  if (tarballPath && fs.existsSync(tarballPath)) {
97
97
  try {
98
98
  fs.rmSync(tarballPath, { force: true });
99
- } catch {}
99
+ } catch {
100
+ // Best effort: cleanup failures must not mask the original error.
101
+ }
100
102
  }
101
103
  if (extractDir && fs.existsSync(extractDir)) {
102
104
  try {
103
105
  fs.rmSync(extractDir, { recursive: true, force: true });
104
- } catch {}
106
+ } catch {
107
+ // Best effort: cleanup failures must not mask the original error.
108
+ }
105
109
  }
106
110
  }