@westbayberry/dg 1.1.5 → 1.2.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/dist/index.mjs CHANGED
@@ -910,15 +910,15 @@ var require_route = __commonJS({
910
910
  };
911
911
  }
912
912
  function wrapConversion(toModel, graph) {
913
- const path2 = [graph[toModel].parent, toModel];
913
+ const path = [graph[toModel].parent, toModel];
914
914
  let fn = conversions[graph[toModel].parent][toModel];
915
915
  let cur = graph[toModel].parent;
916
916
  while (graph[cur].parent) {
917
- path2.unshift(graph[cur].parent);
917
+ path.unshift(graph[cur].parent);
918
918
  fn = link2(conversions[graph[cur].parent][cur], fn);
919
919
  cur = graph[cur].parent;
920
920
  }
921
- fn.conversion = path2;
921
+ fn.conversion = path;
922
922
  return fn;
923
923
  }
924
924
  module.exports = function(fromModel) {
@@ -1590,8 +1590,8 @@ var require_source = __commonJS({
1590
1590
  // src/telemetry.ts
1591
1591
  import { platform, arch } from "node:os";
1592
1592
  function telemetryEnabled() {
1593
- if (process.env.DG_TELEMETRY === "0") return false;
1594
1593
  if (process.env.DO_NOT_TRACK === "1") return false;
1594
+ if (process.env.DG_TELEMETRY === "0") return false;
1595
1595
  return true;
1596
1596
  }
1597
1597
  function classifyError(err) {
@@ -1894,6 +1894,7 @@ __export(auth_exports, {
1894
1894
  pollAuthSession: () => pollAuthSession,
1895
1895
  preflightAuthCheck: () => preflightAuthCheck,
1896
1896
  readTermsAcceptedAt: () => readTermsAcceptedAt,
1897
+ recordScanCapReached: () => recordScanCapReached,
1897
1898
  recordScanNoticeShown: () => recordScanNoticeShown,
1898
1899
  saveCredentials: () => saveCredentials,
1899
1900
  saveCredentialsFromToken: () => saveCredentialsFromToken,
@@ -2004,17 +2005,17 @@ function ensureConfigDir() {
2004
2005
  mkdirSync(dir, { recursive: true, mode: 448 });
2005
2006
  }
2006
2007
  }
2007
- function ensureConfigPerms(path2) {
2008
+ function ensureConfigPerms(path) {
2008
2009
  let st;
2009
2010
  try {
2010
- st = lstatSync(path2);
2011
+ st = lstatSync(path);
2011
2012
  } catch {
2012
2013
  return;
2013
2014
  }
2014
2015
  if (!st || typeof st.isSymbolicLink !== "function") return;
2015
2016
  if (st.isSymbolicLink()) {
2016
2017
  process.stderr.write(
2017
- `Warning: ${path2} is a symlink; refusing to chmod (would affect the symlink target). Replace with a regular file to enforce 0o600.
2018
+ `Warning: ${path} is a symlink; refusing to chmod (would affect the symlink target). Replace with a regular file to enforce 0o600.
2018
2019
  `
2019
2020
  );
2020
2021
  return;
@@ -2023,12 +2024,12 @@ function ensureConfigPerms(path2) {
2023
2024
  const mode = (st.mode ?? 384) & 511;
2024
2025
  if (mode === 384) return;
2025
2026
  process.stderr.write(
2026
- `Warning: ${path2} has perms 0o${mode.toString(8)} (expected 0o600); re-tightening.
2027
+ `Warning: ${path} has perms 0o${mode.toString(8)} (expected 0o600); re-tightening.
2027
2028
  `
2028
2029
  );
2029
2030
  let fd;
2030
2031
  try {
2031
- fd = openSync(path2, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
2032
+ fd = openSync(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
2032
2033
  fchmodSync(fd, 384);
2033
2034
  } catch {
2034
2035
  } finally {
@@ -2185,11 +2186,11 @@ async function fetchCliPolicy(opts) {
2185
2186
  return null;
2186
2187
  }
2187
2188
  }
2188
- async function postCliEvent(path2, body, apiUrl, apiKey, timeoutMs = 3e3) {
2189
+ async function postCliEvent(path, body, apiUrl, apiKey, timeoutMs = 3e3) {
2189
2190
  try {
2190
2191
  const ctrl = new AbortController();
2191
2192
  const timer = setTimeout(() => ctrl.abort(), timeoutMs);
2192
- await globalThis.fetch(`${apiUrl}${path2}`, {
2193
+ await globalThis.fetch(`${apiUrl}${path}`, {
2193
2194
  method: "POST",
2194
2195
  headers: {
2195
2196
  "Content-Type": "application/json",
@@ -2263,9 +2264,9 @@ function authStatusCachePath() {
2263
2264
  }
2264
2265
  function readCachedAuthStatus() {
2265
2266
  try {
2266
- const path2 = authStatusCachePath();
2267
- if (!existsSync(path2)) return null;
2268
- const raw = readFileSync(path2, "utf-8");
2267
+ const path = authStatusCachePath();
2268
+ if (!existsSync(path)) return null;
2269
+ const raw = readFileSync(path, "utf-8");
2269
2270
  const cached = JSON.parse(raw);
2270
2271
  if (typeof cached.fetchedAt !== "number") return null;
2271
2272
  if (Date.now() - cached.fetchedAt > AUTH_STATUS_TTL_MS) return null;
@@ -2276,25 +2277,35 @@ function readCachedAuthStatus() {
2276
2277
  }
2277
2278
  function writeCachedAuthStatus(status) {
2278
2279
  try {
2279
- const path2 = authStatusCachePath();
2280
- const dir = dirname(path2);
2280
+ const path = authStatusCachePath();
2281
+ const dir = dirname(path);
2281
2282
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
2282
- writeFileSync(path2, JSON.stringify(status), { mode: 384 });
2283
+ writeFileSync(path, JSON.stringify(status), { mode: 384 });
2283
2284
  } catch {
2284
2285
  }
2285
2286
  }
2287
+ function recordScanCapReached(scansUsed, maxScans, reason) {
2288
+ writeCachedAuthStatus({
2289
+ authenticated: !!getStoredApiKey(),
2290
+ freeTierCapReached: true,
2291
+ scansUsed,
2292
+ scansLimit: maxScans,
2293
+ capReason: reason,
2294
+ fetchedAt: Date.now()
2295
+ });
2296
+ }
2286
2297
  async function preflightAuthCheck(apiUrl) {
2287
2298
  if (process.env.DG_SIMULATE_FREE_CAP_REACHED === "1") {
2288
2299
  throw new PreflightFreeCapReachedError(5, 5);
2289
2300
  }
2301
+ const apiKey = getStoredApiKey();
2290
2302
  const cached = readCachedAuthStatus();
2291
- if (cached) {
2303
+ if (cached && cached.authenticated === !!apiKey) {
2292
2304
  if (cached.freeTierCapReached) {
2293
2305
  throw new PreflightFreeCapReachedError(cached.scansUsed, cached.scansLimit ?? cached.scansUsed, cached.capReason ?? "monthly_limit");
2294
2306
  }
2295
2307
  return cached;
2296
2308
  }
2297
- const apiKey = getStoredApiKey();
2298
2309
  const headers = {};
2299
2310
  if (apiKey) {
2300
2311
  headers["Authorization"] = `Bearer ${apiKey}`;
@@ -2324,17 +2335,17 @@ async function preflightAuthCheck(apiUrl) {
2324
2335
  } catch {
2325
2336
  }
2326
2337
  if (body.freeTierCapReached) {
2327
- const capReason = body.capReason === "prefix_cap" ? "prefix_cap" : "monthly_limit";
2338
+ const capReason2 = body.capReason === "prefix_cap" ? "prefix_cap" : "monthly_limit";
2328
2339
  const status2 = {
2329
2340
  authenticated: !!apiKey,
2330
2341
  freeTierCapReached: true,
2331
2342
  scansUsed: body.scansUsed ?? 0,
2332
2343
  scansLimit: body.maxScans ?? 0,
2333
- capReason,
2344
+ capReason: capReason2,
2334
2345
  fetchedAt: Date.now()
2335
2346
  };
2336
2347
  writeCachedAuthStatus(status2);
2337
- throw new PreflightFreeCapReachedError(status2.scansUsed, status2.scansLimit ?? 0, capReason);
2348
+ throw new PreflightFreeCapReachedError(status2.scansUsed, status2.scansLimit ?? 0, capReason2);
2338
2349
  }
2339
2350
  }
2340
2351
  if (!resp.ok) {
@@ -2352,25 +2363,27 @@ async function preflightAuthCheck(apiUrl) {
2352
2363
  data = await resp.json();
2353
2364
  } catch {
2354
2365
  }
2366
+ const capReason = data.capReason === "prefix_cap" ? "prefix_cap" : "monthly_limit";
2355
2367
  const status = {
2356
2368
  authenticated: !!apiKey,
2357
2369
  freeTierCapReached: !!data.freeTierCapReached,
2358
2370
  scansUsed: data.scansUsed ?? 0,
2359
2371
  scansLimit: data.scansLimit ?? null,
2372
+ capReason,
2360
2373
  tier: data.tier,
2361
2374
  name: data.name,
2362
2375
  fetchedAt: Date.now()
2363
2376
  };
2364
2377
  writeCachedAuthStatus(status);
2365
2378
  if (status.freeTierCapReached) {
2366
- throw new PreflightFreeCapReachedError(status.scansUsed, status.scansLimit ?? 0);
2379
+ throw new PreflightFreeCapReachedError(status.scansUsed, status.scansLimit ?? 0, capReason);
2367
2380
  }
2368
2381
  return status;
2369
2382
  }
2370
2383
  function clearAuthStatusCache() {
2371
2384
  try {
2372
- const path2 = authStatusCachePath();
2373
- if (existsSync(path2)) unlinkSync(path2);
2385
+ const path = authStatusCachePath();
2386
+ if (existsSync(path)) unlinkSync(path);
2374
2387
  } catch {
2375
2388
  }
2376
2389
  }
@@ -2816,7 +2829,7 @@ var init_config = __esm({
2816
2829
  DG_DEBUG=1 Enable debug output
2817
2830
  DG_QUIET=1 Same as passing --quiet
2818
2831
  DG_WORKSPACE Workspace subdirectory to scan
2819
- DG_TELEMETRY=1 Opt in to anonymous crash reports (off by default)
2832
+ DG_TELEMETRY=0 Opt out of anonymous crash reports (on by default; DO_NOT_TRACK=1 also opts out)
2820
2833
  DG_FORCE_POSTINSTALL=1 Force postinstall to run even outside a TTY
2821
2834
  (useful for re-running setup manually)
2822
2835
  DG_NO_RC_EDIT=1 Postinstall installs shims but does not edit
@@ -2865,23 +2878,23 @@ function cacheFile() {
2865
2878
  return dgCachePath("update-check.json");
2866
2879
  }
2867
2880
  function readCache() {
2868
- const path2 = cacheFile();
2881
+ const path = cacheFile();
2869
2882
  let stat = null;
2870
2883
  try {
2871
- stat = lstatSync2(path2, { throwIfNoEntry: false }) ?? null;
2884
+ stat = lstatSync2(path, { throwIfNoEntry: false }) ?? null;
2872
2885
  } catch {
2873
2886
  return null;
2874
2887
  }
2875
2888
  if (!stat) return null;
2876
2889
  if (!stat.isFile() || stat.size > MAX_CACHE_BYTES) {
2877
2890
  try {
2878
- unlinkSync2(path2);
2891
+ unlinkSync2(path);
2879
2892
  } catch {
2880
2893
  }
2881
2894
  return null;
2882
2895
  }
2883
2896
  try {
2884
- const raw = readFileSync3(path2, "utf-8");
2897
+ const raw = readFileSync3(path, "utf-8");
2885
2898
  const data = JSON.parse(raw);
2886
2899
  if (typeof data.latest === "string" && typeof data.checkedAt === "number") {
2887
2900
  return data;
@@ -2892,14 +2905,14 @@ function readCache() {
2892
2905
  }
2893
2906
  function writeCache(entry) {
2894
2907
  try {
2895
- const path2 = cacheFile();
2896
- const parent = dirname3(path2);
2908
+ const path = cacheFile();
2909
+ const parent = dirname3(path);
2897
2910
  if (!existsSync3(parent)) mkdirSync2(parent, { recursive: true, mode: 448 });
2898
2911
  const serialized = JSON.stringify(entry);
2899
2912
  if (serialized.length > MAX_CACHE_BYTES) return;
2900
- writeFileSync2(path2, serialized, { encoding: "utf-8", mode: 384 });
2913
+ writeFileSync2(path, serialized, { encoding: "utf-8", mode: 384 });
2901
2914
  try {
2902
- chmodSync2(path2, 384);
2915
+ chmodSync2(path, 384);
2903
2916
  } catch {
2904
2917
  }
2905
2918
  } catch {
@@ -3050,17 +3063,17 @@ function ensureUnderHome(target) {
3050
3063
  throw new UnsafePathError(`refusing to operate on path outside $HOME: ${target}`, target);
3051
3064
  }
3052
3065
  }
3053
- function lstatNoSymlink(path2) {
3066
+ function lstatNoSymlink(path) {
3054
3067
  let st;
3055
3068
  try {
3056
- st = lstatSync3(path2);
3069
+ st = lstatSync3(path);
3057
3070
  } catch (e) {
3058
3071
  const code = e.code;
3059
3072
  if (code === "ENOENT") return;
3060
3073
  throw e;
3061
3074
  }
3062
3075
  if (st.isSymbolicLink()) {
3063
- throw new UnsafePathError(`refusing: ${path2} is a symlink`, path2);
3076
+ throw new UnsafePathError(`refusing: ${path} is a symlink`, path);
3064
3077
  }
3065
3078
  }
3066
3079
  function safeMkdirRecursive(target, mode = 493) {
@@ -3090,9 +3103,9 @@ function atomicWriteFile(target, content, mode = 420) {
3090
3103
  writeFileSync3(tmp, content, { mode });
3091
3104
  renameSync(tmp, target);
3092
3105
  }
3093
- function isExecutable(path2) {
3106
+ function isExecutable(path) {
3094
3107
  try {
3095
- const st = statSync(path2);
3108
+ const st = statSync(path);
3096
3109
  return st.isFile() && (st.mode & 73) !== 0;
3097
3110
  } catch {
3098
3111
  return false;
@@ -3117,9 +3130,9 @@ var init_safe_fs = __esm({
3117
3130
  "src/shims/safe-fs.ts"() {
3118
3131
  "use strict";
3119
3132
  UnsafePathError = class extends Error {
3120
- constructor(message, path2) {
3133
+ constructor(message, path) {
3121
3134
  super(message);
3122
- this.path = path2;
3135
+ this.path = path;
3123
3136
  this.name = "UnsafePathError";
3124
3137
  }
3125
3138
  };
@@ -3151,16 +3164,29 @@ var init_templates = __esm({
3151
3164
  "mamba"
3152
3165
  ];
3153
3166
  UNIX_SHIM_BODY = `#!/bin/sh
3167
+ nonce=$(cat "$HOME/.dg/state/shim-nonce" 2>/dev/null)
3154
3168
  if [ -n "\${DG_SHIM_ACTIVE:-}" ]; then
3155
3169
  cleaned_path=$(printf '%s' "$PATH" | awk -v RS=':' -v ORS=':' '$0 != ENVIRON["HOME"]"/.dg/shims"' | sed 's/:$//')
3156
- real_bin=$(PATH="$cleaned_path" command -v __ECOSYSTEM__)
3157
- if [ -n "$real_bin" ]; then
3158
- exec "$real_bin" "$@"
3170
+ # Only honor DG_SHIM_ACTIVE as the recursion guard when it equals the
3171
+ # on-disk nonce \u2014 that is the value our own wrapper sets before running the
3172
+ # real installer. A non-matching value was set outside dg (stale or a
3173
+ # bypass attempt) and must NOT skip scanning.
3174
+ if [ -n "$nonce" ] && [ "$DG_SHIM_ACTIVE" = "$nonce" ]; then
3175
+ real_bin=$(PATH="$cleaned_path" command -v __ECOSYSTEM__)
3176
+ if [ -n "$real_bin" ]; then
3177
+ exec "$real_bin" "$@"
3178
+ fi
3179
+ echo "dg: real __ECOSYSTEM__ not found on PATH" >&2
3180
+ exit 127
3181
+ fi
3182
+ if [ -z "$nonce" ]; then
3183
+ echo "dg: shim nonce missing (reinstall dg) \u2014 cannot verify; passing through __ECOSYSTEM__ UNSCANNED." >&2
3184
+ real_bin=$(PATH="$cleaned_path" command -v __ECOSYSTEM__)
3185
+ if [ -n "$real_bin" ]; then exec "$real_bin" "$@"; fi
3186
+ exit 127
3159
3187
  fi
3160
- echo "dg: real __ECOSYSTEM__ not found on PATH" >&2
3161
- exit 127
3188
+ echo "dg: ignoring externally-set DG_SHIM_ACTIVE (nonce mismatch); scanning __ECOSYSTEM__." >&2
3162
3189
  fi
3163
- nonce=$(cat "$HOME/.dg/state/shim-nonce" 2>/dev/null)
3164
3190
  dg_entry=$(cat "$HOME/.dg/state/dg-entry" 2>/dev/null)
3165
3191
  if [ -n "$dg_entry" ] && [ -x "$dg_entry" ]; then
3166
3192
  DG_SHIM_ACTIVE="$nonce" DG_SHIM_PARENT_PATH="$PATH" exec "$dg_entry" __wrap __ECOSYSTEM__ "$@"
@@ -3180,6 +3206,8 @@ exit 127
3180
3206
  `;
3181
3207
  WINDOWS_SHIM_BODY = `@echo off
3182
3208
  setlocal enabledelayedexpansion
3209
+ set "DG_SHIM_NONCE="
3210
+ if exist "%USERPROFILE%\\.dg\\state\\shim-nonce" set /p DG_SHIM_NONCE=<"%USERPROFILE%\\.dg\\state\\shim-nonce"
3183
3211
  if not "%DG_SHIM_ACTIVE%"=="" (
3184
3212
  set "_dg_realpath="
3185
3213
  for /f "tokens=*" %%i in ('where __ECOSYSTEM__ 2^>nul') do (
@@ -3187,15 +3215,25 @@ if not "%DG_SHIM_ACTIVE%"=="" (
3187
3215
  if not defined _dg_realpath set "_dg_realpath=%%i"
3188
3216
  )
3189
3217
  )
3190
- if defined _dg_realpath (
3191
- "!_dg_realpath!" %*
3192
- exit /b !errorlevel!
3218
+ rem Only honor DG_SHIM_ACTIVE as the recursion guard when it equals the nonce.
3219
+ if "%DG_SHIM_ACTIVE%"=="!DG_SHIM_NONCE!" (
3220
+ if defined _dg_realpath (
3221
+ "!_dg_realpath!" %*
3222
+ exit /b !errorlevel!
3223
+ )
3224
+ echo dg: real __ECOSYSTEM__ not found on PATH 1>&2
3225
+ exit /b 127
3193
3226
  )
3194
- echo dg: real __ECOSYSTEM__ not found on PATH 1>&2
3195
- exit /b 127
3227
+ if "!DG_SHIM_NONCE!"=="" (
3228
+ echo dg: shim nonce missing ^(reinstall dg^) -- passing through __ECOSYSTEM__ UNSCANNED. 1>&2
3229
+ if defined _dg_realpath (
3230
+ "!_dg_realpath!" %*
3231
+ exit /b !errorlevel!
3232
+ )
3233
+ exit /b 127
3234
+ )
3235
+ echo dg: ignoring externally-set DG_SHIM_ACTIVE ^(nonce mismatch^); scanning __ECOSYSTEM__. 1>&2
3196
3236
  )
3197
- set "DG_SHIM_NONCE="
3198
- if exist "%USERPROFILE%\\.dg\\state\\shim-nonce" set /p DG_SHIM_NONCE=<"%USERPROFILE%\\.dg\\state\\shim-nonce"
3199
3237
  set "DG_ENTRY="
3200
3238
  if exist "%USERPROFILE%\\.dg\\state\\dg-entry" set /p DG_ENTRY=<"%USERPROFILE%\\.dg\\state\\dg-entry"
3201
3239
  set "DG_SHIM_ACTIVE=!DG_SHIM_NONCE!"
@@ -3237,9 +3275,9 @@ exit /b 127
3237
3275
  import { homedir as homedir3 } from "node:os";
3238
3276
  import { join as join3 } from "node:path";
3239
3277
  import { existsSync as existsSync5, readFileSync as readFileSync4, lstatSync as lstatSync4, appendFileSync, writeFileSync as writeFileSync4 } from "node:fs";
3240
- function expandHome(path2) {
3241
- if (path2.startsWith("~/")) return join3(homedir3(), path2.slice(2));
3242
- return path2;
3278
+ function expandHome(path) {
3279
+ if (path.startsWith("~/")) return join3(homedir3(), path.slice(2));
3280
+ return path;
3243
3281
  }
3244
3282
  function detectShellKind(envShell) {
3245
3283
  const s = (envShell ?? "").toLowerCase();
@@ -3463,6 +3501,7 @@ var init_resolve = __esm({
3463
3501
  var install_exports = {};
3464
3502
  __export(install_exports, {
3465
3503
  bypassLogPath: () => bypassLogPath,
3504
+ childInstallEnv: () => childInstallEnv,
3466
3505
  currentDgEntry: () => currentDgEntry,
3467
3506
  dgShimDir: () => dgShimDir,
3468
3507
  ensureDgEntryRecorded: () => ensureDgEntryRecorded,
@@ -3509,15 +3548,15 @@ function ensureDgEntryRecorded(targetHome = homedir5()) {
3509
3548
  try {
3510
3549
  const entry = currentDgEntry();
3511
3550
  if (!entry) return;
3512
- const path2 = dgEntryStatePath(targetHome);
3551
+ const path = dgEntryStatePath(targetHome);
3513
3552
  let existing = null;
3514
3553
  try {
3515
- existing = readFileSync6(path2, "utf-8").trim();
3554
+ existing = readFileSync6(path, "utf-8").trim();
3516
3555
  } catch {
3517
3556
  }
3518
3557
  if (existing === entry) return;
3519
- safeMkdirRecursive(dirname5(path2), 493);
3520
- safeWriteFile(path2, entry + "\n", 420);
3558
+ safeMkdirRecursive(dirname5(path), 493);
3559
+ safeWriteFile(path, entry + "\n", 420);
3521
3560
  } catch {
3522
3561
  }
3523
3562
  }
@@ -3529,11 +3568,21 @@ function readNonce() {
3529
3568
  return null;
3530
3569
  }
3531
3570
  }
3532
- function chownIfNeeded(path2, uid, gid) {
3571
+ function childInstallEnv() {
3572
+ const env3 = {};
3573
+ for (const [k, v] of Object.entries(process.env)) {
3574
+ if (k.startsWith("DG_")) continue;
3575
+ env3[k] = v;
3576
+ }
3577
+ const nonce = readNonce();
3578
+ if (nonce) env3.DG_SHIM_ACTIVE = nonce;
3579
+ return env3;
3580
+ }
3581
+ function chownIfNeeded(path, uid, gid) {
3533
3582
  if (uid === void 0 || gid === void 0) return;
3534
3583
  if (process.platform === "win32") return;
3535
3584
  try {
3536
- chownSync(path2, uid, gid);
3585
+ chownSync(path, uid, gid);
3537
3586
  } catch {
3538
3587
  }
3539
3588
  }
@@ -28803,10 +28852,10 @@ var require_react_reconciler_development = __commonJS({
28803
28852
  var setErrorHandler = null;
28804
28853
  var setSuspenseHandler = null;
28805
28854
  {
28806
- var copyWithDeleteImpl = function(obj, path2, index2) {
28807
- var key = path2[index2];
28855
+ var copyWithDeleteImpl = function(obj, path, index2) {
28856
+ var key = path[index2];
28808
28857
  var updated = isArray(obj) ? obj.slice() : assign({}, obj);
28809
- if (index2 + 1 === path2.length) {
28858
+ if (index2 + 1 === path.length) {
28810
28859
  if (isArray(updated)) {
28811
28860
  updated.splice(key, 1);
28812
28861
  } else {
@@ -28814,11 +28863,11 @@ var require_react_reconciler_development = __commonJS({
28814
28863
  }
28815
28864
  return updated;
28816
28865
  }
28817
- updated[key] = copyWithDeleteImpl(obj[key], path2, index2 + 1);
28866
+ updated[key] = copyWithDeleteImpl(obj[key], path, index2 + 1);
28818
28867
  return updated;
28819
28868
  };
28820
- var copyWithDelete = function(obj, path2) {
28821
- return copyWithDeleteImpl(obj, path2, 0);
28869
+ var copyWithDelete = function(obj, path) {
28870
+ return copyWithDeleteImpl(obj, path, 0);
28822
28871
  };
28823
28872
  var copyWithRenameImpl = function(obj, oldPath, newPath, index2) {
28824
28873
  var oldKey = oldPath[index2];
@@ -28856,17 +28905,17 @@ var require_react_reconciler_development = __commonJS({
28856
28905
  }
28857
28906
  return copyWithRenameImpl(obj, oldPath, newPath, 0);
28858
28907
  };
28859
- var copyWithSetImpl = function(obj, path2, index2, value) {
28860
- if (index2 >= path2.length) {
28908
+ var copyWithSetImpl = function(obj, path, index2, value) {
28909
+ if (index2 >= path.length) {
28861
28910
  return value;
28862
28911
  }
28863
- var key = path2[index2];
28912
+ var key = path[index2];
28864
28913
  var updated = isArray(obj) ? obj.slice() : assign({}, obj);
28865
- updated[key] = copyWithSetImpl(obj[key], path2, index2 + 1, value);
28914
+ updated[key] = copyWithSetImpl(obj[key], path, index2 + 1, value);
28866
28915
  return updated;
28867
28916
  };
28868
- var copyWithSet = function(obj, path2, value) {
28869
- return copyWithSetImpl(obj, path2, 0, value);
28917
+ var copyWithSet = function(obj, path, value) {
28918
+ return copyWithSetImpl(obj, path, 0, value);
28870
28919
  };
28871
28920
  var findHook = function(fiber, id) {
28872
28921
  var currentHook2 = fiber.memoizedState;
@@ -28876,10 +28925,10 @@ var require_react_reconciler_development = __commonJS({
28876
28925
  }
28877
28926
  return currentHook2;
28878
28927
  };
28879
- overrideHookState = function(fiber, id, path2, value) {
28928
+ overrideHookState = function(fiber, id, path, value) {
28880
28929
  var hook = findHook(fiber, id);
28881
28930
  if (hook !== null) {
28882
- var newState = copyWithSet(hook.memoizedState, path2, value);
28931
+ var newState = copyWithSet(hook.memoizedState, path, value);
28883
28932
  hook.memoizedState = newState;
28884
28933
  hook.baseState = newState;
28885
28934
  fiber.memoizedProps = assign({}, fiber.memoizedProps);
@@ -28889,10 +28938,10 @@ var require_react_reconciler_development = __commonJS({
28889
28938
  }
28890
28939
  }
28891
28940
  };
28892
- overrideHookStateDeletePath = function(fiber, id, path2) {
28941
+ overrideHookStateDeletePath = function(fiber, id, path) {
28893
28942
  var hook = findHook(fiber, id);
28894
28943
  if (hook !== null) {
28895
- var newState = copyWithDelete(hook.memoizedState, path2);
28944
+ var newState = copyWithDelete(hook.memoizedState, path);
28896
28945
  hook.memoizedState = newState;
28897
28946
  hook.baseState = newState;
28898
28947
  fiber.memoizedProps = assign({}, fiber.memoizedProps);
@@ -28915,8 +28964,8 @@ var require_react_reconciler_development = __commonJS({
28915
28964
  }
28916
28965
  }
28917
28966
  };
28918
- overrideProps = function(fiber, path2, value) {
28919
- fiber.pendingProps = copyWithSet(fiber.memoizedProps, path2, value);
28967
+ overrideProps = function(fiber, path, value) {
28968
+ fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value);
28920
28969
  if (fiber.alternate) {
28921
28970
  fiber.alternate.pendingProps = fiber.pendingProps;
28922
28971
  }
@@ -28925,8 +28974,8 @@ var require_react_reconciler_development = __commonJS({
28925
28974
  scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
28926
28975
  }
28927
28976
  };
28928
- overridePropsDeletePath = function(fiber, path2) {
28929
- fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path2);
28977
+ overridePropsDeletePath = function(fiber, path) {
28978
+ fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path);
28930
28979
  if (fiber.alternate) {
28931
28980
  fiber.alternate.pendingProps = fiber.pendingProps;
28932
28981
  }
@@ -36994,8 +37043,8 @@ var init_ErrorOverview = __esm({
36994
37043
  init_dist3();
36995
37044
  init_Box();
36996
37045
  init_Text();
36997
- cleanupPath = (path2) => {
36998
- return path2?.replace(`file://${cwd()}/`, "");
37046
+ cleanupPath = (path) => {
37047
+ return path?.replace(`file://${cwd()}/`, "");
36999
37048
  };
37000
37049
  stackUtils = new import_stack_utils.default({
37001
37050
  cwd: cwd(),
@@ -41664,18 +41713,19 @@ async function readNdjsonAnalyzeResponse(response, onPackageProgress) {
41664
41713
  }
41665
41714
  buf += decoder.decode();
41666
41715
  if (buf.trim()) processLine(buf);
41667
- if (!finalPayload) {
41716
+ const payload = finalPayload;
41717
+ if (!payload) {
41668
41718
  throw new APIError(
41669
41719
  "Streaming response ended without a result event",
41670
41720
  0,
41671
41721
  ""
41672
41722
  );
41673
41723
  }
41674
- if (typeof finalPayload.score !== "number" || !Array.isArray(finalPayload.packages)) {
41724
+ if (typeof payload.score !== "number" || !Array.isArray(payload.packages)) {
41675
41725
  throw new APIError("Invalid streamed result payload", 0, "");
41676
41726
  }
41677
- checkVersionFloor(finalPayload);
41678
- return sanitizeResponse(finalPayload);
41727
+ checkVersionFloor(payload);
41728
+ return sanitizeResponse(payload);
41679
41729
  }
41680
41730
  async function callPyPIAnalyzeAPI(packages, config, onProgress) {
41681
41731
  const batchSize = config.apiKey ? BATCH_SIZE : ANON_BATCH_SIZE;
@@ -41992,9 +42042,9 @@ function parseLockfile(content) {
41992
42042
  const lockfileVersion = json.lockfileVersion ?? 1;
41993
42043
  const packages = /* @__PURE__ */ new Map();
41994
42044
  if (lockfileVersion >= 2 && json.packages) {
41995
- for (const [path2, entry] of Object.entries(json.packages)) {
41996
- if (path2 === "") continue;
41997
- const pathName = extractPackageName(path2);
42045
+ for (const [path, entry] of Object.entries(json.packages)) {
42046
+ if (path === "") continue;
42047
+ const pathName = extractPackageName(path);
41998
42048
  if (!pathName) continue;
41999
42049
  const e = entry;
42000
42050
  const entryName = typeof e.name === "string" && e.name.length > 0 ? e.name : void 0;
@@ -42266,12 +42316,12 @@ var init_parse_package_json = __esm({
42266
42316
  // src/lockfile/index.ts
42267
42317
  import { readFileSync as readFileSync8, existsSync as existsSync11, statSync as statSync3 } from "node:fs";
42268
42318
  import { join as join6 } from "node:path";
42269
- function readFileSafe(path2) {
42270
- const size = statSync3(path2).size;
42319
+ function readFileSafe(path) {
42320
+ const size = statSync3(path).size;
42271
42321
  if (size > MAX_LOCKFILE_BYTES) {
42272
- throw new Error(`Lockfile too large (${(size / 1024 / 1024).toFixed(0)} MB, max 50 MB): ${path2}`);
42322
+ throw new Error(`Lockfile too large (${(size / 1024 / 1024).toFixed(0)} MB, max 50 MB): ${path}`);
42273
42323
  }
42274
- return readFileSync8(path2, "utf-8");
42324
+ return readFileSync8(path, "utf-8");
42275
42325
  }
42276
42326
  function discoverChanges(cwd2, config) {
42277
42327
  if (config.workspace) {
@@ -42537,6 +42587,12 @@ var init_lockfile = __esm({
42537
42587
  });
42538
42588
 
42539
42589
  // src/commands/wrapper-shared.ts
42590
+ function injectIgnoreScripts(args, opts) {
42591
+ if (!opts.isNpmFamily) return args;
42592
+ if (!(opts.strict || opts.dgNoScripts)) return args;
42593
+ if (args.includes("--ignore-scripts")) return args;
42594
+ return [...args, "--ignore-scripts"];
42595
+ }
42540
42596
  function stripDgFlags(args) {
42541
42597
  let dgForce = false;
42542
42598
  let dgForceReason;
@@ -42619,9 +42675,7 @@ function realNpmBinary() {
42619
42675
  return real;
42620
42676
  }
42621
42677
  function shimSentinelEnv() {
42622
- const nonce = readNonce();
42623
- if (!nonce) return process.env;
42624
- return { ...process.env, DG_SHIM_ACTIVE: nonce };
42678
+ return childInstallEnv();
42625
42679
  }
42626
42680
  function parseNpmArgs(args) {
42627
42681
  const { filtered, dgForce, dgForceReason, dgNoScripts } = stripDgFlags(args);
@@ -42889,9 +42943,9 @@ function readLockfilePins(cwd2) {
42889
42943
  ...Object.keys(root.optionalDependencies ?? {}),
42890
42944
  ...Object.keys(root.peerDependencies ?? {})
42891
42945
  ]);
42892
- for (const [path2, entry] of Object.entries(lock.packages ?? {})) {
42893
- if (path2 === "") continue;
42894
- const m = path2.match(/^node_modules\/((?:@[^/]+\/)?[^/]+)$/);
42946
+ for (const [path, entry] of Object.entries(lock.packages ?? {})) {
42947
+ if (path === "") continue;
42948
+ const m = path.match(/^node_modules\/((?:@[^/]+\/)?[^/]+)$/);
42895
42949
  if (!m) continue;
42896
42950
  const name = m[1];
42897
42951
  if (!directDeps.has(name)) continue;
@@ -43067,6 +43121,22 @@ function parsePipSpec(spec) {
43067
43121
  versionSpec: match[2] ?? null
43068
43122
  };
43069
43123
  }
43124
+ function pinPipArgs(rawArgs, userSpecs, resolved) {
43125
+ const specNames = new Set(userSpecs.map((s) => parsePipSpec(s).name.toLowerCase()));
43126
+ const pinByName = /* @__PURE__ */ new Map();
43127
+ for (const p of resolved) {
43128
+ const key = p.name.toLowerCase();
43129
+ if (specNames.has(key) && !pinByName.has(key)) {
43130
+ pinByName.set(key, `${p.name}==${p.version}`);
43131
+ }
43132
+ }
43133
+ const specSet = new Set(userSpecs);
43134
+ return rawArgs.map((a) => {
43135
+ if (!specSet.has(a)) return a;
43136
+ const { name } = parsePipSpec(a);
43137
+ return pinByName.get(name.toLowerCase()) ?? a;
43138
+ });
43139
+ }
43070
43140
  async function resolvePipVersion(spec) {
43071
43141
  const { name, versionSpec } = parsePipSpec(spec);
43072
43142
  if (versionSpec?.startsWith("==")) {
@@ -43128,9 +43198,7 @@ async function resolvePackages2(specs) {
43128
43198
  return { resolved, failed };
43129
43199
  }
43130
43200
  function shimSentinelEnv2() {
43131
- const nonce = readNonce();
43132
- if (!nonce) return process.env;
43133
- return { ...process.env, DG_SHIM_ACTIVE: nonce };
43201
+ return childInstallEnv();
43134
43202
  }
43135
43203
  async function detectPipBinary() {
43136
43204
  const py3 = walkPathExcludingShimDir("python3");
@@ -43333,6 +43401,153 @@ var init_pip_wrapper = __esm({
43333
43401
  }
43334
43402
  });
43335
43403
 
43404
+ // src/wrapper/route.ts
43405
+ function routeVerdict(input) {
43406
+ const { verdict, mode, strict, dgForce } = input;
43407
+ const refuseHard = mode === "block" || strict;
43408
+ const refuseWarn = strict;
43409
+ const D = (outcome, refuseExitCode, extra) => ({
43410
+ verdict,
43411
+ outcome,
43412
+ refuseExitCode,
43413
+ bypassed: false,
43414
+ ...extra
43415
+ });
43416
+ if (verdict === "free_cap") return D("refuse", 1);
43417
+ if (verdict === "pass") return D("install", 0);
43418
+ if (dgForce && (verdict === "warn" || verdict === "block")) {
43419
+ return D("install", 0, { audit: "override", bypassed: true });
43420
+ }
43421
+ switch (verdict) {
43422
+ case "warn":
43423
+ if (mode === "off") return D("install", 0);
43424
+ if (refuseWarn) return D("refuse", 1);
43425
+ return D("prompt", 1);
43426
+ // warn/block mode → prompt; decline → 1
43427
+ case "block":
43428
+ if (mode === "off") return D("install", 0);
43429
+ return D("refuse", 2, { audit: "block" });
43430
+ case "analysis_incomplete":
43431
+ if (dgForce) return D("install", 0, { bypassed: true });
43432
+ if (refuseHard) return D("refuse", 4);
43433
+ return D("install", 0);
43434
+ // off/warn → install with a "could not verify" notice
43435
+ case "scan_failed":
43436
+ case "tree_failed":
43437
+ if (dgForce) return D("install", 0, { bypassed: true });
43438
+ if (refuseHard) return D("refuse", 2);
43439
+ return D("install", 0);
43440
+ // best-effort under off/warn
43441
+ case "resolve_failed":
43442
+ if (dgForce) return D("passthrough", 0, { bypassed: true });
43443
+ if (refuseHard) return D("refuse", 2);
43444
+ return D("passthrough", 0);
43445
+ // nothing resolved to scan → hand off to the installer
43446
+ default: {
43447
+ const _never = verdict;
43448
+ void _never;
43449
+ return D(refuseHard ? "refuse" : "install", refuseHard ? 4 : 0);
43450
+ }
43451
+ }
43452
+ }
43453
+ var init_route = __esm({
43454
+ "src/wrapper/route.ts"() {
43455
+ "use strict";
43456
+ }
43457
+ });
43458
+
43459
+ // src/wrapper/run.ts
43460
+ function verdictFromResult(result, gates = {}) {
43461
+ if (gates.scanFailed) return "scan_failed";
43462
+ if (gates.treeFailed) return "tree_failed";
43463
+ if (gates.resolveFailed) return "resolve_failed";
43464
+ const action = result?.action;
43465
+ if (action === "pass" || action === "warn" || action === "block") return action;
43466
+ return "analysis_incomplete";
43467
+ }
43468
+ function scanVerdictOf(decision) {
43469
+ if (decision.bypassed && (decision.verdict === "warn" || decision.verdict === "block")) {
43470
+ return "override";
43471
+ }
43472
+ switch (decision.verdict) {
43473
+ case "pass":
43474
+ return "pass";
43475
+ case "warn":
43476
+ return "warn";
43477
+ case "block":
43478
+ return "block";
43479
+ case "analysis_incomplete":
43480
+ return "analysis_incomplete";
43481
+ case "scan_failed":
43482
+ case "tree_failed":
43483
+ case "resolve_failed":
43484
+ return "scan_failed";
43485
+ default:
43486
+ return "skipped";
43487
+ }
43488
+ }
43489
+ async function executeDecision(decision, ctx, presenter) {
43490
+ await presenter.announce(decision, ctx);
43491
+ if (decision.audit) {
43492
+ await presenter.audit(decision.audit, ctx);
43493
+ }
43494
+ const scanVerdict = scanVerdictOf(decision);
43495
+ const base = (installRan, installExitCode) => ({
43496
+ scanVerdict,
43497
+ installRan,
43498
+ installExitCode,
43499
+ error: ctx.error,
43500
+ suppressedByAllowlist: ctx.suppressedCount > 0 ? ctx.suppressedCount : void 0
43501
+ });
43502
+ switch (decision.outcome) {
43503
+ case "refuse":
43504
+ presenter.emit(base(false, null));
43505
+ return decision.refuseExitCode;
43506
+ case "install": {
43507
+ const code = await presenter.runInstall(ctx.installArgs);
43508
+ presenter.emit(base(true, code));
43509
+ return code;
43510
+ }
43511
+ case "passthrough": {
43512
+ const code = await presenter.runInstall(ctx.passthroughArgs);
43513
+ presenter.emit(base(true, code));
43514
+ return code;
43515
+ }
43516
+ case "prompt": {
43517
+ const proceed = await presenter.confirmProceed(ctx);
43518
+ if (!proceed) {
43519
+ presenter.emit(base(false, null));
43520
+ return decision.refuseExitCode;
43521
+ }
43522
+ if (presenter.onPromptAccepted) await presenter.onPromptAccepted(ctx);
43523
+ const code = await presenter.runInstall(ctx.installArgs);
43524
+ presenter.emit(base(true, code));
43525
+ return code;
43526
+ }
43527
+ default: {
43528
+ const _never = decision.outcome;
43529
+ void _never;
43530
+ return 1;
43531
+ }
43532
+ }
43533
+ }
43534
+ var init_run = __esm({
43535
+ "src/wrapper/run.ts"() {
43536
+ "use strict";
43537
+ }
43538
+ });
43539
+
43540
+ // src/wrapper/json.ts
43541
+ function emitWrapperJson(config, result) {
43542
+ if (!config.json) return;
43543
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
43544
+ }
43545
+ var init_json = __esm({
43546
+ "src/wrapper/json.ts"() {
43547
+ "use strict";
43548
+ }
43549
+ });
43550
+
43336
43551
  // src/security/artifact_integrity.ts
43337
43552
  function normalize(h, algo) {
43338
43553
  if (!h) return void 0;
@@ -43427,16 +43642,16 @@ function collectNpmLockSha512Hashes(packageLock) {
43427
43642
  const root = packageLock;
43428
43643
  const packages = root["packages"];
43429
43644
  if (packages && typeof packages === "object") {
43430
- for (const [path2, raw] of Object.entries(packages)) {
43431
- if (path2 === "") continue;
43645
+ for (const [path, raw] of Object.entries(packages)) {
43646
+ if (path === "") continue;
43432
43647
  if (!raw || typeof raw !== "object") continue;
43433
43648
  const entry = raw;
43434
43649
  const version = typeof entry.version === "string" ? entry.version : void 0;
43435
43650
  const integrity = typeof entry.integrity === "string" ? entry.integrity : void 0;
43436
43651
  if (!version || !integrity) continue;
43437
- const idx = path2.lastIndexOf("node_modules/");
43652
+ const idx = path.lastIndexOf("node_modules/");
43438
43653
  if (idx < 0) continue;
43439
- const name = path2.slice(idx + "node_modules/".length);
43654
+ const name = path.slice(idx + "node_modules/".length);
43440
43655
  if (!name) continue;
43441
43656
  const hex = npmIntegrityToSha512Hex(integrity);
43442
43657
  if (hex) out.set(`${name}@${version}`, hex);
@@ -43678,7 +43893,7 @@ var init_FileSavePrompt = __esm({
43678
43893
  return;
43679
43894
  }
43680
43895
  if ((key.backspace || key.delete) && !state.filterText) {
43681
- const parentDir = path.dirname(state.directory);
43896
+ const parentDir = dirname8(state.directory);
43682
43897
  if (parentDir !== state.directory) {
43683
43898
  dispatch({ type: "SET_DIRECTORY", directory: parentDir, entries: listDirectory(parentDir) });
43684
43899
  }
@@ -43691,7 +43906,7 @@ var init_FileSavePrompt = __esm({
43691
43906
  if (key.return) {
43692
43907
  const entry = filteredEntries[state.browserCursor];
43693
43908
  if (!entry || !entry.isDirectory) return;
43694
- const newDir = entry.name === ".." ? path.dirname(state.directory) : path.join(state.directory, entry.name);
43909
+ const newDir = entry.name === ".." ? dirname8(state.directory) : join8(state.directory, entry.name);
43695
43910
  dispatch({ type: "SET_DIRECTORY", directory: newDir, entries: listDirectory(newDir) });
43696
43911
  return;
43697
43912
  }
@@ -43967,9 +44182,9 @@ async function scanDir(dir, depth, _root) {
43967
44182
  }
43968
44183
  return { subdirs, lockfileHits };
43969
44184
  }
43970
- async function isSafeRegularFile(path2) {
44185
+ async function isSafeRegularFile(path) {
43971
44186
  try {
43972
- const st = await lstat(path2);
44187
+ const st = await lstat(path);
43973
44188
  return st.isFile() && !st.isSymbolicLink();
43974
44189
  } catch {
43975
44190
  return false;
@@ -44104,9 +44319,9 @@ function discoverProjectsConsolidated(projectPaths, config) {
44104
44319
  const seenPy = /* @__PURE__ */ new Set();
44105
44320
  const unparseable = [];
44106
44321
  let rawCount = 0;
44107
- for (const path2 of projectPaths) {
44322
+ for (const path of projectPaths) {
44108
44323
  try {
44109
- const d = discoverChanges(path2, config);
44324
+ const d = discoverChanges(path, config);
44110
44325
  for (const pkg of d.packages) {
44111
44326
  rawCount++;
44112
44327
  const key = `${pkg.name}@${pkg.version}`;
@@ -44122,7 +44337,7 @@ function discoverProjectsConsolidated(projectPaths, config) {
44122
44337
  pythonPackages.push(pkg);
44123
44338
  }
44124
44339
  } catch {
44125
- unparseable.push(path2);
44340
+ unparseable.push(path);
44126
44341
  }
44127
44342
  }
44128
44343
  return {
@@ -44371,21 +44586,21 @@ function validateAllowlist(raw) {
44371
44586
  return { ok: true, allowlist: { schemaVersion: 1, rules, packages } };
44372
44587
  }
44373
44588
  function loadAllowlist(cwd2) {
44374
- const path2 = findAllowlistFile(cwd2);
44375
- if (!path2) return null;
44589
+ const path = findAllowlistFile(cwd2);
44590
+ if (!path) return null;
44376
44591
  let raw;
44377
44592
  try {
44378
- raw = JSON.parse(readFileSync10(path2, "utf-8"));
44593
+ raw = JSON.parse(readFileSync10(path, "utf-8"));
44379
44594
  } catch (e) {
44380
44595
  process.stderr.write(
44381
- `dg: .dg-allowlist.json at ${path2} is not valid JSON (${e.message}). Ignoring.
44596
+ `dg: .dg-allowlist.json at ${path} is not valid JSON (${e.message}). Ignoring.
44382
44597
  `
44383
44598
  );
44384
44599
  return null;
44385
44600
  }
44386
44601
  const validation = validateAllowlist(raw);
44387
44602
  if (!validation.ok) {
44388
- process.stderr.write(`dg: .dg-allowlist.json at ${path2} failed validation:
44603
+ process.stderr.write(`dg: .dg-allowlist.json at ${path} failed validation:
44389
44604
  `);
44390
44605
  for (const err of validation.errors) {
44391
44606
  process.stderr.write(` ${err.path}: ${err.message}
@@ -44395,7 +44610,7 @@ function loadAllowlist(cwd2) {
44395
44610
  `);
44396
44611
  return null;
44397
44612
  }
44398
- return { ...validation.allowlist, sourcePath: path2 };
44613
+ return { ...validation.allowlist, sourcePath: path };
44399
44614
  }
44400
44615
  function findingRuleId(f) {
44401
44616
  return (f.category ?? f.title ?? "").toString();
@@ -44540,6 +44755,7 @@ function buildSarif(response, opts = {}) {
44540
44755
  });
44541
44756
  }
44542
44757
  const findings = pkg.findings ?? [];
44758
+ let pkgResultCount = 0;
44543
44759
  for (const f of findings) {
44544
44760
  const id = findingId(f);
44545
44761
  const level = severityToLevel(f.severity);
@@ -44563,6 +44779,32 @@ function buildSarif(response, opts = {}) {
44563
44779
  }],
44564
44780
  partialFingerprints: { dg_finding: findingFingerprint(pkg, f) }
44565
44781
  });
44782
+ pkgResultCount++;
44783
+ }
44784
+ if (pkgResultCount === 0 && (pkg.action === "block" || pkg.action === "warn")) {
44785
+ const ruleId = pkg.action === "block" ? "dg.block" : "dg.warn";
44786
+ const level = pkg.action === "block" ? "error" : "warning";
44787
+ if (!ruleMap.has(ruleId)) {
44788
+ ruleMap.set(ruleId, {
44789
+ id: ruleId,
44790
+ name: ruleId,
44791
+ shortDescription: {
44792
+ text: pkg.action === "block" ? "Package blocked by Dependency Guardian" : "Package flagged (warn) by Dependency Guardian"
44793
+ },
44794
+ defaultConfiguration: { level }
44795
+ });
44796
+ }
44797
+ results.push({
44798
+ ruleId,
44799
+ level,
44800
+ message: { text: `${pkg.name} ${pkg.version}: ${pkg.action} verdict from Dependency Guardian (no individual findings surfaced at this tier).` },
44801
+ locations: [{
44802
+ physicalLocation: {
44803
+ artifactLocation: { uri: opts.lockfileUri ?? `dg:${pkg.name}@${pkg.version}` }
44804
+ }
44805
+ }],
44806
+ partialFingerprints: { dg_finding: `${pkg.name}@${pkg.version}#${ruleId}`.slice(0, 240) }
44807
+ });
44566
44808
  }
44567
44809
  }
44568
44810
  return {
@@ -44699,7 +44941,7 @@ function shouldFireAudit(policy, decisionAction) {
44699
44941
  return false;
44700
44942
  }
44701
44943
  function flagPackages(packages) {
44702
- return packages.filter((p) => p.score >= 60);
44944
+ return packages.filter((p) => (p.action ?? "pass") !== "pass");
44703
44945
  }
44704
44946
  async function dispatchPublishCheckAudit(opts) {
44705
44947
  try {
@@ -44726,8 +44968,8 @@ function bypassLogPath2() {
44726
44968
  }
44727
44969
  function recordBypassLocally(record) {
44728
44970
  try {
44729
- const path2 = bypassLogPath2();
44730
- const dir = dirname10(path2);
44971
+ const path = bypassLogPath2();
44972
+ const dir = dirname10(path);
44731
44973
  if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
44732
44974
  const full = {
44733
44975
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -44738,7 +44980,7 @@ function recordBypassLocally(record) {
44738
44980
  reason: record.reason,
44739
44981
  packages: record.packages
44740
44982
  };
44741
- appendFileSync3(path2, JSON.stringify(full) + "\n", { mode: 384 });
44983
+ appendFileSync3(path, JSON.stringify(full) + "\n", { mode: 384 });
44742
44984
  } catch {
44743
44985
  }
44744
44986
  }
@@ -44797,10 +45039,6 @@ function note(config, text) {
44797
45039
  if (config.quiet) return;
44798
45040
  process.stderr.write(text);
44799
45041
  }
44800
- function emitWrapperJson(config, result) {
44801
- if (!config.json) return;
44802
- process.stdout.write(JSON.stringify(result, null, 2) + "\n");
44803
- }
44804
45042
  function wrapperPackagesFromResult(result, topLevelNames) {
44805
45043
  return result.packages.map((p) => ({
44806
45044
  name: p.name,
@@ -44811,13 +45049,13 @@ function wrapperPackagesFromResult(result, topLevelNames) {
44811
45049
  }));
44812
45050
  }
44813
45051
  function loadPackageLockJson(cwd2 = process.cwd()) {
44814
- const path2 = resolvePath(cwd2, "package-lock.json");
44815
- if (!existsSync17(path2)) return null;
45052
+ const path = resolvePath(cwd2, "package-lock.json");
45053
+ if (!existsSync17(path)) return null;
44816
45054
  try {
44817
- const st = lstatSync5(path2);
45055
+ const st = lstatSync5(path);
44818
45056
  if (!st.isFile()) return null;
44819
45057
  if (st.size > MAX_PACKAGE_LOCK_BYTES) return null;
44820
- return JSON.parse(readFileSync11(path2, "utf8"));
45058
+ return JSON.parse(readFileSync11(path, "utf8"));
44821
45059
  } catch {
44822
45060
  return null;
44823
45061
  }
@@ -44897,7 +45135,7 @@ function maybePrintFirstScanNotice(config) {
44897
45135
  if (getScanNoticeShownAt2() !== null) return;
44898
45136
  process.stderr.write(
44899
45137
  import_chalk5.default.dim(
44900
- " Dependency Guardian sends package names + versions to api.westbayberry.com.\n Anonymous crash reports are on; set DG_TELEMETRY=0 to opt out.\n Terms: https://westbayberry.com/terms \xB7 Privacy: https://westbayberry.com/privacy\n"
45138
+ " Dependency Guardian sends package names + versions to api.westbayberry.com.\n Anonymous crash reports are on to help us fix early bugs (no source, paths, or package names);\n opt out with DG_TELEMETRY=0 or DO_NOT_TRACK=1.\n Terms: https://westbayberry.com/terms \xB7 Privacy: https://westbayberry.com/privacy\n"
44901
45139
  )
44902
45140
  );
44903
45141
  recordScanNoticeShown2();
@@ -45228,14 +45466,7 @@ async function runStatic(config) {
45228
45466
  }
45229
45467
  if (discovery.pythonPackages.length > 0) {
45230
45468
  const pyResult = await callPyPIAnalyzeAPI(discovery.pythonPackages, config);
45231
- result.packages.push(...pyResult.packages);
45232
- result.score = Math.max(result.score, pyResult.score);
45233
- if (pyResult.action === "block" || pyResult.action === "warn" && result.action === "pass") {
45234
- result.action = pyResult.action;
45235
- }
45236
- if (pyResult.freeScansRemaining !== void 0) {
45237
- result.freeScansRemaining = pyResult.freeScansRemaining;
45238
- }
45469
+ result = mergeResponses([result, pyResult]);
45239
45470
  }
45240
45471
  clearSpinner();
45241
45472
  dbg(
@@ -45292,11 +45523,7 @@ async function runStatic(config) {
45292
45523
  printTrialBanner(result, config);
45293
45524
  process.exit(scanExitCode(result.action, config.mode));
45294
45525
  }
45295
- function mergeProjectConfig(config, _argv) {
45296
- return config;
45297
- }
45298
45526
  async function runStaticNpm(npmArgs, config) {
45299
- config = mergeProjectConfig(config, process.argv.slice(2));
45300
45527
  const parsed = parseNpmArgs(npmArgs);
45301
45528
  if (!parsed.shouldScan) {
45302
45529
  const code = await runNpm(parsed.rawArgs);
@@ -45399,11 +45626,11 @@ function buildInstallArgs(parsed, topLevelSpecs, tree, config) {
45399
45626
  if (tree.length > 0 && topLevelSpecs.length > 0) {
45400
45627
  args = pinTopLevelArgs(args, topLevelSpecs, tree);
45401
45628
  }
45402
- const wantsNoScripts = parsed.dgNoScripts || config.strict;
45403
- if (wantsNoScripts && !args.includes("--ignore-scripts")) {
45404
- args = [...args, "--ignore-scripts"];
45405
- }
45406
- return args;
45629
+ return injectIgnoreScripts(args, {
45630
+ isNpmFamily: true,
45631
+ strict: config.strict,
45632
+ dgNoScripts: parsed.dgNoScripts
45633
+ });
45407
45634
  }
45408
45635
  async function scanAndInstallStatic(resolved, parsed, config) {
45409
45636
  const topLevelSpecs = resolved.map((p) => `${p.name}@${p.version}`);
@@ -45432,6 +45659,14 @@ async function scanAndInstallStatic(resolved, parsed, config) {
45432
45659
  process.stderr.write(
45433
45660
  import_chalk5.default.dim(" Only top-level packages would be scanned; transitive deps would slip through. Use --dg-force to bypass.\n\n")
45434
45661
  );
45662
+ emitWrapperJson(config, {
45663
+ ecosystem: "npm",
45664
+ packages: [],
45665
+ scanVerdict: "scan_failed",
45666
+ installRan: false,
45667
+ installExitCode: null,
45668
+ error: { code: "tree_resolution_failed", message: treeError }
45669
+ });
45435
45670
  process.exit(2);
45436
45671
  }
45437
45672
  process.stderr.write(
@@ -45540,120 +45775,110 @@ async function scanAndInstallStatic(resolved, parsed, config) {
45540
45775
  );
45541
45776
  }
45542
45777
  }
45543
- if (result.action === "pass") {
45544
- const topLevel = resolved.map((p) => `${p.name} ${p.version}`);
45545
- const passLine = topLevel.length === 1 ? ` ${import_chalk5.default.green("\u2713")} ${import_chalk5.default.bold("DG verified:")} ${topLevel[0]} is safe` : ` ${import_chalk5.default.green("\u2713")} ${import_chalk5.default.bold("DG verified:")} ${topLevel.length} packages safe`;
45546
- const installLine = topLevel.length === 1 ? ` ${import_chalk5.default.green("\u2713")} Installing ${topLevel[0]}` : ` ${import_chalk5.default.green("\u2713")} Installing ${topLevel.join(", ")}`;
45547
- process.stderr.write(`${passLine}
45778
+ const npmInstallArgs = buildInstallArgs(parsed, topLevelSpecs, tree.packages, config);
45779
+ const npmTopLevelNames = new Set(resolved.map((p) => p.name));
45780
+ const npmDecision = routeVerdict({
45781
+ verdict: verdictFromResult(result),
45782
+ mode: config.mode,
45783
+ strict: config.strict,
45784
+ dgForce: parsed.dgForce
45785
+ });
45786
+ const npmPresenter = {
45787
+ async announce(d) {
45788
+ if (d.verdict === "pass") {
45789
+ const topLevel2 = resolved.map((p) => `${p.name} ${p.version}`);
45790
+ const passLine = topLevel2.length === 1 ? ` ${import_chalk5.default.green("\u2713")} ${import_chalk5.default.bold("DG verified:")} ${topLevel2[0]} is safe` : ` ${import_chalk5.default.green("\u2713")} ${import_chalk5.default.bold("DG verified:")} ${topLevel2.length} packages safe`;
45791
+ const installLine = topLevel2.length === 1 ? ` ${import_chalk5.default.green("\u2713")} Installing ${topLevel2[0]}` : ` ${import_chalk5.default.green("\u2713")} Installing ${topLevel2.join(", ")}`;
45792
+ process.stderr.write(`${passLine}
45548
45793
  ${installLine}
45549
45794
  `);
45550
- if (parsed.dgNoScripts || config.strict) {
45551
- const why = config.strict ? "--strict" : "--dg-no-scripts";
45552
- process.stderr.write(import_chalk5.default.dim(` ${why}: lifecycle scripts will be suppressed during install.
45795
+ if (parsed.dgNoScripts || config.strict) {
45796
+ const why = config.strict ? "--strict" : "--dg-no-scripts";
45797
+ process.stderr.write(import_chalk5.default.dim(` ${why}: lifecycle scripts will be suppressed during install.
45553
45798
  `));
45554
- }
45555
- printTrialBanner(result, config);
45556
- process.stderr.write("\n");
45557
- const code = await runNpm(buildInstallArgs(parsed, topLevelSpecs, tree.packages, config), { stdoutToStderr: !!config.json });
45558
- emitWrapperJson(config, {
45559
- ecosystem: "npm",
45560
- packages: wrapperPackagesFromResult(result, new Set(resolved.map((p) => p.name))),
45561
- scanVerdict: "pass",
45562
- installRan: true,
45563
- installExitCode: code
45564
- });
45565
- process.exit(code);
45566
- }
45567
- const output = renderResultStatic(result, config);
45568
- (config.json ? process.stderr : process.stdout).write(output + "\n");
45569
- printTrialBanner(result, config);
45570
- if (result.action === "warn") {
45571
- const { promptYesNo: promptYesNo2 } = await Promise.resolve().then(() => (init_prompt(), prompt_exports));
45572
- const topLevel = resolved.map((p) => `${p.name} ${p.version}`).join(", ");
45573
- process.stderr.write(
45574
- ` ${import_chalk5.default.yellow("\u26A0")} ${import_chalk5.default.bold("DG flagged")} ${topLevel}.
45575
- `
45576
- );
45577
- const proceed = await promptYesNo2({
45578
- defaultAnswer: "y",
45579
- message: ` Proceed?`
45580
- });
45581
- if (!proceed) {
45582
- process.stderr.write(import_chalk5.default.dim(" Install cancelled by user.\n\n"));
45583
- emitWrapperJson(config, {
45584
- ecosystem: "npm",
45585
- packages: wrapperPackagesFromResult(result, new Set(resolved.map((p) => p.name))),
45586
- scanVerdict: "warn",
45587
- installRan: false,
45588
- installExitCode: null
45589
- });
45590
- process.exit(1);
45591
- }
45592
- const code = await runNpm(buildInstallArgs(parsed, topLevelSpecs, tree.packages, config), { stdoutToStderr: !!config.json });
45593
- emitWrapperJson(config, {
45594
- ecosystem: "npm",
45595
- packages: wrapperPackagesFromResult(result, new Set(resolved.map((p) => p.name))),
45596
- scanVerdict: "warn",
45597
- installRan: true,
45598
- installExitCode: code
45599
- });
45600
- process.exit(code);
45601
- }
45602
- if (result.action === "block") {
45603
- if (parsed.dgForce) {
45604
- const { dispatchInstallAudit: dispatchInstallAudit3 } = await Promise.resolve().then(() => (init_audit_dispatcher(), audit_dispatcher_exports));
45605
- await dispatchInstallAudit3({
45799
+ }
45800
+ printTrialBanner(result, config);
45801
+ process.stderr.write("\n");
45802
+ return;
45803
+ }
45804
+ const output = renderResultStatic(result, config);
45805
+ (config.json ? process.stderr : process.stdout).write(output + "\n");
45806
+ printTrialBanner(result, config);
45807
+ const topLevel = resolved.map((p) => `${p.name} ${p.version}`).join(", ");
45808
+ if (d.verdict === "warn") {
45809
+ process.stderr.write(` ${import_chalk5.default.yellow("\u26A0")} ${import_chalk5.default.bold("DG flagged")} ${topLevel}.
45810
+ `);
45811
+ } else if (d.verdict === "block") {
45812
+ if (d.outcome === "install") {
45813
+ process.stderr.write(import_chalk5.default.yellow(import_chalk5.default.bold(" --dg-force: Bypassing block. Install at your own risk.\n\n")));
45814
+ } else {
45815
+ const blockedSpecArg = topLevelSpecs.length > 0 ? topLevelSpecs.join(" ") : topLevel.split(", ").map((s) => s.replace(/ /g, "@")).join(" ");
45816
+ process.stderr.write(
45817
+ ` ${import_chalk5.default.red("\u2717")} ${import_chalk5.default.bold("DG blocked")} ${topLevel}.
45818
+ Real install was NOT run. To override:
45819
+ ` + import_chalk5.default.dim(` npm install ${blockedSpecArg} --dg-force --dg-force-reason="<reason>"
45820
+
45821
+ `)
45822
+ );
45823
+ }
45824
+ } else if (d.verdict === "analysis_incomplete") {
45825
+ if (d.outcome === "refuse") {
45826
+ const specArg = topLevelSpecs.length > 0 ? topLevelSpecs.join(" ") : resolved.map((p) => `${p.name}@${p.version}`).join(" ");
45827
+ process.stderr.write(
45828
+ ` ${import_chalk5.default.cyan("?")} ${import_chalk5.default.bold("DG could not verify")} ${topLevel} \u2014 treating as unverified, not safe.
45829
+ Real install was NOT run (--mode block). To override:
45830
+ ` + import_chalk5.default.dim(` npm install ${specArg} --dg-force --dg-force-reason="<reason>"
45831
+
45832
+ `)
45833
+ );
45834
+ } else {
45835
+ process.stderr.write(import_chalk5.default.dim(` DG could not fully verify ${topLevel}; proceeding (mode is not block).
45836
+
45837
+ `));
45838
+ }
45839
+ }
45840
+ },
45841
+ async confirmProceed() {
45842
+ const { promptYesNo: promptYesNo2 } = await Promise.resolve().then(() => (init_prompt(), prompt_exports));
45843
+ const proceed = await promptYesNo2({ defaultAnswer: "y", message: ` Proceed?` });
45844
+ if (!proceed) process.stderr.write(import_chalk5.default.dim(" Install cancelled by user.\n\n"));
45845
+ return proceed;
45846
+ },
45847
+ runInstall: (args) => runNpm(args, { stdoutToStderr: !!config.json }),
45848
+ async audit(kind) {
45849
+ const { dispatchInstallAudit: dispatchInstallAudit2 } = await Promise.resolve().then(() => (init_audit_dispatcher(), audit_dispatcher_exports));
45850
+ await dispatchInstallAudit2({
45606
45851
  ecosystem: "npm",
45607
45852
  packages: result.packages,
45608
- decisionAction: "override",
45609
- bypassed: true,
45610
- bypassReason: parsed.dgForceReason
45853
+ decisionAction: kind,
45854
+ bypassed: kind === "override",
45855
+ bypassReason: kind === "override" ? parsed.dgForceReason : void 0
45611
45856
  });
45612
- process.stderr.write(
45613
- import_chalk5.default.yellow(
45614
- import_chalk5.default.bold(
45615
- " --dg-force: Bypassing block. Install at your own risk.\n\n"
45616
- )
45617
- )
45618
- );
45619
- const code = await runNpm(buildInstallArgs(parsed, topLevelSpecs, tree.packages, config), { stdoutToStderr: !!config.json });
45857
+ },
45858
+ emit(record) {
45620
45859
  emitWrapperJson(config, {
45621
45860
  ecosystem: "npm",
45622
- packages: wrapperPackagesFromResult(result, new Set(resolved.map((p) => p.name))),
45623
- scanVerdict: "override",
45624
- installRan: true,
45625
- installExitCode: code
45861
+ packages: wrapperPackagesFromResult(result, npmTopLevelNames),
45862
+ scanVerdict: record.scanVerdict,
45863
+ installRan: record.installRan,
45864
+ installExitCode: record.installExitCode,
45865
+ error: record.error
45626
45866
  });
45627
- process.exit(code);
45628
45867
  }
45629
- const { dispatchInstallAudit: dispatchInstallAudit2 } = await Promise.resolve().then(() => (init_audit_dispatcher(), audit_dispatcher_exports));
45630
- await dispatchInstallAudit2({
45631
- ecosystem: "npm",
45632
- packages: result.packages,
45633
- decisionAction: "block",
45634
- bypassed: false
45635
- });
45636
- const topLevelBlocked = resolved.map((p) => `${p.name} ${p.version}`).join(", ");
45637
- const blockedSpecArg = topLevelSpecs.length > 0 ? topLevelSpecs.join(" ") : topLevelBlocked.split(", ").map((s) => s.replace(/ /g, "@")).join(" ");
45638
- process.stderr.write(
45639
- ` ${import_chalk5.default.red("\u2717")} ${import_chalk5.default.bold("DG blocked")} ${topLevelBlocked}.
45640
- Real install was NOT run. To override:
45641
- ` + import_chalk5.default.dim(` npm install ${blockedSpecArg} --dg-force --dg-force-reason="<reason>"
45642
-
45643
- `)
45644
- );
45645
- emitWrapperJson(config, {
45646
- ecosystem: "npm",
45647
- packages: wrapperPackagesFromResult(result, new Set(resolved.map((p) => p.name))),
45648
- scanVerdict: "block",
45649
- installRan: false,
45650
- installExitCode: null
45651
- });
45652
- process.exit(2);
45653
- }
45868
+ };
45869
+ const npmCtx = {
45870
+ ecosystem: "npm",
45871
+ apiKind: "npm",
45872
+ installArgs: npmInstallArgs,
45873
+ passthroughArgs: parsed.rawArgs,
45874
+ result,
45875
+ resolved,
45876
+ suppressedCount: 0,
45877
+ dgForceReason: parsed.dgForceReason
45878
+ };
45879
+ process.exit(await executeDecision(npmDecision, npmCtx, npmPresenter));
45654
45880
  }
45655
45881
  async function runStaticPip(pipArgs, config) {
45656
- config = mergeProjectConfig(config, process.argv.slice(2));
45657
45882
  const parsed = parsePipArgs(pipArgs);
45658
45883
  if (!parsed.shouldScan) {
45659
45884
  const code = await runPip(parsed.rawArgs, { stdoutToStderr: !!config.json });
@@ -45740,6 +45965,17 @@ async function runStaticPip(pipArgs, config) {
45740
45965
  )
45741
45966
  );
45742
45967
  }
45968
+ const isRequirementsInstall = !!parsed.requirementsFile;
45969
+ if (isRequirementsInstall && !(pipTreeOk && pipTree.hashes.size > 0) && (config.mode === "block" || config.strict) && !parsed.dgForce) {
45970
+ process.stderr.write(
45971
+ import_chalk5.default.red(import_chalk5.default.bold(" BLOCKED: ")) + import_chalk5.default.red(`a -r requirements install cannot be pinned to exact versions and no dry-run hashes are available to verify the artifacts.
45972
+ `) + import_chalk5.default.dim(` Install aborted${config.strict ? " in --strict" : " in --mode block"} (resolve\u2192install mismatch risk). Use --mode warn or --dg-force to bypass.
45973
+
45974
+ `)
45975
+ );
45976
+ process.exit(2);
45977
+ }
45978
+ const pipInstallArgs = pinPipArgs(parsed.rawArgs, parsed.packages, resolved);
45743
45979
  note(
45744
45980
  config,
45745
45981
  import_chalk5.default.dim(
@@ -45768,14 +46004,14 @@ async function runStaticPip(pipArgs, config) {
45768
46004
  process.stderr.write(
45769
46005
  import_chalk5.default.yellow(import_chalk5.default.bold(" --dg-force: Bypassing block. Install at your own risk.\n\n"))
45770
46006
  );
45771
- const code2 = await runPip(parsed.rawArgs, { stdoutToStderr: !!config.json });
46007
+ const code2 = await runPip(pipInstallArgs, { stdoutToStderr: !!config.json });
45772
46008
  process.exit(code2);
45773
46009
  }
45774
46010
  process.stderr.write(
45775
46011
  import_chalk5.default.yellow(` Warning: Scan failed (${msg}). Proceeding with install (mode=${config.mode}).
45776
46012
  `)
45777
46013
  );
45778
- const code = await runPip(parsed.rawArgs, { stdoutToStderr: !!config.json });
46014
+ const code = await runPip(pipInstallArgs, { stdoutToStderr: !!config.json });
45779
46015
  process.exit(code);
45780
46016
  return;
45781
46017
  }
@@ -45803,105 +46039,101 @@ async function runStaticPip(pipArgs, config) {
45803
46039
  }
45804
46040
  }
45805
46041
  const pipTopLevelNames = new Set(resolved.map((p) => p.name));
45806
- if (result.action === "pass") {
45807
- const topLevel = resolved.map((p) => `${p.name} ${p.version}`);
45808
- const passLine = topLevel.length === 1 ? ` ${import_chalk5.default.green("\u2713")} ${import_chalk5.default.bold("DG verified:")} ${topLevel[0]} is safe` : ` ${import_chalk5.default.green("\u2713")} ${import_chalk5.default.bold("DG verified:")} ${topLevel.length} packages safe`;
45809
- const installLine = topLevel.length === 1 ? ` ${import_chalk5.default.green("\u2713")} Installing ${topLevel[0]}` : ` ${import_chalk5.default.green("\u2713")} Installing ${topLevel.join(", ")}`;
45810
- process.stderr.write(`${passLine}
46042
+ const pipDecision = routeVerdict({
46043
+ verdict: verdictFromResult(result),
46044
+ mode: config.mode,
46045
+ strict: config.strict,
46046
+ dgForce: parsed.dgForce
46047
+ });
46048
+ const pipPresenter = {
46049
+ async announce(d) {
46050
+ if (d.verdict === "pass") {
46051
+ const topLevel2 = resolved.map((p) => `${p.name} ${p.version}`);
46052
+ const passLine = topLevel2.length === 1 ? ` ${import_chalk5.default.green("\u2713")} ${import_chalk5.default.bold("DG verified:")} ${topLevel2[0]} is safe` : ` ${import_chalk5.default.green("\u2713")} ${import_chalk5.default.bold("DG verified:")} ${topLevel2.length} packages safe`;
46053
+ const installLine = topLevel2.length === 1 ? ` ${import_chalk5.default.green("\u2713")} Installing ${topLevel2[0]}` : ` ${import_chalk5.default.green("\u2713")} Installing ${topLevel2.join(", ")}`;
46054
+ process.stderr.write(`${passLine}
45811
46055
  ${installLine}
45812
46056
  `);
45813
- printTrialBanner(result, config);
45814
- process.stderr.write("\n");
45815
- const code = await runPip(parsed.rawArgs, { stdoutToStderr: !!config.json });
45816
- emitWrapperJson(config, {
45817
- ecosystem: "pypi",
45818
- packages: wrapperPackagesFromResult(result, pipTopLevelNames),
45819
- scanVerdict: "pass",
45820
- installRan: true,
45821
- installExitCode: code
45822
- });
45823
- process.exit(code);
45824
- }
45825
- const output = renderResultStatic(result, config);
45826
- (config.json ? process.stderr : process.stdout).write(output + "\n");
45827
- printTrialBanner(result, config);
45828
- if (result.action === "warn") {
45829
- const { promptYesNo: promptYesNo2 } = await Promise.resolve().then(() => (init_prompt(), prompt_exports));
45830
- const topLevel = resolved.map((p) => `${p.name} ${p.version}`).join(", ");
45831
- process.stderr.write(
45832
- ` ${import_chalk5.default.yellow("\u26A0")} ${import_chalk5.default.bold("DG flagged")} ${topLevel}.
45833
- `
45834
- );
45835
- const proceed = await promptYesNo2({ defaultAnswer: "y", message: " Proceed?" });
45836
- if (!proceed) {
45837
- process.stderr.write(import_chalk5.default.dim(" Install cancelled by user.\n\n"));
45838
- emitWrapperJson(config, {
45839
- ecosystem: "pypi",
45840
- packages: wrapperPackagesFromResult(result, pipTopLevelNames),
45841
- scanVerdict: "warn",
45842
- installRan: false,
45843
- installExitCode: null
45844
- });
45845
- process.exit(1);
45846
- }
45847
- const code = await runPip(parsed.rawArgs, { stdoutToStderr: !!config.json });
45848
- emitWrapperJson(config, {
45849
- ecosystem: "pypi",
45850
- packages: wrapperPackagesFromResult(result, pipTopLevelNames),
45851
- scanVerdict: "warn",
45852
- installRan: true,
45853
- installExitCode: code
45854
- });
45855
- process.exit(code);
45856
- }
45857
- if (result.action === "block") {
45858
- if (parsed.dgForce) {
45859
- const { dispatchInstallAudit: dispatchInstallAudit3 } = await Promise.resolve().then(() => (init_audit_dispatcher(), audit_dispatcher_exports));
45860
- await dispatchInstallAudit3({
46057
+ printTrialBanner(result, config);
46058
+ process.stderr.write("\n");
46059
+ return;
46060
+ }
46061
+ const output = renderResultStatic(result, config);
46062
+ (config.json ? process.stderr : process.stdout).write(output + "\n");
46063
+ printTrialBanner(result, config);
46064
+ const topLevel = resolved.map((p) => `${p.name} ${p.version}`).join(", ");
46065
+ if (d.verdict === "warn") {
46066
+ process.stderr.write(` ${import_chalk5.default.yellow("\u26A0")} ${import_chalk5.default.bold("DG flagged")} ${topLevel}.
46067
+ `);
46068
+ } else if (d.verdict === "block") {
46069
+ if (d.outcome === "install") {
46070
+ process.stderr.write(import_chalk5.default.yellow(import_chalk5.default.bold(" --dg-force: Bypassing block. Install at your own risk.\n\n")));
46071
+ } else {
46072
+ const pipBlockedSpecArg = resolved.map((p) => `${p.name}==${p.version}`).join(" ");
46073
+ process.stderr.write(
46074
+ ` ${import_chalk5.default.red("\u2717")} ${import_chalk5.default.bold("DG blocked")} ${topLevel}.
46075
+ Real install was NOT run. To override:
46076
+ ` + import_chalk5.default.dim(` pip install ${pipBlockedSpecArg} --dg-force --dg-force-reason="<reason>"
46077
+
46078
+ `)
46079
+ );
46080
+ }
46081
+ } else if (d.verdict === "analysis_incomplete") {
46082
+ if (d.outcome === "refuse") {
46083
+ const pipSpecArg = resolved.map((p) => `${p.name}==${p.version}`).join(" ");
46084
+ process.stderr.write(
46085
+ ` ${import_chalk5.default.cyan("?")} ${import_chalk5.default.bold("DG could not verify")} ${topLevel} \u2014 treating as unverified, not safe.
46086
+ Real install was NOT run (--mode block). To override:
46087
+ ` + import_chalk5.default.dim(` pip install ${pipSpecArg} --dg-force --dg-force-reason="<reason>"
46088
+
46089
+ `)
46090
+ );
46091
+ } else {
46092
+ process.stderr.write(import_chalk5.default.dim(` DG could not fully verify ${topLevel}; proceeding (mode is not block).
46093
+
46094
+ `));
46095
+ }
46096
+ }
46097
+ },
46098
+ async confirmProceed() {
46099
+ const { promptYesNo: promptYesNo2 } = await Promise.resolve().then(() => (init_prompt(), prompt_exports));
46100
+ const proceed = await promptYesNo2({ defaultAnswer: "y", message: " Proceed?" });
46101
+ if (!proceed) process.stderr.write(import_chalk5.default.dim(" Install cancelled by user.\n\n"));
46102
+ return proceed;
46103
+ },
46104
+ runInstall: (args) => runPip(args, { stdoutToStderr: !!config.json }),
46105
+ async audit(kind) {
46106
+ const { dispatchInstallAudit: dispatchInstallAudit2 } = await Promise.resolve().then(() => (init_audit_dispatcher(), audit_dispatcher_exports));
46107
+ await dispatchInstallAudit2({
45861
46108
  ecosystem: "pypi",
45862
46109
  packages: result.packages,
45863
- decisionAction: "override",
45864
- bypassed: true,
45865
- bypassReason: parsed.dgForceReason
46110
+ decisionAction: kind,
46111
+ bypassed: kind === "override",
46112
+ bypassReason: kind === "override" ? parsed.dgForceReason : void 0
45866
46113
  });
45867
- process.stderr.write(
45868
- import_chalk5.default.yellow(import_chalk5.default.bold(" --dg-force: Bypassing block. Install at your own risk.\n\n"))
45869
- );
45870
- const code = await runPip(parsed.rawArgs, { stdoutToStderr: !!config.json });
46114
+ },
46115
+ emit(record) {
45871
46116
  emitWrapperJson(config, {
45872
46117
  ecosystem: "pypi",
45873
46118
  packages: wrapperPackagesFromResult(result, pipTopLevelNames),
45874
- scanVerdict: "override",
45875
- installRan: true,
45876
- installExitCode: code
46119
+ scanVerdict: record.scanVerdict,
46120
+ installRan: record.installRan,
46121
+ installExitCode: record.installExitCode,
46122
+ error: record.error
45877
46123
  });
45878
- process.exit(code);
45879
46124
  }
45880
- const { dispatchInstallAudit: dispatchInstallAudit2 } = await Promise.resolve().then(() => (init_audit_dispatcher(), audit_dispatcher_exports));
45881
- await dispatchInstallAudit2({
45882
- ecosystem: "pypi",
45883
- packages: result.packages,
45884
- decisionAction: "block",
45885
- bypassed: false
45886
- });
45887
- const pipTopLevelBlocked = resolved.map((p) => `${p.name} ${p.version}`).join(", ");
45888
- const pipBlockedSpecArg = resolved.map((p) => `${p.name}==${p.version}`).join(" ");
45889
- process.stderr.write(
45890
- ` ${import_chalk5.default.red("\u2717")} ${import_chalk5.default.bold("DG blocked")} ${pipTopLevelBlocked}.
45891
- Real install was NOT run. To override:
45892
- ` + import_chalk5.default.dim(` pip install ${pipBlockedSpecArg} --dg-force --dg-force-reason="<reason>"
45893
-
45894
- `)
45895
- );
45896
- emitWrapperJson(config, {
45897
- ecosystem: "pypi",
45898
- packages: wrapperPackagesFromResult(result, pipTopLevelNames),
45899
- scanVerdict: "block",
45900
- installRan: false,
45901
- installExitCode: null
45902
- });
45903
- process.exit(2);
45904
- }
46125
+ };
46126
+ const pipCtx = {
46127
+ ecosystem: "pypi",
46128
+ apiKind: "pypi",
46129
+ installArgs: pipInstallArgs,
46130
+ passthroughArgs: parsed.rawArgs,
46131
+ result,
46132
+ resolved,
46133
+ suppressedCount: 0,
46134
+ dgForceReason: parsed.dgForceReason
46135
+ };
46136
+ process.exit(await executeDecision(pipDecision, pipCtx, pipPresenter));
45905
46137
  }
45906
46138
  async function runStaticLogin() {
45907
46139
  const {
@@ -46014,6 +46246,10 @@ var init_static_output = __esm({
46014
46246
  init_lockfile();
46015
46247
  init_npm_wrapper();
46016
46248
  init_pip_wrapper();
46249
+ init_wrapper_shared();
46250
+ init_route();
46251
+ init_run();
46252
+ init_json();
46017
46253
  init_artifact_integrity();
46018
46254
  init_sanitize();
46019
46255
  init_format_helpers();
@@ -46808,11 +47044,11 @@ var init_InteractiveResultsView = __esm({
46808
47044
  const scanUsage = usageDisplay ? usageDisplay.text : result.freeScansRemaining !== void 0 ? `${result.freeScansRemaining.toLocaleString()} packages left` : scanUsageProp;
46809
47045
  const usageNearLimit = usageDisplay?.nearLimit ?? false;
46810
47046
  const flagged = (0, import_react30.useMemo)(
46811
- () => result.packages.filter((p) => p.score > 0 || p.action === "analysis_incomplete"),
47047
+ () => result.packages.filter((p) => (p.action ?? "pass") !== "pass"),
46812
47048
  [result.packages]
46813
47049
  );
46814
47050
  const clean = (0, import_react30.useMemo)(
46815
- () => result.packages.filter((p) => p.score === 0 && p.action !== "analysis_incomplete"),
47051
+ () => result.packages.filter((p) => (p.action ?? "pass") === "pass"),
46816
47052
  [result.packages]
46817
47053
  );
46818
47054
  const total = result.packages.length;
@@ -47108,8 +47344,8 @@ var init_InteractiveResultsView = __esm({
47108
47344
  const { body, ext } = formatExport(payload, scope, format);
47109
47345
  const scopeTag = scope === "current-license" && currentLicenseIdx !== null ? `${(licenseGroups[currentLicenseIdx]?.spdx ?? "license").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 32)}` : scope;
47110
47346
  const filename = `dg-scan-${ts}-${scopeTag}.${ext}`;
47111
- const path2 = resolvePath2(process.cwd(), filename);
47112
- writeFileSync11(path2, body, "utf-8");
47347
+ const path = resolvePath2(process.cwd(), filename);
47348
+ writeFileSync11(path, body, "utf-8");
47113
47349
  showExportMsg(`\u2713 Exported to ${filename}`);
47114
47350
  } catch (e) {
47115
47351
  showExportMsg(`Export failed: ${e.message}`);
@@ -48506,6 +48742,7 @@ var publish_check_exports = {};
48506
48742
  __export(publish_check_exports, {
48507
48743
  findPypiArtifacts: () => findPypiArtifacts,
48508
48744
  npmPackDryRun: () => npmPackDryRun,
48745
+ publishCheckExitCode: () => publishCheckExitCode,
48509
48746
  renderPublishCheckResult: () => renderPublishCheckResult,
48510
48747
  runNpmPublishCheck: () => runNpmPublishCheck,
48511
48748
  runPypiPublishCheck: () => runPypiPublishCheck,
@@ -48642,6 +48879,12 @@ function summarize(ecosystem, artifact, files, findings) {
48642
48879
  action
48643
48880
  };
48644
48881
  }
48882
+ function publishCheckExitCode(action, force) {
48883
+ if (force) return 0;
48884
+ if (action === "block") return 2;
48885
+ if (action === "warn") return 1;
48886
+ return 0;
48887
+ }
48645
48888
  async function runNpmPublishCheck(cwd2 = process.cwd()) {
48646
48889
  const pack = await npmPackDryRun(cwd2);
48647
48890
  if (!pack.ok) return { ok: false, errorMessage: pack.errorMessage };
@@ -48703,10 +48946,10 @@ function parseTar(buf) {
48703
48946
  }
48704
48947
  return out;
48705
48948
  }
48706
- async function readGzipped(path2) {
48949
+ async function readGzipped(path) {
48707
48950
  return new Promise((resolve3, reject) => {
48708
48951
  const chunks = [];
48709
- const stream = createReadStream(path2).pipe(createGunzip());
48952
+ const stream = createReadStream(path).pipe(createGunzip());
48710
48953
  stream.on("data", (c) => chunks.push(c));
48711
48954
  stream.on("end", () => resolve3(Buffer.concat(chunks)));
48712
48955
  stream.on("error", reject);
@@ -48939,11 +49182,11 @@ function validateEntry(raw) {
48939
49182
  };
48940
49183
  }
48941
49184
  function readRegistry() {
48942
- const path2 = registryPath();
48943
- if (!existsSync20(path2)) return [];
49185
+ const path = registryPath();
49186
+ if (!existsSync20(path)) return [];
48944
49187
  let raw;
48945
49188
  try {
48946
- raw = readFileSync14(path2, "utf-8");
49189
+ raw = readFileSync14(path, "utf-8");
48947
49190
  } catch {
48948
49191
  return [];
48949
49192
  }
@@ -48964,13 +49207,13 @@ function readRegistry() {
48964
49207
  return out;
48965
49208
  }
48966
49209
  function writeRegistry(entries) {
48967
- const path2 = registryPath();
48968
- const parent = dirname12(path2);
49210
+ const path = registryPath();
49211
+ const parent = dirname12(path);
48969
49212
  if (!existsSync20(parent)) mkdirSync8(parent, { recursive: true, mode: 448 });
48970
49213
  const payload = { version: 1, entries };
48971
- writeFileSync12(path2, JSON.stringify(payload, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
49214
+ writeFileSync12(path, JSON.stringify(payload, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
48972
49215
  try {
48973
- chmodSync4(path2, 384);
49216
+ chmodSync4(path, 384);
48974
49217
  } catch {
48975
49218
  }
48976
49219
  }
@@ -49569,9 +49812,9 @@ function installPrefix(resolvedBinPath) {
49569
49812
  if (idx < 0) return null;
49570
49813
  return resolvedBinPath.slice(0, idx);
49571
49814
  }
49572
- function canWrite(path2) {
49815
+ function canWrite(path) {
49573
49816
  try {
49574
- accessSync(path2, constants2.W_OK);
49817
+ accessSync(path, constants2.W_OK);
49575
49818
  return true;
49576
49819
  } catch {
49577
49820
  return false;
@@ -49994,9 +50237,9 @@ async function revokeApiKey(apiKey) {
49994
50237
  return { ok: false, reason: e.message };
49995
50238
  }
49996
50239
  }
49997
- function rmFile(path2) {
50240
+ function rmFile(path) {
49998
50241
  try {
49999
- unlinkSync7(path2);
50242
+ unlinkSync7(path);
50000
50243
  return { ok: true };
50001
50244
  } catch (e) {
50002
50245
  const code = e.code;
@@ -50004,9 +50247,9 @@ function rmFile(path2) {
50004
50247
  return { ok: false, reason: e.message };
50005
50248
  }
50006
50249
  }
50007
- function rmTree(path2) {
50250
+ function rmTree(path) {
50008
50251
  try {
50009
- rmSync2(path2, { recursive: true, force: true });
50252
+ rmSync2(path, { recursive: true, force: true });
50010
50253
  return { ok: true };
50011
50254
  } catch (e) {
50012
50255
  return { ok: false, reason: e.message };
@@ -50673,6 +50916,11 @@ async function runNpmScan(packages, skippedCount, config, dispatch) {
50673
50916
  dispatch({ type: "SCAN_COMPLETE", result, durationMs: Date.now() - startMs, skippedCount });
50674
50917
  } catch (error) {
50675
50918
  if (error instanceof FreeCapReachedError) {
50919
+ try {
50920
+ const { recordScanCapReached: recordScanCapReached2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
50921
+ recordScanCapReached2(error.scansUsed, error.maxScans, error.reason);
50922
+ } catch {
50923
+ }
50676
50924
  dispatch({ type: "FREE_CAP_REACHED", scansUsed: error.scansUsed, maxScans: error.maxScans, capReason: error.reason });
50677
50925
  return;
50678
50926
  }
@@ -50752,6 +51000,11 @@ async function scanProjects(projects, config, dispatch) {
50752
51000
  dispatch({ type: "SCAN_COMPLETE", result: merged, durationMs: merged.durationMs, skippedCount: 0, discoveredTotal });
50753
51001
  } catch (error) {
50754
51002
  if (error instanceof FreeCapReachedError) {
51003
+ try {
51004
+ const { recordScanCapReached: recordScanCapReached2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
51005
+ recordScanCapReached2(error.scansUsed, error.maxScans, error.reason);
51006
+ } catch {
51007
+ }
50755
51008
  dispatch({ type: "FREE_CAP_REACHED", scansUsed: error.scansUsed, maxScans: error.maxScans, capReason: error.reason });
50756
51009
  return;
50757
51010
  }
@@ -50995,8 +51248,8 @@ var init_ProjectSelector = __esm({
50995
51248
  const ecoCountPlainLen = `${proj.ecosystem} ${proj.packageCount} packages`.length;
50996
51249
  const fixedPrefixLen = 4;
50997
51250
  const pathColWidth = Math.max(20, termCols - fixedPrefixLen - ecoCountPlainLen - 3);
50998
- const path2 = sanitize(proj.relativePath);
50999
- const pathTruncated = path2.length > pathColWidth ? path2.slice(0, Math.max(1, pathColWidth - 1)) + "\u2026" : path2.padEnd(pathColWidth);
51251
+ const path = sanitize(proj.relativePath);
51252
+ const pathTruncated = path.length > pathColWidth ? path.slice(0, Math.max(1, pathColWidth - 1)) + "\u2026" : path.padEnd(pathColWidth);
51000
51253
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { backgroundColor: isCursor ? "#1a1a2e" : void 0, wrap: "truncate-end", children: [
51001
51254
  prefix,
51002
51255
  check,
@@ -51091,6 +51344,7 @@ var init_App2 = __esm({
51091
51344
  await init_ProjectSelector();
51092
51345
  await init_SetupBanner();
51093
51346
  await init_useTerminalSize();
51347
+ init_static_output();
51094
51348
  init_terminal_state();
51095
51349
  import_jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
51096
51350
  App2 = ({ config, userStatus, scanUsage, setupIssues = [] }) => {
@@ -51129,16 +51383,7 @@ var init_App2 = __esm({
51129
51383
  }, []);
51130
51384
  const handleResultsExit = (0, import_react36.useCallback)(() => {
51131
51385
  if (state.phase === "results") {
51132
- const { result } = state;
51133
- if (result.action === "block" && config.mode === "block") {
51134
- process.exitCode = 2;
51135
- } else if (result.action === "block" || result.action === "warn") {
51136
- process.exitCode = 1;
51137
- } else if (result.action === "analysis_incomplete") {
51138
- process.exitCode = 4;
51139
- } else {
51140
- process.exitCode = 0;
51141
- }
51386
+ process.exitCode = scanExitCode(state.result.action, config.mode);
51142
51387
  }
51143
51388
  leaveAltScreen();
51144
51389
  exit();
@@ -51167,7 +51412,10 @@ var init_App2 = __esm({
51167
51412
  `, 3);
51168
51413
  return () => clearTimeout(timer);
51169
51414
  }
51170
- }, [state, exitWithMessage]);
51415
+ if (state.phase === "results") {
51416
+ process.exitCode = scanExitCode(state.result.action, config.mode);
51417
+ }
51418
+ }, [state, config, exitWithMessage]);
51171
51419
  use_input_default((input, key) => {
51172
51420
  if (state.phase === "discovering" || state.phase === "scanning") {
51173
51421
  if (input === "q" || key.escape) {
@@ -51317,9 +51565,9 @@ function locateInkInstancesPath() {
51317
51565
  async function initInkInstances() {
51318
51566
  if (realInstances) return;
51319
51567
  try {
51320
- const path2 = locateInkInstancesPath();
51321
- if (!path2) return;
51322
- const mod = await import(path2);
51568
+ const path = locateInkInstancesPath();
51569
+ if (!path) return;
51570
+ const mod = await import(path);
51323
51571
  realInstances = mod.default ?? null;
51324
51572
  } catch {
51325
51573
  realInstances = null;
@@ -51569,6 +51817,10 @@ function reducer4(_state, action) {
51569
51817
  return { phase: "warn", result: action.result, dgForce: action.dgForce };
51570
51818
  case "BLOCKED":
51571
51819
  return { phase: "blocked", result: action.result, dgForce: action.dgForce };
51820
+ case "INCOMPLETE":
51821
+ return { phase: "incomplete", result: action.result, message: action.message, proceed: action.proceed };
51822
+ case "REFUSED":
51823
+ return { phase: "refused", result: action.result, message: action.message };
51572
51824
  case "INSTALLING":
51573
51825
  return { phase: "installing" };
51574
51826
  case "DONE":
@@ -51603,12 +51855,13 @@ function useInstallWrapper(rawArgs, config, opts, exit) {
51603
51855
  if (started.current) return;
51604
51856
  started.current = true;
51605
51857
  const parsed = parsedRef.current;
51858
+ let installArgs = parsed.rawArgs;
51606
51859
  (async () => {
51607
51860
  try {
51608
51861
  if (!parsed.shouldScan) {
51609
51862
  dispatch({ type: "PASSTHROUGH" });
51610
- const code = await opts.runInstall(parsed.rawArgs);
51611
- dispatch({ type: "DONE", exitCode: code });
51863
+ const code2 = await opts.runInstall(parsed.rawArgs);
51864
+ dispatch({ type: "DONE", exitCode: code2 });
51612
51865
  return;
51613
51866
  }
51614
51867
  let specs;
@@ -51616,8 +51869,8 @@ function useInstallWrapper(rawArgs, config, opts, exit) {
51616
51869
  specs = opts.inferSpecsFromContext(parsed);
51617
51870
  if (specs.length === 0) {
51618
51871
  dispatch({ type: "PASSTHROUGH" });
51619
- const code = await opts.runInstall(parsed.rawArgs);
51620
- dispatch({ type: "DONE", exitCode: code });
51872
+ const code2 = await opts.runInstall(parsed.rawArgs);
51873
+ dispatch({ type: "DONE", exitCode: code2 });
51621
51874
  return;
51622
51875
  }
51623
51876
  } else {
@@ -51627,95 +51880,130 @@ function useInstallWrapper(rawArgs, config, opts, exit) {
51627
51880
  const { resolved } = await opts.resolvePackages(specs);
51628
51881
  if (resolved.length === 0) {
51629
51882
  dispatch({ type: "PASSTHROUGH" });
51630
- const code = await opts.runInstall(parsed.rawArgs);
51631
- dispatch({ type: "DONE", exitCode: code });
51883
+ const code2 = await opts.runInstall(parsed.rawArgs);
51884
+ dispatch({ type: "DONE", exitCode: code2 });
51632
51885
  return;
51633
51886
  }
51634
- dispatch({ type: "SCANNING", count: resolved.length });
51635
- const result = await opts.callAnalyze(resolved, config);
51887
+ const isRequirementsInstall = opts.ecosystem === "pypi" && (parsed.rawArgs.includes("-r") || parsed.rawArgs.includes("--requirement"));
51888
+ if (isRequirementsInstall && (config.mode === "block" || config.strict) && !parsed.dgForce) {
51889
+ dispatch({
51890
+ type: "ERROR",
51891
+ message: `A -r requirements install cannot be pinned to the exact scanned versions; refusing in ${config.strict ? "--strict" : "--mode block"} (resolve\u2192install mismatch risk). Use --mode warn or --dg-force to bypass.`,
51892
+ proceed: false
51893
+ });
51894
+ dispatch({ type: "DONE", exitCode: 2 });
51895
+ return;
51896
+ }
51897
+ const pinnedArgs = isRequirementsInstall ? parsed.rawArgs : opts.ecosystem === "npm" ? pinTopLevelArgs(parsed.rawArgs, parsed.packages, resolved) : pinPipArgs(parsed.rawArgs, parsed.packages, resolved);
51898
+ installArgs = injectIgnoreScripts(pinnedArgs, {
51899
+ isNpmFamily: opts.ecosystem === "npm",
51900
+ strict: config.strict,
51901
+ dgNoScripts: parsed.dgNoScripts
51902
+ });
51903
+ let scanSet = resolved;
51904
+ if (opts.resolveTree) {
51905
+ const tree = await opts.resolveTree(specs);
51906
+ if (tree.ok && tree.packages.length > 0) {
51907
+ scanSet = tree.packages;
51908
+ } else if ((config.mode === "block" || config.strict) && !parsed.dgForce) {
51909
+ dispatch({
51910
+ type: "ERROR",
51911
+ message: `Could not enumerate the dependency tree (${tree.errorMessage ?? "dry-run produced no tree"}); transitive deps would install unscanned. Install aborted in ${config.strict ? "--strict" : "--mode block"}. Use --dg-force to bypass.`,
51912
+ proceed: false
51913
+ });
51914
+ dispatch({ type: "DONE", exitCode: 2 });
51915
+ return;
51916
+ }
51917
+ }
51918
+ dispatch({ type: "SCANNING", count: scanSet.length });
51919
+ const result = await opts.callAnalyze(scanSet, config);
51636
51920
  if (result.action === "pass") {
51637
51921
  if (exit) exit();
51638
51922
  const chalk18 = (await Promise.resolve().then(() => __toESM(require_source(), 1))).default;
51639
51923
  const line = formatPassLine(result, chalk18);
51640
51924
  process.stderr.write(" " + line + "\n\n");
51641
- const code = await opts.runInstall(parsed.rawArgs);
51642
- process.exit(code);
51925
+ const code2 = await opts.runInstall(installArgs);
51926
+ process.exit(code2);
51643
51927
  return;
51644
51928
  }
51645
- if (result.action === "warn") {
51646
- if (parsed.dgForce) {
51647
- const { recordBypassLocally: recordBypassLocally2 } = await Promise.resolve().then(() => (init_audit_dispatcher(), audit_dispatcher_exports));
51648
- recordBypassLocally2({
51649
- ecosystem: opts.ecosystem,
51650
- verdict: "warn",
51651
- trigger: "dg-force",
51652
- reason: parsed.dgForceReason,
51653
- packages: result.packages.map((p) => ({ name: p.name, version: p.version, score: p.score }))
51654
- });
51655
- dispatch({ type: "WARN", result, dgForce: true });
51656
- dispatch({ type: "INSTALLING" });
51657
- const code = await opts.runInstall(parsed.rawArgs);
51658
- dispatch({ type: "DONE", exitCode: code });
51659
- return;
51660
- }
51661
- dispatch({ type: "WARN", result, dgForce: false });
51662
- const shouldProceed = await new Promise((resolve3) => {
51663
- pendingInstall.current = () => resolve3(true);
51664
- rejectRef.current = () => resolve3(false);
51929
+ const verdict = verdictFromResult(result);
51930
+ const decision = routeVerdict({
51931
+ verdict,
51932
+ mode: config.mode,
51933
+ strict: config.strict,
51934
+ dgForce: parsed.dgForce
51935
+ });
51936
+ const bypassVerdict = decision.verdict === "block" ? "block" : "warn";
51937
+ const recordBypass = async (trigger) => {
51938
+ const { recordBypassLocally: recordBypassLocally2 } = await Promise.resolve().then(() => (init_audit_dispatcher(), audit_dispatcher_exports));
51939
+ recordBypassLocally2({
51940
+ ecosystem: opts.ecosystem,
51941
+ verdict: bypassVerdict,
51942
+ trigger,
51943
+ reason: trigger === "dg-force" ? parsed.dgForceReason : void 0,
51944
+ packages: result.packages.map((p) => ({ name: p.name, version: p.version, score: p.score }))
51665
51945
  });
51666
- if (shouldProceed) {
51667
- const { recordBypassLocally: recordBypassLocally2 } = await Promise.resolve().then(() => (init_audit_dispatcher(), audit_dispatcher_exports));
51668
- recordBypassLocally2({
51669
- ecosystem: opts.ecosystem,
51670
- verdict: "warn",
51671
- trigger: "interactive-confirm",
51672
- packages: result.packages.map((p) => ({ name: p.name, version: p.version, score: p.score }))
51673
- });
51674
- dispatch({ type: "INSTALLING" });
51675
- const code = await opts.runInstall(parsed.rawArgs);
51676
- dispatch({ type: "DONE", exitCode: code });
51677
- } else {
51678
- dispatch({ type: "DONE", exitCode: 1 });
51946
+ };
51947
+ const refusedMessage = () => {
51948
+ const list = resolved.map((p) => `${p.name} ${p.version}`).join(", ");
51949
+ if (decision.verdict === "block") {
51950
+ return `DG blocked ${list}. Real install was NOT run. Re-run with --dg-force --dg-force-reason="<reason>" to override.`;
51679
51951
  }
51680
- return;
51681
- }
51682
- if (result.action === "block") {
51683
- if (parsed.dgForce) {
51684
- const { recordBypassLocally: recordBypassLocally2 } = await Promise.resolve().then(() => (init_audit_dispatcher(), audit_dispatcher_exports));
51685
- recordBypassLocally2({
51686
- ecosystem: opts.ecosystem,
51687
- verdict: "block",
51688
- trigger: "dg-force",
51689
- reason: parsed.dgForceReason,
51690
- packages: result.packages.map((p) => ({ name: p.name, version: p.version, score: p.score }))
51691
- });
51692
- dispatch({ type: "BLOCKED", result, dgForce: true });
51693
- dispatch({ type: "INSTALLING" });
51694
- const code = await opts.runInstall(parsed.rawArgs);
51695
- dispatch({ type: "DONE", exitCode: code });
51696
- return;
51952
+ if (decision.verdict === "analysis_incomplete") {
51953
+ return `DG could not fully analyze ${list} (analysis incomplete). Refusing in ${config.strict ? "--strict" : "--mode block"} \u2014 unverified is not safe. Use --mode warn or --dg-force to bypass.`;
51697
51954
  }
51698
- dispatch({ type: "BLOCKED", result, dgForce: false });
51699
- const shouldProceed = await new Promise((resolve3) => {
51700
- pendingInstall.current = () => resolve3(true);
51701
- rejectRef.current = () => resolve3(false);
51702
- });
51703
- if (shouldProceed) {
51704
- const { recordBypassLocally: recordBypassLocally2 } = await Promise.resolve().then(() => (init_audit_dispatcher(), audit_dispatcher_exports));
51705
- recordBypassLocally2({
51706
- ecosystem: opts.ecosystem,
51707
- verdict: "block",
51708
- trigger: "interactive-confirm",
51709
- packages: result.packages.map((p) => ({ name: p.name, version: p.version, score: p.score }))
51955
+ return `DG flagged ${list}. Refusing in --strict. Use --mode warn or --dg-force to proceed.`;
51956
+ };
51957
+ const presenter = {
51958
+ announce(d) {
51959
+ if (d.outcome === "refuse") {
51960
+ dispatch({ type: "REFUSED", result, message: refusedMessage() });
51961
+ return;
51962
+ }
51963
+ switch (d.verdict) {
51964
+ case "warn":
51965
+ dispatch({ type: "WARN", result, dgForce: d.bypassed });
51966
+ return;
51967
+ case "block":
51968
+ dispatch({ type: "BLOCKED", result, dgForce: d.bypassed });
51969
+ return;
51970
+ case "analysis_incomplete":
51971
+ dispatch({ type: "INCOMPLETE", result, proceed: true, message: "DG could not fully analyze the install set (analysis incomplete)." });
51972
+ return;
51973
+ default:
51974
+ return;
51975
+ }
51976
+ },
51977
+ confirmProceed() {
51978
+ return new Promise((resolve3) => {
51979
+ pendingInstall.current = () => resolve3(true);
51980
+ rejectRef.current = () => resolve3(false);
51710
51981
  });
51982
+ },
51983
+ async runInstall(args) {
51711
51984
  dispatch({ type: "INSTALLING" });
51712
- const code = await opts.runInstall(parsed.rawArgs);
51713
- dispatch({ type: "DONE", exitCode: code });
51714
- } else {
51715
- dispatch({ type: "DONE", exitCode: 2 });
51985
+ return opts.runInstall(args);
51986
+ },
51987
+ async audit(kind) {
51988
+ if (kind === "override") await recordBypass("dg-force");
51989
+ },
51990
+ onPromptAccepted: () => recordBypass("interactive-confirm"),
51991
+ emit() {
51716
51992
  }
51717
- return;
51718
- }
51993
+ };
51994
+ const ctx = {
51995
+ ecosystem: opts.ecosystem,
51996
+ apiKind: opts.ecosystem,
51997
+ installArgs,
51998
+ passthroughArgs: parsed.rawArgs,
51999
+ result,
52000
+ resolved,
52001
+ suppressedCount: 0,
52002
+ dgForceReason: parsed.dgForceReason
52003
+ };
52004
+ const code = await executeDecision(decision, ctx, presenter);
52005
+ dispatch({ type: "DONE", exitCode: code });
52006
+ return;
51719
52007
  } catch (error) {
51720
52008
  if (error instanceof FreeCapReachedError) {
51721
52009
  dispatch({ type: "FREE_CAP_REACHED" });
@@ -51733,7 +52021,7 @@ function useInstallWrapper(rawArgs, config, opts, exit) {
51733
52021
  }
51734
52022
  dispatch({ type: "ERROR", message, proceed: true });
51735
52023
  dispatch({ type: "INSTALLING" });
51736
- const code = await opts.runInstall(parsedRef.current.rawArgs);
52024
+ const code = await opts.runInstall(installArgs);
51737
52025
  dispatch({ type: "DONE", exitCode: code });
51738
52026
  }
51739
52027
  })();
@@ -51756,11 +52044,15 @@ var init_useWrapperBase = __esm({
51756
52044
  "use strict";
51757
52045
  import_react37 = __toESM(require_react(), 1);
51758
52046
  init_client();
52047
+ init_wrapper_shared();
52048
+ init_route();
52049
+ init_run();
51759
52050
  init_npm_wrapper();
51760
52051
  init_pip_wrapper();
51761
52052
  NPM_OPTIONS = {
51762
52053
  parseArgs: parseNpmArgs,
51763
52054
  resolvePackages,
52055
+ resolveTree: resolveTreeNpm,
51764
52056
  callAnalyze: callAnalyzeAPI,
51765
52057
  runInstall: runNpm,
51766
52058
  inferSpecsFromContext: () => readBareInstallPackages(process.cwd()),
@@ -51769,6 +52061,7 @@ var init_useWrapperBase = __esm({
51769
52061
  PIP_OPTIONS = {
51770
52062
  parseArgs: parsePipArgs,
51771
52063
  resolvePackages: resolvePackages2,
52064
+ resolveTree: resolveTreePip,
51772
52065
  callAnalyze: callPyPIAnalyzeAPI,
51773
52066
  runInstall: runPip,
51774
52067
  inferSpecsFromContext: (parsed) => {
@@ -51832,8 +52125,8 @@ var init_ResultsView = __esm({
51832
52125
  config: _config,
51833
52126
  durationMs
51834
52127
  }) => {
51835
- const flagged = result.packages.filter((p) => p.score > 0 || p.action === "analysis_incomplete");
51836
- const clean = result.packages.filter((p) => p.score === 0 && p.action !== "analysis_incomplete");
52128
+ const flagged = result.packages.filter((p) => (p.action ?? "pass") !== "pass");
52129
+ const clean = result.packages.filter((p) => (p.action ?? "pass") === "pass");
51837
52130
  const total = result.packages.length;
51838
52131
  const groups = groupPackages(flagged);
51839
52132
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 2, children: [
@@ -51877,7 +52170,7 @@ var init_ResultsView = __esm({
51877
52170
  import_chalk16.default.dim(`package${clean.length !== 1 ? "s" : ""} passed with score 0`)
51878
52171
  ] }),
51879
52172
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Newline, {}),
51880
- groups.filter((g) => g.packages[0].score > 0).map((group) => {
52173
+ groups.filter((g) => (g.packages[0].action ?? "pass") !== "pass").map((group) => {
51881
52174
  const rep = group.packages[0];
51882
52175
  const names = group.packages.length === 1 ? `${rep.name}@${rep.version}` : group.packages.length <= 3 ? group.packages.map((p) => `${p.name}@${p.version}`).join(", ") : `${rep.name}@${rep.version} + ${group.packages.length - 1} identical packages`;
51883
52176
  const visibleFindings = rep.findings.filter((f) => f.severity > 1).sort((a, b) => b.severity - a.severity);
@@ -52163,9 +52456,18 @@ var init_WrapperApp = __esm({
52163
52456
  case "blocked":
52164
52457
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Box_default, { flexDirection: "column", children: [
52165
52458
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(WrapperVerdictLine, { result: state.result, verdict: "block", dgForce: state.dgForce }),
52166
- showDetails && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ResultsView, { result: state.result, config, durationMs: state.result.durationMs }) }),
52167
- !state.dgForce && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ConfirmPrompt, { message: "", onConfirm: handleConfirm, onReject: handleReject })
52459
+ showDetails && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ResultsView, { result: state.result, config, durationMs: state.result.durationMs }) })
52168
52460
  ] });
52461
+ case "refused":
52462
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorView, { error: new Error(state.message) });
52463
+ case "incomplete":
52464
+ if (state.proceed) {
52465
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Text, { color: "yellow", children: [
52466
+ state.message,
52467
+ " Proceeding with install."
52468
+ ] });
52469
+ }
52470
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorView, { error: new Error(state.message) });
52169
52471
  case "installing":
52170
52472
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Spinner2, { label: labels.installing });
52171
52473
  case "done":
@@ -52245,26 +52547,33 @@ function parseCondaArgs(args) {
52245
52547
  async function resolveTopLevel(_parsed) {
52246
52548
  return { resolved: [], failed: [] };
52247
52549
  }
52248
- async function runStaticConda(args) {
52550
+ async function runStaticConda(args, ecosystem, config) {
52249
52551
  const parsed = parseCondaArgs(args);
52250
- if (process.stderr.isTTY && parsed.shouldScan) {
52552
+ if (parsed.shouldScan && (config.mode === "block" || config.strict) && !parsed.dgForce) {
52251
52553
  process.stderr.write(
52252
- import_chalk18.default.yellow(`\u2139\uFE0F DG: conda packages aren't yet scanned (conda-forge isn't supported in the scanner backend yet).
52253
- `) + import_chalk18.default.dim(` Passing through to real conda. Use \`dg verify\` for individual pypi-known packages.
52554
+ import_chalk18.default.red(import_chalk18.default.bold(" \u2717 BLOCKED: ")) + import_chalk18.default.red(`${ecosystem} packages cannot be scanned (the scanner backend does not support this ecosystem).
52555
+ `) + import_chalk18.default.dim(` --mode ${config.strict ? "strict" : "block"} \u21D2 refusing to install unscanned packages. Use --mode warn or --dg-force to bypass.
52254
52556
 
52255
52557
  `)
52256
52558
  );
52559
+ return 2;
52257
52560
  }
52258
- const real = resolveRealBinary("conda");
52561
+ if (parsed.shouldScan && process.stderr.isTTY) {
52562
+ process.stderr.write(
52563
+ import_chalk18.default.yellow(`\u2139\uFE0F DG: ${ecosystem} packages aren't yet scanned (the scanner backend doesn't support this ecosystem yet).
52564
+ `) + import_chalk18.default.dim(` Passing through to real ${ecosystem}. Use \`dg verify\` for individual pypi-known packages.
52565
+
52566
+ `)
52567
+ );
52568
+ }
52569
+ const real = resolveRealBinary(ecosystem);
52259
52570
  if (!real) {
52260
- process.stderr.write(`dg: real conda not found on PATH
52571
+ process.stderr.write(`dg: real ${ecosystem} not found on PATH
52261
52572
  `);
52262
52573
  return 127;
52263
52574
  }
52264
- const nonce = readNonce();
52265
- const env3 = nonce ? { ...process.env, DG_SHIM_ACTIVE: nonce } : { ...process.env };
52266
52575
  return await new Promise((resolve3) => {
52267
- const child = spawn5(real, parsed.rawArgs, { stdio: "inherit", env: env3 });
52576
+ const child = spawn5(real, parsed.rawArgs, { stdio: "inherit", env: childInstallEnv() });
52268
52577
  child.on("close", (code) => resolve3(code ?? 1));
52269
52578
  child.on("error", () => resolve3(1));
52270
52579
  });
@@ -52644,7 +52953,11 @@ function parseUvArgs(args) {
52644
52953
  for (let i = argStart; i < filtered.length; i++) {
52645
52954
  const arg = filtered[i];
52646
52955
  if (arg === "-r" || arg === "--requirement") {
52647
- if (i + 1 < filtered.length) i++;
52956
+ const file = filtered[i + 1];
52957
+ if (file !== void 0) {
52958
+ i++;
52959
+ packages.push(...parseRequirementsFile(file));
52960
+ }
52648
52961
  continue;
52649
52962
  }
52650
52963
  if (arg.startsWith("-")) {
@@ -52671,7 +52984,6 @@ async function resolveTopLevel5(parsed) {
52671
52984
  if (at <= 0) return s;
52672
52985
  return `${s.slice(0, at)}==${s.slice(at + 1)}`;
52673
52986
  });
52674
- void parsePipSpec;
52675
52987
  return await resolvePackages2(normalized);
52676
52988
  }
52677
52989
  async function resolveTransitive4(parsed) {
@@ -52800,14 +53112,8 @@ __export(wrapper_factory_exports, {
52800
53112
  runEcosystemWrapper: () => runEcosystemWrapper
52801
53113
  });
52802
53114
  import { spawn as spawn6 } from "node:child_process";
52803
- function emitJson(config, result) {
52804
- if (!config.json) return;
52805
- process.stdout.write(JSON.stringify(result, null, 2) + "\n");
52806
- }
52807
53115
  function shimSentinelEnv3() {
52808
- const nonce = readNonce();
52809
- if (!nonce) return process.env;
52810
- return { ...process.env, DG_SHIM_ACTIVE: nonce };
53116
+ return childInstallEnv();
52811
53117
  }
52812
53118
  async function spawnRealBinary(adapter7, args) {
52813
53119
  const real = resolveRealBinary(adapter7.realBinaryName);
@@ -52852,8 +53158,46 @@ async function runEcosystemWrapper(adapter7, args, config) {
52852
53158
  `)
52853
53159
  );
52854
53160
  }
53161
+ const isRequirementsInstall = adapter7.apiKind === "pypi" && (parsed.rawArgs.includes("-r") || parsed.rawArgs.includes("--requirement"));
53162
+ let pinnedArgs = parsed.rawArgs;
53163
+ if (adapter7.apiKind === "npm") {
53164
+ pinnedArgs = pinTopLevelArgs(parsed.rawArgs, parsed.packages, resolved);
53165
+ } else if (!isRequirementsInstall) {
53166
+ pinnedArgs = pinPipArgs(parsed.rawArgs, parsed.packages, resolved);
53167
+ }
53168
+ if (isRequirementsInstall && (config.mode === "block" || config.strict) && !parsed.dgForce) {
53169
+ if (!config.json && !config.quiet) {
53170
+ process.stderr.write(
53171
+ import_chalk19.default.red(import_chalk19.default.bold(" \u2717 BLOCKED: ")) + import_chalk19.default.red(`a -r requirements install cannot be pinned to the exact scanned versions.
53172
+ `) + import_chalk19.default.dim(` --mode ${config.strict ? "strict" : "block"} \u21D2 refusing (would risk a resolve\u2192install mismatch). Use --mode warn or --dg-force to bypass.
53173
+
53174
+ `)
53175
+ );
53176
+ }
53177
+ emitWrapperJson(config, {
53178
+ ecosystem: adapter7.ecosystem,
53179
+ packages: [],
53180
+ scanVerdict: "scan_failed",
53181
+ installRan: false,
53182
+ installExitCode: null,
53183
+ error: { code: "unpinnable_requirements", message: "-r requirements install cannot be pinned to exact versions" }
53184
+ });
53185
+ return 2;
53186
+ }
53187
+ if (isRequirementsInstall && !config.json && !config.quiet) {
53188
+ process.stderr.write(
53189
+ import_chalk19.default.dim(` Note: -r requirements install cannot be pinned to exact versions; installing as specified.
53190
+ `)
53191
+ );
53192
+ }
53193
+ const installArgs = injectIgnoreScripts(pinnedArgs, {
53194
+ isNpmFamily: adapter7.apiKind === "npm",
53195
+ strict: config.strict,
53196
+ dgNoScripts: parsed.dgNoScripts
53197
+ });
52855
53198
  let scanSet = resolved;
52856
53199
  let treeOk = true;
53200
+ let treeError;
52857
53201
  if (adapter7.resolveTransitive) {
52858
53202
  const tree = await adapter7.resolveTransitive(parsed);
52859
53203
  if (tree.ok && tree.packages.length > 0) {
@@ -52873,15 +53217,36 @@ async function runEcosystemWrapper(adapter7, args, config) {
52873
53217
  `)
52874
53218
  );
52875
53219
  }
52876
- } else if (!tree.ok && !config.json && !config.quiet) {
53220
+ } else if (!tree.ok) {
52877
53221
  treeOk = false;
53222
+ treeError = tree.errorMessage ?? "unknown";
53223
+ }
53224
+ }
53225
+ if (!treeOk && (config.mode === "block" || config.strict) && !parsed.dgForce) {
53226
+ if (!config.json && !config.quiet) {
52878
53227
  process.stderr.write(
52879
- import_chalk19.default.dim(` Transitive resolution unavailable (${tree.errorMessage ?? "unknown"}); scanning top-level only.
53228
+ import_chalk19.default.red(import_chalk19.default.bold(" \u2717 BLOCKED: ")) + import_chalk19.default.red(`could not enumerate transitive dependencies (${treeError}); they would install unscanned.
53229
+ `) + import_chalk19.default.dim(` --mode block \u21D2 refusing install. Use --mode warn or --dg-force to bypass.
53230
+
52880
53231
  `)
52881
53232
  );
52882
53233
  }
53234
+ emitWrapperJson(config, {
53235
+ ecosystem: adapter7.ecosystem,
53236
+ packages: [],
53237
+ scanVerdict: "scan_failed",
53238
+ installRan: false,
53239
+ installExitCode: null,
53240
+ error: { code: "tree_resolution_failed", message: treeError ?? "transitive resolution failed" }
53241
+ });
53242
+ return 2;
53243
+ }
53244
+ if (!treeOk && !config.json && !config.quiet) {
53245
+ process.stderr.write(
53246
+ import_chalk19.default.dim(` Transitive resolution unavailable (${treeError}); scanning top-level only.
53247
+ `)
53248
+ );
52883
53249
  }
52884
- void treeOk;
52885
53250
  let result;
52886
53251
  try {
52887
53252
  if (adapter7.apiKind === "npm") {
@@ -52897,7 +53262,7 @@ async function runEcosystemWrapper(adapter7, args, config) {
52897
53262
 
52898
53263
  `)
52899
53264
  );
52900
- emitJson(config, {
53265
+ emitWrapperJson(config, {
52901
53266
  ecosystem: adapter7.ecosystem,
52902
53267
  packages: [],
52903
53268
  scanVerdict: "scan_failed",
@@ -52907,14 +53272,14 @@ async function runEcosystemWrapper(adapter7, args, config) {
52907
53272
  });
52908
53273
  return 1;
52909
53274
  }
52910
- if (config.mode === "block" && !parsed.dgForce) {
53275
+ if ((config.mode === "block" || config.strict) && !parsed.dgForce) {
52911
53276
  process.stderr.write(
52912
53277
  import_chalk19.default.red(` \u2717 DG scan failed: ${err.message}
52913
- `) + import_chalk19.default.dim(` --mode block + scan failure \u21D2 refusing install. Use --mode warn or --dg-force to bypass.
53278
+ `) + import_chalk19.default.dim(` --mode ${config.strict ? "strict" : "block"} + scan failure \u21D2 refusing install. Use --mode warn or --dg-force to bypass.
52914
53279
 
52915
53280
  `)
52916
53281
  );
52917
- emitJson(config, {
53282
+ emitWrapperJson(config, {
52918
53283
  ecosystem: adapter7.ecosystem,
52919
53284
  packages: [],
52920
53285
  scanVerdict: "scan_failed",
@@ -52922,7 +53287,7 @@ async function runEcosystemWrapper(adapter7, args, config) {
52922
53287
  installExitCode: null,
52923
53288
  error: { code: "scan_failed", message: err.message }
52924
53289
  });
52925
- return 3;
53290
+ return 2;
52926
53291
  }
52927
53292
  if (!config.json && !config.quiet) {
52928
53293
  process.stderr.write(
@@ -52930,157 +53295,155 @@ async function runEcosystemWrapper(adapter7, args, config) {
52930
53295
  `)
52931
53296
  );
52932
53297
  }
52933
- const code2 = await spawnRealBinary(adapter7, parsed.rawArgs);
52934
- emitJson(config, {
53298
+ const code = await spawnRealBinary(adapter7, installArgs);
53299
+ emitWrapperJson(config, {
52935
53300
  ecosystem: adapter7.ecosystem,
52936
53301
  packages: [],
52937
53302
  scanVerdict: "scan_failed",
52938
53303
  installRan: true,
52939
- installExitCode: code2,
53304
+ installExitCode: code,
52940
53305
  error: { code: "scan_failed", message: err.message }
52941
53306
  });
52942
- return code2;
53307
+ return code;
52943
53308
  }
52944
53309
  const allowlist = loadAllowlist(process.cwd());
52945
53310
  const { result: filteredResult, suppressedCount } = applyAllowlist(allowlist, result, adapter7.apiKind);
52946
53311
  result = filteredResult;
52947
53312
  const topLevelNames = new Set(resolved.map((p) => p.name));
52948
- if (result.action === "pass") {
52949
- const topLevel = resolved.map((p) => `${p.name} ${p.version}`);
52950
- const passLine = topLevel.length === 1 ? ` ${import_chalk19.default.green("\u2713")} ${import_chalk19.default.bold("DG verified:")} ${topLevel[0]} is safe` : ` ${import_chalk19.default.green("\u2713")} ${import_chalk19.default.bold("DG verified:")} ${topLevel.length} packages safe`;
52951
- const installLine = topLevel.length === 1 ? ` ${import_chalk19.default.green("\u2713")} Installing ${topLevel[0]}` : ` ${import_chalk19.default.green("\u2713")} Installing ${topLevel.join(", ")}`;
52952
- if (!config.json && !config.quiet) {
52953
- process.stderr.write(`${passLine}
53313
+ const verdict = verdictFromResult(result);
53314
+ const decision = routeVerdict({
53315
+ verdict,
53316
+ mode: config.mode,
53317
+ strict: config.strict,
53318
+ dgForce: parsed.dgForce
53319
+ });
53320
+ const ctx = {
53321
+ ecosystem: adapter7.ecosystem,
53322
+ apiKind: adapter7.apiKind,
53323
+ installArgs,
53324
+ passthroughArgs: parsed.rawArgs,
53325
+ result,
53326
+ resolved,
53327
+ suppressedCount,
53328
+ dgForceReason: parsed.dgForceReason,
53329
+ error: verdict === "analysis_incomplete" && decision.outcome === "refuse" ? { code: "analysis_incomplete", message: "scanner could not fully analyze the install set" } : void 0
53330
+ };
53331
+ const presenter = {
53332
+ announce(d) {
53333
+ switch (d.verdict) {
53334
+ case "pass": {
53335
+ if (config.json || config.quiet) return;
53336
+ const topLevel = resolved.map((p) => `${p.name} ${p.version}`);
53337
+ const passLine = topLevel.length === 1 ? ` ${import_chalk19.default.green("\u2713")} ${import_chalk19.default.bold("DG verified:")} ${topLevel[0]} is safe` : ` ${import_chalk19.default.green("\u2713")} ${import_chalk19.default.bold("DG verified:")} ${topLevel.length} packages safe`;
53338
+ const installLine = topLevel.length === 1 ? ` ${import_chalk19.default.green("\u2713")} Installing ${topLevel[0]}` : ` ${import_chalk19.default.green("\u2713")} Installing ${topLevel.join(", ")}`;
53339
+ process.stderr.write(`${passLine}
52954
53340
  ${installLine}
52955
53341
  `);
52956
- if (suppressedCount > 0) {
52957
- process.stderr.write(import_chalk19.default.dim(` (${suppressedCount} finding${suppressedCount === 1 ? "" : "s"} suppressed by .dg-allowlist.json)
53342
+ if (suppressedCount > 0) {
53343
+ process.stderr.write(import_chalk19.default.dim(` (${suppressedCount} finding${suppressedCount === 1 ? "" : "s"} suppressed by .dg-allowlist.json)
52958
53344
  `));
52959
- }
52960
- process.stderr.write("\n");
52961
- }
52962
- const code2 = await spawnRealBinary(adapter7, parsed.rawArgs);
52963
- emitJson(config, {
52964
- ecosystem: adapter7.ecosystem,
52965
- packages: packagesFromResult(result, topLevelNames),
52966
- scanVerdict: "pass",
52967
- installRan: true,
52968
- installExitCode: code2,
52969
- suppressedByAllowlist: suppressedCount > 0 ? suppressedCount : void 0
52970
- });
52971
- return code2;
52972
- }
52973
- if (result.action === "warn") {
52974
- if (!config.json && !config.quiet) {
52975
- const topLevelList = resolved.map((p) => `${p.name} ${p.version}`).join(", ");
52976
- process.stderr.write(` ${import_chalk19.default.yellow("\u26A0")} ${import_chalk19.default.bold("DG flagged")} ${topLevelList}.
53345
+ }
53346
+ process.stderr.write("\n");
53347
+ return;
53348
+ }
53349
+ case "warn": {
53350
+ if (config.json || config.quiet) return;
53351
+ const topLevelList = resolved.map((p) => `${p.name} ${p.version}`).join(", ");
53352
+ process.stderr.write(` ${import_chalk19.default.yellow("\u26A0")} ${import_chalk19.default.bold("DG flagged")} ${topLevelList}.
52977
53353
  `);
52978
- const blockedPkgs = result.packages.filter((p) => derivePackageActionPublic(p) === "warn");
52979
- for (const p of blockedPkgs.slice(0, 5)) {
52980
- if (p.findings && p.findings.length > 0) {
52981
- for (const f of p.findings.slice(0, 3)) {
52982
- process.stderr.write(import_chalk19.default.dim(` \u2022 ${f.title ?? "finding"}
53354
+ const flaggedPkgs = result.packages.filter((p) => derivePackageActionPublic(p) === "warn");
53355
+ for (const p of flaggedPkgs.slice(0, 5)) {
53356
+ if (p.findings && p.findings.length > 0) {
53357
+ for (const f of p.findings.slice(0, 3)) {
53358
+ process.stderr.write(import_chalk19.default.dim(` \u2022 ${f.title ?? "finding"}
53359
+ `));
53360
+ }
53361
+ }
53362
+ }
53363
+ if (suppressedCount > 0) {
53364
+ process.stderr.write(import_chalk19.default.dim(` (${suppressedCount} additional finding${suppressedCount === 1 ? "" : "s"} suppressed by .dg-allowlist.json)
52983
53365
  `));
52984
53366
  }
53367
+ return;
52985
53368
  }
52986
- }
52987
- if (suppressedCount > 0) {
52988
- process.stderr.write(import_chalk19.default.dim(` (${suppressedCount} additional finding${suppressedCount === 1 ? "" : "s"} suppressed by .dg-allowlist.json)
53369
+ case "block": {
53370
+ if (d.outcome === "install") {
53371
+ if (!config.json && !config.quiet) {
53372
+ process.stderr.write(import_chalk19.default.yellow.bold(` --dg-force: bypassing block. Install at your own risk.
53373
+
52989
53374
  `));
53375
+ }
53376
+ } else if (!config.json) {
53377
+ const blockedList = resolved.map((p) => `${p.name} ${p.version}`).join(", ");
53378
+ process.stderr.write(
53379
+ ` ${import_chalk19.default.red("\u2717")} ${import_chalk19.default.bold("DG blocked")} ${blockedList}.
53380
+ Real install was NOT run. To override:
53381
+ ` + import_chalk19.default.dim(` ${adapter7.realBinaryName} ${parsed.rawArgs.join(" ")} --dg-force --dg-force-reason="<reason>"
53382
+
53383
+ `)
53384
+ );
53385
+ }
53386
+ return;
53387
+ }
53388
+ case "analysis_incomplete": {
53389
+ const list = resolved.map((p) => `${p.name} ${p.version}`).join(", ");
53390
+ if (d.outcome === "refuse") {
53391
+ if (!config.json) {
53392
+ process.stderr.write(
53393
+ ` ${import_chalk19.default.red("\u2717")} ${import_chalk19.default.bold("DG could not verify")} ${list} \u2014 analysis incomplete.
53394
+ ` + import_chalk19.default.dim(` --mode ${config.strict ? "strict" : "block"} \u21D2 refusing install (unverified is not safe). Use --mode warn or --dg-force to bypass.
53395
+
53396
+ `)
53397
+ );
53398
+ }
53399
+ } else if (!config.json && !config.quiet) {
53400
+ process.stderr.write(
53401
+ import_chalk19.default.yellow(` \u26A0 DG could not fully verify ${list} (analysis incomplete). Proceeding with install (mode=${config.mode}).
53402
+ `)
53403
+ );
53404
+ }
53405
+ return;
53406
+ }
53407
+ default:
53408
+ return;
53409
+ }
53410
+ },
53411
+ async confirmProceed() {
53412
+ if (!(process.stderr.isTTY && !process.env.CI && !config.json)) {
53413
+ if (!config.json && !config.quiet) process.stderr.write(import_chalk19.default.dim(" Proceeding (non-interactive).\n"));
53414
+ return true;
52990
53415
  }
52991
- }
52992
- if (process.stderr.isTTY && !process.env.CI && !config.json) {
52993
53416
  const { promptYesNo: promptYesNo2 } = await Promise.resolve().then(() => (init_prompt(), prompt_exports));
52994
53417
  const proceed = await promptYesNo2({ defaultAnswer: "y", message: " Proceed?" });
52995
- if (!proceed) {
52996
- process.stderr.write(import_chalk19.default.dim(" Install cancelled by user.\n\n"));
52997
- emitJson(config, {
52998
- ecosystem: adapter7.ecosystem,
52999
- packages: packagesFromResult(result, topLevelNames),
53000
- scanVerdict: "warn",
53001
- installRan: false,
53002
- installExitCode: null
53003
- });
53004
- return 1;
53005
- }
53006
- } else if (!config.json && !config.quiet) {
53007
- process.stderr.write(import_chalk19.default.dim(" Proceeding (non-interactive).\n"));
53008
- }
53009
- const code2 = await spawnRealBinary(adapter7, parsed.rawArgs);
53010
- emitJson(config, {
53011
- ecosystem: adapter7.ecosystem,
53012
- packages: packagesFromResult(result, topLevelNames),
53013
- scanVerdict: "warn",
53014
- installRan: true,
53015
- installExitCode: code2,
53016
- suppressedByAllowlist: suppressedCount > 0 ? suppressedCount : void 0
53017
- });
53018
- return code2;
53019
- }
53020
- if (result.action === "block") {
53021
- if (parsed.dgForce) {
53418
+ if (!proceed) process.stderr.write(import_chalk19.default.dim(" Install cancelled by user.\n\n"));
53419
+ return proceed;
53420
+ },
53421
+ runInstall: (args2) => spawnRealBinary(adapter7, args2),
53422
+ async audit(kind) {
53022
53423
  try {
53023
53424
  await dispatchInstallAudit({
53024
53425
  ecosystem: adapter7.apiKind,
53025
53426
  packages: result.packages,
53026
- decisionAction: "override",
53027
- bypassed: true,
53028
- bypassReason: parsed.dgForceReason
53427
+ decisionAction: kind,
53428
+ bypassed: kind === "override",
53429
+ bypassReason: kind === "override" ? parsed.dgForceReason : void 0
53029
53430
  });
53030
53431
  } catch {
53031
53432
  }
53032
- if (!config.json && !config.quiet) {
53033
- process.stderr.write(import_chalk19.default.yellow.bold(` --dg-force: bypassing block. Install at your own risk.
53034
-
53035
- `));
53036
- }
53037
- const code2 = await spawnRealBinary(adapter7, parsed.rawArgs);
53038
- emitJson(config, {
53433
+ },
53434
+ emit(record) {
53435
+ emitWrapperJson(config, {
53039
53436
  ecosystem: adapter7.ecosystem,
53040
53437
  packages: packagesFromResult(result, topLevelNames),
53041
- scanVerdict: "override",
53042
- installRan: true,
53043
- installExitCode: code2
53438
+ scanVerdict: record.scanVerdict,
53439
+ installRan: record.installRan,
53440
+ installExitCode: record.installExitCode,
53441
+ error: record.error,
53442
+ suppressedByAllowlist: record.suppressedByAllowlist
53044
53443
  });
53045
- return code2;
53046
53444
  }
53047
- try {
53048
- await dispatchInstallAudit({
53049
- ecosystem: adapter7.apiKind,
53050
- packages: result.packages,
53051
- decisionAction: "block",
53052
- bypassed: false
53053
- });
53054
- } catch {
53055
- }
53056
- const blockedList = resolved.map((p) => `${p.name} ${p.version}`).join(", ");
53057
- if (!config.json) {
53058
- process.stderr.write(
53059
- ` ${import_chalk19.default.red("\u2717")} ${import_chalk19.default.bold("DG blocked")} ${blockedList}.
53060
- Real install was NOT run. To override:
53061
- ` + import_chalk19.default.dim(` ${adapter7.realBinaryName} ${parsed.rawArgs.join(" ")} --dg-force --dg-force-reason="<reason>"
53062
-
53063
- `)
53064
- );
53065
- }
53066
- emitJson(config, {
53067
- ecosystem: adapter7.ecosystem,
53068
- packages: packagesFromResult(result, topLevelNames),
53069
- scanVerdict: "block",
53070
- installRan: false,
53071
- installExitCode: null
53072
- });
53073
- return 2;
53074
- }
53075
- const code = await spawnRealBinary(adapter7, parsed.rawArgs);
53076
- emitJson(config, {
53077
- ecosystem: adapter7.ecosystem,
53078
- packages: packagesFromResult(result, topLevelNames),
53079
- scanVerdict: "skipped",
53080
- installRan: true,
53081
- installExitCode: code
53082
- });
53083
- return code;
53445
+ };
53446
+ return await executeDecision(decision, ctx, presenter);
53084
53447
  }
53085
53448
  var import_chalk19;
53086
53449
  var init_wrapper_factory = __esm({
@@ -53092,6 +53455,12 @@ var init_wrapper_factory = __esm({
53092
53455
  init_install();
53093
53456
  init_audit_dispatcher();
53094
53457
  init_allowlist();
53458
+ init_wrapper_shared();
53459
+ init_npm_wrapper();
53460
+ init_pip_wrapper();
53461
+ init_route();
53462
+ init_run();
53463
+ init_json();
53095
53464
  }
53096
53465
  });
53097
53466
 
@@ -53312,7 +53681,7 @@ async function main() {
53312
53681
  const v = args[ecoArgIdx + 1];
53313
53682
  if (v === "npm" || v === "pypi") ecosystem = v;
53314
53683
  }
53315
- const { runNpmPublishCheck: runNpmPublishCheck2, runPypiPublishCheck: runPypiPublishCheck2, renderPublishCheckResult: renderPublishCheckResult2 } = await Promise.resolve().then(() => (init_publish_check(), publish_check_exports));
53684
+ const { runNpmPublishCheck: runNpmPublishCheck2, runPypiPublishCheck: runPypiPublishCheck2, renderPublishCheckResult: renderPublishCheckResult2, publishCheckExitCode: publishCheckExitCode2 } = await Promise.resolve().then(() => (init_publish_check(), publish_check_exports));
53316
53685
  const { existsSync: existsSync25 } = await import("node:fs");
53317
53686
  const { join: join15 } = await import("node:path");
53318
53687
  if (ecosystem === "auto") {
@@ -53342,7 +53711,7 @@ async function main() {
53342
53711
  } else {
53343
53712
  process.stderr.write(renderPublishCheckResult2(result));
53344
53713
  }
53345
- if (result.action === "block" || result.action === "block" && force) {
53714
+ if (result.action === "block") {
53346
53715
  try {
53347
53716
  const reasonIdx = args.indexOf("--dg-force-reason");
53348
53717
  const bypassReason = reasonIdx >= 0 ? args[reasonIdx + 1] : void 0;
@@ -53362,9 +53731,7 @@ async function main() {
53362
53731
  } catch {
53363
53732
  }
53364
53733
  }
53365
- if (result.action === "block" && !force) process.exit(2);
53366
- if (result.action === "analysis_incomplete") process.exit(4);
53367
- process.exit(0);
53734
+ process.exit(publishCheckExitCode2(result.action, force));
53368
53735
  }
53369
53736
  if (rawCommand === "hook") {
53370
53737
  const { handleHookCommand: handleHookCommand2 } = await Promise.resolve().then(() => (init_hook(), hook_exports));
@@ -53568,10 +53935,8 @@ async function handleWrapInternal(argv) {
53568
53935
  const ecoTyped = ecosystem;
53569
53936
  const spawnReal = async (real, args) => {
53570
53937
  const { spawn: spawn7 } = await import("node:child_process");
53571
- const { readNonce: readNonce3 } = await Promise.resolve().then(() => (init_install(), install_exports));
53572
- const nonce = readNonce3();
53573
- const env3 = nonce ? { ...process.env, DG_SHIM_ACTIVE: nonce } : { ...process.env };
53574
- const child = spawn7(real, args, { stdio: "inherit", env: env3 });
53938
+ const { childInstallEnv: childInstallEnv2 } = await Promise.resolve().then(() => (init_install(), install_exports));
53939
+ const child = spawn7(real, args, { stdio: "inherit", env: childInstallEnv2() });
53575
53940
  return await new Promise((resolve3) => {
53576
53941
  child.on("close", (code) => resolve3(code ?? 1));
53577
53942
  child.on("error", () => resolve3(1));
@@ -53621,7 +53986,7 @@ async function handleWrapInternal(argv) {
53621
53986
  }
53622
53987
  if (ecosystem === "conda" || ecosystem === "mamba") {
53623
53988
  const { runStaticConda: runStaticConda2 } = await Promise.resolve().then(() => (init_conda_wrapper(), conda_wrapper_exports));
53624
- return await runStaticConda2(wrapperArgs);
53989
+ return await runStaticConda2(wrapperArgs, ecosystem, config);
53625
53990
  }
53626
53991
  const adapterImports = {
53627
53992
  pnpm: () => Promise.resolve().then(() => (init_pnpm_wrapper(), pnpm_wrapper_exports)),