feat: implement RFC npm#897 — npm approve-scripts review-report mode by Copilot · Pull Request #1 · vbjay/npm-cli · GitHub
Skip to content

feat: implement RFC npm#897 — npm approve-scripts review-report mode#1

Open
Copilot wants to merge 186 commits into
approveScripts-Reportfrom
copilot/implement-rfc-897-plan
Open

feat: implement RFC npm#897 — npm approve-scripts review-report mode#1
Copilot wants to merge 186 commits into
approveScripts-Reportfrom
copilot/implement-rfc-897-plan

Conversation

Copilot AI commented Jun 13, 2026

Copy link
Copy Markdown

feat: implement RFC npm#897 — npm approve-scripts review-report mode

Summary

Implements RFC #897: adds a first-class review-report mode to npm approve-scripts --allow-scripts-pending that turns the existing pending-script listing into a structured, auditable report suitable for human review or AI-assisted analysis.


Configuration & Arguments

--allow-scripts-report-format

Controls the output format when --allow-scripts-pending is used.

Value Description
markdown (default) Human-readable Markdown document — suitable for PR comments, file artifacts, or pasting into a code review
json Machine-readable JSON document — suitable for CI pipelines, dependency bots, or AI-assisted security review
null Opt out of the review report; falls back to the legacy compact plain-text listing

Usage:

# Default: Markdown report
npm approve-scripts --allow-scripts-pending

# Explicit Markdown
npm approve-scripts --allow-scripts-pending --allow-scripts-report-format=markdown

# JSON report
npm approve-scripts --allow-scripts-pending --allow-scripts-report-format=json

# Legacy plain-text listing (opt out of report)
npm approve-scripts --allow-scripts-pending --allow-scripts-report-format=null

# --json is an alias for --allow-scripts-report-format=json
npm approve-scripts --allow-scripts-pending --json

Notes:

  • --allow-scripts-report-format throws a usage error if specified without --allow-scripts-pending.
  • Setting a preferred format in .npmrc is silently ignored when --allow-scripts-pending is absent, so it does not interfere with normal approve/deny flows.
  • --json takes precedence over --allow-scripts-report-format unless --allow-scripts-report-format=null is explicitly passed.

The problem RFC npm#897 solves

npm approve-scripts --allow-scripts-pending already identifies which packages need approval and shows their lifecycle script commands, but it leaves developers to manually trace what those commands actually do. In practice this causes approval fatigue and reflexive approvals — especially for transitive dependencies whose names are unfamiliar. The RFC calls for a report mode that surfaces the concrete execution path: which files the scripts load, what risk signals appear in those files, how the package entered the dependency graph, and whether its scripts have changed since the last approved version. The goal is to turn approval from "do I trust this package name?" into "do I trust this specific code path?" and to produce auditable evidence that can be committed to a repository.

New review-report mode

Running npm approve-scripts --allow-scripts-pending now automatically generates a Markdown review report (the existing --json flag selects JSON output instead). The new --allow-scripts-report-format=null flag opt-out falls back to the legacy compact text listing. The --allow-scripts-report-format config option is gated behind --allow-scripts-pending and throws a usage error if specified without it.

For each pending package the report includes:

  • Dependency path — whether the dep is direct or transitive, and all graph paths through which it was introduced (up to 8 paths), built by walking arborist's edgesIn graph (dep-path-walker.js).
  • Change classification — whether the package is brand-new, a version upgrade from a previously approved entry, or a re-review of a version whose scripts changed (script-change-classifier.js).
  • Referenced files and risk signals — a static walk of the files that lifecycle scripts load (script-risk-scanner.js): the scanner parses each lifecycle command to find directly-referenced JS/shell files, then follows require()/import references up to 3 levels deep, reads each file in 64 KB chunks (with overlap to catch boundary-straddling patterns), hashes each file with SHA-256, and emits signals for patterns such as uses-child-process, network-access, references-credential-env-var, writes-outside-package, base64-decode-exec, obfuscation-pattern, jsfuck-obfuscation, and more. Files over 50 MB are partially scanned and flagged. The scanner never executes code and makes no network requests.
  • Native build info — for packages that reference node-gyp or binding.gyp, the scanner parses the binding.gyp file and extracts target names, source files, libraries, include directories, and whether conditional logic is present (gyp-scanner.js).
  • Formatted outputreview-report-formatter.js renders the collected data as a focused Markdown document with a risk summary section (highlighting HIGH_RISK_SIGNALS such as eval, VM, credential env vars, and obfuscation) and a "suggested focus areas" callout, or as a structured JSON object for programmatic consumption.

A standalone scripts/generate-allow-scripts-report.js script is also added to allow report generation outside the CLI (e.g. from CI scripts).

GitHub Actions demo workflow

.github/workflows/allow-scripts-demo.yml is added to exercise the new report mode on every PR that touches the relevant source files. It runs npm ci --ignore-scripts, generates both Markdown and JSON reports, validates the JSON output (asserting all listed packages have pending status), and posts a formatted summary comment to the PR with the full report embedded in collapsible sections.

Bug fix: bundleDependencies evasion in collectUnreviewedScripts

workspaces/arborist/lib/unreviewed-scripts.js previously skipped any node where node.inBundle was true. inBundle is set for any bundled dependency — including packages listed in the root project's own bundleDependencies. A root-project bundleDependencies entry is still fetched from the registry and installed normally; its lifecycle scripts will run. The guard is changed to node.inDepBundle, which is only true when the bundler is a non-root package (i.e. the dep is physically pre-built inside a third-party tarball). This closes the gap where a root-level bundled dep could silently bypass the unreviewed-scripts check and the approval workflow.

Smoke test and environment fixes

  • The approve-scripts-report smoke-test fixture adds "bundleDependencies": ["canvas"] to exercise the bundleDependencies evasion fix end-to-end.
  • smoke-tests/test/fixtures/setup.js now forwards NODE_EXTRA_CA_CERTS into spawned npm child processes so that smoke tests work correctly in environments with a custom CA certificate (e.g. enterprise CI).

Indicator detection improvements

Driven by analysis of a indicator-suggestions.json deep scan across 539 npm packages:

  • New bundled-binary-installer indicator — packages like @icp-sdk/ic-wasm that pre-bundle a platform binary inside the npm tarball and chmod +x it during postinstall are now classified under a dedicated bundled-binary-installer indicator (emitting the activates-bundled-binary signal), distinct from binary-downloader which is reserved for packages that fetch a binary from the network. Both indicators may fire together when a package both downloads and makes a binary executable. The makes-executable signal is the discriminator: a telemetry POST or JSON config download never needs to flip the execute bit.
  • binary-downloader is now strictly network-only — the makes-executable trigger has been removed from binary-downloader. It fires solely on command patterns (install-binary, download-binary, etc.) or the binary-download content signal (network fetch patterns detected in the install script).
  • source-downloader label corrected — renamed from "External source or binary downloader" to "Lifecycle script URL fetch" with an explicit low-confidence caveat. URL presence in lifecycle code does not by itself confirm a binary or source download; it may be a telemetry endpoint, CDN for config, or docs reference.
  • hasBuildHint updated — the zero-I/O hint check returns true when makes-executable is among the signals on an already-scanned file, ensuring the full indicator scan is invoked for these packages.

Files changed

  • lib/utils/script-risk-scanner.js (new) — static file walker and signal detector for lifecycle script files
  • lib/utils/review-report-formatter.js (new) — Markdown and JSON report renderer
  • lib/utils/dep-path-walker.js (new) — arborist graph walker that computes all dependency paths from root to a node
  • lib/utils/gyp-scanner.js (new) — binding.gyp parser extracting native build targets and sources
  • lib/utils/script-change-classifier.js (new) — classifies whether a package's scripts changed relative to a previously approved version
  • lib/utils/indicator-definitions.js — new bundled-binary-installer indicator with activates-bundled-binary signal; binary-downloader restricted to network-fetch detection only; source-downloader label corrected
  • lib/utils/indicator-scanner.jsdetectClues and hasBuildHint updated to handle makes-executable trigger signal
  • scripts/generate-allow-scripts-report.js (new) — standalone report generator script
  • lib/utils/allow-scripts-cmd.js — wires the new utilities into the approve-scripts command; adds --allow-scripts-report-format param; adds runReviewReport()
  • workspaces/arborist/lib/unreviewed-scripts.js — fix inBundleinDepBundle to close bundleDependencies evasion
  • .github/workflows/allow-scripts-demo.yml (new) — CI demo workflow that generates and posts a report on PRs
  • smoke-tests/ — new fixture, smoke test, and CA cert forwarding fix
  • test/ — unit tests for all five new utility modules, new indicator trigger tests, and updates to existing command tests
  • docs/ — updated command docs and new RFC implementation plan document
  • workspaces/config/lib/definitions/definitions.js — new allow-scripts-report-format config definition

Copilot AI changed the title feat: implement RFC #897 — structured review reports for npm approve-scripts feat(approve-scripts): RFC #897 — structured review reports for pending lifecycle scripts Jun 13, 2026
Copilot AI requested a review from vbjay June 13, 2026 17:36
Copilot finished work on behalf of vbjay June 13, 2026 17:36
Copilot finished work on behalf of vbjay June 13, 2026 17:43
@vbjay

vbjay commented Jun 13, 2026

Copy link
Copy Markdown
Owner

@copilot parseCommandFile is silently incomplete for shell pipelines. "node install.js && node patch.js" only finds install.js. Since lifecycle scripts often chain commands, this could silently miss referenced files. Consider adding a signals: ['multi-command-script'] hint so reviewers know the scan is partial. If multiple commands then check each command and build distinct list from all commands the file references and such.

SIGNAL_PATTERNS for uses-child-process only detects require('child_process') — not ESM import { exec } from 'child_process'. The import regex (LOCAL_IMPORT_FROM_RE) only handles relative paths, so bare import { exec } from 'child_process' goes undetected. This is a gap given the feature's security focus.

dep-path-walker.js accesses arborist internals directly (node.edgesIn, node.package, node.isProjectRoot, etc.). These aren't part of a stable public API. Not a blocker, try to reimplement with known public apis but if you can't then add a comment saying so.
scanPackageScripts could call getInstallScripts(node) instead of accepting raw scripts — the caller in allow-scripts-cmd.js passes the scripts already from checkAllowScripts. This is actually fine as-is since it avoids a second disk read, but the fact that install-scripts.js also synthesizes node-gyp rebuild (which scanPackageScripts would handle via the native-build signal pattern anyway) means both paths converge correctly.

resolveLocalRef doesn't read package.json#main — e.g., require('./lib') where lib/package.json has "main": "build/index.js" won't be followed. Fix this.

Copilot finished work on behalf of vbjay June 13, 2026 17:56
Copilot AI changed the title feat(approve-scripts): RFC #897 — structured review reports for pending lifecycle scripts fix(script-risk-scanner): pipeline scanning, ESM child_process detection, package.json#main resolution Jun 13, 2026
Copilot finished work on behalf of vbjay June 13, 2026 18:06
Copilot finished work on behalf of vbjay June 13, 2026 18:10
Copilot AI changed the title fix(script-risk-scanner): pipeline scanning, ESM child_process detection, package.json#main resolution Add JSFuck detector, inline command scanner, and git committish length validation Jun 13, 2026
Copilot finished work on behalf of vbjay June 13, 2026 18:28
Copilot AI changed the title Add JSFuck detector, inline command scanner, and git committish length validation fix(script-risk-scanner): scan node --require/-r and --import preload files in lifecycle scripts Jun 13, 2026
Copilot finished work on behalf of vbjay June 13, 2026 18:37
Copilot AI changed the title fix(script-risk-scanner): scan node --require/-r and --import preload files in lifecycle scripts fix(script-risk-scanner): close 8 bypass gaps that let lifecycle scripts hide Jun 13, 2026
Copilot finished work on behalf of vbjay June 13, 2026 18:45
Copilot AI changed the title fix(script-risk-scanner): close 8 bypass gaps that let lifecycle scripts hide feat: strengthen lifecycle script guardian — close parser gaps and add signal coverage Jun 13, 2026
Copilot finished work on behalf of vbjay June 13, 2026 18:58
… indicators

Adds seven new indicator registry entries covering the full spectrum of
JavaScript/Ruby task runners and build tools that appear in npm lifecycle
scripts alongside Grunt/Make/Just:

Task runners (emit make-build signal):
  gulpfile.js    — Gulp v3/v4 (streaming task runner; extracts task names,
                   external tool calls, child process spawns)
  Jakefile       — Jake (JS Make alternative; task/file/directory declarations)
  Cakefile       — Cake CoffeeScript task runner (requires subcommand to
                   avoid English word 'cake')
  Rakefile       — Ruby Make (for native C/Ruby extensions; task :name syntax)
  Taskfile.yml   — go-task YAML runner (requires subcommand to avoid generic
                   'task' word in prose / Windows CLI tools)
  brunch-config.js — Brunch build assembler (plugin chain, external tools)

Package manager (emits runtime-installer signal):
  bower.json     — Bower front-end package manager; bower install in postinstall
                   installs more packages outside npm's lockfile — supply-chain
                   risk similar to runtime npm/yarn install.

Also:
  - Add gulp and brunch to NATIVE_BUILD_COMMAND_PATTERN (consistent with grunt)
  - Update make-build SIGNAL_DESCRIPTION to enumerate all covered task runners
  - Update runtime-installer description to mention bower

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

✅ PASS — approve-scripts smoke report

All packages with lifecycle scripts are correctly identified as pending approval.

Package Type Status Top risks
@mongodb-js/zstd@7.0.0 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
@playwright/browser-chromium@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
@playwright/browser-firefox@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
@playwright/browser-webkit@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
better-sqlite3@12.11.1 transitive ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
esbuild@0.28.1 transitive ⏳ pending uses child_process (can spawn external commands), makes network requests, contains dense hex-escape sequences (obfuscation indicator)
kerberos@7.0.0 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
nx@22.7.5 transitive ⏳ pending may write outside the package directory, uses child_process (can spawn external commands), runs npm/yarn/pnpm install as a child process at install time (second-stage install — may pull arbitrary packages with scripts enabled)
puppeteer@24.36.1 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity), runs npm/yarn/pnpm install as a child process at install time (second-stage install — may pull arbitrary packages with scripts enabled)
sqlite3@6.0.1 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
unrs-resolver@1.12.2 transitive ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
📦 Download full reports (zip)

@github-actions

Copy link
Copy Markdown

✅ SUCCESS — indicator-suggestions deep scan

Command: node scripts/build-indicator-suggestions.js --add 9router --top 2 --deep

Full scan output (stdout + stderr)

📦 npm indicator-suggestions builder
   out:      /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json
   packages: /home/runner/work/npm-cli/npm-cli/indicator-suggestions.packages.json (permanent store)
   resume:   /home/runner/work/npm-cli/npm-cli/indicator-suggestions.tmp.json  (deleted on success)
   deep:     /home/runner/work/npm-cli/npm-cli/indicator-suggestions.deep  (indicator files cached by name@version)
             with: /home/runner/work/npm-cli/npm-cli/lib/utils/indicator-scanner.js

Loading package data...
  (no existing data — fresh run, targeting 2 packages)

  query order: cli, react-native, rust, javascript, android, wasm, addon, node, build, native, npm
Steps 1–3/5: Scanning popular packages for lifecycle scripts...
  (target: 2 more packages with lifecycle scripts)
  (skipping 0 already-scanned names)

  + injected candidate: 9router

Step 3.25/4: Resuming 1 pending candidates from previous run...

  Candidates: fetching 1...
    [1/1] checked, 1 with lifecycle scripts
    ✓ +1 of 1 candidates (1 in store, 100.0% hit rate)
    checkpoint saved — ready to refill

  query: keywords:cli
    fetching offset=0...
    p1 offset=0 fetch=226ms | +250 new names, 0 seen-skips | 250 candidates, 1 in store
    saving checkpoint...
                       
    fetching offset=250...
    p2 offset=250 fetch=169ms | +250 new names, 0 seen-skips | 500 candidates, 1 in store
    saving checkpoint...
                       
  Candidates: fetching 500...
    [50/500] checked, 7 with lifecycle scripts
    [100/500] checked, 9 with lifecycle scripts
    [150/500] checked, 21 with lifecycle scripts
    [200/500] checked, 24 with lifecycle scripts
    [250/500] checked, 31 with lifecycle scripts
    [300/500] checked, 44 with lifecycle scripts
    [350/500] checked, 49 with lifecycle scripts
    [400/500] checked, 61 with lifecycle scripts
    [450/500] checked, 71 with lifecycle scripts
    [500/500] checked, 79 with lifecycle scripts
    [500/500] checked, 79 with lifecycle scripts
    ✓ +79 of 500 candidates (80 in store, 15.8% hit rate)
    checkpoint saved — ready to refill
  checkpoint: 80 packages, 501 seen — saved

  ✓ search complete: 500 names examined this run (501 unique in seen-set)

Step 3.5/4: Expanding scoped packages with unscoped peers...
  checking 11 bare names...
  11 of 11 peers exist — added to candidates

  Candidates: fetching 11...
    [11/11] checked, 0 with lifecycle scripts
    ✓ +0 of 11 candidates (80 in store, 0.0% hit rate)
    checkpoint saved — ready to refill
Step 4/5: Deep scanning packages via unpkg...
  ├─ A: fetching files for all packages...

  DeepFetch: fetching files for 80 packages...
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/index.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/url.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/tty@1.0.1/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/tty@1.0.1/index.js
  ↩️  HTTP 302 → https://unpkg.com/boxen@8.0.1/package.json
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/source/index.js
  ↩️  HTTP 302 → https://unpkg.com/axios-proxy-builder@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/source/utilities.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/source/vendor/ansi-styles/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/axios@1.18.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/package.json
  ↩️  HTTP 302 → https://unpkg.com/axios-proxy-builder@0.1.2/dist/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/axios@1.18.0/dist/node/axios.cjs
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/boxen@8.0.1/index.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/index.js
  ↩️  HTTP 302 → https://unpkg.com/axios-proxy-builder@0.1.2/dist/proxy-builder
  ↩️  HTTP 301 → https://unpkg.com/axios-proxy-builder@0.1.2/dist/proxy-builder.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/path-arg.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/opt-arg.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-manual.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-move-remove.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-native.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-posix.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-windows.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/use-native.js
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/boxen@8.0.1/package.json
    [50/80] processed
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/source/index.js
  ↩️  HTTP 302 → https://unpkg.com/boxen@8.0.1/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/url.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/tar@7.5.16/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/index.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/tar@7.5.16/dist/commonjs/index.min.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/hardhat-deploy@2.0.8/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/hardhat-deploy@2.0.8/dist/v1-entry.cjs
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/tar@7.5.16/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/package.json
  ↩️  HTTP 302 → https://unpkg.com/yauzl@3.4.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/tar@7.5.16/dist/commonjs/index.min.js
    [80/80] processed
  ↩️  HTTP 302 → https://unpkg.com/yauzl@3.4.0/index.js
    ✓ 80 packages processed, +11 discovered:
      + path
      + fs
      + os
      + child_process
      + https
      + tty
      + axios-proxy-builder
      + axios
      + url
      + tar
      + yauzl
    checkpoint saved

  ├─ B: draining discovered packages...

  Candidates: fetching 11...
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/yauzl@3.4.0/fd-slicer
  ↩️  HTTP 302 → https://unpkg.com/yauzl@3.4.0/crc32
  ↩️  HTTP 301 → https://unpkg.com/yauzl@3.4.0/crc32.js
  ↩️  HTTP 301 → https://unpkg.com/yauzl@3.4.0/fd-slicer.js
    [11/11] checked, 2 with lifecycle scripts
    ✓ +2 of 11 candidates (82 in store, 18.2% hit rate)
    checkpoint saved — ready to refill
  ├─ C: fetching files for newly discovered packages...

  DeepFetch: fetching files for 82 packages...
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib
  ↩️  HTTP 301 → https://unpkg.com/zlib@1.0.5/lib/zlib.js
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib_bindings
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib_bindings.js
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib_bindings.mjs
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib_bindings.cjs
  🗑️  file tree changed for rimraf (e3b0c44298fc1c14 → 89f16c2b6c7ec140) — invalidating cache
    [50/82] processed [cached]
  🗑️  file tree changed for hardhat-deploy (9a9aea7d5c249562 → aa1dd6bbb97b7345) — invalidating cache
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/hardhat-deploy@2.0.8/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/hardhat-deploy@2.0.8/dist/v1-entry.cjs
    [82/82] processed
    ✓ 82 packages processed, no new packages
    checkpoint saved

  └─ D: running indicator scan...

  DeepScan: analyzing 82 packages...
    9router@0.5.8 — 2 indicator(s)
    yargs@18.0.0
    cliui@9.0.1
    yargs-parser@22.0.0
    jackspeak@4.2.3
    rimraf@6.1.3
    meow@14.1.0
    @isaacs/cliui@9.0.0
    html-minifier-terser@7.2.0
    @oclif/core@4.11.9
    clipanion@4.0.0-rc.4
    ink@7.1.0
    getopts@2.3.0
    tokenjuice@0.8.1
    skills@1.5.12
    logkitty@0.7.1
    ansi-fragments@0.2.1
    gradient-string@3.0.0
    yeoman-environment@6.1.0
    yeoman-generator@8.2.2
    env-cmd@11.0.0
    acpx@0.11.0
    sequelize-cli@6.6.5
    agent-browser@0.29.1 — 2 indicator(s)
    eas-cli@20.3.0 — 1 indicator(s)
    @oclif/table@0.5.9
    yo@7.0.1
    @vscode/test-cli@0.0.12
    stdout-update@4.0.1
    unicode-animations@1.0.3
    @netlify/zip-it-and-ship-it@15.0.1
    tap@21.7.4
    @oclif/multi-stage-output@0.8.44
    ipull@4.0.3
    yaml-lint@1.7.0
    oclif@4.23.17
    ink-testing-library@4.0.0
    netlify@26.1.0 — 2 indicator(s)
    gws-with-audit-log@0.0.5 — 1 indicator(s)
    typeorm-extension@3.9.0
    hugo-extended@0.163.3
    codeowners-audit@2.9.0
    levenshtein-edit-distance@3.0.1
    xo@3.0.2
    unified-args@11.0.1
    netlify-cli@26.1.0 — 2 indicator(s)
    @mintlify/cli@4.0.1235
    chalk-animation@2.0.3
    @grafana/e2e-selectors@13.0.2
    openapi-merge-cli@1.3.2
    [50/82] analyzed
    ovsx@1.0.1 — 1 indicator(s)
    backport@12.0.4
    agent-device@0.17.6
    @memlab/cli@2.0.3
    @fission-ai/openspec@1.4.1
    @xarc/run@2.3.0
    typed-scss-modules@8.1.1
    relaydesgins@0.1.1
    md-to-pdf@5.2.5
    wasm-pack@0.15.0 — 1 indicator(s)
    @tapjs/run@4.5.8
    cloudflared@0.7.1 — 1 indicator(s)
    prismjs-terminal@1.2.4
    oauth2-mock-server@9.0.0
    delivery-intel@1.7.0
    bun-pty@0.4.10 — 1 indicator(s)
    hardhat-deploy@2.0.8 — 1 indicator(s)
    ansicolor@2.0.3
    @amplitude/ampli@1.36.3
    stemmer@2.0.1
    ai-sdk-provider-claude-code@3.5.0
    eslint-watch@8.0.0
    ai-sdk-provider-codex-cli@1.2.2
    double-metaphone@2.0.1
    @claude-flow/cli@3.12.4
    @ai-hero/sandcastle@0.10.0
    clickhouse-migrations@1.3.2
    cline@3.0.29 — 1 indicator(s)
    @infisical/cli@0.43.96 — 1 indicator(s)
    bun-git-hooks@0.3.2
    axios@1.18.0 — 1 indicator(s)
    tar@7.5.16
    [82/82] analyzed
    ✓ 82 packages analyzed

  ✓ manifests saved to /home/runner/work/npm-cli/npm-cli/indicator-suggestions.packages.json

Step 5/5: Analyzing...

✅ Done!
   New this run:         82
   Found via deep scan:  11 new packages added — discovered by following require() imports across package boundaries during file fetch
   With lifecycle scripts: 82 (of 522 total examined)
   Covered by existing indicator defs: 18 (of 82 with lifecycle scripts)
   Uncategorized builds: 4 (have build hint, no matching indicator)
   Lifecycle-only (no build hint): 60 (postinstall/setup scripts, not native builders)
   Pattern gaps found:   0

   ⚠️  Only 22% of lifecycle-script packages are covered by existing indicators (18 of 82).
   Consider reviewing /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json and indicator-definitions.js with an AI assistant:
   ask it to compare the uncategorizedPackages entries against the existing indicator
   registry and suggest new commandPatterns, signals, or indicator entries. Improvements
   affect both approve-scripts (production scanning) and this suggestion tool.

   Written to: /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json



