Skip to content

Night Watch Dead Code

cixzhang edited this page Jul 13, 2026 · 2 revisions

Night Watch — Dead Code

Assigned to: Cindy's Navi (cixzhang)

Goal: Keep the codebase free of dead code by detecting unused files, exports, types, and devDependencies and removing the high-confidence ones via PR.

Tooling: knip, run from outside the repo. knip only reads a checkout to resolve the module graph, so the binary, its config (knip.jsonc), and the runner live in the Night Watch operator's workspace, not in facebook/astryx. Nothing is added to the public repo's package.json or lockfile. knip reads each package's exports/bin as entry points, so published API surfaces are never reported as unused.

The canonical tooling lives in the operator's workspace at memory/xds-night-watch/deadcode-tools/:

  • knip.jsonc: the public-API-safe config (entry patterns + ignores)
  • check-deadcode.mjs: the runner (invokes knip with --directory <checkout> --config knip.jsonc, handles the small-host memory workaround)
  • package.json: pins knip
  • bootstrap.sh: re-materializes the tools dir on the (ephemeral) xds node and installs knip

Because the xds node is wiped on sandbox reset, each run first bootstraps the tools dir from the workspace copies, then runs the detector against a checkout that has node_modules installed.

Schedule

Once per night, producing one PR per run (max). Hourly cron with the standard business-hours guard (skip 9am-5pm PST on weekdays); a state gate limits it to one real run per day.

Why This Role Exists

Dead code accumulates silently. A helper no one calls after a refactor, a type left behind, a devDependency for a removed workflow. None of it breaks tests or CI, so nothing catches it. This role runs a real, reproducible tool and turns its high-confidence findings into small, safe removals.

Scope

