---
"@ux-team/tvu-design-system": minor
---

Fix a silent no-op in **15 of the 16 audit CLIs shipped in this package**, and make "scanned nothing" an error instead of a pass. **Consumer action may be required — read the second half.**

## 1. The shipped audit CLIs did not run at all under pnpm (or any symlinked layout)

Every audit script in this package guards its CLI entry point so that `import`ing it (to reuse its
exported detectors) does not run the scan. Fifteen of them hand-wrote that guard, in three variants:

```js
process.argv[1] === fileURLToPath(import.meta.url)
import.meta.url === `file://${process.argv[1]}`
resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))   // ← resolve() does NOT resolve symlinks
```

All three compare a path that **has not** been symlink-resolved (`process.argv[1]`, the path you
typed) against one that **has** (`import.meta.url`, which the ESM loader realpaths). So if any
segment of the path is a symlink, the guard concludes "I am being imported" and **the entire CLI
body never executes — exit code 0, empty stdout, empty stderr.**

pnpm's standard layout always hits this: `node_modules/@ux-team/tvu-design-system` is a symlink into
`node_modules/.pnpm/…`, and the commands documented in `templates/*.yml` and
`docs/CONSUMER_AUDIT_SETUP.md` invoke exactly that path. Measured, same source tree and same real
violation: through the symlink → `exit 0`, zero output; bypassing the symlink → `exit 1` naming the
violation.

**⇒ If you wired any of these into CI on a pnpm install, that job has been green while auditing
nothing.** After upgrading it will actually scan, so it may report violations for the first time.
**Those are pre-existing findings that were never surfaced, not new violations introduced by this
release.**

Scripts affected: `audit-mockup-conformance` (the documented mockup entry point, and the parent that
runs six other mockup rules), `validate-upstream-gate`, `audit-stale-anchors`, and every
`audit-mockup-*` CLI. `audit-product-code` was already fixed in the previous release; it now shares
the same implementation so a sixteenth variant cannot reappear.

The guard is now one shared module — `scripts/lib/is-cli-entry.mjs` (also shipped) — which
realpaths both sides before comparing, with a literal-equality fast path and a `false` fallback if
`realpath` throws.

## 2. Scanning zero files/nodes now fails closed (exit 2), with an explicit `--allow-empty` opt-out

`audit-product-code --dir <path>` used to print `✅ pass · scanned 0 files` and exit 0 when the
directory did not exist, was empty, or had everything filtered out by `--ext`. A green run that
scanned nothing looked identical to a green run that scanned everything — so a renamed source
directory or a wrong CI `working-directory` left the audit permanently green while checking nothing.

It now exits **2** with a message saying the result is not interpretable, and naming the likely
causes. `audit-mockup-library-binding` gets the same treatment: its denominator is *scanned Figma
nodes*, that count was previously computed and then discarded, and **every line of its output now
carries `scanned N nodes`**.

**If your scan surface can legitimately be empty** (optional package, gradual rollout), pass
`--allow-empty`: it exits 0 as before, but prints that the denominator was zero so the log never
reads as a real pass.

## Why `minor` and not `patch`

The previous release shipped the same class of fix as a `patch`. That understated it: consumer-visible
behaviour changes in both halves — a previously-green CI job can turn red, and a previously-passing
invocation can now exit 2 and need a flag. `minor` is the honest signal that this upgrade may require
action. No component API, prop, token, or style changed.