Generated indicator-suggestions.json
{
  "$ai": {
    "role": "You are an expert in the npm ecosystem, native Node.js addon build tooling, supply-chain security, and the structure of indicator-definitions.js in the npm/cli repository.",
    "thisFileIs": "A dataset generated by scripts/build-indicator-suggestions.js. It scans popular npm packages for lifecycle scripts and compares them against the INDICATOR_REGISTRY in lib/utils/indicator-definitions.js. Your job is to review the data below and propose concrete improvements to that file.",
    "howToReadThisFile": "Every top-level section (meta, coverage, existingDefinitionCoverage, uncategorizedPackages, commandPatternGaps) has a \"description\" field that explains what the section contains and how to interpret it, and a \"data\" field with the actual content.  Read the description first, then inspect data.",
    "sourceFile": "lib/utils/indicator-definitions.js",
    "tasks": [
      {
        "priority": 1,
        "task": "Review commandPatternGaps.data where frequency >= 3 OR weeklyDownloadTotal >= 50000.",
        "action": "For each: decide whether the token warrants a NEW indicator entry (new build tool not yet covered) or just an additional commandPattern on an EXISTING entry.  Ignore tokens that are generic JS keywords (const, require, stdio, inherit) or non-build tools (tsc, oclif, lint, rimraf)."
      },
      {
        "priority": 2,
        "task": "Review uncategorizedPackages.data sorted by weeklyDownloads descending.",
        "action": "For each: examine lifecycleScripts, commandTokens, detectedSignals, and inferredIndicatorFiles.  Propose a new INDICATOR_REGISTRY entry OR explain why it should not be added.  Focus on packages with weeklyDownloads > 10000."
      },
      {
        "priority": 3,
        "task": "Review existingDefinitionCoverage.data entries with low matchedCount.",
        "action": "Propose additional commandPatterns that would match real packages listed in the uncategorized or gap sections."
      }
    ],
    "outputFormat": {
      "forNewIndicatorEntry": {
        "file": "<the filename used as the registry key, e.g. \"Gruntfile.js\">",
        "label": "<short human-readable label>",
        "commandPatterns": [
          "<regex source strings — will be compiled with new RegExp(...)>"
        ],
        "signals": {
          "onFound": [
            "<signal name>"
          ],
          "onWarning": []
        },
        "scannerSteps": [
          "{ type: \"regex\", pattern: \"...\", group: 1, label: \"...\" }",
          "{ type: \"regex-all\", pattern: \"...\", group: 1, label: \"...\" }",
          "{ type: \"glob\", pattern: \"**/*.ext\", label: \"...\", maxDisplay: 20 }",
          "{ type: \"signal\", pattern: \"...\", signal: \"<signal-name>\" }"
        ],
        "addToNativeBuildCommandPattern": "<true if this tool compiles native code>",
        "rationale": "<why this indicator is needed and what real packages triggered it>"
      },
      "forExistingEntryUpdate": {
        "existingKey": "<key in INDICATOR_REGISTRY to update>",
        "addCommandPatterns": [
          "<new regex source strings>"
        ],
        "rationale": "<which packages would now be matched>"
      }
    },
    "availableSignals": [
      "native-build           — Compiles a native binary (.node addon, .so, .dylib) via node-gyp, CMake, Autoconf, or similar",
      "rust-native            — Compiles a Rust-backed native addon (Cargo.toml / neon)",
      "wasm-build             — Compiles a WebAssembly (.wasm) module",
      "android-native         — Android JNI/NDK native module",
      "gyp-conditions         — binding.gyp has platform/arch conditions (may behave differently per OS)",
      "make-build             — Makefile or task-runner driven build (Grunt/Gulp/Jake/Cake/Rake/Brunch/Taskfile) that can execute arbitrary shell commands",
      "binary-download        — Downloads a pre-built binary at install time (node-pre-gyp, prebuild-install, etc.)",
      "activates-bundled-binary — Makes a file bundled inside the npm tarball executable (chmod +x / fs.chmod with execute bit)",
      "runtime-installer      — Installs additional packages at runtime via npm/pnpm/yarn/bower install as a child process",
      "external-url           — Fetches a URL (curl/wget/node https) at install time",
      "source-downloader      — Fetches source code (curl/wget/git clone) at install time",
      "obfuscation-pattern    — Uses Base64/hex decoding or eval/Function() to hide executed code"
    ]
  },
  "meta": {
    "description": "Run metadata: when generated, registry size limit (topN), whether cross-package import following was enabled (deepScan), and which indicator filenames are currently registered.",
    "data": {
      "generatedAt": "2026-06-22T04:02:03.729Z",
      "topN": 2,
      "deepScan": true,
      "registryDefinitions": [
        "binding.gyp",
        "Cargo.toml",
        "build.rs",
        "CMakeLists.txt",
        "CMakePresets.json",
        "configure.ac",
        "android/build.gradle",
        "binary-downloader",
        "bundled-binary-installer",
        "runtime-installer",
        "source-downloader",
        "Makefile",
        "bower.json",
        "brunch-config.js",
        "Gruntfile.js",
        "gulpfile.js",
        "Jakefile",
        "Cakefile",
        "Rakefile",
        "Taskfile.yml",
        "Justfile"
      ]
    }
  },
  "coverage": {
    "description": "Aggregate counts for this run. totalScanned = registry pages fetched. uniqueNamesConsidered = distinct package names seen. withLifecycleScripts = had install/postinstall/etc. matchedByExistingDefinitions = covered by at least one indicator. uncategorizedBuildPackages = build signal present but no indicator matched. lifecycleOnlyNoBuildHint = lifecycle scripts with no build tool detected.",
    "data": {
      "totalScanned": 500,
      "uniqueNamesConsidered": 522,
      "withLifecycleScripts": 82,
      "matchedByExistingDefinitions": 18,
      "uncategorizedBuildPackages": 4,
      "lifecycleOnlyNoBuildHint": 60
    }
  },
  "existingDefinitionCoverage": {
    "description": "Per-indicator match counts against real packages. Each entry in data is keyed by indicator filename (e.g. \"binding.gyp\") with matchedCount = how many scanned packages matched that indicator's commandPatterns, and packages = the matched names. Low matchedCount relative to expected prevalence suggests commandPatterns need broadening.",
    "data": {
      "source-downloader": {
        "matchedCount": 5,
        "packages": [
          "@infisical/cli",
          "gws-with-audit-log",
          "netlify",
          "netlify-cli",
          "wasm-pack"
        ]
      },
      "runtime-installer": {
        "matchedCount": 4,
        "packages": [
          "9router",
          "hardhat-deploy",
          "netlify",
          "netlify-cli"
        ]
      },
      "bundled-binary-installer": {
        "matchedCount": 3,
        "packages": [
          "9router",
          "agent-browser",
          "cline"
        ]
      },
      "Cargo.toml": {
        "matchedCount": 2,
        "packages": [
          "agent-browser",
          "bun-pty"
        ]
      },
      "binding.gyp": {
        "matchedCount": 2,
        "packages": [
          "eas-cli",
          "ovsx"
        ]
      },
      "binary-downloader": {
        "matchedCount": 1,
        "packages": [
          "cloudflared"
        ]
      },
      "gulpfile.js": {
        "matchedCount": 1,
        "packages": [
          "axios"
        ]
      }
    }
  },
  "uncategorizedPackages": {
    "description": "Packages that have a build signal (native compile, binary download, runtime-installer, etc.) but no existing indicator definition matched them. Each item has: name, version, weeklyDownloads, lifecycleScripts, buildDependencies, commandTokens, inferredIndicatorFiles, detectedSignals, suggestedSignal. Sorted by weeklyDownloads descending — highest-value gaps first.",
    "data": [
      {
        "name": "@netlify/zip-it-and-ship-it",
        "version": "15.0.1",
        "weeklyDownloads": 531758,
        "lifecycleScripts": {
          "prepack": "npm run build"
        },
        "buildDependencies": [],
        "commandTokens": [],
        "inferredIndicatorFiles": [],
        "suggestedSignal": null
      },
      {
        "name": "hugo-extended",
        "version": "0.163.3",
        "weeklyDownloads": 240864,
        "lifecycleScripts": {
          "postinstall": "node postinstall.js"
        },
        "buildDependencies": [],
        "commandTokens": [],
        "inferredIndicatorFiles": [],
        "detectedSignals": [
          "reads-process-env",
          "requires-local-file"
        ],
        "suggestedSignal": "reads-process-env",
        "scannedFiles": [
          "postinstall.js"
        ]
      },
      {
        "name": "@mintlify/cli",
        "version": "4.0.1235",
        "weeklyDownloads": 193556,
        "lifecycleScripts": {
          "prepare": "npm run build"
        },
        "buildDependencies": [],
        "commandTokens": [],
        "inferredIndicatorFiles": [
          "binding.gyp"
        ],
        "suggestedSignal": "native-build"
      },
      {
        "name": "@claude-flow/cli",
        "version": "3.12.4",
        "weeklyDownloads": 46522,
        "lifecycleScripts": {
          "postinstall": "node ./scripts/postinstall.cjs"
        },
        "buildDependencies": [],
        "commandTokens": [],
        "inferredIndicatorFiles": [],
        "detectedSignals": [
          "writes-file"
        ],
        "suggestedSignal": "writes-file",
        "scannedFiles": [
          "scripts/postinstall.cjs"
        ]
      }
    ]
  },
  "commandPatternGaps": {
    "description": "Command tokens that appear in multiple uncategorized build packages but are not covered by any existing commandPatterns entry. Each item has: token, frequency (package count), weeklyDownloadTotal, packages, suggestedCommandPattern. Sorted by frequency × downloads — entries with frequency >= 3 or weeklyDownloadTotal >= 50000 are highest priority.",
    "data": []
  }
}

📦 Download full scan artifacts (zip)

script-risk-scanner.js:
- references-credential-env-var: add bracket notation (process.env['KEY'])
  and expand to CI secrets (NODE_AUTH_TOKEN, ACTIONS_RUNTIME_TOKEN,
  RUNNER_TOKEN, GOOGLE_APPLICATION_CREDENTIALS, AZURE_CLIENT_SECRET)
- network-access: add undici, ky, needle, phin, cross-fetch; add ESM
  import-from form so ESM-only HTTP packages are caught; global fetch()
  already covered by existing pattern
- writes-outside-package: remove process.cwd() heuristic — fired on
  any path construction including read-only ones (too many false positives);
  keep only the '../' string-literal heuristic
- dynamic-require: new signal — require() with a non-literal argument
  (variable/expression); loaded module cannot be statically determined
- wasm-load: new signal — WebAssembly.instantiate/compile/streaming;
  pre-compiled WASM payloads bypass all JS-level static analysis
- binary-download: extend XXXX_BINARY_PATH to also match XXXX_BINARY
  (covers packages that set a binary name var, not a full path)
- runtime-installer: add bun (bun add/install) and deno (deno install)
  alongside existing npm/yarn/pnpm coverage

indicator-definitions.js:
- Fix floating Makefile comment (was between android/build.gradle and
  binary-downloader; now correctly placed above the Makefile entry)
- Cargo.toml: add 'cargo install' commandPattern
- New: meson.build indicator (native-build signal; targets, deps, programs)
- New: build.gradle root-level entry (android-native; covers React Native
  packages that place Gradle at the package root, not android/)
- New variant aliases: gulpfile.ts, gulpfile.mjs, Gruntfile.coffee,
  Jakefile.js, Taskfile.yaml — registry key IS the on-disk filename so each
  variant needs its own entry
- NATIVE_BUILD_COMMAND_PATTERN: add meson setup/compile/build and ninja
- SIGNAL_DESCRIPTIONS: add wasm-load and dynamic-require; update
  native-build to mention Meson; update runtime-installer to mention bun/deno

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

✅ PASS — approve-scripts smoke report

All packages with lifecycle scripts are correctly identified as pending approval.

Package Type Status Top risks
@mongodb-js/zstd@7.0.0 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
@playwright/browser-chromium@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
@playwright/browser-firefox@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
@playwright/browser-webkit@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
better-sqlite3@12.11.1 transitive ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
esbuild@0.28.1 transitive ⏳ pending uses child_process (can spawn external commands), makes network requests, contains dense hex-escape sequences (obfuscation indicator)
kerberos@7.0.0 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
nx@22.7.5 transitive ⏳ pending may write outside the package directory, uses child_process (can spawn external commands), runs npm/yarn/pnpm install as a child process at install time (second-stage install — may pull arbitrary packages with scripts enabled)
puppeteer@24.36.1 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity), runs npm/yarn/pnpm install as a child process at install time (second-stage install — may pull arbitrary packages with scripts enabled)
sqlite3@6.0.1 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
unrs-resolver@1.12.2 transitive ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
📦 Download full reports (zip)

@github-actions

Copy link
Copy Markdown

✅ SUCCESS — indicator-suggestions deep scan

Command: node scripts/build-indicator-suggestions.js --add 9router --top 2 --deep

Full scan output (stdout + stderr)

📦 npm indicator-suggestions builder
   out:      /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json
   packages: /home/runner/work/npm-cli/npm-cli/indicator-suggestions.packages.json (permanent store)
   resume:   /home/runner/work/npm-cli/npm-cli/indicator-suggestions.tmp.json  (deleted on success)
   deep:     /home/runner/work/npm-cli/npm-cli/indicator-suggestions.deep  (indicator files cached by name@version)
             with: /home/runner/work/npm-cli/npm-cli/lib/utils/indicator-scanner.js

Loading package data...
  (no existing data — fresh run, targeting 2 packages)

  query order: node, build, addon, npm, android, javascript, cli, rust, native, react-native, wasm
Steps 1–3/5: Scanning popular packages for lifecycle scripts...
  (target: 2 more packages with lifecycle scripts)
  (skipping 0 already-scanned names)

  + injected candidate: 9router

Step 3.25/4: Resuming 1 pending candidates from previous run...

  Candidates: fetching 1...
    [1/1] checked, 1 with lifecycle scripts
    ✓ +1 of 1 candidates (1 in store, 100.0% hit rate)
    checkpoint saved — ready to refill

  query: keywords:node
    fetching offset=0...
    p1 offset=0 fetch=223ms | +250 new names, 0 seen-skips | 250 candidates, 1 in store
    saving checkpoint...
                       
    fetching offset=250...
    p2 offset=250 fetch=231ms | +250 new names, 0 seen-skips | 500 candidates, 1 in store
    saving checkpoint...
                       
  Candidates: fetching 500...
    [50/500] checked, 15 with lifecycle scripts
    [100/500] checked, 23 with lifecycle scripts
    [150/500] checked, 28 with lifecycle scripts
    [200/500] checked, 31 with lifecycle scripts
    [250/500] checked, 35 with lifecycle scripts
    [300/500] checked, 44 with lifecycle scripts
    [350/500] checked, 58 with lifecycle scripts
    [400/500] checked, 69 with lifecycle scripts
    [450/500] checked, 77 with lifecycle scripts
    [500/500] checked, 89 with lifecycle scripts
    [500/500] checked, 89 with lifecycle scripts
    ✓ +89 of 500 candidates (90 in store, 17.8% hit rate)
    checkpoint saved — ready to refill
  checkpoint: 90 packages, 501 seen — saved

  ✓ search complete: 500 names examined this run (501 unique in seen-set)

Step 3.5/4: Expanding scoped packages with unscoped peers...
  checking 32 bare names...
  32 of 32 peers exist — added to candidates

  Candidates: fetching 32...
    [32/32] checked, 2 with lifecycle scripts
    ✓ +2 of 32 candidates (92 in store, 6.3% hit rate)
    checkpoint saved — ready to refill
Step 4/5: Deep scanning packages via unpkg...
  ├─ A: fetching files for all packages...

  DeepFetch: fetching files for 92 packages...
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/package.json
  ↩️  HTTP 302 → https://unpkg.com/http@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/registry.npmjs.org@1.0.1/package.json
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib
  ↩️  HTTP 302 → https://unpkg.com/http@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 301 → https://unpkg.com/zlib@1.0.5/lib/zlib.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/registry.npmjs.org@1.0.1/index.js
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib_bindings
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib_bindings.js
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib_bindings.mjs
    [50/92] processed
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib_bindings.cjs
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/package.json
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/axios@1.18.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/adm-zip@0.5.17/package.json
  ↩️  HTTP 302 → https://unpkg.com/targz@1.0.1/package.json
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/url.js
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/adm-zip@0.5.17/adm-zip.js
  ↩️  HTTP 302 → https://unpkg.com/targz@1.0.1/index.js
  ↩️  HTTP 302 → https://unpkg.com/axios@1.18.0/dist/node/axios.cjs
  ↩️  HTTP 302 → https://unpkg.com/adm-zip@0.5.17/util
  ↩️  HTTP 302 → https://unpkg.com/adm-zip@0.5.17/zipEntry
  ↩️  HTTP 302 → https://unpkg.com/adm-zip@0.5.17/zipFile
  ↩️  HTTP 301 → https://unpkg.com/adm-zip@0.5.17/util/index.js
  ↩️  HTTP 302 → https://unpkg.com/targz@1.0.1/lib/targz.js
  ↩️  HTTP 301 → https://unpkg.com/adm-zip@0.5.17/zipEntry.js
  ↩️  HTTP 301 → https://unpkg.com/adm-zip@0.5.17/zipFile.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/http-browserify@1.7.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/http-browserify@1.7.0/index.js
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/tar@7.5.16/package.json
  ↩️  HTTP 302 → https://unpkg.com/unzipper@0.12.5/package.json
  ↩️  HTTP 302 → https://unpkg.com/needle@3.5.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/url-join@5.0.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/sumchecker@3.0.1/package.json
  ↩️  HTTP 302 → https://unpkg.com/libnpmconfig@1.2.1/package.json
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/path-arg.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-move-remove.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-native.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/opt-arg.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-manual.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-windows.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-posix.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/use-native.js
  ↩️  HTTP 302 → https://unpkg.com/http-browserify@1.7.0/lib/request
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/source/index.js
  ↩️  HTTP 302 → https://unpkg.com/unzipper@0.12.5/unzip.js
  ↩️  HTTP 302 → https://unpkg.com/needle@3.5.0/lib/needle
  ↩️  HTTP 301 → https://unpkg.com/http-browserify@1.7.0/lib/request.js
  ↩️  HTTP 301 → https://unpkg.com/needle@3.5.0/lib/needle.js
  ↩️  HTTP 302 → https://unpkg.com/url-join@5.0.0/lib/url-join.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/tar@7.5.16/dist/commonjs/index.min.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/sumchecker@3.0.1/index.js
  ↩️  HTTP 302 → https://unpkg.com/libnpmconfig@1.2.1/index.js
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/source/utilities.js
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/source/vendor/ansi-styles/index.js
  ↩️  HTTP 302 → https://unpkg.com/unzipper@0.12.5/lib/parse
  ↩️  HTTP 302 → https://unpkg.com/unzipper@0.12.5/lib/parseOne
  ↩️  HTTP 302 → https://unpkg.com/unzipper@0.12.5/lib/extract
  ↩️  HTTP 302 → https://unpkg.com/unzipper@0.12.5/lib/Open
  ↩️  HTTP 301 → https://unpkg.com/unzipper@0.12.5/lib/parseOne.js
  ↩️  HTTP 301 → https://unpkg.com/unzipper@0.12.5/lib/extract.js
  ↩️  HTTP 301 → https://unpkg.com/unzipper@0.12.5/lib/parse.js
  ↩️  HTTP 301 → https://unpkg.com/unzipper@0.12.5/lib/Open/index.js
  ↩️  HTTP 302 → https://unpkg.com/needle@3.5.0/lib/multipart
  ↩️  HTTP 302 → https://unpkg.com/needle@3.5.0/lib/auth
  ↩️  HTTP 302 → https://unpkg.com/needle@3.5.0/lib/querystring
  ↩️  HTTP 302 → https://unpkg.com/needle@3.5.0/lib/cookies
  ↩️  HTTP 302 → https://unpkg.com/needle@3.5.0/lib/parsers
  ↩️  HTTP 301 → https://unpkg.com/needle@3.5.0/lib/multipart.js
  ↩️  HTTP 301 → https://unpkg.com/needle@3.5.0/lib/auth.js
  ↩️  HTTP 301 → https://unpkg.com/needle@3.5.0/lib/querystring.js
  ↩️  HTTP 301 → https://unpkg.com/needle@3.5.0/lib/parsers.js
  ↩️  HTTP 301 → https://unpkg.com/needle@3.5.0/lib/cookies.js
  ↩️  HTTP 302 → https://unpkg.com/needle@3.5.0/lib/decoder
  ↩️  HTTP 302 → https://unpkg.com/needle@3.5.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/needle@3.5.0/lib/utils
  ↩️  HTTP 301 → https://unpkg.com/needle@3.5.0/lib/utils.js
  ↩️  HTTP 301 → https://unpkg.com/needle@3.5.0/lib/decoder.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
    [92/92] processed
    ✓ 92 packages processed, +18 discovered:
      + child_process
      + path
      + fs
      + zlib
      + http
      + registry.npmjs.org
      + url
      + https
      + adm-zip
      + rimraf
      + http-browserify
      + chalk
      + unzipper
      + needle
      + tar
      + url-join
      + sumchecker
      + libnpmconfig
    checkpoint saved

  ├─ B: draining discovered packages...

  Candidates: fetching 18...
    [18/18] checked, 3 with lifecycle scripts
    ✓ +3 of 18 candidates (95 in store, 16.7% hit rate)
    checkpoint saved — ready to refill
  ├─ C: fetching files for newly discovered packages...

  DeepFetch: fetching files for 95 packages...
  🗑️  file tree changed for axios (e3b0c44298fc1c14 → 60cb91b1d78bfbe6) — invalidating cache
    [50/95] processed [cached]
    [95/95] processed
    ✓ 95 packages processed, no new packages
    checkpoint saved

  └─ D: running indicator scan...

  DeepScan: analyzing 95 packages...
    9router@0.5.8 — 2 indicator(s)
    resolve@1.22.12
    is-core-module@2.16.2
    axios@1.18.0 — 3 indicator(s)
    unist-util-stringify-position@4.0.0
    unist-util-is@6.0.1
    mdast-util-to-string@4.0.0
    ts-node@10.9.2
    unist-util-visit@5.1.0
    cross-fetch@4.1.0
    unist-util-position@5.0.0
    fastest-levenshtein@1.0.16
    node-abi@4.31.0
    is-bun-module@2.0.0
    node-exports-info@1.6.0
    import-meta-resolve@4.2.0
    mdast-util-definitions@6.0.0
    unist-util-find-after@5.0.0
    @azure/msal-node@5.2.5
    estree-util-visit@2.0.0
    eslint-plugin-n@18.1.0
    log4js@6.9.1
    natural-orderby@5.0.0
    unist-util-position-from-estree@2.0.0
    @openfeature/server-sdk@1.22.0
    @dual-bundle/import-meta-resolve@4.2.1
    unist-util-filter@5.0.1
    unist-util-remove@4.0.0
    bun@1.3.14 — 2 indicator(s)
    @xmldom/is-dom-node@1.0.1
    unist-util-find-all-after@5.0.0
    @azure/arm-resources@7.0.0
    mdast-util-compact@5.0.0
    openapi-typescript-codegen@0.31.0
    node-liblzma@5.0.2 — 1 indicator(s)
    unist-util-select@5.1.0
    customerio-node@5.0.1
    unist-util-map@4.0.0
    @azure/arm-authorization@9.0.0
    @ladjs/country-language@1.0.3
    popsicle@12.1.2
    iso-url@1.2.1
    mdast-util-heading-style@3.0.0
    @azure/arm-containerinstance@9.1.0
    @azure/arm-monitor@7.0.0
    @azure/arm-sql@10.0.0
    @azure/arm-mysql@5.1.0
    @azure/arm-databricks@3.0.0
    @azure/arm-mysql-flexible@3.1.0
    @azure/msal-node-extensions@5.2.5 — 1 indicator(s)
    [50/95] analyzed
    @azure/arm-mariadb@2.1.0
    fetch-to-node@2.1.0
    @azure/arm-postgresql@6.1.0
    @azure/arm-sqlvirtualmachine@4.1.1
    @azure/arm-synapse@8.0.0
    saml2-js@4.0.4
    unist-util-find@3.0.0
    @azure/arm-features@3.1.0
    @azure/arm-privatedns@3.3.0
    ibm_db@4.0.1 — 2 indicator(s)
    @azure/arm-managementgroups@2.0.2
    unique-username-generator@1.5.1
    @op-engineering/op-sqlite@17.0.0 — 1 indicator(s)
    @azure/arm-rediscache@8.2.0
    universal-base64@2.1.0
    @isaacs/ts-node-temp-fork-for-pr-2009@10.9.7
    whatsapp-web.js@1.34.7
    @azure/arm-search@3.3.0
    @azure/arm-locks@2.1.0
    ts-sinon@2.0.2
    mermaid-isomorphic@3.1.0
    winston-transport-sentry-node@3.0.0
    shift-ast@7.0.0
    meteor-node-stubs@1.2.27
    @vscode/spdlog@0.15.8 — 1 indicator(s)
    @azure/arm-resources-subscriptions@2.1.0
    @azure/arm-security@5.0.0
    @azure/arm-appinsights@4.0.0
    @pact-foundation/pact-node@10.18.0 — 1 indicator(s)
    zstd.ts@1.1.3
    eslint-config-node@4.1.0
    @stacksjs/clarity@0.3.28
    lambda-log@3.1.0
    @azure/arm-automation@10.1.1
    openapi-typescript-fetch@2.2.1
    ts-node-maintained@10.9.6
    v8-profiler-next@1.10.0 — 2 indicator(s)
    unist-util-find-all-before@5.0.0
    esm-wallaby@3.2.35
    @pedrouid/environment@1.0.1
    spdlog@0.13.7 — 1 indicator(s)
    pact-node@1.3.8
    zlib@1.0.5 — 1 indicator(s)
    rimraf@6.1.3
    tar@7.5.16
    [95/95] analyzed
    ✓ 95 packages analyzed

  ✓ manifests saved to /home/runner/work/npm-cli/npm-cli/indicator-suggestions.packages.json

Step 5/5: Analyzing...

✅ Done!
   New this run:         95
   Found via deep scan:  18 new packages added — discovered by following require() imports across package boundaries during file fetch
   With lifecycle scripts: 95 (of 551 total examined)
   Covered by existing indicator defs: 18 (of 95 with lifecycle scripts)
   Uncategorized builds: 2 (have build hint, no matching indicator)
   Lifecycle-only (no build hint): 75 (postinstall/setup scripts, not native builders)
   Pattern gaps found:   0

   ⚠️  Only 19% of lifecycle-script packages are covered by existing indicators (18 of 95).
   Consider reviewing /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json and indicator-definitions.js with an AI assistant:
   ask it to compare the uncategorizedPackages entries against the existing indicator
   registry and suggest new commandPatterns, signals, or indicator entries. Improvements
   affect both approve-scripts (production scanning) and this suggestion tool.

   Written to: /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json