Detect and, for high-confidence categories, remove:

  • Unused files in packages/* (source files imported by nothing, not reachable from any entry point)
  • Unused exports in packages/* (an exported symbol no other module imports)
  • Unused exported types in packages/*
  • Unused devDependencies (a dev dependency listed in a package.json that nothing references)
  • Duplicate exports (the same symbol exported under two names)

Confidence tiers (what to auto-remove vs. flag)

Auto-PR only the categories that are safe to delete mechanically. Everything else is flagged in the PR's Needs Review section, not deleted.

Category Action
Unused internal export / type in packages/* not in any package's exports map Auto-remove
Unused file in packages/* not in any exports/bin and not matched by an entry pattern Auto-remove
Unused devDependency Auto-remove (remove the line; verify install/build/test)
Duplicate export Flag (renames are judgment calls)
Anything in apps/* Flag only. App module graphs need built workspace deps to resolve, so knip under-resolves them and produces false positives. Never auto-delete app code.
Anything matched by a knip.jsonc entry pattern (*.doc.mjs, stories, Next.js route files) Never touch (entry points by design)
Anything under packages/cli/templates/** or internal/vibe-tests/.baseline/** Never touch (standalone scaffolding / reference fixtures, already ignored in config)
Unused / unlisted / unresolved runtime dependencies Do NOT act. Defer to the Dependencies and Quartermaster roles

High-confidence deletion rule

Before deleting any export, type, or file, confirm all of:

  1. It is reported by the detector's JSON output in an auto-removable category above.
  2. A repo-wide search (git grep) for the symbol/file basename returns no references outside its own definition.
  3. It is not a dynamic import, StyleX codegen output, CLI template source, or a name referenced from a non-TS surface (.md, .mjs doc, JSON, config). Static analysis misses these.
  4. Removing it leaves pnpm build and pnpm test green.

If any check fails, flag it, do not delete it.

Does NOT Do

  • Touch runtime dependencies (unused/unlisted/unresolved deps). That is Dependencies + Quartermaster
  • Delete anything in apps/* (flag only)
  • Component convention fixes (theming, API, a11y, export hygiene). That is Component Auditor
  • CI fixes. That is QA
  • Merge or approve PRs. Humans do that
  • Refactor passing code or restructure modules. Removal only, no rewrites
  • Add anything to the repo's own toolchain (package.json, lockfile, CI). The detector stays out-of-repo
  • File issues for individual findings. Findings live in the run log and the PR body

Nightly Checklist

Step 1: Guard and setup

  1. Clone the wiki fresh; confirm this role is still assigned to your human in Night Watch Roles. If not, disable the cron job and stop.
  2. Apply the business-hours guard. If business hours, NO_REPLY.
  3. Check memory/xds-night-watch-state.json for deadCode.lastRunAt. If it is from today (PST), skip and NO_REPLY.
  4. On the xds node, fetch latest origin/main and create a worktree from it (for the PR branch).
  5. Bootstrap the out-of-repo tools: run bash memory/xds-night-watch/deadcode-tools/bootstrap.sh (copied to the node). This installs knip in the tools dir if needed.

Step 2: Detect

knip needs a checkout with node_modules to resolve imports. The shared main checkout has them; the analysis is read-only, so point the detector at a checkout that is installed:

TOOLS=/vercel/sandbox/repos/xds-nightwatch-tools/deadcode
node "$TOOLS/check-deadcode.mjs" --repo /vercel/sandbox/repos/xds --reporter json > /tmp/deadcode.json

Parse the JSON. Bucket every finding into the confidence tiers above. Discard the runtime-dependency and apps/* buckets (flag-only).

Step 3: Verify each auto-removable finding

For each candidate export/type/file, run the four-point high-confidence deletion rule (git grep for references, rule out dynamic/codegen/template/non-TS use). Demote anything that fails to the Needs Review list. Make the actual edits in your worktree, not the shared checkout.

Step 4: Remove

  • Delete unused files; remove unused exports/types from their source; drop unused devDependencies lines.
  • Run pnpm install (if a devDependency was removed), then pnpm build && pnpm test in the worktree.
  • If the build or tests break, revert the offending removal and keep the rest. One bad removal must not block the batch.

Step 5: PR

  • One PR per night. Branch: a plain semantic name, chore/dead-code-YYYY-MM-DD (no navi/ prefix; the commit guard blocks agent-named branches on this repo).
  • Commit with --no-verify (husky hooks hang in worktrees). Commits use the node's default git author.
  • Title: chore: remove dead code (YYYY-MM-DD). Body: list what was removed, grouped by category, with a short justification per group. Put everything flagged (duplicates, apps/*, anything that failed verification) under a Needs Review heading.
  • Public-repo hygiene: scrub the branch, commit, and PR body of internal refs and agent fingerprints (see Night Watch Overview Principle 6 and the repo's leak rules). The only permitted footer is Night Watch — Dead Code.
  • Publish ready for review (not draft).
  • If nothing is auto-removable, do not open a PR. Log findings and update state.

Step 5b: Refinement loop

Same as the Component Auditor: record the pushed HEAD sha, schedule a one-shot recheck ~5 min out, check gh pr checks, fix CI failures in place (SHA-collision guard first), max 3 attempts, then hand off to daytime review.

Step 6: Log and update state

  • Log findings and actions to memory/xds-night-watch/{date}.md under a ## Dead Code section.
  • Update memory/xds-night-watch-state.json.

State Schema

{
  "deadCode": {
    "lastRunAt": "ISO timestamp",
    "prsFiled": ["#NNNN"],
    "lastRemovedCount": { "files": 0, "exports": 0, "types": 0, "devDependencies": 0 },
    "flaggedForReview": ["packages/core/src/foo.ts:Bar (duplicate export)"]
  }
}

Config Maintenance

knip.jsonc (in the operator's workspace) is the source of truth for what counts as an entry point. When a finding is a false positive because a legitimate entry pattern is missing (a new codegen file type, a new app route convention), fix knip.jsonc first, then re-run. Do not silence a real category with a blanket ignore. Keep the workspace copy and the node copy in sync via bootstrap.sh.

Notes

  • knip's parser reserves a large virtual buffer; on small, swapless hosts the runner sets vm.overcommit_memory=1 when permitted. If you see "Array buffer allocation failed", run on a larger host or with privileges to set that sysctl.
  • The detector is a report, run out-of-repo. It never touches the repo toolchain and never blocks anyone.
  • knip needs node_modules in the target checkout; a bare worktree without an install will not resolve. Point --repo at an installed checkout.

History

Date Result
2026-07-13 Role created. Out-of-repo knip tooling built and validated on the xds node (public API, CLI templates, and vibe-test baselines correctly excluded; real findings: unused internal exports, types, and devDependencies).

Clone this wiki locally