Generated indicator-suggestions.json
{
  "$ai": {
    "role": "You are an expert in the npm ecosystem, native Node.js addon build tooling, supply-chain security, and the structure of indicator-definitions.js in the npm/cli repository.",
    "thisFileIs": "A dataset generated by scripts/build-indicator-suggestions.js. It scans popular npm packages for lifecycle scripts and compares them against the INDICATOR_REGISTRY in lib/utils/indicator-definitions.js. Your job is to review the data below and propose concrete improvements to that file.",
    "howToReadThisFile": "Every top-level section (meta, coverage, existingDefinitionCoverage, uncategorizedPackages, commandPatternGaps) has a \"description\" field that explains what the section contains and how to interpret it, and a \"data\" field with the actual content.  Read the description first, then inspect data.",
    "sourceFile": "lib/utils/indicator-definitions.js",
    "tasks": [
      {
        "priority": 1,
        "task": "Review commandPatternGaps.data where frequency >= 3 OR weeklyDownloadTotal >= 50000.",
        "action": "For each: decide whether the token warrants a NEW indicator entry (new build tool not yet covered) or just an additional commandPattern on an EXISTING entry.  Ignore tokens that are generic JS keywords (const, require, stdio, inherit) or non-build tools (tsc, oclif, lint, rimraf)."
      },
      {
        "priority": 2,
        "task": "Review uncategorizedPackages.data sorted by weeklyDownloads descending.",
        "action": "For each: examine lifecycleScripts, commandTokens, detectedSignals, and inferredIndicatorFiles.  Propose a new INDICATOR_REGISTRY entry OR explain why it should not be added.  Focus on packages with weeklyDownloads > 10000."
      },
      {
        "priority": 3,
        "task": "Review existingDefinitionCoverage.data entries with low matchedCount.",
        "action": "Propose additional commandPatterns that would match real packages listed in the uncategorized or gap sections."
      }
    ],
    "outputFormat": {
      "forNewIndicatorEntry": {
        "file": "<the filename used as the registry key, e.g. \"Gruntfile.js\">",
        "label": "<short human-readable label>",
        "commandPatterns": [
          "<regex source strings — will be compiled with new RegExp(...)>"
        ],
        "signals": {
          "onFound": [
            "<signal name>"
          ],
          "onWarning": []
        },
        "scannerSteps": [
          "{ type: \"regex\", pattern: \"...\", group: 1, label: \"...\" }",
          "{ type: \"regex-all\", pattern: \"...\", group: 1, label: \"...\" }",
          "{ type: \"glob\", pattern: \"**/*.ext\", label: \"...\", maxDisplay: 20 }",
          "{ type: \"signal\", pattern: \"...\", signal: \"<signal-name>\" }"
        ],
        "addToNativeBuildCommandPattern": "<true if this tool compiles native code>",
        "rationale": "<why this indicator is needed and what real packages triggered it>"
      },
      "forExistingEntryUpdate": {
        "existingKey": "<key in INDICATOR_REGISTRY to update>",
        "addCommandPatterns": [
          "<new regex source strings>"
        ],
        "rationale": "<which packages would now be matched>"
      }
    },
    "availableSignals": [
      "native-build           — Compiles a native binary (.node addon, .so, .dylib) via node-gyp, CMake, Meson, Autoconf, or similar",
      "rust-native            — Compiles a Rust-backed native addon (Cargo.toml / neon)",
      "wasm-build             — Compiles a WebAssembly (.wasm) module from Rust source (wasm-bindgen)",
      "wasm-load              — Loads a pre-compiled WebAssembly binary at runtime via WebAssembly.instantiate/compile — content is not inspectable by static analysis",
      "android-native         — Android JNI/NDK native module",
      "gyp-conditions         — binding.gyp has platform/arch conditions (may behave differently per OS)",
      "make-build             — Makefile or task-runner driven build (Grunt/Gulp/Jake/Cake/Rake/Brunch/Taskfile) that can execute arbitrary shell commands",
      "binary-download        — Downloads a pre-built binary at install time (node-pre-gyp, prebuild-install, etc.)",
      "activates-bundled-binary — Makes a file bundled inside the npm tarball executable (chmod +x / fs.chmod with execute bit)",
      "runtime-installer      — Installs additional packages at runtime via npm/pnpm/yarn/bun/deno/bower install as a child process",
      "external-url           — Fetches a URL (curl/wget/node https) at install time",
      "source-downloader      — Fetches source code (curl/wget/git clone) at install time",
      "dynamic-require        — Calls require() with a non-literal argument — the loaded module cannot be statically determined",
      "obfuscation-pattern    — Uses Base64/hex decoding or eval/Function() to hide executed code"
    ]
  },
  "meta": {
    "description": "Run metadata: when generated, registry size limit (topN), whether cross-package import following was enabled (deepScan), and which indicator filenames are currently registered.",
    "data": {
      "generatedAt": "2026-06-22T04:29:57.138Z",
      "topN": 2,
      "deepScan": true,
      "registryDefinitions": [
        "binding.gyp",
        "Cargo.toml",
        "build.rs",
        "CMakeLists.txt",
        "CMakePresets.json",
        "configure.ac",
        "meson.build",
        "android/build.gradle",
        "binary-downloader",
        "bundled-binary-installer",
        "runtime-installer",
        "source-downloader",
        "build.gradle",
        "Makefile",
        "bower.json",
        "brunch-config.js",
        "Gruntfile.js",
        "gulpfile.js",
        "Jakefile",
        "Cakefile",
        "Rakefile",
        "Taskfile.yml",
        "gulpfile.ts",
        "gulpfile.mjs",
        "Gruntfile.coffee",
        "Jakefile.js",
        "Taskfile.yaml",
        "Justfile"
      ]
    }
  },
  "coverage": {
    "description": "Aggregate counts for this run. totalScanned = registry pages fetched. uniqueNamesConsidered = distinct package names seen. withLifecycleScripts = had install/postinstall/etc. matchedByExistingDefinitions = covered by at least one indicator. uncategorizedBuildPackages = build signal present but no indicator matched. lifecycleOnlyNoBuildHint = lifecycle scripts with no build tool detected.",
    "data": {
      "totalScanned": 500,
      "uniqueNamesConsidered": 551,
      "withLifecycleScripts": 95,
      "matchedByExistingDefinitions": 18,
      "uncategorizedBuildPackages": 2,
      "lifecycleOnlyNoBuildHint": 75
    }
  },
  "existingDefinitionCoverage": {
    "description": "Per-indicator match counts against real packages. Each entry in data is keyed by indicator filename (e.g. \"binding.gyp\") with matchedCount = how many scanned packages matched that indicator's commandPatterns, and packages = the matched names. Low matchedCount relative to expected prevalence suggests commandPatterns need broadening.",
    "data": {
      "binding.gyp": {
        "matchedCount": 6,
        "packages": [
          "@azure/msal-node-extensions",
          "@vscode/spdlog",
          "ibm_db",
          "node-liblzma",
          "spdlog",
          "v8-profiler-next"
        ]
      },
      "runtime-installer": {
        "matchedCount": 3,
        "packages": [
          "9router",
          "bun",
          "v8-profiler-next"
        ]
      },
      "source-downloader": {
        "matchedCount": 3,
        "packages": [
          "@pact-foundation/pact-node",
          "bun",
          "ibm_db"
        ]
      },
      "bundled-binary-installer": {
        "matchedCount": 1,
        "packages": [
          "9router"
        ]
      },
      "gulpfile.js": {
        "matchedCount": 1,
        "packages": [
          "axios"
        ]
      },
      "gulpfile.ts": {
        "matchedCount": 1,
        "packages": [
          "axios"
        ]
      },
      "gulpfile.mjs": {
        "matchedCount": 1,
        "packages": [
          "axios"
        ]
      },
      "android/build.gradle": {
        "matchedCount": 1,
        "packages": [
          "@op-engineering/op-sqlite"
        ]
      },
      "Makefile": {
        "matchedCount": 1,
        "packages": [
          "zlib"
        ]
      }
    }
  },
  "uncategorizedPackages": {
    "description": "Packages that have a build signal (native compile, binary download, runtime-installer, etc.) but no existing indicator definition matched them. Each item has: name, version, weeklyDownloads, lifecycleScripts, buildDependencies, commandTokens, inferredIndicatorFiles, detectedSignals, suggestedSignal. Sorted by weeklyDownloads descending — highest-value gaps first.",
    "data": [
      {
        "name": "whatsapp-web.js",
        "version": "1.34.7",
        "weeklyDownloads": 95137,
        "lifecycleScripts": {
          "prepare": "is-ci || husky"
        },
        "buildDependencies": [],
        "commandTokens": [
          "is-ci"
        ],
        "inferredIndicatorFiles": [],
        "suggestedSignal": null
      },
      {
        "name": "pact-node",
        "version": "1.3.8",
        "weeklyDownloads": 0,
        "lifecycleScripts": {
          "postinstall": "node ./scripts/check-dependencies.js"
        },
        "buildDependencies": [],
        "commandTokens": [],
        "inferredIndicatorFiles": [],
        "suggestedSignal": null,
        "scannedFiles": [
          "scripts/check-dependencies.js"
        ]
      }
    ]
  },
  "commandPatternGaps": {
    "description": "Command tokens that appear in multiple uncategorized build packages but are not covered by any existing commandPatterns entry. Each item has: token, frequency (package count), weeklyDownloadTotal, packages, suggestedCommandPattern. Sorted by frequency × downloads — entries with frequency >= 3 or weeklyDownloadTotal >= 50000 are highest priority.",
    "data": []
  }
}

📦 Download full scan artifacts (zip)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

✅ PASS — approve-scripts smoke report

All packages with lifecycle scripts are correctly identified as pending approval.

Package Type Status Top risks
@mongodb-js/zstd@7.0.0 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
@playwright/browser-chromium@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
@playwright/browser-firefox@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
@playwright/browser-webkit@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
better-sqlite3@12.11.1 transitive ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
esbuild@0.28.1 transitive ⏳ pending uses child_process (can spawn external commands), makes network requests, contains dense hex-escape sequences (obfuscation indicator)
kerberos@7.0.0 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
nx@22.7.5 transitive ⏳ pending may write outside the package directory, uses child_process (can spawn external commands), runs npm/yarn/pnpm install as a child process at install time (second-stage install — may pull arbitrary packages with scripts enabled)
puppeteer@24.36.1 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity), runs npm/yarn/pnpm install as a child process at install time (second-stage install — may pull arbitrary packages with scripts enabled)
sqlite3@6.0.1 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
unrs-resolver@1.12.2 transitive ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
📦 Download full reports (zip)

@github-actions

Copy link
Copy Markdown

✅ SUCCESS — indicator-suggestions deep scan

Command: node scripts/build-indicator-suggestions.js --add 9router --top 100 --deep

Full scan output (stdout + stderr)

📦 npm indicator-suggestions builder
   out:      /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json
   packages: /home/runner/work/npm-cli/npm-cli/indicator-suggestions.packages.json (permanent store)
   resume:   /home/runner/work/npm-cli/npm-cli/indicator-suggestions.tmp.json  (deleted on success)
   deep:     /home/runner/work/npm-cli/npm-cli/indicator-suggestions.deep  (indicator files cached by name@version)
             with: /home/runner/work/npm-cli/npm-cli/lib/utils/indicator-scanner.js

Loading package data...
  (no existing data — fresh run, targeting 100 packages)

  query order: cli, rust, javascript, node, native, react-native, npm, build, addon, android, wasm
Steps 1–3/5: Scanning popular packages for lifecycle scripts...
  (target: 100 more packages with lifecycle scripts)
  (skipping 0 already-scanned names)

  + injected candidate: 9router

Step 3.25/4: Resuming 1 pending candidates from previous run...

  Candidates: fetching 1...
    [1/1] checked, 1 with lifecycle scripts
    ✓ +1 of 1 candidates (1 in store, 100.0% hit rate)
    checkpoint saved — ready to refill

  query: keywords:cli
    fetching offset=0...
    p1 offset=0 fetch=187ms | +250 new names, 0 seen-skips | 250 candidates, 1 in store
    saving checkpoint...
                       
    fetching offset=250...
    p2 offset=250 fetch=309ms | +250 new names, 0 seen-skips | 500 candidates, 1 in store
    saving checkpoint...
                       
  Candidates: fetching 500...
    [50/500] checked, 7 with lifecycle scripts
    [100/500] checked, 9 with lifecycle scripts
    [150/500] checked, 21 with lifecycle scripts
    [200/500] checked, 24 with lifecycle scripts
    [250/500] checked, 31 with lifecycle scripts
    [300/500] checked, 44 with lifecycle scripts
    [350/500] checked, 49 with lifecycle scripts
    [400/500] checked, 61 with lifecycle scripts
    [450/500] checked, 71 with lifecycle scripts
    [500/500] checked, 79 with lifecycle scripts
    [500/500] checked, 79 with lifecycle scripts
    ✓ +79 of 500 candidates (80 in store, 15.8% hit rate)
    checkpoint saved — ready to refill
    fetching offset=500...
    p3 offset=500 fetch=330ms | +249 new names, 1 seen-skips | 249 candidates, 80 in store
    saving checkpoint...
                       
    fetching offset=750...
    p4 offset=750 fetch=283ms | +250 new names, 0 seen-skips | 499 candidates, 80 in store
    saving checkpoint...
                       
  Candidates: fetching 499...
    [50/499] checked, 8 with lifecycle scripts
    [100/499] checked, 14 with lifecycle scripts
    [150/499] checked, 21 with lifecycle scripts
    [200/499] checked, 28 with lifecycle scripts
    [250/499] checked, 39 with lifecycle scripts
    [300/499] checked, 54 with lifecycle scripts
    [350/499] checked, 67 with lifecycle scripts
    [400/499] checked, 77 with lifecycle scripts
    [450/499] checked, 93 with lifecycle scripts
    [499/499] checked, 108 with lifecycle scripts
    ✓ +108 of 499 candidates (188 in store, 21.6% hit rate)
    checkpoint saved — ready to refill
  checkpoint: 188 packages, 1000 seen — saved

  ✓ search complete: 1,000 names examined this run (1,000 unique in seen-set)

Step 3.5/4: Expanding scoped packages with unscoped peers...
  checking 39 bare names...
  39 of 39 peers exist — added to candidates

  Candidates: fetching 39...
    [39/39] checked, 1 with lifecycle scripts
    ✓ +1 of 39 candidates (189 in store, 2.6% hit rate)
    checkpoint saved — ready to refill
Step 4/5: Deep scanning packages via unpkg...
  ├─ A: fetching files for all packages...

  DeepFetch: fetching files for 189 packages...
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/package.json
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/url.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/index.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/tty@1.0.1/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/tty@1.0.1/index.js
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/boxen@8.0.1/package.json
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/source/index.js
  ↩️  HTTP 302 → https://unpkg.com/boxen@8.0.1/index.js
  ↩️  HTTP 302 → https://unpkg.com/axios-proxy-builder@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/axios@1.18.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/package.json
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/source/utilities.js
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/source/vendor/ansi-styles/index.js
  ↩️  HTTP 302 → https://unpkg.com/axios-proxy-builder@0.1.2/dist/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/axios@1.18.0/dist/node/axios.cjs
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/index.js
  ↩️  HTTP 302 → https://unpkg.com/axios-proxy-builder@0.1.2/dist/proxy-builder
  ↩️  HTTP 301 → https://unpkg.com/axios-proxy-builder@0.1.2/dist/proxy-builder.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/opt-arg.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/path-arg.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-manual.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-move-remove.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-native.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-posix.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/rimraf-windows.js
  ↩️  HTTP 302 → https://unpkg.com/rimraf@6.1.3/dist/commonjs/use-native.js
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/boxen@8.0.1/package.json
    [50/189] processed
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/source/index.js
  ↩️  HTTP 302 → https://unpkg.com/boxen@8.0.1/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/url.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/tar@7.5.16/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/tar@7.5.16/dist/commonjs/index.min.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/hardhat-deploy@2.0.8/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/hardhat-deploy@2.0.8/dist/v1-entry.cjs
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/tar@7.5.16/package.json
  ↩️  HTTP 302 → https://unpkg.com/yauzl@3.4.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/tar@7.5.16/dist/commonjs/index.min.js
  ↩️  HTTP 302 → https://unpkg.com/yauzl@3.4.0/index.js
  ↩️  HTTP 301 → https://unpkg.com/zlib@1.0.5/lib/zlib.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/yauzl@3.4.0/fd-slicer
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib_bindings
  ↩️  HTTP 302 → https://unpkg.com/yauzl@3.4.0/crc32
  ↩️  HTTP 301 → https://unpkg.com/yauzl@3.4.0/fd-slicer.js
  ↩️  HTTP 301 → https://unpkg.com/yauzl@3.4.0/crc32.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/better-sqlite3@12.11.1/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib_bindings.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib_bindings.mjs
  ↩️  HTTP 302 → https://unpkg.com/better-sqlite3@12.11.1/lib/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/module@2.0.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/url.js
  ↩️  HTTP 302 → https://unpkg.com/better-sqlite3@12.11.1/lib/database
  ↩️  HTTP 302 → https://unpkg.com/better-sqlite3@12.11.1/lib/sqlite-error
  ↩️  HTTP 302 → https://unpkg.com/zlib@1.0.5/lib/zlib_bindings.cjs
  ↩️  HTTP 301 → https://unpkg.com/better-sqlite3@12.11.1/lib/database.js
  ↩️  HTTP 301 → https://unpkg.com/better-sqlite3@12.11.1/lib/sqlite-error.js
  ↩️  HTTP 302 → https://unpkg.com/module@2.0.0/dist/index.js
  ↩️  HTTP 302 → https://unpkg.com/crypto@1.0.1/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/stream@0.0.3/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
    [100/189] processed
  ↩️  HTTP 302 → https://unpkg.com/crypto@1.0.1/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/stream@0.0.3/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/module@2.0.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/package.json
  ↩️  HTTP 302 → https://unpkg.com/module@2.0.0/dist/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/url.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
    [150/189] processed
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/http@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/index.js
  ↩️  HTTP 302 → https://unpkg.com/http@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/http@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/http@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/chalk@5.6.2/source/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/crypto@1.0.1/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/url.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/crypto@1.0.1/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/package.json
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/https@1.0.0/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/os@0.1.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/url@0.11.4/url.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
    [189/189] processed
    ✓ 189 packages processed, +16 discovered:
      + child_process
      + fs
      + os
      + path
      + url
      + https
      + tty
      + axios-proxy-builder
      + axios
      + tar
      + zlib
      + yauzl
      + better-sqlite3
      + crypto
      + stream
      + http
    checkpoint saved

  ├─ B: draining discovered packages...

  Candidates: fetching 16...
    [16/16] checked, 4 with lifecycle scripts
    ✓ +4 of 16 candidates (193 in store, 25.0% hit rate)
    checkpoint saved — ready to refill
  ├─ C: fetching files for newly discovered packages...

  DeepFetch: fetching files for 193 packages...
  🗑️  file tree changed for rimraf (e3b0c44298fc1c14 → 89f16c2b6c7ec140) — invalidating cache
    [50/193] processed [cached]
  🗑️  file tree changed for hardhat-deploy (9a9aea7d5c249562 → aa1dd6bbb97b7345) — invalidating cache
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/package.json
  ↩️  HTTP 302 → https://unpkg.com/hardhat-deploy@2.0.8/package.json
  ↩️  HTTP 302 → https://unpkg.com/child_process@1.0.2/index.js
  ↩️  HTTP 302 → https://unpkg.com/path@0.12.7/path.js
  ↩️  HTTP 302 → https://unpkg.com/fs@0.0.1-security/index.js
  ↩️  HTTP 302 → https://unpkg.com/hardhat-deploy@2.0.8/dist/v1-entry.cjs
    [100/193] processed [cached]
    [150/193] processed [cached]
    [193/193] processed
    ✓ 193 packages processed, no new packages
    checkpoint saved

  └─ D: running indicator scan...

  DeepScan: analyzing 193 packages...
    9router@0.5.8 — 2 indicator(s)
    yargs@18.0.0
    cliui@9.0.1
    yargs-parser@22.0.0
    jackspeak@4.2.3
    rimraf@6.1.3
    meow@14.1.0
    @isaacs/cliui@9.0.0
    html-minifier-terser@7.2.0
    @oclif/core@4.11.9
    clipanion@4.0.0-rc.4
    ink@7.1.0
    skills@1.5.12
    getopts@2.3.0
    tokenjuice@0.8.1
    logkitty@0.7.1
    gradient-string@3.0.0
    yeoman-environment@6.1.0
    ansi-fragments@0.2.1
    yeoman-generator@8.2.2
    env-cmd@11.0.0
    sequelize-cli@6.6.5
    acpx@0.11.0
    agent-browser@0.29.1 — 2 indicator(s)
    eas-cli@20.3.0 — 1 indicator(s)
    @oclif/table@0.5.9
    yo@7.0.1
    @vscode/test-cli@0.0.12
    stdout-update@4.0.1
    unicode-animations@1.0.3
    @netlify/zip-it-and-ship-it@15.0.1
    tap@21.7.4
    @oclif/multi-stage-output@0.8.44
    ipull@4.0.3
    yaml-lint@1.7.0
    oclif@4.23.17
    ink-testing-library@4.0.0
    typeorm-extension@3.9.0
    gws-with-audit-log@0.0.5 — 1 indicator(s)
    netlify@26.1.0 — 2 indicator(s)
    hugo-extended@0.163.3
    codeowners-audit@2.9.0
    levenshtein-edit-distance@3.0.1
    xo@3.0.2
    unified-args@11.0.1
    netlify-cli@26.1.0 — 2 indicator(s)
    @mintlify/cli@4.0.1235
    chalk-animation@2.0.3
    @grafana/e2e-selectors@13.0.2
    openapi-merge-cli@1.3.2
    [50/193] analyzed
    backport@12.0.4
    ovsx@1.0.1 — 1 indicator(s)
    agent-device@0.17.6
    @memlab/cli@2.0.3
    @fission-ai/openspec@1.4.1
    @xarc/run@2.3.0
    typed-scss-modules@8.1.1
    relaydesgins@0.1.1
    md-to-pdf@5.2.5
    wasm-pack@0.15.0 — 1 indicator(s)
    @tapjs/run@4.5.8
    cloudflared@0.7.1 — 1 indicator(s)
    prismjs-terminal@1.2.4
    oauth2-mock-server@9.0.0
    delivery-intel@1.7.0
    bun-pty@0.4.10 — 1 indicator(s)
    hardhat-deploy@2.0.8 — 1 indicator(s)
    ansicolor@2.0.3
    @amplitude/ampli@1.36.3
    stemmer@2.0.1
    ai-sdk-provider-claude-code@3.5.0
    eslint-watch@8.0.0
    ai-sdk-provider-codex-cli@1.2.2
    double-metaphone@2.0.1
    @claude-flow/cli@3.12.4
    @ai-hero/sandcastle@0.10.0
    clickhouse-migrations@1.3.2
    cline@3.0.29 — 1 indicator(s)
    @infisical/cli@0.43.96 — 1 indicator(s)
    bun-git-hooks@0.3.2
    @marp-team/marp-cli@4.4.0 — 1 indicator(s)
    pkg-install@1.0.0
    @jrichman/ink@7.0.1-beta.4
    instar@1.3.641 — 1 indicator(s)
    process.argv@1.0.0
    stdio@2.1.3
    @kilocode/cli@7.3.50 — 2 indicator(s)
    syllable@5.0.1
    @salesforce/cli-plugins-testkit@5.3.62
    pastel@4.0.1
    ink-scroll-view@0.3.7
    dice-coefficient@2.1.1
    @transifex/cli@8.0.0
    @googleworkspace/cli@0.22.5 — 2 indicator(s)
    html-minifier-next@7.0.0
    typeorm-fixtures-cli@4.1.0
    ink-scroll-list@0.4.1
    command-join@3.0.0
    @oh-my-pi/pi-coding-agent@16.1.13
    transloadit@4.10.8
    [100/193] analyzed
    hexo-cli@4.3.2
    elm-test@0.19.1-revision17
    zod2md@0.3.3
    @mimo-ai/cli@0.1.1 — 1 indicator(s)
    interactive-commander@0.6.0
    installed-check@10.0.1
    node-credstasher@1.0.4
    @bike4mind/cli@0.16.0 — 2 indicator(s)
    wonjang-agent@1.67.2 — 1 indicator(s)
    xycolors@0.1.2
    @morphllm/morphsdk@0.2.184
    reasonix@0.53.2
    cli-high@0.4.3
    dotenv-mono@1.5.1
    codeowners-generator@2.5.0
    markdansi@0.3.1
    skill-check@1.2.0
    devmoji@2.3.0
    ssl-checker@3.0.1
    react-client@1.2.3
    merge-drivers@1.0.4
    @zendesk/zcli-core@1.1.0
    @kitelev/exocortex-cli@16.147.1 — 1 indicator(s)
    @miller-tech/uap@1.60.0
    @launchdarkly/ldcli@3.0.4
    elm-review@2.13.5
    bump-cli@2.10.1
    first-tree-staging@0.5.9-staging.493.1
    @zendesk/zcli@1.1.1
    can-npm-publish@1.3.6
    @jackwener/opencli@1.8.4
    firecrawl-cli@1.19.18
    @mindexec/cli@0.2.340
    icoa-cli@2.19.305
    @metamask/client-mcp-core@0.5.0
    @zendesk/zcli-apps@1.1.0
    agent-afk@4.29.1 — 1 indicator(s)
    nemar-cli@0.8.69
    oh-my-codex@0.18.14 — 1 indicator(s)
    skillfish@1.0.38
    tsnite@0.2.3
    @gitlawb/openclaude@0.19.0
    @zendesk/zcli-themes@1.1.0
    @contentstack/cli-command@1.8.3
    clang-format-node@3.0.6 — 1 indicator(s)
    inquirer-sortable-checkbox@1.0.1
    codexui-android@0.1.125 — 1 indicator(s)
    @qoder-ai/qodercli@1.0.24 — 1 indicator(s)
    npkill@0.12.2 — 3 indicator(s)
    github-release-from-changelog@3.0.0
    [150/193] analyzed
    suppress-exit-code@3.2.0
    zod-opts@1.0.0
    aidevops@3.21.11
    npm-run-all-next@1.4.2
    paqad-ai@1.26.2 — 1 indicator(s)
    skykoi@2026.3.348 — 2 indicator(s)
    @xdevplatform/xurl@1.0.3 — 1 indicator(s)
    frogress-bar@0.1.0
    ts@0.2.2
    zodcli@0.0.4
    omnius@1.0.329 — 1 indicator(s)
    @cparra/apexdocs@3.22.1
    json-to-plain-text@1.2.0
    purescript@0.15.16
    firebase-tools-with-isolate@15.22.0
    block-no-verify@1.3.0
    minify-xml@4.5.2
    @contentstack/cli-config@1.20.4
    @probelabs/maid@0.0.29
    @prisma/cli@3.0.0-beta.14
    @iola_adm/iola-cli@0.2.69 — 2 indicator(s)
    carrot-scan@6.0.1
    adhdev@0.9.81
    debug-color2@1.3.2
    @jheavenknows/bluerouter@4.0.43 — 2 indicator(s)
    generate-react-cli@11.0.4
    branch-name-lint@3.0.1
    @newpeak/barista-cli@0.2.63
    codeam-cli@2.39.79 — 1 indicator(s)
    @mablhq/mabl-cli@2.115.13
    @contentstack/cli-cm-import@1.33.3
    @contentstack/cli@1.63.1
    @contentstack/cli-cm-migrate-rte@1.7.0
    @zendesk/zcli-connectors@1.1.1
    opensrc@0.7.2 — 2 indicator(s)
    dingtalk-workspace-cli@1.0.39 — 1 indicator(s)
    yarpm@1.2.0
    @aashari/mcp-server-atlassian-jira@3.3.0 — 1 indicator(s)
    apexdocs@0.0.6
    axios@1.18.0 — 3 indicator(s)
    tar@7.5.16
    zlib@1.0.5 — 1 indicator(s)
    better-sqlite3@12.11.1 — 1 indicator(s)
    [193/193] analyzed
    ✓ 193 packages analyzed

  ✓ manifests saved to /home/runner/work/npm-cli/npm-cli/indicator-suggestions.packages.json

Step 5/5: Analyzing...

✅ Done!
   New this run:         193
   Already-seen skips:   1 (0% of results were repeats)
   Found via deep scan:  16 new packages added — discovered by following require() imports across package boundaries during file fetch
   With lifecycle scripts: 193 (of 1,054 total examined)
   Covered by existing indicator defs: 55 (of 193 with lifecycle scripts)
   Uncategorized builds: 11 (have build hint, no matching indicator)
   Lifecycle-only (no build hint): 127 (postinstall/setup scripts, not native builders)
   Pattern gaps found:   1

   ⚠️  Only 28% of lifecycle-script packages are covered by existing indicators (55 of 193).
   Consider reviewing /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json and indicator-definitions.js with an AI assistant:
   ask it to compare the uncategorizedPackages entries against the existing indicator
   registry and suggest new commandPatterns, signals, or indicator entries. Improvements
   affect both approve-scripts (production scanning) and this suggestion tool.

   Written to: /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json



Generated indicator-suggestions.json
{
  "$ai": {
    "role": "You are an expert in the npm ecosystem, native Node.js addon build tooling, supply-chain security, and the structure of indicator-definitions.js in the npm/cli repository.",
    "thisFileIs": "A dataset generated by scripts/build-indicator-suggestions.js. It scans popular npm packages for lifecycle scripts and compares them against the INDICATOR_REGISTRY in lib/utils/indicator-definitions.js. Your job is to review the data below and propose concrete improvements to that file.",
    "howToReadThisFile": "Every top-level section (meta, coverage, existingDefinitionCoverage, uncategorizedPackages, commandPatternGaps) has a \"description\" field that explains what the section contains and how to interpret it, and a \"data\" field with the actual content.  Read the description first, then inspect data.",
    "sourceFile": "lib/utils/indicator-definitions.js",
    "tasks": [
      {
        "priority": 1,
        "task": "Review commandPatternGaps.data where frequency >= 3 OR weeklyDownloadTotal >= 50000.",
        "action": "For each: decide whether the token warrants a NEW indicator entry (new build tool not yet covered) or just an additional commandPattern on an EXISTING entry.  Ignore tokens that are generic JS keywords (const, require, stdio, inherit) or non-build tools (tsc, oclif, lint, rimraf)."
      },
      {
        "priority": 2,
        "task": "Review uncategorizedPackages.data sorted by weeklyDownloads descending.",
        "action": "For each: examine lifecycleScripts, commandTokens, detectedSignals, and inferredIndicatorFiles.  Propose a new INDICATOR_REGISTRY entry OR explain why it should not be added.  Focus on packages with weeklyDownloads > 10000."
      },
      {
        "priority": 3,
        "task": "Review existingDefinitionCoverage.data entries with low matchedCount.",
        "action": "Propose additional commandPatterns that would match real packages listed in the uncategorized or gap sections."
      }
    ],
    "outputFormat": {
      "forNewIndicatorEntry": {
        "file": "<the filename used as the registry key, e.g. \"Gruntfile.js\">",
        "label": "<short human-readable label>",
        "commandPatterns": [
          "<regex source strings — will be compiled with new RegExp(...)>"
        ],
        "signals": {
          "onFound": [
            "<signal name>"
          ],
          "onWarning": []
        },
        "scannerSteps": [
          "{ type: \"regex\", pattern: \"...\", group: 1, label: \"...\" }",
          "{ type: \"regex-all\", pattern: \"...\", group: 1, label: \"...\" }",
          "{ type: \"glob\", pattern: \"**/*.ext\", label: \"...\", maxDisplay: 20 }",
          "{ type: \"signal\", pattern: \"...\", signal: \"<signal-name>\" }"
        ],
        "addToNativeBuildCommandPattern": "<true if this tool compiles native code>",
        "rationale": "<why this indicator is needed and what real packages triggered it>"
      },
      "forExistingEntryUpdate": {
        "existingKey": "<key in INDICATOR_REGISTRY to update>",
        "addCommandPatterns": [
          "<new regex source strings>"
        ],
        "rationale": "<which packages would now be matched>"
      }
    },
    "availableSignals": [
      "native-build           — Compiles a native binary (.node addon, .so, .dylib) via node-gyp, CMake, Meson, Autoconf, or similar",
      "rust-native            — Compiles a Rust-backed native addon (Cargo.toml / neon)",
      "wasm-build             — Compiles a WebAssembly (.wasm) module from Rust source (wasm-bindgen)",
      "wasm-load              — Loads a pre-compiled WebAssembly binary at runtime via WebAssembly.instantiate/compile — content is not inspectable by static analysis",
      "android-native         — Android JNI/NDK native module",
      "gyp-conditions         — binding.gyp has platform/arch conditions (may behave differently per OS)",
      "make-build             — Makefile or task-runner driven build (Grunt/Gulp/Jake/Cake/Rake/Brunch/Taskfile) that can execute arbitrary shell commands",
      "binary-download        — Downloads a pre-built binary at install time (node-pre-gyp, prebuild-install, etc.)",
      "activates-bundled-binary — Makes a file bundled inside the npm tarball executable (chmod +x / fs.chmod with execute bit)",
      "runtime-installer      — Installs additional packages at runtime via npm/pnpm/yarn/bun/deno/bower install as a child process",
      "external-url           — Fetches a URL (curl/wget/node https) at install time",
      "source-downloader      — Fetches source code (curl/wget/git clone) at install time",
      "dynamic-require        — Calls require() with a non-literal argument — the loaded module cannot be statically determined",
      "obfuscation-pattern    — Uses Base64/hex decoding or eval/Function() to hide executed code"
    ]
  },
  "meta": {
    "description": "Run metadata: when generated, registry size limit (topN), whether cross-package import following was enabled (deepScan), and which indicator filenames are currently registered.",
    "data": {
      "generatedAt": "2026-06-22T04:39:10.090Z",
      "topN": 100,
      "deepScan": true,
      "registryDefinitions": [
        "binding.gyp",
        "Cargo.toml",
        "build.rs",
        "CMakeLists.txt",
        "CMakePresets.json",
        "configure.ac",
        "meson.build",
        "android/build.gradle",
        "binary-downloader",
        "bundled-binary-installer",
        "runtime-installer",
        "source-downloader",
        "build.gradle",
        "Makefile",
        "bower.json",
        "brunch-config.js",
        "Gruntfile.js",
        "gulpfile.js",
        "Jakefile",
        "Cakefile",
        "Rakefile",
        "Taskfile.yml",
        "gulpfile.ts",
        "gulpfile.mjs",
        "Gruntfile.coffee",
        "Jakefile.js",
        "Taskfile.yaml",
        "Justfile"
      ]
    }
  },
  "coverage": {
    "description": "Aggregate counts for this run. totalScanned = registry pages fetched. uniqueNamesConsidered = distinct package names seen. withLifecycleScripts = had install/postinstall/etc. matchedByExistingDefinitions = covered by at least one indicator. uncategorizedBuildPackages = build signal present but no indicator matched. lifecycleOnlyNoBuildHint = lifecycle scripts with no build tool detected.",
    "data": {
      "totalScanned": 1000,
      "uniqueNamesConsidered": 1054,
      "withLifecycleScripts": 193,
      "matchedByExistingDefinitions": 55,
      "uncategorizedBuildPackages": 11,
      "lifecycleOnlyNoBuildHint": 127
    }
  },
  "existingDefinitionCoverage": {
    "description": "Per-indicator match counts against real packages. Each entry in data is keyed by indicator filename (e.g. \"binding.gyp\") with matchedCount = how many scanned packages matched that indicator's commandPatterns, and packages = the matched names. Low matchedCount relative to expected prevalence suggests commandPatterns need broadening.",
    "data": {
      "bundled-binary-installer": {
        "matchedCount": 16,
        "packages": [
          "9router",
          "@aashari/mcp-server-atlassian-jira",
          "@googleworkspace/cli",
          "@jheavenknows/bluerouter",
          "@kilocode/cli",
          "@kitelev/exocortex-cli",
          "@mimo-ai/cli",
          "@xdevplatform/xurl",
          "agent-browser",
          "clang-format-node",
          "cline",
          "codexui-android",
          "dingtalk-workspace-cli",
          "opensrc",
          "paqad-ai",
          "wonjang-agent"
        ]
      },
      "runtime-installer": {
        "matchedCount": 12,
        "packages": [
          "9router",
          "@bike4mind/cli",
          "@iola_adm/iola-cli",
          "@jheavenknows/bluerouter",
          "@kilocode/cli",
          "@qoder-ai/qodercli",
          "agent-afk",
          "codeam-cli",
          "hardhat-deploy",
          "netlify",
          "netlify-cli",
          "omnius"
        ]
      },
      "source-downloader": {
        "matchedCount": 9,
        "packages": [
          "@googleworkspace/cli",
          "@infisical/cli",
          "@iola_adm/iola-cli",
          "@marp-team/marp-cli",
          "gws-with-audit-log",
          "instar",
          "netlify",
          "netlify-cli",
          "wasm-pack"
        ]
      },
      "Cargo.toml": {
        "matchedCount": 4,
        "packages": [
          "agent-browser",
          "bun-pty",
          "oh-my-codex",
          "opensrc"
        ]
      },
      "binding.gyp": {
        "matchedCount": 4,
        "packages": [
          "@bike4mind/cli",
          "better-sqlite3",
          "eas-cli",
          "ovsx"
        ]
      },
      "gulpfile.js": {
        "matchedCount": 2,
        "packages": [
          "axios",
          "npkill"
        ]
      },
      "gulpfile.ts": {
        "matchedCount": 2,
        "packages": [
          "axios",
          "npkill"
        ]
      },
      "gulpfile.mjs": {
        "matchedCount": 2,
        "packages": [
          "axios",
          "npkill"
        ]
      },
      "binary-downloader": {
        "matchedCount": 1,
        "packages": [
          "cloudflared"
        ]
      },
      "android/build.gradle": {
        "matchedCount": 1,
        "packages": [
          "skykoi"
        ]
      },
      "build.gradle": {
        "matchedCount": 1,
        "packages": [
          "skykoi"
        ]
      },
      "Makefile": {
        "matchedCount": 1,
        "packages": [
          "zlib"
        ]
      }
    }
  },
  "uncategorizedPackages": {
    "description": "Packages that have a build signal (native compile, binary download, runtime-installer, etc.) but no existing indicator definition matched them. Each item has: name, version, weeklyDownloads, lifecycleScripts, buildDependencies, commandTokens, inferredIndicatorFiles, detectedSignals, suggestedSignal. Sorted by weeklyDownloads descending — highest-value gaps first.",
    "data": [
      {
        "name": "@netlify/zip-it-and-ship-it",
        "version": "15.0.1",
        "weeklyDownloads": 531758,
        "lifecycleScripts": {
          "prepack": "npm run build"
        },
        "buildDependencies": [],
        "commandTokens": [],
        "inferredIndicatorFiles": [],
        "suggestedSignal": null
      },
      {
        "name": "hugo-extended",
        "version": "0.163.3",
        "weeklyDownloads": 240864,
        "lifecycleScripts": {
          "postinstall": "node postinstall.js"
        },
        "buildDependencies": [],
        "commandTokens": [],
        "inferredIndicatorFiles": [],
        "detectedSignals": [
          "reads-process-env",
          "requires-local-file"
        ],
        "suggestedSignal": "reads-process-env",
        "scannedFiles": [
          "postinstall.js"
        ]
      },
      {
        "name": "@mintlify/cli",
        "version": "4.0.1235",
        "weeklyDownloads": 193556,
        "lifecycleScripts": {
          "prepare": "npm run build"
        },
        "buildDependencies": [],
        "commandTokens": [],
        "inferredIndicatorFiles": [
          "binding.gyp"
        ],
        "suggestedSignal": "native-build"
      },
      {
        "name": "@claude-flow/cli",
        "version": "3.12.4",
        "weeklyDownloads": 46522,
        "lifecycleScripts": {
          "postinstall": "node ./scripts/postinstall.cjs"
        },
        "buildDependencies": [],
        "commandTokens": [],
        "inferredIndicatorFiles": [],
        "detectedSignals": [
          "writes-file"
        ],
        "suggestedSignal": "writes-file",
        "scannedFiles": [
          "scripts/postinstall.cjs"
        ]
      },
      {
        "name": "@salesforce/cli-plugins-testkit",
        "version": "5.3.62",
        "weeklyDownloads": 38653,
        "lifecycleScripts": {
          "prepare": "sf-install",
          "prepack": "sf-prepack"
        },
        "buildDependencies": [],
        "commandTokens": [
          "sf-install",
          "sf-prepack"
        ],
        "inferredIndicatorFiles": [],
        "suggestedSignal": null
      },
      {
        "name": "@zendesk/zcli-apps",
        "version": "1.1.0",
        "weeklyDownloads": 12515,
        "lifecycleScripts": {
          "prepack": "tsc && ../../scripts/prepack.sh"
        },
        "buildDependencies": [],
        "commandTokens": [
          "tsc"
        ],
        "inferredIndicatorFiles": [],
        "suggestedSignal": null
      },
      {
        "name": "adhdev",
        "version": "0.9.81",
        "weeklyDownloads": 10868,
        "lifecycleScripts": {
          "preinstall": "node -e \"const major=Number.parseInt(process.versions.node.split('.')[0],10); if (process.platform === 'win32' && major >= 24) { console.error('\\n✗ ADHDev does not currently support Node.js 24+ on Windows.\\n  Install Node.js 22.x on Windows, then retry.\\n'); process.exit(1); }\""
        },
        "buildDependencies": [],
        "commandTokens": [
          "const",
          "major",
          "number.parseint",
          "process.versions.node.split",
          "process.platform",
          "win32",
          "console.error",
          "adhdev",
          "does",
          "not",
          "currently",
          "support",
          "windows",
          "retry"
        ],
        "inferredIndicatorFiles": [],
        "suggestedSignal": null
      },
      {
        "name": "firebase-tools-with-isolate",
        "version": "15.22.0",
        "weeklyDownloads": 10385,
        "lifecycleScripts": {
          "prepare": "npm run clean && npm run build:publish"
        },
        "buildDependencies": [],
        "commandTokens": [
          "publish"
        ],
        "inferredIndicatorFiles": [],
        "suggestedSignal": null
      },
      {
        "name": "@mablhq/mabl-cli",
        "version": "2.115.13",
        "weeklyDownloads": 9286,
        "lifecycleScripts": {
          "postinstall": "node ./util/postInstallMessage.js"
        },
        "buildDependencies": [],
        "commandTokens": [
          "util"
        ],
        "inferredIndicatorFiles": [],
        "detectedSignals": [
          "uses-child-process",
          "reads-process-env",
          "requires-local-file",
          "writes-file",
          "file-unreadable"
        ],
        "suggestedSignal": "uses-child-process",
        "scannedFiles": [
          "util/postInstallMessage.js",
          "→ child_process",
          "→ fs",
          "→ path",
          "→ chalk",
          "util/agentInstallRegistry.js",
          "→ os"
        ]
      },
      {
        "name": "@gitlawb/openclaude",
        "version": "0.19.0",
        "weeklyDownloads": 7660,
        "lifecycleScripts": {
          "prepack": "npm run build"
        },
        "buildDependencies": [],
        "commandTokens": [],
        "inferredIndicatorFiles": [],
        "suggestedSignal": null
      },
      {
        "name": "@zendesk/zcli-co

…(truncated — 16078 chars total, see artifact for full file)

📦 Download full scan artifacts (zip)

vbjay and others added 7 commits June 22, 2026 01:35
…ge.json)

resolveRelPosix returned null for not-yet-fetched files that already carry
a file extension (e.g. .json, .ts, .yaml), causing the caller's else branch
to append '.js' and try to fetch 'package.json.js' -- which always 404s.

Two-part fix:
- resolveRelPosix: after the extension-probe loop, if absPath already has a
  non-empty extension and is within pkgCacheDir, return the bare relPosix
  immediately so the caller can fetch it rather than falling through to null
- fetchWithRefs else-branch: check path.extname(rel) and use the path as-is
  when it already has an extension; only append .js for bare specifiers

This means require('../package.json') (and similar .ts/.yaml/.json refs)
will now be correctly fetched and defanged in the deep scan cache.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
With 5 concurrent workers, completion lines appear after the work
finishes -- a hung package shows nothing. Adding a 'fetching pkg@ver...'
and 'scanning pkg@ver...' line before each call means the last
unmatched line identifies the problematic package.

Output pattern in DeepScan:
    scanning opencv-build@0.1.9...
    opencv-build@0.1.9 -- 1 indicator(s)
    scanning magic-comments-loader@3.0.0...
    magic-comments-loader@3.0.0

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add UNQUOTED_URL_RE to findExternalUrls so shell scripts with
  unquoted URLs (e.g. curl https://...) emit the external-url signal
- Add external-url detection to detectSignals (bare URL check)
- Add maxDepth option to scanPackageScripts so callers can override
  the recursion cap; update test to pass maxDepth:3 instead of relying
  on the global MAX_DEPTH constant
- Add maxFiles option to scanPackageScripts + scanFile to cap BFS
  at MAX_FILES_DEEP_SCAN=100 per package in deep analysis
- Add maybeSignalFilesLimitReached helper that appends
  'files-limit-reached' signal when the cap is hit
- In build-indicator-suggestions: add NODE_BUILTIN_MODULES set and
  isValidNpmPackageName validator; skip both in fetchBarePackage and
  in deepAnalyzePackage's fetchedPkgs loop to prevent scanning Node
  built-ins and invalid names like 'LLM_TENSOR_NAMES' (false positives
  from JS comments matching BARE_IMPORT_FROM_RE)
- Skip self-referencing packages in deepAnalyzePackage loop to avoid
  double-scanning the same 258-file bundle (node-llama-cpp hang fix)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lock kill

Deep cache directories now use name@version (e.g. node-llama-cpp@3.18.1/)
instead of name-only dirs.  Benefits:
- Different versions of the same package get isolated cache entries
- Cache validity check no longer needs to compare version field in meta
  (the dir name already encodes it); hashDirTree still guards against
  content changes
- same package@v1 and package@v2 can coexist as separate manifests

fetchBarePackage stores versioned entries in fetchedPkgs (name@version
strings) so deepAnalyzePackage can locate the correct versioned dir via
parseDeepPkgEntry().  Old-format bare-name entries are silently skipped.

Allow same package different versions in manifests/candidates:
- inStore now keys on name@version not just name
- --add flag supports name@version syntax (e.g. --add react@17,react@18)
  and resolves them via the registry at the specified version tag
- getPackageManifest() parses optional @Version suffix from the input name

Lock file zombie-kill: when a stale heartbeat lock belongs to a PID that
is still alive (hung process), send SIGTERM + SIGKILL instead of silently
overwriting the lock — prevents two concurrent instances racing over the
same cache files.

Also adds 256 KB file-size guard for fetchedPkgs bare-dep scans to skip
compiler bundles like typescript.js (9 MB) that would block the event loop.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s for packages.json

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ointing guard, always-resolve bareFollows

- Remove if (!fromCache) guard on follow-resolution: cached packages must
  still have their bareFollows resolved so discovered deps are not lost when a
  run is interrupted after file-fetch but before the checkpoint saved them.

- Add 
esolvedFollows field to deep-scan meta (.meta.json): after resolving a
  package's bare follows via unpkg, the resulting name@version list is written
  back to meta. Subsequent cache-hits use this list directly, skipping the HTTP
  round-trips entirely.

- Add isCheckpointing flag in DrainMode.DeepFetch: prevents two workers from
  racing to write the checkpoint files concurrently. If a save is already in
  flight the second call is a no-op; the in-flight save will capture any state
  added before its snapshot was taken (savePackageCache builds its snapshot
  synchronously before any await).

- Final checkpoint() call after workers complete is unconditional (guard is
  always false at that point) so the last batch of candidates is always saved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Packages discovered via deep-scan require() follows are added to candidates
as 'name@version' strings — they never appear in npm search result pages so
searchDownloads.get() always returns undefined, leaving weeklyDownloads: 0.

When weekly is not in the search-result cache, fetch it from
api.npmjs.org/downloads/point/last-week/{name} inline during the Candidates
drain.  Failure is non-critical (weeklyDownloads stays 0) so deep-scan
packages still appear in the output even if the downloads API is down.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

✅ PASS — approve-scripts smoke report

All packages with lifecycle scripts are correctly identified as pending approval.

Package Type Status Top risks
@mongodb-js/zstd@7.0.0 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
@playwright/browser-chromium@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
@playwright/browser-firefox@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
@playwright/browser-webkit@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
better-sqlite3@12.11.1 transitive ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
esbuild@0.28.1 transitive ⏳ pending uses child_process (can spawn external commands), makes network requests, contains dense hex-escape sequences (obfuscation indicator)
kerberos@7.0.0 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
nx@22.7.5 transitive ⏳ pending may write outside the package directory, uses child_process (can spawn external commands), runs npm/yarn/pnpm install as a child process at install time (second-stage install — may pull arbitrary packages with scripts enabled)
puppeteer@24.36.1 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity), runs npm/yarn/pnpm install as a child process at install time (second-stage install — may pull arbitrary packages with scripts enabled)
sqlite3@6.0.1 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
unrs-resolver@1.12.2 transitive ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
📦 Download full reports (zip)

@github-actions

Copy link
Copy Markdown

✅ SUCCESS — indicator-suggestions deep scan

Command: node scripts/build-indicator-suggestions.js --add 9router --top 100 --deep

Full scan output (stdout + stderr)

📦 npm indicator-suggestions builder
   out:      /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json
   packages: /home/runner/work/npm-cli/npm-cli/indicator-suggestions.packages.json (permanent store)
   resume:   /home/runner/work/npm-cli/npm-cli/indicator-suggestions.tmp.json  (deleted on success)
   deep:     /home/runner/work/npm-cli/npm-cli/indicator-suggestions.deep  (indicator files cached by name@version)
             with: /home/runner/work/npm-cli/npm-cli/lib/utils/indicator-scanner.js

Loading package data...
  (no existing data — fresh run, targeting 100 packages)

  query order: native, cli, node, addon, react-native, javascript, npm, android, wasm, rust, build
Steps 1–3/5: Scanning popular packages for lifecycle scripts...
  (target: 100 more packages with lifecycle scripts)
  (skipping 0 already-scanned names)

  + injected candidate: 9router

Step 3.25/4: Resuming 1 pending candidates from previous run...

  Candidates: fetching 1...
    [1/1] checked, 1 with lifecycle scripts
    ✓ +1 of 1 candidates (1 in store, 100.0% hit rate)
    checkpoint saved — ready to refill

  query: keywords:native
    fetching offset=0...
    p1 offset=0 fetch=335ms | +250 new names, 0 seen-skips | 250 candidates, 1 in store
    saving checkpoint...
                       
    fetching offset=250...
    p2 offset=250 fetch=370ms | +250 new names, 0 seen-skips | 500 candidates, 1 in store
    saving checkpoint...
                       
  Candidates: fetching 500...
    [50/500] checked, 11 with lifecycle scripts
    [100/500] checked, 23 with lifecycle scripts
    [150/500] checked, 33 with lifecycle scripts
    [200/500] checked, 58 with lifecycle scripts
    [250/500] checked, 76 with lifecycle scripts
    [300/500] checked, 88 with lifecycle scripts
    [350/500] checked, 97 with lifecycle scripts
    [400/500] checked, 106 with lifecycle scripts
    [450/500] checked, 109 with lifecycle scripts
    [500/500] checked, 118 with lifecycle scripts
    [500/500] checked, 118 with lifecycle scripts
    ✓ +118 of 500 candidates (119 in store, 23.6% hit rate)
    checkpoint saved — ready to refill
  checkpoint: 119 packages, 501 seen — saved

  ✓ search complete: 500 names examined this run (501 unique in seen-set)

Step 3.5/4: Expanding scoped packages with unscoped peers...
  checking 58 bare names...
  58 of 58 peers exist — added to candidates

  Candidates: fetching 58...
    [50/58] checked, 3 with lifecycle scripts
    [58/58] checked, 6 with lifecycle scripts
    ✓ +6 of 58 candidates (125 in store, 10.3% hit rate)
    checkpoint saved — ready to refill
Step 4/5: Deep scanning packages via unpkg...
  ├─ pass 1: fetching files + collecting follows...

  DeepFetch: 125 packages (0 cached)...
    scanning 9router@0.5.8...
    scanning has-symbols@1.1.0...
    scanning mkdirp@3.0.1...
    scanning async-function@1.0.0...
    scanning generator-function@2.0.1...
    scanning cross-fetch@4.1.0...
    scanning @react-navigation/native-stack@7.17.5...
    scanning react-native-reanimated@4.4.1...
    scanning react-native-worklets@0.9.2...
    scanning async-generator-function@1.0.0...
    scanning preview-email@3.1.3...
    scanning node-liblzma@5.0.3...
    scanning react-native-haptic-feedback@3.0.0...
    scanning react-native-image-picker@8.2.1...
    scanning @capacitor/assets@3.0.5...
    scanning @discordjs/opus@0.10.0...
    scanning zeromq@6.5.0...
    scanning @ladjs/country-language@1.0.3...
    scanning @sentry-internal/node-native-stacktrace@0.5.0...
    scanning array-hyper-unique@2.1.8...
    scanning @revenuecat/purchases-capacitor@13.2.0...
    scanning ruvector@0.2.32...
    scanning @sentry/capacitor@4.1.0...
    scanning @legendapp/motion@2.5.3...
    scanning @gluestack-ui/overlay@0.1.22...
    scanning react-native-fast-crypto@3.0.0...
    scanning bun-pty@0.4.10...
    scanning @gluestack-ui/form-control@0.1.19...
  ↩️  HTTP 302 → https://unpkg.com/detect-libc@2.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/node-abi@3.92.0/package.json
    scanning @gluestack-ui/toast@1.0.9...
      + discovered cmake-ts@1.0.2
    scanning @gluestack-ui/hooks@0.1.13...
    scanning @gluestack-ui/provider@0.1.19...
      + discovered detect-libc@2.1.2
      + discovered node-abi@3.92.0
    scanning @gluestack-ui/icon@0.1.27...
    scanning @gluestack-ui/avatar@0.1.18...
    scanning zstd-napi@0.0.12...
    scanning @gluestack-ui/button@1.0.14...
    scanning @gluestack-ui/checkbox@0.1.39...
    scanning @gluestack-ui/input@0.1.38...
    scanning @gluestack-ui/switch@0.1.29...
    scanning @gluestack-ui/radio@0.1.40...
    scanning @gluestack-ui/transitions@0.1.11...
    scanning @gluestack-ui/progress@0.1.18...
    scanning @gluestack-ui/textarea@0.1.25...
    scanning unsafe-pointer@0.2.0...
    scanning @gluestack-ui/modal@0.1.41...
    scanning @gluestack-ui/alert-dialog@0.1.38...
    scanning @gluestack-ui/spinner@0.1.15...
    scanning @gluestack-ui/accordion@1.0.14...
    scanning @gluestack-ui/menu@0.2.43...
    scanning @gluestack-ui/select@0.1.31...
    scanning @gluestack-ui/pressable@0.1.23...
    scanning @codetrix-studio/capacitor-google-auth@3.4.0-rc.4...
    scanning @gluestack-ui/actionsheet@0.2.53...
    scanning react-native-modalize@2.1.1...
    scanning @gluestack-ui/alert@0.1.16...
    [50] processed
    scanning @gluestack-ui/image@0.1.17...
    scanning @gluestack-ui/divider@0.1.10...
    scanning react-native-network-logger@3.0.0...
    scanning @gluestack-ui/popover@0.1.49...
    scanning @gluestack-ui/slider@0.1.32...
    scanning native-machine-id@0.3.11...
    scanning @gluestack-ui/tooltip@0.1.44...
    scanning @gluestack-ui/link@0.1.29...
    scanning @capacitor/inappbrowser@4.0.0...
    scanning @gluestack-ui/fab@0.1.28...
    scanning react-native-shadow-2@7.1.2...
    scanning @gluestack-style/animation-resolver@1.0.4...
    scanning @gluestack-ui/themed@1.1.73...
    scanning @gluestack-style/legend-motion-animation-driver@1.0.3...
    scanning @gluestack-ui/nativewind-utils@1.0.28...
    scanning @gluestack-ui/config@1.1.20...
    scanning binjumper@0.1.4...
    scanning @capacitor-community/admob@8.0.0...
    scanning react-native-portalize@1.0.7...
    scanning react-native-circular-progress-indicator@4.4.2...
    scanning @react-native-assets/slider@11.0.12...
    scanning @unleash/proxy@1.4.19...
    scanning react-native-monorepo-tools@1.2.1...
    scanning @capacitor-community/http@1.4.1...
    scanning react-native-draglist@3.10.0...
    scanning ref-napi@3.0.3...
    scanning react-native-navigation-mode@1.2.9...
    scanning expo-native-sheet-emojis@2.1.0...
    scanning storage-engine@3.0.7...
    scanning @capacitor/background-runner@3.0.0...
    scanning expo-native-emojis-popup@1.1.2...
    scanning @revenuecat/purchases-capacitor-ui@13.2.0...
    scanning nodegit@0.27.0...
    scanning react-native-restart-newarch@1.0.85...
    scanning @kreuzberg/node@4.9.9...
    scanning @capacitor-community/camera-preview@8.0.1...
    scanning cf-prefs@2.0.1...
    scanning react-native-markdown-renderer@4.1.1...
    scanning @callstack/react-native-brownfield@3.12.0...
    scanning @jasonscheirer/native-progress-bar@1.0.7...
    scanning react-native-screenguard@2.0.2...
    scanning @sentry/node-native-stacktrace@0.5.1...
    scanning react-native-calendar-strip@2.2.6...
    scanning llama-cpp-capacitor@0.1.5...
    scanning @samyok/annoy@1.1.0...
    scanning capacitor-branch-deep-links@10.0.0...
    scanning lru-native2@1.2.6...
    scanning electron-click-drag-plugin@2.0.2...
    scanning react-native-easy-markdown@2.0.0...
    [100] processed
    scanning native-reg@1.1.1...
    scanning @newrelic/newrelic-capacitor-plugin@1.6.6...
    scanning native-hdr-histogram@1.0.0...
  ↩️  HTTP 302 → https://unpkg.com/detect-libc@2.1.2/package.json
  ↩️  HTTP 302 → https://unpkg.com/node-abi@3.92.0/package.json
    scanning react-native-rate-app@2.0.1...
    scanning socketcan@4.2.2...
    scanning @d-fischer/cross-fetch@5.0.5...
    scanning react-native-pdf-jsi@4.4.2...
    scanning @figma/nodegit@0.28.0-figma.9...
    scanning react-native-scanbot-sdk@8.0.0...
    scanning data-fns@1.1.0...
    scanning node-window-manager@2.2.4...
    scanning ref@1.3.5...
    scanning rsa-keygen@1.0.6...
    scanning raknet-native@1.2.3...
    scanning zmq@2.15.3...
    scanning node-mac-permissions@2.5.0...
    scanning motion@12.40.0...
    scanning modal@0.8.0...
    scanning fab@1.0.0-rc.9...
    scanning node@26.3.1...
    scanning native-progress-bar@1.0.3...
    scanning annoy@4.0.0...
  ↩️  HTTP 301 → https://unpkg.com/node@26.3.1/installArchSpecificPackage.js
  ↩️  HTTP 302 → https://unpkg.com/node-bin-setup@1.1.4/package.json
      + discovered node-bin-setup@1.1.4
  ↩️  HTTP 302 → https://unpkg.com/bindings@1.5.0/package.json
  ↩️  HTTP 302 → https://unpkg.com/debug@4.4.3/package.json
  ↩️  HTTP 302 → https://unpkg.com/segfault-handler@1.3.0/package.json
      + discovered debug@4.4.3
      + discovered bindings@1.5.0
      + discovered segfault-handler@1.3.0
    ✓ 125 packages processed, 7 new candidates queued
    checkpoint saved

  ├─ pass 1: draining 7 discovered packages into manifests...

  Candidates: fetching 7...
    [7/7] checked, 1 with lifecycle scripts
    ✓ +1 of 7 candidates (126 in store, 14.3% hit rate)
    checkpoint saved — ready to refill
  ├─ pass 2: fetching files + collecting follows...

  DeepFetch: 126 packages (125 cached)...
    scanning segfault-handler@1.3.0...
    scanning 9router@0.5.8...
    scanning has-symbols@1.1.0...
    scanning mkdirp@3.0.1...
    scanning async-function@1.0.0...
    scanning generator-function@2.0.1...
    scanning cross-fetch@4.1.0...
    scanning @react-navigation/native-stack@7.17.5...
    scanning react-native-reanimated@4.4.1...
    scanning react-native-worklets@0.9.2...
    scanning async-generator-function@1.0.0...
    scanning preview-email@3.1.3...
    scanning node-liblzma@5.0.3...
    scanning react-native-haptic-feedback@3.0.0...
    scanning react-native-image-picker@8.2.1...
    scanning @capacitor/assets@3.0.5...
    scanning @discordjs/opus@0.10.0...
    scanning zeromq@6.5.0...
    scanning @ladjs/country-language@1.0.3...
    scanning @sentry-internal/node-native-stacktrace@0.5.0...
    scanning array-hyper-unique@2.1.8...
    scanning @revenuecat/purchases-capacitor@13.2.0...
    scanning ruvector@0.2.32...
    scanning @sentry/capacitor@4.1.0...
    scanning @legendapp/motion@2.5.3...
    scanning @gluestack-ui/overlay@0.1.22...
    scanning react-native-fast-crypto@3.0.0...
    scanning bun-pty@0.4.10...
    scanning @gluestack-ui/form-control@0.1.19...
    scanning @gluestack-ui/toast@1.0.9...
    scanning @gluestack-ui/hooks@0.1.13...
    scanning @gluestack-ui/provider@0.1.19...
    scanning @gluestack-ui/icon@0.1.27...
    scanning @gluestack-ui/avatar@0.1.18...
    scanning zstd-napi@0.0.12...
    scanning @gluestack-ui/button@1.0.14...
    scanning @gluestack-ui/checkbox@0.1.39...
    scanning @gluestack-ui/input@0.1.38...
    scanning @gluestack-ui/switch@0.1.29...
    scanning @gluestack-ui/radio@0.1.40...
    scanning @gluestack-ui/transitions@0.1.11...
    scanning @gluestack-ui/progress@0.1.18...
    scanning @gluestack-ui/textarea@0.1.25...
    scanning unsafe-pointer@0.2.0...
    scanning @gluestack-ui/modal@0.1.41...
    scanning @gluestack-ui/alert-dialog@0.1.38...
    scanning @gluestack-ui/spinner@0.1.15...
    scanning @gluestack-ui/accordion@1.0.14...
    scanning @gluestack-ui/menu@0.2.43...
    scanning @gluestack-ui/select@0.1.31...
    scanning @gluestack-ui/pressable@0.1.23...
    scanning @codetrix-studio/capacitor-google-auth@3.4.0-rc.4...
    scanning @gluestack-ui/actionsheet@0.2.53...
    [50] processed
    scanning react-native-modalize@2.1.1...
    scanning @gluestack-ui/alert@0.1.16...
    scanning @gluestack-ui/image@0.1.17...
    scanning @gluestack-ui/divider@0.1.10...
    scanning react-native-network-logger@3.0.0...
    scanning @gluestack-ui/popover@0.1.49...
    scanning @gluestack-ui/slider@0.1.32...
    scanning native-machine-id@0.3.11...
    scanning @gluestack-ui/tooltip@0.1.44...
    scanning @gluestack-ui/link@0.1.29...
    scanning @capacitor/inappbrowser@4.0.0...
    scanning @gluestack-ui/fab@0.1.28...
    scanning react-native-shadow-2@7.1.2...
    scanning @gluestack-style/animation-resolver@1.0.4...
    scanning @gluestack-ui/themed@1.1.73...
    scanning @gluestack-style/legend-motion-animation-driver@1.0.3...
    scanning @gluestack-ui/nativewind-utils@1.0.28...
    scanning @gluestack-ui/config@1.1.20...
    scanning binjumper@0.1.4...
    scanning @capacitor-community/admob@8.0.0...
    scanning react-native-portalize@1.0.7...
    scanning react-native-circular-progress-indicator@4.4.2...
    scanning @react-native-assets/slider@11.0.12...
    scanning @unleash/proxy@1.4.19...
    scanning react-native-monorepo-tools@1.2.1...
    scanning @capacitor-community/http@1.4.1...
    scanning react-native-draglist@3.10.0...
    scanning ref-napi@3.0.3...
    scanning react-native-navigation-mode@1.2.9...
    scanning expo-native-sheet-emojis@2.1.0...
    scanning storage-engine@3.0.7...
    scanning @capacitor/background-runner@3.0.0...
    scanning expo-native-emojis-popup@1.1.2...
    scanning @revenuecat/purchases-capacitor-ui@13.2.0...
    scanning nodegit@0.27.0...
    scanning react-native-restart-newarch@1.0.85...
    scanning @kreuzberg/node@4.9.9...
    scanning @capacitor-community/camera-preview@8.0.1...
    scanning cf-prefs@2.0.1...
    scanning react-native-markdown-renderer@4.1.1...
    scanning @callstack/react-native-brownfield@3.12.0...
    scanning @jasonscheirer/native-progress-bar@1.0.7...
    scanning react-native-screenguard@2.0.2...
    scanning @sentry/node-native-stacktrace@0.5.1...
    scanning react-native-calendar-strip@2.2.6...
    scanning llama-cpp-capacitor@0.1.5...
    scanning @samyok/annoy@1.1.0...
    scanning capacitor-branch-deep-links@10.0.0...
    scanning lru-native2@1.2.6...
    scanning electron-click-drag-plugin@2.0.2...
    [100] processed
    scanning react-native-easy-markdown@2.0.0...
    scanning native-reg@1.1.1...
    scanning @newrelic/newrelic-capacitor-plugin@1.6.6...
    scanning native-hdr-histogram@1.0.0...
    scanning react-native-rate-app@2.0.1...
    scanning socketcan@4.2.2...
    scanning @d-fischer/cross-fetch@5.0.5...
    scanning react-native-pdf-jsi@4.4.2...
    scanning @figma/nodegit@0.28.0-figma.9...
    scanning react-native-scanbot-sdk@8.0.0...
    scanning data-fns@1.1.0...
    scanning node-window-manager@2.2.4...
    scanning ref@1.3.5...
    scanning rsa-keygen@1.0.6...
    scanning raknet-native@1.2.3...
    scanning zmq@2.15.3...
    scanning node-mac-permissions@2.5.0...
    scanning motion@12.40.0...
    scanning modal@0.8.0...
    scanning fab@1.0.0-rc.9...
    scanning node@26.3.1...
    scanning native-progress-bar@1.0.3...
    scanning annoy@4.0.0...
    ✓ 126 packages processed, no new candidates
    checkpoint saved

  └─ scanning indicators...

  DeepScan: analyzing 126 packages...
    scanning 9router@0.5.8...
    9router@0.5.8 — 2 indicator(s)
    scanning has-symbols@1.1.0...
    has-symbols@1.1.0
    scanning mkdirp@3.0.1...
    mkdirp@3.0.1
    scanning async-function@1.0.0...
    scanning generator-function@2.0.1...
    async-function@1.0.0
    generator-function@2.0.1
    scanning cross-fetch@4.1.0...
    cross-fetch@4.1.0
    scanning @react-navigation/native-stack@7.17.5...
    @react-navigation/native-stack@7.17.5 — 1 indicator(s)
    scanning react-native-reanimated@4.4.1...
    scanning react-native-worklets@0.9.2...
    react-native-reanimated@4.4.1 — 4 indicator(s)
    react-native-worklets@0.9.2 — 4 indicator(s)
    scanning async-generator-function@1.0.0...
    async-generator-function@1.0.0
    scanning preview-email@3.1.3...
    scanning node-liblzma@5.0.3...
    preview-email@3.1.3
    node-liblzma@5.0.3 — 1 indicator(s)
    scanning react-native-haptic-feedback@3.0.0...
    scanning react-native-image-picker@8.2.1...
    react-native-haptic-feedback@3.0.0 — 1 indicator(s)
    react-native-image-picker@8.2.1 — 1 indicator(s)
    scanning @capacitor/assets@3.0.5...
    @capacitor/assets@3.0.5
    scanning @discordjs/opus@0.10.0...
    scanning zeromq@6.5.0...
    @discordjs/opus@0.10.0 — 1 indicator(s)
    scanning @ladjs/country-language@1.0.3...
    zeromq@6.5.0 — 2 indicator(s)
    @ladjs/country-language@1.0.3
    scanning @sentry-internal/node-native-stacktrace@0.5.0...
    @sentry-internal/node-native-stacktrace@0.5.0 — 1 indicator(s)
    scanning array-hyper-unique@2.1.8...
    array-hyper-unique@2.1.8
    scanning @revenuecat/purchases-capacitor@13.2.0...
    @revenuecat/purchases-capacitor@13.2.0 — 2 indicator(s)
    scanning ruvector@0.2.32...
    scanning @sentry/capacitor@4.1.0...
    ruvector@0.2.32
    scanning @legendapp/motion@2.5.3...
    @legendapp/motion@2.5.3 — 1 indicator(s)
    @sentry/capacitor@4.1.0 — 3 indicator(s)
    scanning @gluestack-ui/overlay@0.1.22...
    @gluestack-ui/overlay@0.1.22
    scanning react-native-fast-crypto@3.0.0...
    scanning bun-pty@0.4.10...
    bun-pty@0.4.10 — 1 indicator(s)
    react-native-fast-crypto@3.0.0 — 1 indicator(s)
    scanning @gluestack-ui/form-control@0.1.19...
    scanning @gluestack-ui/toast@1.0.9...
    @gluestack-ui/form-control@0.1.19
    @gluestack-ui/toast@1.0.9
    scanning @gluestack-ui/hooks@0.1.13...
    @gluestack-ui/hooks@0.1.13
    scanning @gluestack-ui/provider@0.1.19...
    scanning @gluestack-ui/icon@0.1.27...
    @gluestack-ui/provider@0.1.19
    @gluestack-ui/icon@0.1.27
    scanning @gluestack-ui/avatar@0.1.18...
    scanning zstd-napi@0.0.12...
    @gluestack-ui/avatar@0.1.18
    zstd-napi@0.0.12 — 1 indicator(s)
    scanning @gluestack-ui/button@1.0.14...
    @gluestack-ui/button@1.0.14
    scanning @gluestack-ui/checkbox@0.1.39...
    scanning @gluestack-ui/input@0.1.38...
    @gluestack-ui/checkbox@0.1.39
    @gluestack-ui/input@0.1.38
    scanning @gluestack-ui/switch@0.1.29...
    scanning @gluestack-ui/radio@0.1.40...
    @gluestack-ui/switch@0.1.29
    @gluestack-ui/radio@0.1.40
    scanning @gluestack-ui/transitions@0.1.11...
    @gluestack-ui/transitions@0.1.11
    scanning @gluestack-ui/progress@0.1.18...
    scanning @gluestack-ui/textarea@0.1.25...
    scanning unsafe-pointer@0.2.0...
    scanning @gluestack-ui/modal@0.1.41...
    @gluestack-ui/progress@0.1.18
    @gluestack-ui/textarea@0.1.25
    @gluestack-ui/modal@0.1.41
    unsafe-pointer@0.2.0 — 1 indicator(s)
    scanning @gluestack-ui/alert-dialog@0.1.38...
    @gluestack-ui/alert-dialog@0.1.38
    scanning @gluestack-ui/spinner@0.1.15...
    scanning @gluestack-ui/accordion@1.0.14...
    scanning @gluestack-ui/menu@0.2.43...
    scanning @gluestack-ui/select@0.1.31...
    @gluestack-ui/spinner@0.1.15
    @gluestack-ui/accordion@1.0.14
    @gluestack-ui/menu@0.2.43
    @gluestack-ui/select@0.1.31
    scanning @gluestack-ui/pressable@0.1.23...
    @gluestack-ui/pressable@0.1.23
    [50/126] analyzed
    scanning @codetrix-studio/capacitor-google-auth@3.4.0-rc.4...
    scanning @gluestack-ui/actionsheet@0.2.53...
    scanning react-native-modalize@2.1.1...
    scanning @gluestack-ui/alert@0.1.16...
    scanning @gluestack-ui/image@0.1.17...
    react-native-modalize@2.1.1
    @gluestack-ui/actionsheet@0.2.53
    @gluestack-ui/alert@0.1.16
    @gluestack-ui/image@0.1.17
    @codetrix-studio/capacitor-google-auth@3.4.0-rc.4 — 1 indicator(s)
    scanning @gluestack-ui/divider@0.1.10...
    scanning react-native-network-logger@3.0.0...
    scanning @gluestack-ui/popover@0.1.49...
    scanning @gluestack-ui/slider@0.1.32...
    scanning native-machine-id@0.3.11...
    @gluestack-ui/popover@0.1.49
    @gluestack-ui/divider@0.1.10
    @gluestack-ui/slider@0.1.32
    react-native-network-logger@3.0.0 — 1 indicator(s)
    native-machine-id@0.3.11 — 1 indicator(s)
    scanning @gluestack-ui/tooltip@0.1.44...
    scanning @gluestack-ui/link@0.1.29...
    scanning @capacitor/inappbrowser@4.0.0...
    scanning @gluestack-ui/fab@0.1.28...
    scanning react-native-shadow-2@7.1.2...
    @gluestack-ui/tooltip@0.1.44
    @gluestack-ui/fab@0.1.28
    @gluestack-ui/link@0.1.29
    react-native-shadow-2@7.1.2
    @capacitor/inappbrowser@4.0.0 — 2 indicator(s)
    scanning @gluestack-style/animation-resolver@1.0.4...
    scanning @gluestack-ui/themed@1.1.73...
    scanning @gluestack-style/legend-motion-animation-driver@1.0.3...
    scanning @gluestack-ui/nativewind-utils@1.0.28...
    scanning @gluestack-ui/config@1.1.20...
    @gluestack-ui/nativewind-utils@1.0.28
    @gluestack-style/animation-resolver@1.0.4 — 1 indicator(s)
    @gluestack-ui/config@1.1.20
    @gluestack-ui/themed@1.1.73
    @gluestack-style/legend-motion-animation-driver@1.0.3 — 1 indicator(s)
    scanning binjumper@0.1.4...
    scanning @capacitor-community/admob@8.0.0...
    scanning react-native-portalize@1.0.7...
    scanning react-native-circular-progress-indicator@4.4.2...
    scanning @react-native-assets/slider@11.0.12...
    react-native-portalize@1.0.7
    binjumper@0.1.4
    @react-native-assets/slider@11.0.12
    react-native-circular-progress-indicator@4.4.2 — 1 indicator(s)
    @capacitor-community/admob@8.0.0 — 2 indicator(s)
    scanning @unleash/proxy@1.4.19...
    scanning react-native-monorepo-tools@1.2.1...
    scanning @capacitor-community/http@1.4.1...
    scanning react-native-draglist@3.10.0...
    scanning ref-napi@3.0.3...
    react-native-monorepo-tools@1.2.1
    @unleash/proxy@1.4.19
    react-native-draglist@3.10.0
    ref-napi@3.0.3 — 1 indicator(s)
    @capacitor-community/http@1.4.1 — 1 indicator(s)
    scanning react-native-navigation-mode@1.2.9...
    scanning expo-native-sheet-emojis@2.1.0...
    scanning storage-engine@3.0.7...
    scanning @capacitor/background-runner@3.0.0...
    scanning expo-native-emojis-popup@1.1.2...
    storage-engine@3.0.7
    expo-native-sheet-emojis@2.1.0 — 1 indicator(s)
    react-native-navigation-mode@1.2.9 — 1 indicator(s)
    expo-native-emojis-popup@1.1.2 — 1 indicator(s)
    @capacitor/background-runner@3.0.0 — 2 indicator(s)
    scanning @revenuecat/purchases-capacitor-ui@13.2.0...
    scanning nodegit@0.27.0...
    scanning react-native-restart-newarch@1.0.85...
    scanning @kreuzberg/node@4.9.9...
    scanning @capacitor-community/camera-preview@8.0.1...
    nodegit@0.27.0 — 1 indicator(s)
    @kreuzberg/node@4.9.9 — 1 indicator(s)
    @revenuecat/purchases-capacitor-ui@13.2.0 — 2 indicator(s)
    react-native-restart-newarch@1.0.85 — 1 indicator(s)
    @capacitor-community/camera-preview@8.0.1 — 2 indicator(s)
    scanning cf-prefs@2.0.1...
    scanning react-native-markdown-renderer@4.1.1...
    scanning @callstack/react-native-brownfield@3.12.0...
    scanning @jasonscheirer/native-progress-bar@1.0.7...
    scanning react-native-screenguard@2.0.2...
    react-native-markdown-renderer@4.1.1 — 1 indicator(s)
    cf-prefs@2.0.1 — 1 indicator(s)
    @jasonscheirer/native-progress-bar@1.0.7 — 1 indicator(s)
    @callstack/react-native-brownfield@3.12.0 — 1 indicator(s)
    react-native-screenguard@2.0.2 — 1 indicator(s)
    scanning @sentry/node-native-stacktrace@0.5.1...
    scanning react-native-calendar-strip@2.2.6...
    scanning llama-cpp-capacitor@0.1.5...
    react-native-calendar-strip@2.2.6
    scanning @samyok/annoy@1.1.0...
    scanning capacitor-branch-deep-links@10.0.0...
    @samyok/annoy@1.1.0 — 1 indicator(s)
    @sentry/node-native-stacktrace@0.5.1 — 1 indicator(s)
    llama-cpp-capacitor@0.1.5 — 4 indicator(s)
    capacitor-branch-deep-links@10.0.0 — 1 indicator(s)
    [100/126] analyzed
    scanning lru-native2@1.2.6...
    lru-native2@1.2.6 — 1 indicator(s)
    scanning electron-click-drag-plugin@2.0.2...
    scanning react-native-easy-markdown@2.0.0...
    scanning native-reg@1.1.1...
    scanning @newrelic/newrelic-capacitor-plugin@1.6.6...
    electron-click-drag-plugin@2.0.2
    react-native-easy-markdown@2.0.0
    native-reg@1.1.1 — 1 indicator(s)
    @newrelic/newrelic-capacitor-plugin@1.6.6 — 2 indicator(s)
    scanning native-hdr-histogram@1.0.0...
    native-hdr-histogram@1.0.0 — 1 indicator(s)
    scanning react-native-rate-app@2.0.1...
    scanning socketcan@4.2.2...
    scanning @d-fischer/cross-fetch@5.0.5...
    socketcan@4.2.2 — 1 indicator(s)
    scanning react-native-pdf-jsi@4.4.2...
    @d-fischer/cross-fetch@5.0.5 — 1 indicator(s)
    react-native-rate-app@2.0.1 — 1 indicator(s)
    react-native-pdf-jsi@4.4.2 — 2 indicator(s)
    scanning @figma/nodegit@0.28.0-figma.9...
    @figma/nodegit@0.28.0-figma.9 — 1 indicator(s)
    scanning react-native-scanbot-sdk@8.0.0...
    scanning data-fns@1.1.0...
    scanning node-window-manager@2.2.4...
    data-fns@1.1.0
    scanning ref@1.3.5...
    node-window-manager@2.2.4 — 1 indicator(s)
    ref@1.3.5 — 1 indicator(s)
    react-native-scanbot-sdk@8.0.0 — 1 indicator(s)
    scanning rsa-keygen@1.0.6...
    rsa-keygen@1.0.6 — 1 indicator(s)
    scanning raknet-native@1.2.3...
    scanning zmq@2.15.3...
    scanning node-mac-permissions@2.5.0...
    scanning motion@12.40.0...
    motion@12.40.0
    node-mac-permissions@2.5.0 — 1 indicator(s)
    zmq@2.15.3 — 2 indicator(s)
    raknet-native@1.2.3 — 5 indicator(s)
    scanning modal@0.8.0...
    modal@0.8.0
    scanning fab@1.0.0-rc.9...
    scanning node@26.3.1...
    fab@1.0.0-rc.9
    scanning native-progress-bar@1.0.3...
    node@26.3.1
    scanning annoy@4.0.0...
    native-progress-bar@1.0.3 — 1 indicator(s)
    annoy@4.0.0 — 2 indicator(s)
    scanning segfault-handler@1.3.0...
    segfault-handler@1.3.0 — 1 indicator(s)
    [126/126] analyzed
    ✓ 126 packages analyzed

  ✓ manifests saved to /home/runner/work/npm-cli/npm-cli/indicator-suggestions.packages.json

Step 5/5: Analyzing...

✅ Done!
   New this run:         126
   Found via deep scan:  7 new packages added — discovered by following require() imports across package boundaries during file fetch
   With lifecycle scripts: 126 (of 564 total examined)
   Covered by existing indicator defs: 91 (of 126 with lifecycle scripts)
   Uncategorized builds: 0 (have build hint, no matching indicator)
   Lifecycle-only (no build hint): 35 (postinstall/setup scripts, not native builders)
   Pattern gaps found:   0

   Written to: /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json



Generated indicator-suggestions.json
{
  "$ai": {
    "role": "You are an expert in the npm ecosystem, native Node.js addon build tooling, supply-chain security, and the structure of indicator-definitions.js in the npm/cli repository.",
    "thisFileIs": "A dataset generated by scripts/build-indicator-suggestions.js. It scans popular npm packages for lifecycle scripts and compares them against the INDICATOR_REGISTRY in lib/utils/indicator-definitions.js. Your job is to review the data below and propose concrete improvements to that file.",
    "howToReadThisFile": "Every top-level section (meta, coverage, existingDefinitionCoverage, uncategorizedPackages, commandPatternGaps) has a \"description\" field that explains what the section contains and how to interpret it, and a \"data\" field with the actual content.  Read the description first, then inspect data.",
    "sourceFile": "lib/utils/indicator-definitions.js",
    "tasks": [
      {
        "priority": 1,
        "task": "Review commandPatternGaps.data where frequency >= 3 OR weeklyDownloadTotal >= 50000.",
        "action": "For each: decide whether the token warrants a NEW indicator entry (new build tool not yet covered) or just an additional commandPattern on an EXISTING entry.  Ignore tokens that are generic JS keywords (const, require, stdio, inherit) or non-build tools (tsc, oclif, lint, rimraf)."
      },
      {
        "priority": 2,
        "task": "Review uncategorizedPackages.data sorted by weeklyDownloads descending.",
        "action": "For each: examine lifecycleScripts, commandTokens, detectedSignals, and inferredIndicatorFiles.  Propose a new INDICATOR_REGISTRY entry OR explain why it should not be added.  Focus on packages with weeklyDownloads > 10000."
      },
      {
        "priority": 3,
        "task": "Review existingDefinitionCoverage.data entries with low matchedCount.",
        "action": "Propose additional commandPatterns that would match real packages listed in the uncategorized or gap sections."
      }
    ],
    "outputFormat": {
      "forNewIndicatorEntry": {
        "file": "<the filename used as the registry key, e.g. \"Gruntfile.js\">",
        "label": "<short human-readable label>",
        "commandPatterns": [
          "<regex source strings — will be compiled with new RegExp(...)>"
        ],
        "signals": {
          "onFound": [
            "<signal name>"
          ],
          "onWarning": []
        },
        "scannerSteps": [
          "{ type: \"regex\", pattern: \"...\", group: 1, label: \"...\" }",
          "{ type: \"regex-all\", pattern: \"...\", group: 1, label: \"...\" }",
          "{ type: \"glob\", pattern: \"**/*.ext\", label: \"...\", maxDisplay: 20 }",
          "{ type: \"signal\", pattern: \"...\", signal: \"<signal-name>\" }"
        ],
        "addToNativeBuildCommandPattern": "<true if this tool compiles native code>",
        "rationale": "<why this indicator is needed and what real packages triggered it>"
      },
      "forExistingEntryUpdate": {
        "existingKey": "<key in INDICATOR_REGISTRY to update>",
        "addCommandPatterns": [
          "<new regex source strings>"
        ],
        "rationale": "<which packages would now be matched>"
      }
    },
    "availableSignals": [
      "native-build           — Compiles a native binary (.node addon, .so, .dylib) via node-gyp, CMake, Meson, Autoconf, or similar",
      "rust-native            — Compiles a Rust-backed native addon (Cargo.toml / neon)",
      "wasm-build             — Compiles a WebAssembly (.wasm) module from Rust source (wasm-bindgen)",
      "wasm-load              — Loads a pre-compiled WebAssembly binary at runtime via WebAssembly.instantiate/compile — content is not inspectable by static analysis",
      "android-native         — Android JNI/NDK native module",
      "gyp-conditions         — binding.gyp has platform/arch conditions (may behave differently per OS)",
      "make-build             — Makefile or task-runner driven build (Grunt/Gulp/Jake/Cake/Rake/Brunch/Taskfile) that can execute arbitrary shell commands",
      "binary-download        — Downloads a pre-built binary at install time (node-pre-gyp, prebuild-install, etc.)",
      "activates-bundled-binary — Makes a file bundled inside the npm tarball executable (chmod +x / fs.chmod with execute bit)",
      "runtime-installer      — Installs additional packages at runtime via npm/pnpm/yarn/bun/deno/bower install as a child process",
      "external-url           — Fetches a URL (curl/wget/node https) at install time",
      "source-downloader      — Fetches source code (curl/wget/git clone) at install time",
      "dynamic-require        — Calls require() with a non-literal argument — the loaded module cannot be statically determined",
      "obfuscation-pattern    — Uses Base64/hex decoding or eval/Function() to hide executed code"
    ]
  },
  "meta": {
    "description": "Run metadata: when generated, registry size limit (topN), whether cross-package import following was enabled (deepScan), and which indicator filenames are currently registered.",
    "data": {
      "generatedAt": "2026-06-22T11:56:33.302Z",
      "topN": 100,
      "deepScan": true,
      "registryDefinitions": [
        "binding.gyp",
        "Cargo.toml",
        "build.rs",
        "CMakeLists.txt",
        "CMakePresets.json",
        "configure.ac",
        "meson.build",
        "android/build.gradle",
        "binary-downloader",
        "bundled-binary-installer",
        "runtime-installer",
        "source-downloader",
        "build.gradle",
        "Makefile",
        "bower.json",
        "brunch-config.js",
        "Gruntfile.js",
        "gulpfile.js",
        "Jakefile",
        "Cakefile",
        "Rakefile",
        "Taskfile.yml",
        "gulpfile.ts",
        "gulpfile.mjs",
        "Gruntfile.coffee",
        "Jakefile.js",
        "Taskfile.yaml",
        "Justfile"
      ]
    }
  },
  "coverage": {
    "description": "Aggregate counts for this run. totalScanned = registry pages fetched. uniqueNamesConsidered = distinct package names seen. withLifecycleScripts = had install/postinstall/etc. matchedByExistingDefinitions = covered by at least one indicator. uncategorizedBuildPackages = build signal present but no indicator matched. lifecycleOnlyNoBuildHint = lifecycle scripts with no build tool detected.",
    "data": {
      "totalScanned": 500,
      "uniqueNamesConsidered": 564,
      "withLifecycleScripts": 126,
      "matchedByExistingDefinitions": 91,
      "uncategorizedBuildPackages": 0,
      "lifecycleOnlyNoBuildHint": 35
    }
  },
  "existingDefinitionCoverage": {
    "description": "Per-indicator match counts against real packages. Each entry in data is keyed by indicator filename (e.g. \"binding.gyp\") with matchedCount = how many scanned packages matched that indicator's commandPatterns, and packages = the matched names. Low matchedCount relative to expected prevalence suggests commandPatterns need broadening.",
    "data": {
      "android/build.gradle": {
        "matchedCount": 33,
        "packages": [
          "@callstack/react-native-brownfield",
          "@capacitor-community/admob",
          "@capacitor-community/camera-preview",
          "@capacitor-community/http",
          "@capacitor/background-runner",
          "@capacitor/inappbrowser",
          "@codetrix-studio/capacitor-google-auth",
          "@gluestack-style/animation-resolver",
          "@gluestack-style/legend-motion-animation-driver",
          "@legendapp/motion",
          "@newrelic/newrelic-capacitor-plugin",
          "@react-navigation/native-stack",
          "@revenuecat/purchases-capacitor",
          "@revenuecat/purchases-capacitor-ui",
          "@sentry/capacitor",
          "capacitor-branch-deep-links",
          "expo-native-emojis-popup",
          "expo-native-sheet-emojis",
          "llama-cpp-capacitor",
          "react-native-circular-progress-indicator",
          "react-native-fast-crypto",
          "react-native-haptic-feedback",
          "react-native-image-picker",
          "react-native-markdown-renderer",
          "react-native-navigation-mode",
          "react-native-network-logger",
          "react-native-pdf-jsi",
          "react-native-rate-app",
          "react-native-reanimated",
          "react-native-restart-newarch",
          "react-native-scanbot-sdk",
          "react-native-screenguard",
          "react-native-worklets"
        ]
      },
      "binding.gyp": {
        "matchedCount": 26,
        "packages": [
          "@discordjs/opus",
          "@figma/nodegit",
          "@jasonscheirer/native-progress-bar",
          "@samyok/annoy",
          "@sentry-internal/node-native-stacktrace",
          "@sentry/node-native-stacktrace",
          "annoy",
          "cf-prefs",
          "lru-native2",
          "native-hdr-histogram",
          "native-machine-id",
          "native-progress-bar",
          "native-reg",
          "node-liblzma",
          "node-mac-permissions",
          "node-window-manager",
          "nodegit",
          "raknet-native",
          "ref",
          "ref-napi",
          "rsa-keygen",
          "segfault-handler",
          "socketcan",
          "unsafe-pointer",
          "zmq",
          "zstd-napi"
        ]
      },
      "build.gradle": {
        "matchedCount": 12,
        "packages": [
          "@capacitor-community/admob",
          "@capacitor-community/camera-preview",
          "@capacitor/background-runner",
          "@capacitor/inappbrowser",
          "@newrelic/newrelic-capacitor-plugin",
          "@revenuecat/purchases-capacitor",
          "@revenuecat/purchases-capacitor-ui",
          "@sentry/capacitor",
          "llama-cpp-capacitor",
          "react-native-pdf-jsi",
          "react-native-reanimated",
          "react-native-worklets"
        ]
      },
      "CMakeLists.txt": {
        "matchedCount": 5,
        "packages": [
          "llama-cpp-capacitor",
          "raknet-native",
          "react-native-reanimated",
          "react-native-worklets",
          "zeromq"
        ]
      },
      "CMakePresets.json": {
        "matchedCount": 5,
        "packages": [
          "llama-cpp-capacitor",
          "raknet-native",
          "react-native-reanimated",
          "react-native-worklets",
          "zeromq"
        ]
      },
      "runtime-installer": {
        "matchedCount": 3,
        "packages": [
          "9router",
          "@sentry/capacitor",
          "raknet-native"
        ]
      },
      "Makefile": {
        "matchedCount": 3,
        "packages": [
          "@d-fischer/cross-fetch",
          "annoy",
          "zmq"
        ]
      },
      "Cargo.toml": {
        "matchedCount": 2,
        "packages": [
          "@kreuzberg/node",
          "bun-pty"
        ]
      },
      "bundled-binary-installer": {
        "matchedCount": 1,
        "packages": [
          "9router"
        ]
      },
      "source-downloader": {
        "matchedCount": 1,
        "packages": [
          "raknet-native"
        ]
      }
    }
  },
  "uncategorizedPackages": {
    "description": "Packages that have a build signal (native compile, binary download, runtime-installer, etc.) but no existing indicator definition matched them. Each item has: name, version, weeklyDownloads, lifecycleScripts, buildDependencies, commandTokens, inferredIndicatorFiles, detectedSignals, suggestedSignal. Sorted by weeklyDownloads descending — highest-value gaps first.",
    "data": []
  },
  "commandPatternGaps": {
    "description": "Command tokens that appear in multiple uncategorized build packages but are not covered by any existing commandPatterns entry. Each item has: token, frequency (package count), weeklyDownloadTotal, packages, suggestedCommandPattern. Sorted by frequency × downloads — entries with frequency >= 3 or weeklyDownloadTotal >= 50000 are highest priority.",
    "data": []
  }
}

📦 Download full scan artifacts (zip)

savePackageCache now sorts the packages array by name then version before
writing so file diffs are stable across runs (insertions no longer cause
unrelated line churn).

indicator-suggestions-demo.yml:
- Uploads indicator-suggestions.packages.json as an artifact alongside
  indicator-suggestions.json and scan-output.txt.
- Adds a collapsible packages.json section (first 10k chars) to the PR
  comment body so reviewers can inspect the manifest store inline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

✅ PASS — approve-scripts smoke report

All packages with lifecycle scripts are correctly identified as pending approval.

Package Type Status Top risks
@mongodb-js/zstd@7.0.0 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
@playwright/browser-chromium@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
@playwright/browser-firefox@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
@playwright/browser-webkit@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
better-sqlite3@12.11.1 transitive ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
esbuild@0.28.1 transitive ⏳ pending uses child_process (can spawn external commands), makes network requests, contains dense hex-escape sequences (obfuscation indicator)
kerberos@7.0.0 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
nx@22.7.5 transitive ⏳ pending may write outside the package directory, uses child_process (can spawn external commands), runs npm/yarn/pnpm install as a child process at install time (second-stage install — may pull arbitrary packages with scripts enabled)
puppeteer@24.36.1 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity), runs npm/yarn/pnpm install as a child process at install time (second-stage install — may pull arbitrary packages with scripts enabled)
sqlite3@6.0.1 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
unrs-resolver@1.12.2 transitive ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
📦 Download full reports (zip)

@github-actions

Copy link
Copy Markdown

✅ SUCCESS — indicator-suggestions deep scan

Command: node scripts/build-indicator-suggestions.js --add 9router --top 100 --deep

Full scan output (stdout + stderr)

📦 npm indicator-suggestions builder
   out:      /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json
   packages: /home/runner/work/npm-cli/npm-cli/indicator-suggestions.packages.json (permanent store)
   resume:   /home/runner/work/npm-cli/npm-cli/indicator-suggestions.tmp.json  (deleted on success)
   deep:     /home/runner/work/npm-cli/npm-cli/indicator-suggestions.deep  (indicator files cached by name@version)
             with: /home/runner/work/npm-cli/npm-cli/lib/utils/indicator-scanner.js

Loading package data...
  (no existing data — fresh run, targeting 100 packages)

  query order: javascript, android, wasm, react-native, cli, rust, node, build, addon, npm, native
Steps 1–3/5: Scanning popular packages for lifecycle scripts...
  (target: 100 more packages with lifecycle scripts)
  (skipping 0 already-scanned names)

  + injected candidate: 9router

Step 3.25/4: Resuming 1 pending candidates from previous run...

  Candidates: fetching 1...
    [1/1] checked, 1 with lifecycle scripts
    ✓ +1 of 1 candidates (1 in store, 100.0% hit rate)
    checkpoint saved — ready to refill

  query: keywords:javascript
    fetching offset=0...
    p1 offset=0 fetch=115ms | +250 new names, 0 seen-skips | 250 candidates, 1 in store
    saving checkpoint...
                       
    fetching offset=250...
    p2 offset=250 fetch=109ms | +250 new names, 0 seen-skips | 500 candidates, 1 in store
    saving checkpoint...
                       
  Candidates: fetching 500...
    [50/500] checked, 27 with lifecycle scripts
    [100/500] checked, 34 with lifecycle scripts
    [150/500] checked, 42 with lifecycle scripts
    [200/500] checked, 49 with lifecycle scripts
    [250/500] checked, 51 with lifecycle scripts
    [300/500] checked, 61 with lifecycle scripts
    [350/500] checked, 66 with lifecycle scripts
    [400/500] checked, 74 with lifecycle scripts
    [450/500] checked, 77 with lifecycle scripts
    [500/500] checked, 79 with lifecycle scripts
    [500/500] checked, 79 with lifecycle scripts
    ✓ +79 of 500 candidates (80 in store, 15.8% hit rate)
    checkpoint saved — ready to refill
    fetching offset=500...
    p3 offset=500 fetch=101ms | +250 new names, 0 seen-skips | 250 candidates, 80 in store
    saving checkpoint...
                       
    fetching offset=750...
    p4 offset=750 fetch=117ms | +250 new names, 0 seen-skips | 500 candidates, 80 in store
    saving checkpoint...
                       
  Candidates: fetching 500...
    [50/500] checked, 3 with lifecycle scripts
    [100/500] checked, 13 with lifecycle scripts
    [150/500] checked, 15 with lifecycle scripts
    [200/500] checked, 22 with lifecycle scripts
    [250/500] checked, 33 with lifecycle scripts
    [300/500] checked, 43 with lifecycle scripts
    [350/500] checked, 47 with lifecycle scripts
    [400/500] checked, 56 with lifecycle scripts
    [450/500] checked, 66 with lifecycle scripts
    [500/500] checked, 73 with lifecycle scripts
    [500/500] checked, 73 with lifecycle scripts
    ✓ +73 of 500 candidates (153 in store, 14.6% hit rate)
    checkpoint saved — ready to refill
  checkpoint: 153 packages, 1001 seen — saved

  ✓ search complete: 1,000 names examined this run (1,001 unique in seen-set)

Step 3.5/4: Expanding scoped packages with unscoped peers...
  checking 21 bare names...
  21 of 21 peers exist — added to candidates

  Candidates: fetching 21...
    [21/21] checked, 1 with lifecycle scripts
    ✓ +1 of 21 candidates (154 in store, 4.8% hit rate)
    checkpoint saved — ready to refill
Step 4/5: Deep scanning packages via unpkg...
  ├─ pass 1: fetching files + collecting follows...

  DeepFetch: 154 packages (0 cached)...
    scanning 9router@0.5.8...
    scanning hasown@2.0.4...
    scanning es-object-atoms@1.1.2...
    scanning es-errors@1.3.0...
    scanning es-define-property@1.0.1...
    scanning get-intrinsic@1.3.0...
    scanning gopd@1.2.0...
    scanning call-bound@1.0.4...
    scanning has-tostringtag@1.0.2...
    scanning call-bind@1.0.9...
    scanning define-data-property@1.1.4...
    scanning set-function-length@1.2.2...
    scanning get-symbol-description@1.1.0...
    scanning string.prototype.trimstart@1.0.8...
    scanning arraybuffer.prototype.slice@1.0.4...
    scanning is-array-buffer@3.0.5...
    scanning which-boxed-primitive@1.1.1...
    scanning terser@5.48.0...
    scanning is-plain-object@5.0.0...
    scanning is-shared-array-buffer@1.0.4...
    scanning string.prototype.trimend@1.0.10...
    scanning data-view-buffer@1.0.2...
    scanning is-data-view@1.0.2...
    scanning unbox-primitive@1.1.0...
    scanning data-view-byte-length@1.0.2...
    scanning data-view-byte-offset@1.0.1...
    scanning es-iterator-helpers@1.3.3...
    scanning array.prototype.tosorted@1.1.4...
    scanning object.groupby@1.0.3...
    scanning iterator.prototype@1.1.5...
    scanning is-arguments@1.2.0...
    scanning style-to-js@2.0.1...
    scanning estree-util-is-identifier-name@3.0.0...
    scanning html-minifier-terser@7.2.0...
    scanning es5-ext@0.10.64...
    scanning estree-util-visit@2.0.0...
    scanning unist-util-position-from-estree@2.0.0...
    scanning micromark-extension-mdxjs-esm@3.0.0...
    scanning estree-util-build-jsx@3.0.1...
    scanning estree-util-attach-comments@3.0.0...
    scanning esast-util-from-estree@2.0.0...
    scanning natural-orderby@5.0.0...
  ↩️  HTTP 301 → https://unpkg.com/es5-ext@0.10.64/_postinstall.js
    scanning estree-util-scope@1.0.0...
    scanning javascript-stringify@2.1.0...
    scanning typedarray.prototype.slice@1.0.5...
    scanning @fortawesome/react-fontawesome@3.3.1...
    scanning @trivago/prettier-plugin-sort-imports@6.0.2...
    scanning eslint-config-airbnb-typescript@18.0.0...
    scanning dependency-cruiser@17.4.3...
    scanning @ianvs/prettier-plugin-sort-imports@4.7.1...
    scanning enzyme-shallow-equal@1.0.7...
    scanning @tsparticles/engine@4.2.1...
    scanning appium@3.5.2...
    [50] processed
    scanning magic-bytes.js@1.13.0...
    scanning yamux-js@0.2.1...
    scanning cron-schedule@6.0.0...
    scanning asynciterator.prototype@1.0.0...
    scanning well-known-symbols@4.1.0...
    scanning rehype-rewrite@4.0.4...
    scanning rehype-ignore@2.0.3...
    scanning rehype-attr@4.1.3...
    scanning enzyme-adapter-utils@1.14.2...
    scanning nkeys.js@1.1.0...
    scanning remark-github-blockquote-alert@2.1.0...
    scanning vis-data@8.0.4...
    scanning enzyme-adapter-react-16@1.15.8...
    scanning tree-sitter-javascript@0.25.0...
    scanning object-to-formdata@4.5.1...
    scanning json-diff-ts@4.10.4...
    scanning esprima-next@6.0.3...
    scanning vis-network@10.1.0...
    scanning js-xxhash@5.0.1...
    scanning appium-chromium-driver@3.0.2...
    scanning @wojtekmaj/enzyme-adapter-react-17@0.8.0...
    scanning @ibm/telemetry-js@1.11.0...
    scanning @wojtekmaj/enzyme-adapter-utils@0.2.0...
    scanning microsoft-cognitiveservices-speech-sdk@1.50.0...
    scanning google-map-react@2.2.5...
    scanning compressorjs@1.3.0...
    scanning @nats-io/nkeys@2.0.3...
    scanning easymde@2.21.0...
    scanning alasql@4.17.3...
    scanning css-vars-ponyfill@2.4.9...
      + discovered ora@5.4.1
      + discovered axios@1.18.0
      + discovered argparse@2.0.1
      + discovered ajv@8.20.0
      + discovered ajv-formats@3.0.1
      + discovered @appium/schema@1.2.1
      + discovered asyncbox@6.3.0
      + discovered @appium/support@7.2.5
      + discovered semver@7.8.4
      + discovered teen_process@4.1.3
    scanning jora@1.0.0-beta.16...
    scanning string.prototype.replaceall@1.0.11...
    scanning vis-timeline@8.5.1...
    scanning has-dynamic-import@2.1.1...
    scanning xo@3.0.2...
    scanning @tanem/react-nprogress@6.0.3...
    scanning graphql-anywhere@4.2.8...
    scanning jquery-migrate@4.0.2...
    scanning get-css-data@2.1.1...
    scanning react-circle-flags@0.0.29...
    scanning mui-tel-input@11.0.0...
    scanning memlab@2.0.3...
    scanning axios-logger@2.8.1...
    scanning viewerjs@1.11.7...
    scanning @symbiotejs/symbiote@3.8.2...
    scanning @xarc/run@2.3.0...
    scanning bip322-js@3.0.0...
    scanning postcss-prefixwrap@1.58.0...
    scanning @bugsnag/react-native@8.9.0...
    scanning text-case@1.2.11...
    scanning eslint-formatter-summary@2.0.2...
    [100] processed
    scanning mui-one-time-password-input@7.0.0...
    scanning @bitgo/utxo-lib@11.24.0...
    scanning gql-query-builder@3.8.0...
    scanning @techstark/opencv-js@4.12.0-release.1...
    scanning @azure/static-web-apps-cli@2.0.9...
    scanning rollup-plugin-cleanup@3.2.1...
    scanning eslint-watch@8.0.0...
    scanning mui-color-input@9.0.0...
    scanning disposablestack@1.1.8...
    scanning suppressed-error@1.0.3...
    scanning simple-keyboard@3.8.152...
    scanning @rollup/plugin-strip@3.0.4...
    scanning set.prototype.intersection@1.1.8...
    scanning @puckeditor/core@0.21.3...
    scanning react-localization@2.0.6...
    scanning @likashefqet/react-native-image-zoom@4.3.0...
    scanning set.prototype.difference@1.1.7...
    scanning js-cleanup@1.2.0...
    scanning @carbon/charts@1.27.12...
    scanning cel-js@0.8.2...
    scanning @bitgo-beta/utxo-lib@2.4.1...
    scanning doublylinked@2.5.6...
    scanning @crowdin/ota-client@2.1.0...
    scanning set.prototype.issupersetof@1.1.3...
    scanning set.prototype.issubsetof@1.1.4...
    scanning @gluestack-style/react@1.0.57...
    scanning wdio-rerun-service@3.0.1...
    scanning set.prototype.isdisjointfrom@1.1.5...
    scanning c15t@2.1.0...
    scanning country-list-with-dial-code-and-flag@5.1.1...
    scanning storybook-solidjs-vite@10.5.2...
    scanning knex-schema-inspector@3.1.0...
    scanning mergician@2.0.2...
    scanning styled-breakpoints@14.2.0...
    scanning object-traversal@1.0.1...
    scanning weak-napi@2.0.2...
    scanning error-cause@1.0.9...
    scanning givens@1.3.9...
    scanning prettier-plugin-sort-imports@1.8.11...
    scanning jintr@3.3.1...
    scanning @dfinity/utils@4.2.1...
    scanning set.prototype.union@1.1.3...
    scanning @joint/core@4.2.5...
    scanning clientjs@0.2.1...
    scanning google-closure-library@20230802.0.0...
    scanning react-native-bouncy-checkbox@4.1.4...
    scanning md-editor-v3@6.5.1...
    scanning storybook-addon-swc@1.2.0...
    scanning stackdriver-errors-js@0.12.0...
    scanning react-native@0.86.0...
    [150] processed
    ✓ 154 packages processed, 10 new candidates queued
    checkpoint saved

  ├─ pass 1: draining 10 discovered packages into manifests...

  Candidates: fetching 10...
    [10/10] checked, 3 with lifecycle scripts
    ✓ +3 of 10 candidates (157 in store, 30.0% hit rate)
    checkpoint saved — ready to refill
  ├─ pass 2: fetching files + collecting follows...

  DeepFetch: 157 packages (154 cached)...
    scanning axios@1.18.0...
    scanning asyncbox@6.3.0...
    scanning teen_process@4.1.3...
    scanning 9router@0.5.8...
    scanning hasown@2.0.4...
    scanning es-object-atoms@1.1.2...
    scanning es-errors@1.3.0...
    scanning es-define-property@1.0.1...
    scanning get-intrinsic@1.3.0...
    scanning gopd@1.2.0...
    scanning call-bound@1.0.4...
    scanning has-tostringtag@1.0.2...
    scanning call-bind@1.0.9...
    scanning define-data-property@1.1.4...
    scanning set-function-length@1.2.2...
    scanning get-symbol-description@1.1.0...
    scanning string.prototype.trimstart@1.0.8...
    scanning arraybuffer.prototype.slice@1.0.4...
    scanning is-array-buffer@3.0.5...
    scanning which-boxed-primitive@1.1.1...
    scanning terser@5.48.0...
    scanning is-plain-object@5.0.0...
    scanning is-shared-array-buffer@1.0.4...
    scanning string.prototype.trimend@1.0.10...
    scanning data-view-buffer@1.0.2...
    scanning is-data-view@1.0.2...
    scanning unbox-primitive@1.1.0...
    scanning data-view-byte-length@1.0.2...
    scanning data-view-byte-offset@1.0.1...
    scanning es-iterator-helpers@1.3.3...
    scanning array.prototype.tosorted@1.1.4...
    scanning object.groupby@1.0.3...
    scanning iterator.prototype@1.1.5...
    scanning is-arguments@1.2.0...
    scanning style-to-js@2.0.1...
    scanning estree-util-is-identifier-name@3.0.0...
    scanning html-minifier-terser@7.2.0...
    scanning es5-ext@0.10.64...
    scanning estree-util-visit@2.0.0...
    scanning unist-util-position-from-estree@2.0.0...
    scanning micromark-extension-mdxjs-esm@3.0.0...
    scanning estree-util-build-jsx@3.0.1...
    scanning estree-util-attach-comments@3.0.0...
    scanning esast-util-from-estree@2.0.0...
    scanning natural-orderby@5.0.0...
    scanning estree-util-scope@1.0.0...
    scanning javascript-stringify@2.1.0...
    scanning typedarray.prototype.slice@1.0.5...
    scanning @fortawesome/react-fontawesome@3.3.1...
    scanning @trivago/prettier-plugin-sort-imports@6.0.2...
    [50] processed
    scanning eslint-config-airbnb-typescript@18.0.0...
    scanning dependency-cruiser@17.4.3...
    scanning @ianvs/prettier-plugin-sort-imports@4.7.1...
    scanning enzyme-shallow-equal@1.0.7...
    scanning @tsparticles/engine@4.2.1...
    scanning appium@3.5.2...
    scanning magic-bytes.js@1.13.0...
    scanning yamux-js@0.2.1...
    scanning cron-schedule@6.0.0...
    scanning asynciterator.prototype@1.0.0...
    scanning well-known-symbols@4.1.0...
    scanning rehype-rewrite@4.0.4...
    scanning rehype-ignore@2.0.3...
    scanning rehype-attr@4.1.3...
    scanning enzyme-adapter-utils@1.14.2...
    scanning nkeys.js@1.1.0...
    scanning remark-github-blockquote-alert@2.1.0...
    scanning vis-data@8.0.4...
    scanning enzyme-adapter-react-16@1.15.8...
    scanning tree-sitter-javascript@0.25.0...
    scanning object-to-formdata@4.5.1...
    scanning json-diff-ts@4.10.4...
    scanning esprima-next@6.0.3...
    scanning vis-network@10.1.0...
    scanning js-xxhash@5.0.1...
    scanning appium-chromium-driver@3.0.2...
    scanning @wojtekmaj/enzyme-adapter-react-17@0.8.0...
    scanning @ibm/telemetry-js@1.11.0...
    scanning @wojtekmaj/enzyme-adapter-utils@0.2.0...
    scanning microsoft-cognitiveservices-speech-sdk@1.50.0...
    scanning google-map-react@2.2.5...
    scanning compressorjs@1.3.0...
    scanning @nats-io/nkeys@2.0.3...
    scanning easymde@2.21.0...
    scanning alasql@4.17.3...
    scanning css-vars-ponyfill@2.4.9...
    scanning jora@1.0.0-beta.16...
    scanning string.prototype.replaceall@1.0.11...
    scanning vis-timeline@8.5.1...
    scanning has-dynamic-import@2.1.1...
    scanning xo@3.0.2...
    scanning @tanem/react-nprogress@6.0.3...
    scanning graphql-anywhere@4.2.8...
    scanning jquery-migrate@4.0.2...
    scanning get-css-data@2.1.1...
    scanning react-circle-flags@0.0.29...
    scanning mui-tel-input@11.0.0...
    scanning memlab@2.0.3...
    scanning axios-logger@2.8.1...
    scanning viewerjs@1.11.7...
    [100] processed
    scanning @symbiotejs/symbiote@3.8.2...
    scanning @xarc/run@2.3.0...
    scanning bip322-js@3.0.0...
    scanning postcss-prefixwrap@1.58.0...
    scanning @bugsnag/react-native@8.9.0...
    scanning text-case@1.2.11...
    scanning eslint-formatter-summary@2.0.2...
    scanning mui-one-time-password-input@7.0.0...
    scanning @bitgo/utxo-lib@11.24.0...
    scanning gql-query-builder@3.8.0...
    scanning @techstark/opencv-js@4.12.0-release.1...
    scanning @azure/static-web-apps-cli@2.0.9...
    scanning rollup-plugin-cleanup@3.2.1...
    scanning eslint-watch@8.0.0...
    scanning mui-color-input@9.0.0...
    scanning disposablestack@1.1.8...
    scanning suppressed-error@1.0.3...
    scanning simple-keyboard@3.8.152...
    scanning @rollup/plugin-strip@3.0.4...
    scanning set.prototype.intersection@1.1.8...
    scanning @puckeditor/core@0.21.3...
    scanning react-localization@2.0.6...
    scanning @likashefqet/react-native-image-zoom@4.3.0...
    scanning set.prototype.difference@1.1.7...
    scanning js-cleanup@1.2.0...
    scanning @carbon/charts@1.27.12...
    scanning cel-js@0.8.2...
    scanning @bitgo-beta/utxo-lib@2.4.1...
    scanning doublylinked@2.5.6...
    scanning @crowdin/ota-client@2.1.0...
    scanning set.prototype.issupersetof@1.1.3...
    scanning set.prototype.issubsetof@1.1.4...
    scanning @gluestack-style/react@1.0.57...
    scanning wdio-rerun-service@3.0.1...
    scanning set.prototype.isdisjointfrom@1.1.5...
    scanning c15t@2.1.0...
    scanning country-list-with-dial-code-and-flag@5.1.1...
    scanning storybook-solidjs-vite@10.5.2...
    scanning knex-schema-inspector@3.1.0...
    scanning mergician@2.0.2...
    scanning styled-breakpoints@14.2.0...
    scanning object-traversal@1.0.1...
    scanning weak-napi@2.0.2...
    scanning error-cause@1.0.9...
    scanning givens@1.3.9...
    scanning prettier-plugin-sort-imports@1.8.11...
    scanning jintr@3.3.1...
    scanning @dfinity/utils@4.2.1...
    scanning set.prototype.union@1.1.3...
    scanning @joint/core@4.2.5...
    [150] processed
    scanning clientjs@0.2.1...
    scanning google-closure-library@20230802.0.0...
    scanning react-native-bouncy-checkbox@4.1.4...
    scanning md-editor-v3@6.5.1...
    scanning storybook-addon-swc@1.2.0...
    scanning stackdriver-errors-js@0.12.0...
    scanning react-native@0.86.0...
    ✓ 157 packages processed, no new candidates
    checkpoint saved

  └─ scanning indicators...

  DeepScan: analyzing 157 packages...
    scanning 9router@0.5.8...
    9router@0.5.8 — 2 indicator(s)
    scanning hasown@2.0.4...
    hasown@2.0.4
    scanning es-object-atoms@1.1.2...
    es-object-atoms@1.1.2
    scanning es-errors@1.3.0...
    es-errors@1.3.0
    scanning es-define-property@1.0.1...
    es-define-property@1.0.1
    scanning get-intrinsic@1.3.0...
    get-intrinsic@1.3.0
    scanning gopd@1.2.0...
    gopd@1.2.0
    scanning call-bound@1.0.4...
    scanning has-tostringtag@1.0.2...
    call-bound@1.0.4
    has-tostringtag@1.0.2
    scanning call-bind@1.0.9...
    call-bind@1.0.9
    scanning define-data-property@1.1.4...
    scanning set-function-length@1.2.2...
    define-data-property@1.1.4
    set-function-length@1.2.2
    scanning get-symbol-description@1.1.0...
    scanning string.prototype.trimstart@1.0.8...
    get-symbol-description@1.1.0
    string.prototype.trimstart@1.0.8
    scanning arraybuffer.prototype.slice@1.0.4...
    arraybuffer.prototype.slice@1.0.4
    scanning is-array-buffer@3.0.5...
    scanning which-boxed-primitive@1.1.1...
    scanning terser@5.48.0...
    scanning is-plain-object@5.0.0...
    is-array-buffer@3.0.5
    which-boxed-primitive@1.1.1
    terser@5.48.0
    is-plain-object@5.0.0
    scanning is-shared-array-buffer@1.0.4...
    is-shared-array-buffer@1.0.4
    scanning string.prototype.trimend@1.0.10...
    scanning data-view-buffer@1.0.2...
    scanning is-data-view@1.0.2...
    scanning unbox-primitive@1.1.0...
    string.prototype.trimend@1.0.10
    is-data-view@1.0.2
    data-view-buffer@1.0.2
    unbox-primitive@1.1.0
    scanning data-view-byte-length@1.0.2...
    data-view-byte-length@1.0.2
    scanning data-view-byte-offset@1.0.1...
    scanning es-iterator-helpers@1.3.3...
    scanning array.prototype.tosorted@1.1.4...
    scanning object.groupby@1.0.3...
    es-iterator-helpers@1.3.3
    data-view-byte-offset@1.0.1
    array.prototype.tosorted@1.1.4
    object.groupby@1.0.3
    scanning iterator.prototype@1.1.5...
    iterator.prototype@1.1.5
    scanning is-arguments@1.2.0...
    scanning style-to-js@2.0.1...
    scanning estree-util-is-identifier-name@3.0.0...
    scanning html-minifier-terser@7.2.0...
    is-arguments@1.2.0
    style-to-js@2.0.1
    html-minifier-terser@7.2.0
    estree-util-is-identifier-name@3.0.0
    scanning es5-ext@0.10.64...
    es5-ext@0.10.64
    scanning estree-util-visit@2.0.0...
    scanning unist-util-position-from-estree@2.0.0...
    scanning micromark-extension-mdxjs-esm@3.0.0...
    scanning estree-util-build-jsx@3.0.1...
    scanning estree-util-attach-comments@3.0.0...
    unist-util-position-from-estree@2.0.0
    estree-util-visit@2.0.0
    micromark-extension-mdxjs-esm@3.0.0
    estree-util-build-jsx@3.0.1
    estree-util-attach-comments@3.0.0
    scanning esast-util-from-estree@2.0.0...
    scanning natural-orderby@5.0.0...
    scanning estree-util-scope@1.0.0...
    scanning javascript-stringify@2.1.0...
    scanning typedarray.prototype.slice@1.0.5...
    natural-orderby@5.0.0
    esast-util-from-estree@2.0.0
    estree-util-scope@1.0.0
    javascript-stringify@2.1.0
    typedarray.prototype.slice@1.0.5
    scanning @fortawesome/react-fontawesome@3.3.1...
    scanning @trivago/prettier-plugin-sort-imports@6.0.2...
    scanning eslint-config-airbnb-typescript@18.0.0...
    scanning dependency-cruiser@17.4.3...
    scanning @ianvs/prettier-plugin-sort-imports@4.7.1...
    eslint-config-airbnb-typescript@18.0.0
    @trivago/prettier-plugin-sort-imports@6.0.2
    @fortawesome/react-fontawesome@3.3.1
    @ianvs/prettier-plugin-sort-imports@4.7.1
    dependency-cruiser@17.4.3 — 1 indicator(s)
    [50/157] analyzed
    scanning enzyme-shallow-equal@1.0.7...
    scanning @tsparticles/engine@4.2.1...
    scanning appium@3.5.2...
    scanning magic-bytes.js@1.13.0...
    scanning yamux-js@0.2.1...
    enzyme-shallow-equal@1.0.7
    magic-bytes.js@1.13.0
    yamux-js@0.2.1
    @tsparticles/engine@4.2.1 — 1 indicator(s)
    appium@3.5.2 — 3 indicator(s)
    scanning cron-schedule@6.0.0...
    scanning asynciterator.prototype@1.0.0...
    scanning well-known-symbols@4.1.0...
    scanning rehype-rewrite@4.0.4...
    cron-schedule@6.0.0
    well-known-symbols@4.1.0
    asynciterator.prototype@1.0.0
    rehype-rewrite@4.0.4
    scanning rehype-ignore@2.0.3...
    rehype-ignore@2.0.3
    scanning rehype-attr@4.1.3...
    scanning enzyme-adapter-utils@1.14.2...
    scanning nkeys.js@1.1.0...
    scanning remark-github-blockquote-alert@2.1.0...
    rehype-attr@4.1.3
    nkeys.js@1.1.0
    enzyme-adapter-utils@1.14.2
    remark-github-blockquote-alert@2.1.0
    scanning vis-data@8.0.4...
    vis-data@8.0.4
    scanning enzyme-adapter-react-16@1.15.8...
    scanning tree-sitter-javascript@0.25.0...
    scanning object-to-formdata@4.5.1...
    scanning json-diff-ts@4.10.4...
    enzyme-adapter-react-16@1.15.8
    json-diff-ts@4.10.4
    object-to-formdata@4.5.1
    tree-sitter-javascript@0.25.0 — 1 indicator(s)
    scanning esprima-next@6.0.3...
    esprima-next@6.0.3
    scanning vis-network@10.1.0...
    scanning js-xxhash@5.0.1...
    scanning appium-chromium-driver@3.0.2...
    scanning @wojtekmaj/enzyme-adapter-react-17@0.8.0...
    js-xxhash@5.0.1
    vis-network@10.1.0
    appium-chromium-driver@3.0.2
    @wojtekmaj/enzyme-adapter-react-17@0.8.0
    scanning @ibm/telemetry-js@1.11.0...
    @ibm/telemetry-js@1.11.0
    scanning @wojtekmaj/enzyme-adapter-utils@0.2.0...
    scanning microsoft-cognitiveservices-speech-sdk@1.50.0...
    scanning google-map-react@2.2.5...
    scanning compressorjs@1.3.0...
    google-map-react@2.2.5
    compressorjs@1.3.0
    microsoft-cognitiveservices-speech-sdk@1.50.0 — 3 indicator(s)
    @wojtekmaj/enzyme-adapter-utils@0.2.0
    scanning @nats-io/nkeys@2.0.3...
    @nats-io/nkeys@2.0.3
    scanning easymde@2.21.0...
    scanning alasql@4.17.3...
    scanning css-vars-ponyfill@2.4.9...
    scanning jora@1.0.0-beta.16...
    scanning string.prototype.replaceall@1.0.11...
    alasql@4.17.3
    jora@1.0.0-beta.16
    easymde@2.21.0 — 3 indicator(s)
    css-vars-ponyfill@2.4.9
    string.prototype.replaceall@1.0.11
    scanning vis-timeline@8.5.1...
    scanning has-dynamic-import@2.1.1...
    scanning xo@3.0.2...
    scanning @tanem/react-nprogress@6.0.3...
    scanning graphql-anywhere@4.2.8...
    xo@3.0.2
    vis-timeline@8.5.1
    graphql-anywhere@4.2.8
    @tanem/react-nprogress@6.0.3
    has-dynamic-import@2.1.1
    scanning jquery-migrate@4.0.2...
    scanning get-css-data@2.1.1...
    scanning react-circle-flags@0.0.29...
    scanning mui-tel-input@11.0.0...
    scanning memlab@2.0.3...
    react-circle-flags@0.0.29
    get-css-data@2.1.1
    mui-tel-input@11.0.0
    jquery-migrate@4.0.2
    memlab@2.0.3
    scanning axios-logger@2.8.1...
    scanning viewerjs@1.11.7...
    scanning @symbiotejs/symbiote@3.8.2...
    scanning @xarc/run@2.3.0...
    scanning bip322-js@3.0.0...
    viewerjs@1.11.7
    axios-logger@2.8.1
    @symbiotejs/symbiote@3.8.2
    bip322-js@3.0.0
    @xarc/run@2.3.0
    [100/157] analyzed
    scanning postcss-prefixwrap@1.58.0...
    scanning @bugsnag/react-native@8.9.0...
    scanning text-case@1.2.11...
    scanning eslint-formatter-summary@2.0.2...
    scanning mui-one-time-password-input@7.0.0...
    postcss-prefixwrap@1.58.0
    text-case@1.2.11
    eslint-formatter-summary@2.0.2
    mui-one-time-password-input@7.0.0
    @bugsnag/react-native@8.9.0 — 1 indicator(s)
    scanning @bitgo/utxo-lib@11.24.0...
    scanning gql-query-builder@3.8.0...
    scanning @techstark/opencv-js@4.12.0-release.1...
    scanning @azure/static-web-apps-cli@2.0.9...
    scanning rollup-plugin-cleanup@3.2.1...
    @bitgo/utxo-lib@11.24.0
    gql-query-builder@3.8.0
    @techstark/opencv-js@4.12.0-release.1
    @azure/static-web-apps-cli@2.0.9
    rollup-plugin-cleanup@3.2.1
    scanning eslint-watch@8.0.0...
    scanning mui-color-input@9.0.0...
    scanning disposablestack@1.1.8...
    scanning suppressed-error@1.0.3...
    scanning simple-keyboard@3.8.152...
    mui-color-input@9.0.0
    eslint-watch@8.0.0
    simple-keyboard@3.8.152
    suppressed-error@1.0.3
    disposablestack@1.1.8
    scanning @rollup/plugin-strip@3.0.4...
    scanning set.prototype.intersection@1.1.8...
    scanning @puckeditor/core@0.21.3...
    scanning react-localization@2.0.6...
    scanning @likashefqet/react-native-image-zoom@4.3.0...
    @rollup/plugin-strip@3.0.4
    set.prototype.intersection@1.1.8
    @puckeditor/core@0.21.3
    @likashefqet/react-native-image-zoom@4.3.0 — 1 indicator(s)
    react-localization@2.0.6
    scanning set.prototype.difference@1.1.7...
    scanning js-cleanup@1.2.0...
    scanning @carbon/charts@1.27.12...
    scanning cel-js@0.8.2...
    scanning @bitgo-beta/utxo-lib@2.4.1...
    set.prototype.difference@1.1.7
    @carbon/charts@1.27.12
    cel-js@0.8.2
    @bitgo-beta/utxo-lib@2.4.1
    js-cleanup@1.2.0
    scanning doublylinked@2.5.6...
    scanning @crowdin/ota-client@2.1.0...
    scanning set.prototype.issupersetof@1.1.3...
    scanning set.prototype.issubsetof@1.1.4...
    scanning @gluestack-style/react@1.0.57...
    @crowdin/ota-client@2.1.0
    set.prototype.issupersetof@1.1.3
    doublylinked@2.5.6
    set.prototype.issubsetof@1.1.4
    @gluestack-style/react@1.0.57 — 1 indicator(s)
    scanning wdio-rerun-service@3.0.1...
    scanning set.prototype.isdisjointfrom@1.1.5...
    scanning c15t@2.1.0...
    scanning country-list-with-dial-code-and-flag@5.1.1...
    scanning storybook-solidjs-vite@10.5.2...
    set.prototype.isdisjointfrom@1.1.5
    storybook-solidjs-vite@10.5.2
    wdio-rerun-service@3.0.1
    country-list-with-dial-code-and-flag@5.1.1
    c15t@2.1.0 — 1 indicator(s)
    scanning knex-schema-inspector@3.1.0...
    scanning mergician@2.0.2...
    scanning styled-breakpoints@14.2.0...
    scanning object-traversal@1.0.1...
    scanning weak-napi@2.0.2...
    mergician@2.0.2
    styled-breakpoints@14.2.0
    knex-schema-inspector@3.1.0
    object-traversal@1.0.1
    weak-napi@2.0.2 — 1 indicator(s)
    scanning error-cause@1.0.9...
    scanning givens@1.3.9...
    scanning prettier-plugin-sort-imports@1.8.11...
    scanning jintr@3.3.1...
    scanning @dfinity/utils@4.2.1...
    error-cause@1.0.9
    givens@1.3.9
    prettier-plugin-sort-imports@1.8.11
    @dfinity/utils@4.2.1
    jintr@3.3.1
    scanning set.prototype.union@1.1.3...
    scanning @joint/core@4.2.5...
    scanning clientjs@0.2.1...
    scanning google-closure-library@20230802.0.0...
    scanning react-native-bouncy-checkbox@4.1.4...
    set.prototype.union@1.1.3
    google-closure-library@20230802.0.0
    @joint/core@4.2.5 — 2 indicator(s)
    react-native-bouncy-checkbox@4.1.4
    clientjs@0.2.1
    [150/157] analyzed
    scanning md-editor-v3@6.5.1...
    scanning storybook-addon-swc@1.2.0...
    scanning stackdriver-errors-js@0.12.0...
    scanning react-native@0.86.0...
    scanning axios@1.18.0...
    storybook-addon-swc@1.2.0
    md-editor-v3@6.5.1
    stackdriver-errors-js@0.12.0 — 3 indicator(s)
    react-native@0.86.0
    axios@1.18.0 — 3 indicator(s)
    scanning asyncbox@6.3.0...
    scanning teen_process@4.1.3...
    asyncbox@6.3.0
    teen_process@4.1.3
    [157/157] analyzed
    ✓ 157 packages analyzed

  ✓ manifests saved to /home/runner/work/npm-cli/npm-cli/indicator-suggestions.packages.json

Step 5/5: Analyzing...

✅ Done!
   New this run:         157
   Found via deep scan:  10 new packages added — discovered by following require() imports across package boundaries during file fetch
   With lifecycle scripts: 157 (of 1,030 total examined)
   Covered by existing indicator defs: 27 (of 157 with lifecycle scripts)
   Uncategorized builds: 1 (have build hint, no matching indicator)
   Lifecycle-only (no build hint): 129 (postinstall/setup scripts, not native builders)
   Pattern gaps found:   0

   ⚠️  Only 17% of lifecycle-script packages are covered by existing indicators (27 of 157).
   Consider reviewing /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json and indicator-definitions.js with an AI assistant:
   ask it to compare the uncategorizedPackages entries against the existing indicator
   registry and suggest new commandPatterns, signals, or indicator entries. Improvements
   affect both approve-scripts (production scanning) and this suggestion tool.

   Written to: /home/runner/work/npm-cli/npm-cli/indicator-suggestions.json



Generated indicator-suggestions.json
{
  "$ai": {
    "role": "You are an expert in the npm ecosystem, native Node.js addon build tooling, supply-chain security, and the structure of indicator-definitions.js in the npm/cli repository.",
    "thisFileIs": "A dataset generated by scripts/build-indicator-suggestions.js. It scans popular npm packages for lifecycle scripts and compares them against the INDICATOR_REGISTRY in lib/utils/indicator-definitions.js. Your job is to review the data below and propose concrete improvements to that file.",
    "howToReadThisFile": "Every top-level section (meta, coverage, existingDefinitionCoverage, uncategorizedPackages, commandPatternGaps) has a \"description\" field that explains what the section contains and how to interpret it, and a \"data\" field with the actual content.  Read the description first, then inspect data.",
    "sourceFile": "lib/utils/indicator-definitions.js",
    "tasks": [
      {
        "priority": 1,
        "task": "Review commandPatternGaps.data where frequency >= 3 OR weeklyDownloadTotal >= 50000.",
        "action": "For each: decide whether the token warrants a NEW indicator entry (new build tool not yet covered) or just an additional commandPattern on an EXISTING entry.  Ignore tokens that are generic JS keywords (const, require, stdio, inherit) or non-build tools (tsc, oclif, lint, rimraf)."
      },
      {
        "priority": 2,
        "task": "Review uncategorizedPackages.data sorted by weeklyDownloads descending.",
        "action": "For each: examine lifecycleScripts, commandTokens, detectedSignals, and inferredIndicatorFiles.  Propose a new INDICATOR_REGISTRY entry OR explain why it should not be added.  Focus on packages with weeklyDownloads > 10000."
      },
      {
        "priority": 3,
        "task": "Review existingDefinitionCoverage.data entries with low matchedCount.",
        "action": "Propose additional commandPatterns that would match real packages listed in the uncategorized or gap sections."
      }
    ],
    "outputFormat": {
      "forNewIndicatorEntry": {
        "file": "<the filename used as the registry key, e.g. \"Gruntfile.js\">",
        "label": "<short human-readable label>",
        "commandPatterns": [
          "<regex source strings — will be compiled with new RegExp(...)>"
        ],
        "signals": {
          "onFound": [
            "<signal name>"
          ],
          "onWarning": []
        },
        "scannerSteps": [
          "{ type: \"regex\", pattern: \"...\", group: 1, label: \"...\" }",
          "{ type: \"regex-all\", pattern: \"...\", group: 1, label: \"...\" }",
          "{ type: \"glob\", pattern: \"**/*.ext\", label: \"...\", maxDisplay: 20 }",
          "{ type: \"signal\", pattern: \"...\", signal: \"<signal-name>\" }"
        ],
        "addToNativeBuildCommandPattern": "<true if this tool compiles native code>",
        "rationale": "<why this indicator is needed and what real packages triggered it>"
      },
      "forExistingEntryUpdate": {
        "existingKey": "<key in INDICATOR_REGISTRY to update>",
        "addCommandPatterns": [
          "<new regex source strings>"
        ],
        "rationale": "<which packages would now be matched>"
      }
    },
    "availableSignals": [
      "native-build           — Compiles a native binary (.node addon, .so, .dylib) via node-gyp, CMake, Meson, Autoconf, or similar",
      "rust-native            — Compiles a Rust-backed native addon (Cargo.toml / neon)",
      "wasm-build             — Compiles a WebAssembly (.wasm) module from Rust source (wasm-bindgen)",
      "wasm-load              — Loads a pre-compiled WebAssembly binary at runtime via WebAssembly.instantiate/compile — content is not inspectable by static analysis",
      "android-native         — Android JNI/NDK native module",
      "gyp-conditions         — binding.gyp has platform/arch conditions (may behave differently per OS)",
      "make-build             — Makefile or task-runner driven build (Grunt/Gulp/Jake/Cake/Rake/Brunch/Taskfile) that can execute arbitrary shell commands",
      "binary-download        — Downloads a pre-built binary at install time (node-pre-gyp, prebuild-install, etc.)",
      "activates-bundled-binary — Makes a file bundled inside the npm tarball executable (chmod +x / fs.chmod with execute bit)",
      "runtime-installer      — Installs additional packages at runtime via npm/pnpm/yarn/bun/deno/bower install as a child process",
      "external-url           — Fetches a URL (curl/wget/node https) at install time",
      "source-downloader      — Fetches source code (curl/wget/git clone) at install time",
      "dynamic-require        — Calls require() with a non-literal argument — the loaded module cannot be statically determined",
      "obfuscation-pattern    — Uses Base64/hex decoding or eval/Function() to hide executed code"
    ]
  },
  "meta": {
    "description": "Run metadata: when generated, registry size limit (topN), whether cross-package import following was enabled (deepScan), and which indicator filenames are currently registered.",
    "data": {
      "generatedAt": "2026-06-22T12:06:12.539Z",
      "topN": 100,
      "deepScan": true,
      "registryDefinitions": [
        "binding.gyp",
        "Cargo.toml",
        "build.rs",
        "CMakeLists.txt",
        "CMakePresets.json",
        "configure.ac",
        "meson.build",
        "android/build.gradle",
        "binary-downloader",
        "bundled-binary-installer",
        "runtime-installer",
        "source-downloader",
        "build.gradle",
        "Makefile",
        "bower.json",
        "brunch-config.js",
        "Gruntfile.js",
        "gulpfile.js",
        "Jakefile",
        "Cakefile",
        "Rakefile",
        "Taskfile.yml",
        "gulpfile.ts",
        "gulpfile.mjs",
        "Gruntfile.coffee",
        "Jakefile.js",
        "Taskfile.yaml",
        "Justfile"
      ]
    }
  },
  "coverage": {
    "description": "Aggregate counts for this run. totalScanned = registry pages fetched. uniqueNamesConsidered = distinct package names seen. withLifecycleScripts = had install/postinstall/etc. matchedByExistingDefinitions = covered by at least one indicator. uncategorizedBuildPackages = build signal present but no indicator matched. lifecycleOnlyNoBuildHint = lifecycle scripts with no build tool detected.",
    "data": {
      "totalScanned": 1000,
      "uniqueNamesConsidered": 1030,
      "withLifecycleScripts": 157,
      "matchedByExistingDefinitions": 27,
      "uncategorizedBuildPackages": 1,
      "lifecycleOnlyNoBuildHint": 129
    }
  },
  "existingDefinitionCoverage": {
    "description": "Per-indicator match counts against real packages. Each entry in data is keyed by indicator filename (e.g. \"binding.gyp\") with matchedCount = how many scanned packages matched that indicator's commandPatterns, and packages = the matched names. Low matchedCount relative to expected prevalence suggests commandPatterns need broadening.",
    "data": {
      "gulpfile.js": {
        "matchedCount": 4,
        "packages": [
          "axios",
          "easymde",
          "microsoft-cognitiveservices-speech-sdk",
          "stackdriver-errors-js"
        ]
      },
      "gulpfile.ts": {
        "matchedCount": 4,
        "packages": [
          "axios",
          "easymde",
          "microsoft-cognitiveservices-speech-sdk",
          "stackdriver-errors-js"
        ]
      },
      "gulpfile.mjs": {
        "matchedCount": 4,
        "packages": [
          "axios",
          "easymde",
          "microsoft-cognitiveservices-speech-sdk",
          "stackdriver-errors-js"
        ]
      },
      "binding.gyp": {
        "matchedCount": 3,
        "packages": [
          "c15t",
          "tree-sitter-javascript",
          "weak-napi"
        ]
      },
      "android/build.gradle": {
        "matchedCount": 3,
        "packages": [
          "@bugsnag/react-native",
          "@gluestack-style/react",
          "@likashefqet/react-native-image-zoom"
        ]
      },
      "runtime-installer": {
        "matchedCount": 2,
        "packages": [
          "9router",
          "appium"
        ]
      },
      "source-downloader": {
        "matchedCount": 2,
        "packages": [
          "@tsparticles/engine",
          "appium"
        ]
      },
      "bundled-binary-installer": {
        "matchedCount": 1,
        "packages": [
          "9router"
        ]
      },
      "Makefile": {
        "matchedCount": 1,
        "packages": [
          "dependency-cruiser"
        ]
      },
      "binary-downloader": {
        "matchedCount": 1,
        "packages": [
          "appium"
        ]
      },
      "Gruntfile.js": {
        "matchedCount": 1,
        "packages": [
          "@joint/core"
        ]
      },
      "Gruntfile.coffee": {
        "matchedCount": 1,
        "packages": [
          "@joint/core"
        ]
      }
    }
  },
  "uncategorizedPackages": {
    "description": "Packages that have a build signal (native compile, binary download, runtime-installer, etc.) but no existing indicator definition matched them. Each item has: name, version, weeklyDownloads, lifecycleScripts, buildDependencies, commandTokens, inferredIndicatorFiles, detectedSignals, suggestedSignal. Sorted by weeklyDownloads descending — highest-value gaps first.",
    "data": [
      {
        "name": "@azure/static-web-apps-cli",
        "version": "2.0.9",
        "weeklyDownloads": 102539,
        "lifecycleScripts": {
          "prepare": "npm run build"
        },
        "buildDependencies": [],
        "commandTokens": [],
        "inferredIndicatorFiles": [],
        "suggestedSignal": null,
        "scannedFiles": [
          "package.json"
        ]
      }
    ]
  },
  "commandPatternGaps": {
    "description": "Command tokens that appear in multiple uncategorized build packages but are not covered by any existing commandPatterns entry. Each item has: token, frequency (package count), weeklyDownloadTotal, packages, suggestedCommandPattern. Sorted by frequency × downloads — entries with frequency >= 3 or weeklyDownloadTotal >= 50000 are highest priority.",
    "data": []
  }
}

Generated indicator-suggestions.packages.json (manifest store)
{
  "generatedAt": "2026-06-22T12:06:12.517Z",
  "count": 157,
  "discoveryState": {
    "keywordCursors": {
      "keywords:javascript": {
        "from": 1000,
        "startedAt": "2026-06-22T12:04:07.826Z",
        "scannedAt": "2026-06-22T12:05:05.347Z"
      }
    }
  },
  "seenOnlyNames": [
    "tslib",
    "typescript",
    "@babel/parser",
    "jsesc",
    "espree",
    "@typescript-eslint/typescript-estree",
    "@typescript-eslint/parser",
    "esquery",
    "@eslint/js",
    "prelude-ls",
    "eslint",
    "fdir",
    "diff",
    "human-signals",
    "esprima",
    "typescript-eslint",
    "serialize-javascript",
    "@inquirer/type",
    "uglify-js",
    "inquirer",
    "@inquirer/core",
    "regexpu-core",
    "@inquirer/figures",
    "@webassemblyjs/wasm-parser",
    "@webassemblyjs/wast-printer",
    "regenerate",
    "@webassemblyjs/ast",
    "@inquirer/confirm",
    "embla-carousel",
    "embla-carousel-reactive-utils",
    "embla-carousel-react",
    "@xmldom/xmldom",
    "@inquirer/external-editor",
    "reflect-metadata",
    "ast-types-flow",
    "@inquirer/ansi",
    "@inquirer/prompts",
    "enquirer",
    "@inquirer/password",
    "environment",
    "@inquirer/checkbox",
    "@inquirer/select",
    "@inquirer/rawlist",
    "cluster-key-slot",
    "@inquirer/number",
    "@inquirer/input",
    "@azure/abort-controller",
    "@inquirer/expand",
    "@supabase/storage-js",
    "has-values",
    "@inquirer/search",
    "@inquirer/editor",
    "is-reference",
    "@supabase/supabase-js",
    "@supabase/realtime-js",
    "jquery",
    "redis-parser",
    "is-what",
    "@jsonjoy.com/codegen",
    "oxc-parser",
    "@formatjs/ecma402-abstract",
    "redis-errors",
    "@speed-highlight/core",
    "@azure/logger",
    "@oxc-parser/binding-linux-x64-gnu",
    "ext",
    "@azure/core-lro",
    "canvg",
    "three",
    "openid-client",
    "jpeg-js",
    "oauth4webapi",
    "oxlint",
    "remark-mdx",
    "knip",
    "micromark-extension-mdx-expression",
    "micromark-extension-mdx-jsx",
    "@pmmmwh/react-refresh-webpack-plugin",
    "pdf-lib",
    "@typescript/native-preview",
    "memoizerific",
    "strip-comments",
    "css-has-pseudo",
    "@oxc-parser/binding-linux-x64-musl",
    "postcss-focus-visible",
    "eslint-config-airbnb-base",
    "hast-util-to-estree",
    "gray-matter",
    "tsyringe",
    "@azure/storage-blob",
    "oxfmt",
    "css-blank-pseudo",
    "goober",
    "@juggle/resize-observer",
    "@oxlint/binding-linux-x64-gnu",
    "@typescript/native-preview-linux-x64",
    "fuzzysort",
    "array-slice",
    "map-or-similar",
    "cardinal",
    "@oxfmt/binding-linux-x64-gnu",
    "recma-build-jsx",
    "recma-parse",
    "recma-stringify",
    "javascript-natural-sort",
    "babylon",
    "objectorarray",
    "rehype-recma",
    "recma-jsx",
    "object.defaults",
    "oxc-transform",
    "@swc/cli",
    "is-expression",
    "js-string-escape",
    "bmp-js",
    "resedit",
    "@webassemblyjs/wast-parser",
    "karma",
    "html-minifier",
    "editions",
    "pe-library",
    "acorn-node",
    "js2xmlparser",
    "@hey-api/openapi-ts",
    "gifwrap",
    "eslint-config-airbnb",
    "mark.js",
    "@oxc-transform/binding-linux-x64-gnu",
    "detect-gpu",
    "js-library-detector",
    "jsdoc",
    "reserved-identifiers",
    "@appium/support",
    "@appium/schema",
    "@appium/types",
    "blueimp-md5",
    "@oxlint/binding-linux-x64-musl",
    "material-colors",
    "has-symbol-support-x",
    "cloudevents",
    "inversify",
    "has-to-string-tag-x",
    "@hey-api/codegen-core",
    "embla-carousel-autoplay",
    "to-valid-identifier",
    "pdfmake",
    "fastestsmallesttextencoderdecoder",
    "browserify",
    "@promptbook/utils",
    "eol",
    "@oxfmt/binding-linux-x64-musl",
    "tsparticles",
    "dash-ast",
    "bmp-ts",
    "xmldom",
    "@oxc-parser/binding-linux-arm64-gnu",
    "@appium/tsconfig",
    "@tsparticles/confetti",
    "redux-saga",
    "notistack",
    "@tsparticles/vue3",
    "@tsparticles/angular",
    "@appium/logger",
    "@tsparticles/particles",
    "@tsparticles/fireworks",
    "flatpickr",
    "tesseract.js-core",
    "@tsparticles/basic",
    "@tsparticles/vue2",
    "@tsparticles/slim",
    "cropperjs",
    "@oxc-transform/binding-linux-x64-musl",
    "inquirer-autocomplete-prompt",
    "@tsparticles/svelte",
    "meriyah",
    "coffeescript",
    "imask",
    "get-assigned-identifiers",
    "@microsoft/dynamicproto-js",
    "oxc-minify",
    "@lottiefiles/dotlottie-web",
    "array-initial",
    "es5-shim",
    "enzyme",
    "merge-anything",
    "js-file-download",
    "@redux-saga/core",
    "reserved",
    "array-last",
    "jks-js",
    "@tsparticles/all",
    "@lingui/message-utils",
    "@inversifyjs/core",
    "undeclared-identifiers",
    "@appium/base-driver",
    "@oxc-minify/binding-linux-x64-gnu",
    "html2pdf.js",
    "@oxc-parser/binding-linux-arm64-musl",
    "@lottiefiles/dotlottie-react",
    "gradle-to-js",
    "@vimeo/player",
    "@bugsnag/js",
    "@oxc-parser/binding-darwin-arm64",
    "@inversifyjs/common",
    "weakmap-polyfill",
    "@nevware21/ts-utils",
    "@assistant-ui/react",
    "@inversifyjs/reflect-metadata-utils",
    "@stitches/core",
    "tocbot",
    "@stitches/react",
    "tinymce",
    "@oxc-parser/binding-darwin-x64",
    "@appium/base-plugin",
    "@appium/docutils",
    "@probe.gl/stats",
    "@typescript/native-preview-linux-arm64",
    "@math.gl/core",
    "@gsap/react",
    "@nevware21/ts-async",
    "js-binary-schema-parser",
    "@math.gl/web-mercator",
    "e2b",
    "gifuct-js",
    "heimdalljs",
    "broccoli-funnel",
    "@math.gl/types",
    "array.prototype.toreversed",
    "@probe.gl/log",
    "intl-tel-input",
    "@probe.gl/env",
    "@oxc-parser/binding-win32-x64-msvc",
    "is-string-blank",
    "@oxlint/binding-linux-arm64-gnu",
    "@oxc-minify/binding-linux-x64-musl",
    "@mikro-orm/core",
    "@azure/monitor-opentelemetry-exporter",
    "@tracetail/js",
    "heimdalljs-logger",
    "v8n",
    "react-phone-input-2",
    "@oxc-parser/binding-linux-arm-gnueabihf",
    "bwip-js",
    "serialize-to-js",
    "@oxc-parser/binding-linux-riscv64-gnu",
    "embla-carousel-fade",
    "bitcoinjs-lib",
    "@oxc-parser/binding-wasm32-wasi",
    "@oxc-parser/binding-android-arm64",
    "@appium/strongbox",
    "@oxc-parser/binding-win32-arm64-msvc",
    "lokijs",
    "@math.gl/polygon",
    "@oxc-parser/binding-freebsd-x64",
    "@oxc-parser/binding-win32-ia32-msvc",
    "@oxc-parser/binding-linux-s390x-gnu",
    "@oxc-parser/binding-linux-arm-musleabihf",
    "@oxlint/binding-darwin-arm64",
    "@azure/monitor-opentelemetry",
    "@oxc-parser/binding-linux-riscv64-musl",
    "@oxc-parser/binding-openharmony-arm64",
    "@math.gl/sun",
    "@oxc-parser/binding-android-arm-eabi",
    "@mikro-orm/postgresql",
    "mikro-orm",
    "@oxfmt/binding-linux-arm64-gnu",
    "broccoli-babel-transpiler",
    "@typescript/native-preview-darwin-arm64",
    "babel-extract-comments",
    "@pact-foundation/pact",
    "@oxc-parser/binding-linux-ppc64-gnu",
    "@tsparticles/ribbons",
    "wicked-good-xpath",
    "is-identifier",
    "identifier-regex",
    "react-select-event",
    "pdf2json",
    "flowbite",
    "@inversifyjs/prototype-utils",
    "blueimp-canvas-to-blob",
    "react-hot-loader",
    "@mikro-orm/knex",
    "rollbar",
    "@oxlint/binding-win32-x64-msvc",
    "embla-carousel-auto-scroll",
    "jira.js",
    "@azure/storage-queue",
    "embla-carousel-vue",
    "@oxlint/binding-linux-arm64-musl",
    "@inversifyjs/container",
    "@thednp/shorty",
    "@typescript/native-preview-darwin-x64",
    "apollo-client",
    "@oxfmt/binding-darwin-arm64",
    "postcss-functions",
    "@splitsoftware/splitio-commons",
    "@typescript/native-preview-win32-x64",
    "@oxlint/binding-darwin-x64",
    "@deepgram/captions",
    "@mikro-orm/migrations",
    "guess-json-indent",
    "payload",
    "optional-js",
    "@math.gl/culling",
    "string-byte-slice",
    "string-byte-length",
    "@inversifyjs/plugin",
    "@oxc-transform/binding-linux-arm64-gnu",
    "embla-carousel-auto-height",
    "@mikro-orm/cli",
    "embla-carousel-class-names",
    "@splitsoftware/splitio",
    "truncate-json",
    "karma-sourcemap-loader",
    "inquirer-autocomplete-standalone",
    "@math.gl/geospatial",
    "@copilotkit/shared",
    "@typescript/native-preview-win32-arm64",
    "@oxc-transform/binding-darwin-arm64",
    "fontless",
    "@netlify/config",
    "get-object",
    "flow-bin",
    "js-image-generator",
    "@oxfmt/binding-win32-x64-msvc",
    "ts-key-enum",
    "@tsparticles/updater-opacity",
    "@oxlint/binding-win32-arm64-msvc",
    "blueimp-load-image",
    "scope-analyzer",
    "sherif",
    "@tsparticles/shape-circle",
    "@tsparticles/plugin-hex-color",
    "@tinyhttp/type-is",
    "froala-editor",
    "flowbite-datepicker",
    "@phun-ky/typeof",
    "@maskito/core",
    "broccoli-caching-writer",
    "@tsparticles/shape-emoji",
    "react-svg",
    "sa-sdk-javascript",
    "@oxc-transform/binding-win32-x64-msvc",
    "@tsparticles/updater-size",
    "fallow",
    "@tsparticles/updater-out-modes",
    "matter-js",
    "@bufbuild/cel",
    "@oxc-transform/binding-linux-arm64-musl",
    "sherif-linux-x64",
    "@tsparticles/updater-life",
    "@tsparticles/updater-rotate",
    "@blocknote/core",
    "@tsparticles/shape-polygon",
    "@oxfmt/binding-darwin-x64",
    "@tsparticles/shape-star",
    "@tsparticles/shape-image",
    "react-native-size-matters",
    "@oxfmt/binding-linux-arm64-musl",
    "cleave.js",
    "@amcharts/amcharts5",
    "@tanem/svg-injector",
    "buble",
    "@tsparticles/plugin-hsl-color",
    "@tsparticles/plugin-rgb-color",
    "gulp-babel",
    "subtag",
    "@copilotkit/runtime-client-gql",
    "@tsparticles/shape-square",
    "@bufbuild/cel-spec",
    "deslop-js",
    "@blocknote/react",
    "wgsl_reflect",
    "xmldom-qsa",
    "medium-zoom",
    "markerjs2",
    "@oxf

…(truncated — 301139 chars total, see artifact for full file)

📦 Download full scan artifacts (zip)

vbjay and others added 7 commits June 22, 2026 08:30
Remove inline per-package download fetch from the Candidates worker.
Packages not found in searchDownloads are now pushed to manifests with
state:'lifecycle' and weeklyDownloads:0 immediately; a dedicated
Downloads drain resolves their counts in one pass at the end.

DrainMode.Downloads:
- Filters manifests where state === 'lifecycle' (no search-result count)
- Non-scoped packages batched up to 128 per request using the npm bulk
  downloads API (comma-separated names in the URL path)
- Scoped packages fetched individually (bulk endpoint doesn't support them)
- Both paths use fetchJson — redirect-following and 429 back-off included
- Single-name vs multi-name response shape handled (flat vs nested object)
- Saves checkpoint after resolution

drain(DrainMode.Downloads) is called once after all search/deep BFS loops
complete and before the final savePackageCache, keeping the hot candidate-
drain loop free of extra HTTP round-trips.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Names with 2+ dots in the package portion look like domain names
(registry.npmjs.org, cdn.example.com) not npm packages. Legitimate
packages with dots (core.js, socket.io, highlight.js) have at most one.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Stale meta.json files written before the hostname-rejection fix could still
contain invalid entries (e.g. registry.npmjs.org@1.0.1) in resolvedFollows.
The fast-path now parses name@version, checks isValidNpmPackageName(name),
and skips NODE_BUILTIN_MODULES — same gates as the slow-path.

Also add [scan] progress lines before scanPackageScripts and
scanBuildIndicatorsForPackage in deepAnalyzePackage so a stalled package
is immediately visible in stderr instead of silently blocking a worker.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Header now says 'checking N packages against M indicators' (M = 28)
- Per-package pre-line changed from 'scanning' to 'checking'
- deepAnalyzePackage logs 'checking pkg@v against N indicators' before
  the indicator scan so a stall is immediately visible
- Result line always shows indicator count: '— N indicator(s)' even when
  N=0 (previously blank), [cached] appended when served from cache

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After the summary line, print a sub-line per matched indicator:
  blake3-neon@0.2.0 — 2 indicator(s)
    ✓ binding.gyp — GYP build descriptor [native-build]
    ✓ Cargo.toml — Rust build descriptor [native-build]
Packages with 0 indicators get no sub-lines.  Signals are shown in
brackets.  Works for both fresh scans and cache hits.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Export detectClues and investigate from indicator-scanner.js so
deepAnalyzePackage can run them one-at-a-time with a log line before
each step:
  [deepScan] pkg: scanning JS files...
  [deepScan] pkg: JS scan done (N refs) — detecting clues...
  [deepScan] pkg: checking against 28 indicators...
  [deepScan] pkg: N clue(s) found — investigating...
  [deepScan] pkg: investigating binding.gyp...
  [deepScan] pkg: binding.gyp done
  [deepScan] pkg: investigating Cargo.toml...
  ...
If the process stalls, the last printed line identifies exactly which
indicator file caused the hang.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
re.exec() in a while loop requires the g (global) flag to advance
lastIndex between iterations.  Without it, lastIndex stays 0 and the
loop spins forever on the first match — causing the DeepScan hang on
bootstrap-treeview (bower.json) and any brunch-config.js package.

Fix in two places:
1. genericScanner: always forces g flag when constructing the regex for
   the exec loop, so a missing g in a pattern definition cannot hang again.
2. indicator-definitions.js: add missing g flag to the two offending
   regex-all patterns (bower.json 'dependencies' and brunch-config.js
   'plugins'), making the definitions self-consistent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

✅ PASS — approve-scripts smoke report

All packages with lifecycle scripts are correctly identified as pending approval.

Package Type Status Top risks
@mongodb-js/zstd@7.0.0 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
@playwright/browser-chromium@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
@playwright/browser-firefox@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
@playwright/browser-webkit@1.60.0 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
better-sqlite3@12.11.1 transitive ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
esbuild@0.28.1 transitive ⏳ pending uses child_process (can spawn external commands), makes network requests, contains dense hex-escape sequences (obfuscation indicator)
kerberos@7.0.0 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
nx@22.7.5 transitive ⏳ pending may write outside the package directory, uses child_process (can spawn external commands), runs npm/yarn/pnpm install as a child process at install time (second-stage install — may pull arbitrary packages with scripts enabled)
puppeteer@24.36.1 direct ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity), runs npm/yarn/pnpm install as a child process at install time (second-stage install — may pull arbitrary packages with scripts enabled)
sqlite3@6.0.1 direct ⏳ pending lifecycle script compiles a native binary (.node addon), GYP build has platform-specific conditions - review each branch
unrs-resolver@1.12.2 transitive ⏳ pending downloads a prebuilt binary at install time (supply-chain risk - verify source and integrity)
📦 Download full reports (zip)

@github-actions

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants