monorepo-package-governance-review
Reviews monorepo task-graph configuration (Turborepo tasks/caching, Nx task pipelines) alongside dependency and lockfile governance (pnpm catalogs, npm overrides, lifecycle-script risk) to prevent false-green CI from stale cache reuse and unpinned supply-chain exposure.
Install
npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/monorepo-package-governance-review
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
git clone https://github.com/VincentChuWaiChow/vanguard-frontier-agentic.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vincentchuwaichow/vanguard-frontier-agentic collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Monorepo & Package Governance Review
Purpose
Monorepo tooling and dependency governance fail together more often than separately: a missing task-graph edge lets a stale build pass CI, and an unpinned dependency range lets that same task silently resolve a different, possibly compromised, package version next time. This skill reviews both the task graph (Turborepo/Nx) and the dependency/lockfile layer (pnpm catalogs, npm overrides, lifecycle scripts) as one governance surface, without stuffing bundler internals, framework-specific build guidance, or CI-vendor setup into every prompt.
When to use
Use this skill when the user asks to:
- review
turbo.json(tasks,dependsOn,inputs,outputs,env) ornx.json(targetDefaults,namedInputs) task-graph configuration, - diagnose a false-green CI result where a task passed despite a real upstream code change (stale cache reuse),
- review
package.json/lockfile version-pin policy, pnpmcatalog/catalogs, or npmoverrides/resolutions, - audit lifecycle scripts (
postinstall/preinstall/prepare) introduced by a new or bumped dependency, - consolidate duplicate/drifted dependency versions across monorepo packages,
- review remote-cache (Turborepo Remote Cache / Nx Cloud) token handling in CI config,
- audit
.npmrcscope-to-registry mappings for scoped internal packages (dependency-confusion exposure), verifypackage-lock.jsonis committed and CI enforces a frozen install (npm ci, notnpm install), or review lifecycle-script (postinstall/preinstall/prepare) allowlisting for a new dependency.
Lean operating rules
- Classify the monorepo tool in scope first (Turborepo vs Nx vs plain npm/pnpm workspaces) from actual config files present (
turbo.json,nx.json,pnpm-workspace.yaml) — do not assume tooling from the user's phrasing alone. - Trace every cross-package build/test task dependency to its actual
dependsOn(Turborepo) ortargetDefaults/dependsOn(Nx) edge; a task that consumes another package's build output without a graph edge is a false-green risk, not a style nit. - Check that cache
inputs(NxnamedInputs) andenv/globalEnv(Turborepo) include every file and environment variable that actually affects a task's output; an incomplete cache key is a correctness bug that manifests as a stale pass, not merely a performance issue. - Note that Turborepo's legacy
pipelinekey was renamed totasks; flagpipelineusage as needing a version check, not silent modernization, since the correct key depends on the installed Turborepo major version. - Treat any lockfile change unaccompanied by a matching
package.json/pnpm-workspace.yamlchange (or vice versa) as suspicious and requiring explanation before approval. - Require pinning or
catalog:/npmoverridesconsolidation for security-sensitive or high-blast-radius dependencies; do not blanket-require exact pins if the team runs a working Renovate/Dependabot auto-merge policy with CI gating — ask before assuming the policy is absent. - Flag any new dependency's
postinstall/preinstall/preparescript by quoting its actual script content frompackage.json, not by assuming intent from the package name alone. - Verify current pnpm
catalog:/catalogs:syntax and Turborepo/Nx task-config key names against Context7-sourced docs before recommending a diff, since these keys and defaults have changed across major versions (see Context7 Documentation Protocol below). - Load only the reference needed for the component in scope; do not dump both the task-graph and dependency-governance reference into a single-focus review.
Context7 Documentation Protocol
Before making any version-specific claim about Turborepo (tasks vs legacy pipeline, dependsOn semantics, inputs/outputs/env/globalEnv behavior, Remote Cache auth), Nx (targetDefaults, namedInputs, dependsOn, nx.json vs per-project project.json/package.json nx block), pnpm (catalog/catalogs syntax in pnpm-workspace.yaml, the catalog: protocol in overrides), or npm registry/supply-chain behavior (.npmrc scope-to-registry mapping, npm ci vs npm install frozen-install semantics, allowScripts/npm approve-scripts lifecycle-script gating):
- Call
mcp__Context7__resolve-library-idfor the library in scope (Turborepo,Nx,pnpm, ornpm) if not already resolved in this session. - Call
mcp__Context7__query-docswith a specific query naming the exact config key or behavior in question (for example: "turbo.json tasks dependsOn env cache hash", "nx.json targetDefaults namedInputs", "pnpm-workspace.yaml catalog catalogs syntax", "npmrc scope registry mapping allowScripts npm ci"). - Prefer the result over training-data recall — config key names and defaults have changed across major versions of all three tools.
- If Context7 is unavailable or returns no relevant result, state the claim as
documentation-based (unverified this session)and recommend the user confirm against the installed tool version's own docs before applying any diff. - Never invent a config key, CLI flag, or default value that Context7 did not return or that is not directly visible in the repo's own config file.
References
Load these only when needed:
- Task-graph review — use for
turbo.json/nx.jsonfalse-green diagnosis: missingdependsOnedges, incomplete cacheinputs/env, and legacypipelinevstasksdrift. - Dependency and lockfile governance — use for pnpm
catalog/catalogs, npmoverrides, lockfile/package.json mismatch, and lifecycle-script (postinstall) risk review. - npm supply-chain governance — use for dependency-confusion review of
.npmrcscope-to-registry mappings, unscoped registry auth tokens,package-lock.jsoncommitment/frozen-install (npm ci) enforcement, andallowScriptslifecycle-script gating for new/bumped dependencies.
Response minimum
Return, at minimum:
- whether the finding is task-graph (false-green risk), dependency-version (supply-chain/reproducibility risk), or both,
- exact missing edge / cache-key gap / unpinned range with file and line citation,
- evidence level (
live repo evidence,documentation-based, orinference) and the Turborepo/Nx/pnpm version the guidance targets, - proposed config diff (not applied — this skill is static-review-only),
- security caveat on any lifecycle script or committed token found, and a rollback/verification note (for example, which command to run locally to confirm the cache-key fix produces a miss on the affected change).
Files (vanguard-frontier-agentic)
-
references
-
dependency-lockfile-governance.md 7.7 KB
# Dependency and Lockfile Governance Use this reference for reviewing dependency version policy, pnpm catalogs, npm overrides, lockfile/manifest consistency, and lifecycle-script risk in a monorepo workspace. > Version note: pnpm `catalog`/`catalogs` syntax and the `catalog:` protocol are documented at `pnpm.io/catalogs` and `pnpm.io/pnpm-workspace_yaml`. Confirm the installed pnpm major version supports catalogs (a relatively recent feature) via Context7 before recommending a migration to it. ## What people get wrong The naive story is: > "The lockfile is just an implementation detail; reviewing `package.json` is enough." Wrong. A `package.json` range like `^4.2.0` and a lockfile pinned to `4.2.0` can silently diverge from what actually gets installed the next time `install` runs without `--frozen-lockfile` (or the pnpm/npm equivalent). Dependency governance review has at least three separate concerns, and collapsing them into "check package.json" misses two of the three: 1. **declared range** — what `package.json` (or a pnpm catalog entry) says is acceptable, 2. **resolved version** — what the lockfile actually pins right now, 3. **install-time behavior** — whether CI enforces the lockfile (frozen install) or silently re-resolves and drifts. ## Officially grounded shape From pnpm docs (`catalogs`, `pnpm-workspace_yaml`, `settings`): - `pnpm-workspace.yaml` top-level `catalog:` defines a default catalog of shared dependency versions, referenced in any package's `package.json` via the `catalog:` protocol (or `catalog:default` explicitly). - `catalogs:` (plural) defines named catalogs (e.g., `react17`, `react18`) for cases where different packages in the workspace intentionally need different major versions of the same dependency during a migration. - The `catalog:` protocol can also be used inside `pnpm-workspace.yaml`'s own `overrides` block (`overrides: { foo: "catalog:" }`) to force a single resolved version workspace-wide while keeping the source of truth in one catalog entry. - Catalogs solve version **drift** (same dependency, different ranges in different packages) but do not by themselves solve version **pinning** (exact vs range) — those are separate governance questions. npm's dependency-governance primitives (`overrides` in `package.json`, `resolutions` in Yarn) serve a similar drift-consolidation role but operate differently: `overrides` forces a resolved version across the entire tree regardless of what nested dependencies request, which can mask a legitimate need for two different major versions if applied too broadly. ## Non-negotiable design rules ### 1. Lockfile changes must be explainable by a manifest change If a lockfile diff shows resolved-version changes with no corresponding `package.json`/`pnpm-workspace.yaml` diff, treat that as suspicious until explained (could be a legitimate transitive-dependency update from a fresh `install`, or could indicate an out-of-band lockfile edit or a compromised registry response). Ask for the `install` command/log that produced the diff before approving. ### 2. Catalog/override consolidation is a security control, not just tidiness Multiple packages independently specifying loose ranges (`^4.0.0`) for the same security-sensitive dependency means each package can independently resolve to a different patch/minor version over time, widening the set of versions that must be trusted. Consolidating via a pnpm catalog entry or npm `overrides` forces one resolved version, shrinking the audit surface — recommend this specifically for auth, crypto, or serialization libraries, not indiscriminately for every dependency. ### 3. Pinning policy depends on the team's automation, not a blanket rule Do not require exact-version pins (`4.2.0` instead of `^4.2.0`) unless asked, unless the dependency is security-sensitive, or unless there is no automated update+CI-gate pipeline (Renovate/Dependabot with required status checks) in place. A team with working automated updates and a frozen-lockfile CI gate already has reproducibility; blanket exact-pinning there adds churn without adding safety. Ask about the update-automation setup before recommending a pinning policy change. ### 4. Lifecycle scripts are quoted, not summarized When a new or bumped dependency introduces a `postinstall`, `preinstall`, or `prepare` script, quote the script's actual content from the dependency's `package.json` (or lockfile-recorded script) in the finding. Do not infer safety or danger from the package name or its stated purpose — a script literally reading `node scripts/postinstall.js` requires opening that file, not accepting the name as self-explanatory. ### 5. Frozen installs are the enforcement mechanism — verify CI actually uses one A correct lockfile is meaningless if CI runs a non-frozen install (allowing silent re-resolution). Check the CI workflow for the frozen-install flag appropriate to the package manager (e.g., pnpm's `--frozen-lockfile`, npm's `ci` command) before concluding that lockfile governance is actually enforced end-to-end. ## Minimal safe review flow 1. Identify the package manager and workspace layout (`pnpm-workspace.yaml`, npm/yarn `workspaces` field in root `package.json`). 2. For the dependency/package in scope, compare the declared range(s) across every package that references it — flag drift (same dependency, different ranges, no catalog/override tying them together). 3. Cross-check the lockfile's resolved version(s) against declared ranges; flag any resolved version outside a declared range (should not normally happen, but indicates a manifest/lockfile edit order-of-operations problem) and any case with multiple distinct resolved versions for what should be one consolidated dependency. 4. For any new/bumped dependency in the diff, check its `package.json` for `postinstall`/`preinstall`/`prepare` and quote the script content verbatim if present. 5. Check the CI workflow for a frozen/`ci`-style install command; flag if absent. 6. Report findings with exact file:line citations; propose the catalog/override/pin diff without applying it. ## Adversarial checklist Before recommending a dependency-governance change, answer these: - Does the lockfile diff have a corresponding manifest diff, or is it unexplained? - For the dependency being consolidated, is there a legitimate reason two packages need different major versions (active migration), or is the split accidental drift? - Does CI actually enforce the lockfile with a frozen-install flag, or can a fresh `install` silently re-resolve? - If a lifecycle script is present, have I read its actual content, not just its filename? - Is the pinning policy I'm recommending consistent with whether this team already runs automated dependency updates with CI gating? If you cannot answer these from repo evidence, say so rather than issuing a blanket pinning recommendation. ## When to push back Push back if the user asks to: - "just pin everything to exact versions" without checking whether an update-automation + CI-gate pipeline already provides reproducibility — this trades update velocity for a pinning policy that may not add real safety, - consolidate every dependency into one catalog entry regardless of legitimate multi-version migration needs (e.g., forcibly unifying `react17`/`react18` catalogs mid-migration), - accept a lockfile diff with no explainable manifest change "because CI passed" — a passing CI run does not prove the lockfile change was legitimate, only that the resolved versions installed successfully, - ignore a `postinstall` script because "it's a well-known package" — package reputation does not substitute for reading the actual script content in the version being introduced. Those are not shortcuts. They trade a diagnosable supply-chain question for an assumption that will not hold under a compromised-dependency scenario. -
npm-supply-chain-governance.md 12.9 KB
# npm Dependency-Confusion & Supply-Chain Governance Use this reference for reviewing npm registry configuration (`.npmrc`), lockfile commitment/enforcement, and lifecycle-script exposure as a dependency-confusion and supply-chain surface — distinct from (and complementary to) the pnpm-catalog/npm-`overrides` drift-consolidation concerns in `dependency-lockfile-governance.md`. This reference is static-review-only: read and grep `.npmrc`, `package.json`, `package-lock.json`, and CI workflow files; never run `npm install`, `npm ci`, or any registry-touching command. > Version note: all claims below are grounded in npm CLI docs (`scope.md`, `npmrc.md`, `npm-ci.md`, `npm-approve-scripts.md`) via Context7 on the current stable npm CLI (v10+) documentation set. Label any claim not directly confirmed there as `documentation-based (unverified this session)` rather than asserting it as current default behavior. ## What people get wrong The naive story is: > "If it's in `package.json` and installs cleanly, the dependency is trusted." Wrong. A scoped internal package name (`@myco/internal-lib`) is only routed to a private registry if `.npmrc` (or CI's registry config) explicitly maps that scope to it. Absent that mapping, npm's default resolution falls back to the public registry — and an attacker who publishes a same-named (or higher-versioned) public package under that scope can have it installed instead of the intended internal package. This is **dependency confusion**, and it is a configuration-file finding, not an install-time anomaly you'd only catch by running the install. ## Officially grounded facts From npm docs (`scope.md`, `npmrc.md`, `npm-ci.md`, `npm-approve-scripts.md`): 1. **Scope-to-registry mapping is the dependency-confusion prevention gate.** `@scope:registry=https://private-registry.example.com` in `.npmrc` (or set via `npm config set @myco:registry=...` / `npm login --registry=... --scope=@myco`) routes every package under that scope to the private registry. A scoped package referenced in `package.json` with no matching `@scope:registry` entry anywhere in the effective `.npmrc` chain (project, user, CI env) resolves against the public npm registry by default. 2. **`npm ci` requires and enforces a committed `package-lock.json`; `npm install` does not.** `npm ci` is documented as designed for automated/CI environments: it requires an existing lockfile, errors if the lockfile's dependencies don't match `package.json`, and never modifies `package.json`/`package-lock.json` — "ensuring a consistent and frozen state." `npm install` can silently re-resolve and rewrite the lockfile on drift. A CI workflow that runs `npm install` instead of `npm ci` gives up frozen-install enforcement even if a lockfile is committed. 3. **Dependency lifecycle scripts (`preinstall`/`install`/`postinstall`/`prepare`) are blocked by default and gated by the `allowScripts` field.** `npm approve-scripts` manages an `allowScripts` field in the project's own `package.json`; by default, dependency install scripts are blocked and npm silently skips them for packages not listed in `allowScripts`. A newly added or bumped dependency that ships a `postinstall` script is only a live risk if it is (a) already present in `allowScripts`, or (b) the project has disabled this gate (e.g., an older npm major without the block, or `--ignore-scripts` not enforced the other way). Confirm which npm major is in use before asserting the default-blocked behavior applies — treat as `documentation-based (unverified this session)` if the installed version can't be confirmed from repo evidence. 4. **Registry-auth credentials must be scoped to a registry host/path, never set bare.** npm docs explicitly contrast a "bad config" (`_authToken=MYTOKEN`, unscoped, applies globally) against "good config" scoped forms: `//registry.example.com/:_authToken=MYTOKEN` (applies to that host) or the narrower `//registry.example.com/myorg/:_authToken=MYTOKEN1` (applies only under that org path). The settings `_auth`, `_authToken`, `username`, `_password`, `email`, `cafile`, `certfile`, and `keyfile` "must all be scoped to a specific registry" — an unscoped `_authToken` line in `.npmrc` is itself a finding, independent of whether the token value is a live secret. 5. **A lockfile with no committed `package-lock.json` at all is a stronger finding than a stale one.** If the repo has no `package-lock.json` (or it is `.gitignore`d), there is nothing for `npm ci` to enforce against — every install re-resolves ranges live, and the install-time set of resolved versions is unverifiable from the repo alone. Check `.gitignore` for a `package-lock.json` entry as part of this review, not just its presence in the working tree. ## Non-negotiable design rules ### 1. Every referenced scope needs a traceable registry mapping For each `@scope/name` package referenced anywhere in `package.json` (dependencies, devDependencies, peerDependencies) that is not a well-known public-scope package (e.g., `@types/*` is normally fine unmapped), confirm the scope has a corresponding `@scope:registry=` entry in a `.npmrc` file in scope (project-level `.npmrc` committed to the repo, or documented CI environment/secrets config). If you cannot find the mapping in the repo and the user has not pointed you at a CI-side equivalent, report it as an open finding — do not assume it exists in CI secrets you cannot see. ### 2. Lockfile presence, commitment, and CI enforcement are three separate checks Do not collapse "there's a `package-lock.json`" into "lockfile governance is fine." Check, separately: (a) does `package-lock.json` exist in the working tree, (b) is it tracked by git (not `.gitignore`d, and appears in `git log`/`git ls-files` history rather than only untracked), and (c) does the CI workflow invoke `npm ci` (or an equivalent frozen-install command) rather than `npm install`. All three must hold for the lockfile to function as a supply-chain control. ### 3. New/bumped dependencies are checked for lifecycle scripts before merge, not after When a diff adds or bumps a dependency, check that dependency's `package.json` (in `node_modules` if present, or its published manifest) for `preinstall`/`install`/`postinstall`/`prepare`. If present, quote the script content verbatim (same rule as in `dependency-lockfile-governance.md`) and check whether it is already listed in the root `package.json`'s `allowScripts`. A script silently added to `allowScripts` in the same diff that adds the dependency is worth flagging on its own — it means a reviewer approved script execution for a package they may not have separately vetted. ### 4. Unscoped credential lines in `.npmrc` are a blocking finding regardless of the token's live validity Do not wait to determine whether a token found in `.npmrc` is still active before flagging it. An unscoped `_authToken=...` (or `_auth=`, `_password=`, etc.) line is a structural misconfiguration per npm's own documented guidance, independent of whether the credential is currently valid, because it would apply to *every* registry request npm makes, including ones to the public registry. ### 5. Version-range looseness on sensitive dependencies is a supply-chain question, not a churn question For dependencies with elevated blast radius (auth, crypto, serialization, CI/build tooling itself), an unpinned wide range (`^1.0.0`, `*`, `latest`) combined with either no lockfile or a non-frozen CI install means the actual installed version is effectively whatever the registry serves at install time. Flag this pairing specifically — a wide range alone, with a committed lockfile and enforced frozen install, is a narrower and less urgent finding than the same range with no enforcement. ## Minimal safe review flow 1. List every `@scope/name` dependency in `package.json`; for each non-public-convention scope, search `.npmrc` (project, and any repo-documented CI config) for a matching `@scope:registry=` entry. Flag any scope with no mapping found in repo evidence. 2. Confirm `package-lock.json` exists, is tracked by git, and is not listed in `.gitignore`. 3. Search CI workflow files for the install command; flag `npm install` where `npm ci` is expected, and flag any workflow with no explicit install-frozen step at all. 4. For new/bumped dependencies in the diff, check for lifecycle scripts and cross-reference the root `package.json`'s `allowScripts` array; quote any script found verbatim. 5. Grep `.npmrc` for `_authToken`, `_auth`, `_password`, `_password`, `email`, `cafile`, `certfile`, `keyfile` keys; flag any instance not prefixed with a `//host/[path]:` scope fragment. 6. Report findings with exact file:line citations; propose the `.npmrc` scope entry / CI command / `allowScripts` diff without applying it. ## Greppable sinks (static patterns, no install required) 1. **Missing scope-to-registry mapping for an internal scoped package** - Dangerous pattern: a scoped package name appears in `package.json` (e.g., `grep -E '"@[a-z0-9-]+/[a-z0-9.-]+"' package.json` matching a non-`@types` scope) but `.npmrc` has zero `@scope:registry=` lines for that scope (`grep '@<scope>:registry' .npmrc` returns nothing). - Safe pattern: every non-public scope referenced in `package.json` has a matching `@scope:registry=` line in a committed `.npmrc`, or the user has confirmed an equivalent CI-side registry config exists. 2. **Unscoped auth token or credential in `.npmrc`** - Dangerous pattern: a line at the start of `.npmrc` sets `_authToken`, `_auth`, or `_password` directly (no `//host/path:` prefix) — greppable as an unprefixed `_authToken`, `_auth`, or `_password` key assignment. - Safe pattern: credentials appear only as `//<registry-host>/[optional-path]:_authToken=...` (host- or path-scoped), matching npm's documented good-config form. 3. **CI install step using `npm install` instead of `npm ci` with a committed lockfile present** - Dangerous pattern: `package-lock.json` is tracked in git, but the CI workflow contains `npm install` (not `npm ci`) ahead of build/test steps — searchable via `grep -n 'npm install' .github/workflows/*.yml` (or the equivalent CI config path) with no adjacent `npm ci`. - Safe pattern: CI workflow's dependency-install step is `npm ci`, and a committed `package-lock.json` exists for it to enforce against. 4. **New dependency lifecycle script with no corresponding `allowScripts` review** - Dangerous pattern: diff adds a dependency whose `package.json` contains `"postinstall"` (or `preinstall`/`prepare`), and the root `package.json`'s `allowScripts` array either doesn't exist or doesn't list that package — meaning the reviewer has not made an explicit allow/deny decision visible in the diff. - Safe pattern: the new dependency's lifecycle script is either absent, or present and accompanied by an explicit, reviewed `allowScripts` entry (or a documented `--ignore-scripts` policy) in the same diff. ## Adversarial checklist Before closing out a supply-chain review as clean, answer these: - For every scoped package referenced in the diff, did I find an explicit `@scope:registry=` mapping in repo evidence, or am I assuming one exists in CI secrets I cannot see? - Is `package-lock.json` both present *and* tracked by git *and* enforced by `npm ci` in CI — not just one or two of the three? - Did I search `.npmrc` for every credential-shaped key (`_auth`, `_authToken`, `_password`, `email`, `cafile`, `certfile`, `keyfile`), not just `_authToken`? - For any new/bumped dependency, did I check its actual `package.json` for lifecycle scripts rather than assuming a well-known package name is safe? - Did I confirm which npm major version is in play before asserting the default-blocked lifecycle-script behavior applies, or did I label it `documentation-based (unverified this session)` because the version isn't visible from repo evidence? If you cannot answer these from repo evidence, say so rather than declaring the supply-chain surface clean. ## When to push back Push back if the user asks to: - treat a missing `@scope:registry` mapping as low-priority because "we've never had an incident" — dependency confusion is a pre-registration attack against a name that doesn't yet need to exist maliciously; absence of a past incident is not evidence of absence of exposure, - accept `npm install` in CI because "the lockfile is there anyway" — a committed lockfile with no frozen-install enforcement provides no guarantee about what CI actually installed, - allowlist a dependency's lifecycle script "for now" without reading its content, to unblock a merge — this converts a reviewable static finding into an unreviewed trust decision, - treat an unscoped `_authToken` in `.npmrc` as fine because "it's a read-only token" — the structural risk is which hosts receive the credential, independent of the token's granted permissions. Those are not shortcuts. They trade a diagnosable, static, pre-install finding for an assumption that only fails once a malicious package is actually published or a credential actually leaks to the wrong host. -
task-graph-review.md 7.6 KB
# Task-Graph Review (Turborepo / Nx) Use this reference for reviewing `turbo.json` or `nx.json` task-graph configuration to catch false-green CI: a task that reports success/cache-hit without actually re-running against a real upstream change. > Version note: Turborepo renamed the top-level `pipeline` key to `tasks` (current schema uses `tasks`). Nx task defaults live in `nx.json` under `targetDefaults`, or per-project under an `nx` block in `package.json`/`project.json`. Verify which key the installed major version expects via Context7 before proposing a diff — do not silently rewrite `pipeline` to `tasks` (or vice versa) without confirming the installed version. ## What people get wrong The naive story is: > "The task graph is just for parallelism/ordering; caching is a separate, unrelated performance feature." Wrong. In both Turborepo and Nx, the task graph and the cache key are the same mechanism: a task's cache hit/miss is computed from its declared `inputs` (Nx) or the combination of source files plus `env`/`globalEnv` (Turborepo), plus the recursive hash of everything in its `dependsOn` chain. A gap in either the dependency edges or the cache-key inputs does not just cause wasted rebuilds — it causes **wrongly cached passes**: CI reports green using output from a version of the code that no longer exists in the branch being tested. ## Officially grounded shape From Turborepo docs (`configuring-tasks`, `caching`, `reference/configuration`): - `tasks.<name>.dependsOn` — declares task ordering and cross-package dependency. `"^build"` means "the `build` task of every package this package depends on must run first." Omitting this for a task that reads another package's build output is the single most common false-green cause. - `tasks.<name>.outputs` — file globs that get cached. Without `outputs`, nothing is cached for that task (Turborepo does not silently assume `dist/**`). - `tasks.<name>.inputs` — restricts which source files affect the cache hash for that task specifically; if omitted, Turborepo hashes the package's full file set by default (verify current default behavior against docs for the installed version, since defaults have changed across majors). - `tasks.<name>.env` / top-level `globalEnv` — environment variables that affect the cache hash. A task whose output depends on an env var not listed here can produce a cache **hit** even though the env var changed — a classic false-green pattern (e.g., `API_SERVICE_KEY` changed but `build` still serves the stale cached bundle). From Nx docs (`concepts/task-pipeline-configuration`, `reference/project-configuration`, `features/cache-task-results`): - `targetDefaults.<target>.dependsOn` — same role as Turborepo's `dependsOn`; `"^build"` again means upstream-project build first. - `namedInputs` + `targetDefaults.<target>.inputs` — Nx's cache-key input set. A `production` named input that excludes test files is common; if a target's `inputs` accidentally excludes a file that genuinely affects its output (e.g., a shared `tsconfig.json` not covered by any named input), that target can cache-hit on a real change. - `outputs` — same caching role as Turborepo; must match what the target actually writes. - Per-project `nx` blocks in `package.json` or `project.json` can override `targetDefaults` — always check both locations before concluding a target has no cache configuration. ## Non-negotiable design rules ### 1. Every cross-package read needs a graph edge If package B's task reads package A's build output (imports `A/dist`, reads a generated type file, etc.), B's task must declare `dependsOn: ["^build"]` (or the Nx equivalent) referencing that relationship. Do not accept "it happens to work because of task ordering in CI scripts" as a substitute — that is fragile and breaks under `--parallel`/affected-only runs. ### 2. Cache key completeness is a correctness property, not an optimization Treat a missing `env` entry or an under-scoped `inputs`/`namedInputs` pattern as a bug report, not a suggestion. Ask: "if I change only this file/env var and nothing else, does the declared cache key change?" If the answer is no and the file/var genuinely affects output, that is a blocking finding. ### 3. Do not conflate `outputs` misconfiguration with `inputs` misconfiguration `outputs` gaps cause **missing** cached artifacts (annoying, but safe — forces a rebuild). `inputs`/`env` gaps cause **wrongly reused** cached artifacts (unsafe — false green). Report these as distinct severities; do not lump them into one "caching issue" finding. ### 4. Legacy `pipeline` key requires a version check, not a silent rewrite If a `turbo.json` uses `pipeline` instead of `tasks`, do not assume it is simply outdated syntax to "fix." Confirm the installed Turborepo major version via `package.json`/lockfile before recommending the rename, since `pipeline` may still be the correct key for an older pinned version. ### 5. Remote cache tokens are CI secrets, not repo config `TURBO_TOKEN`/`TURBO_TEAM` (Turborepo Remote Cache) or Nx Cloud access tokens must be sourced from CI secret stores (e.g., referenced as `${{ secrets.TURBO_TOKEN }}` in a GitHub Actions workflow), never hardcoded in `turbo.json`, `nx.json`, or committed CI YAML. Any literal token-shaped string in these files is an automatic blocking finding. ## Minimal safe review flow 1. Identify the tool (`turbo.json` present → Turborepo; `nx.json` present → Nx; both possible in migration). 2. For the task(s) in scope, list every `dependsOn`/`targetDefaults.dependsOn` edge and cross-check against actual cross-package imports/reads in the source (grep for imports of other workspace packages' build output paths). 3. For each task with caching enabled, list declared `inputs`/`env`/`globalEnv` and cross-check against files/env vars the task's underlying command actually reads (config files, `.env` references, shared tsconfig/eslint config). 4. Check for any remote-cache token or registry auth token literal in `turbo.json`, `nx.json`, or CI workflow files. 5. Report gaps with exact file:line citations and the specific missing key/edge — do not apply the fix; propose the diff. ## Adversarial checklist Before signing off on a task-graph config, answer these: - If I changed one source file in an upstream package and nothing else, would every downstream task that reads it show a cache miss? - If I changed one environment variable that affects a task's build output, would that task show a cache miss? - Does every `outputs` glob match what the underlying build command actually writes — no more, no less? - Is there a task that depends on another package's output without declaring `dependsOn` for it, relying instead on incidental script ordering? - Is a remote-cache or registry token present as a literal anywhere in this config or adjacent CI YAML? If you cannot answer all of these from repo evidence, the review is incomplete — say so rather than approving. ## When to push back Push back if the user asks to: - "just add `dependsOn: []` everywhere to speed things up" — this removes correctness guarantees, not just performance overhead, - silently rewrite `pipeline` to `tasks` (or the reverse) without checking the installed version first, - disable caching entirely as a workaround for a false-green bug instead of fixing the missing `inputs`/`env` entry — that hides the symptom without fixing the cache-key gap, and reintroduces it the moment caching is re-enabled, - commit a remote-cache token directly into `turbo.json`/`nx.json`/CI YAML "just to get CI working." Those are not shortcuts. They trade a diagnosable correctness bug for either wasted CI time or a live credential exposure.
-
-
metadata.json 2.5 KB
{ "id": "monorepo-package-governance-review", "name": "Monorepo & Package Governance Review", "type": "skill", "provider": "frontend", "harnesses": [ "claude-code", "cursor", "codex", "gemini", "kiro", "other" ], "summary": "Reviews monorepo task-graph configuration (Turborepo/Nx) and dependency/version governance (pnpm catalogs, npm overrides, lockfile integrity) together to stop false-green CI cache reuse and unpinned supply-chain exposure, with config-key claims grounded in Context7-sourced docs and progressive reference loading. Also covers npm dependency-confusion review: .npmrc scope-to-registry mapping, package-lock.json commitment/frozen-install (npm ci) enforcement, and allowScripts lifecycle-script gating.", "source_type": "original", "official_docs": [ "https://turborepo.com/docs/crafting-your-repository/configuring-tasks", "https://turborepo.com/docs/crafting-your-repository/caching", "https://nx.dev/concepts/task-pipeline-configuration", "https://pnpm.io/catalogs", "https://pnpm.io/pnpm-workspace_yaml", "https://github.com/npm/cli/blob/latest/docs/lib/content/using-npm/scope.md", "https://github.com/npm/cli/blob/latest/docs/lib/content/configuring-npm/npmrc.md", "https://github.com/npm/cli/blob/latest/docs/lib/content/commands/npm-ci.md", "https://github.com/npm/cli/blob/latest/docs/lib/content/commands/npm-approve-scripts.md" ], "security_notes": "Static-review-only skill: reads and greps turbo.json, nx.json, package.json, pnpm-workspace.yaml, .npmrc, and lockfiles but never runs install, build, or CI commands. Remote-cache and registry auth tokens must be CI-scoped secrets, never committed to turbo.json/nx.json/.npmrc; any token-shaped literal found in a config file is an automatic blocking finding, and any .npmrc credential key (_auth/_authToken/_password/etc.) not scoped to a specific registry host/path is a blocking finding regardless of the token's live validity. Any dependency lifecycle script (postinstall/preinstall/prepare) introduced in the same PR as a workspace-topology change warrants extra scrutiny and must be quoted verbatim in the finding, not summarized from the package name; cross-reference it against the root package.json's allowScripts field. A scoped package (@scope/name) referenced in package.json with no matching @scope:registry mapping in .npmrc is a dependency-confusion finding.", "last_verified": "2026-07-03", "path": "skills/frontend/monorepo-package-governance-review", "author": "github: VincentChuWaiChow", "version": "0.2.0" } -
SKILL.md 7 KB
--- name: monorepo-package-governance-review description: Reviews monorepo task-graph configuration (Turborepo tasks/caching, Nx task pipelines) alongside dependency and lockfile governance (pnpm catalogs, npm overrides, lifecycle-script risk) to prevent false-green CI from stale cache reuse and unpinned supply-chain exposure. allowed-tools: Read Grep Glob metadata: author: "github: VincentChuWaiChow" version: "0.2.0" updated: "2026-07-03" category: platform --- # Monorepo & Package Governance Review ## Purpose Monorepo tooling and dependency governance fail together more often than separately: a missing task-graph edge lets a stale build pass CI, and an unpinned dependency range lets that same task silently resolve a different, possibly compromised, package version next time. This skill reviews both the task graph (Turborepo/Nx) and the dependency/lockfile layer (pnpm catalogs, npm overrides, lifecycle scripts) as one governance surface, without stuffing bundler internals, framework-specific build guidance, or CI-vendor setup into every prompt. ## When to use Use this skill when the user asks to: - review `turbo.json` (`tasks`, `dependsOn`, `inputs`, `outputs`, `env`) or `nx.json` (`targetDefaults`, `namedInputs`) task-graph configuration, - diagnose a false-green CI result where a task passed despite a real upstream code change (stale cache reuse), - review `package.json`/lockfile version-pin policy, pnpm `catalog`/`catalogs`, or npm `overrides`/`resolutions`, - audit lifecycle scripts (`postinstall`/`preinstall`/`prepare`) introduced by a new or bumped dependency, - consolidate duplicate/drifted dependency versions across monorepo packages, - review remote-cache (Turborepo Remote Cache / Nx Cloud) token handling in CI config, - audit `.npmrc` scope-to-registry mappings for scoped internal packages (dependency-confusion exposure), verify `package-lock.json` is committed and CI enforces a frozen install (`npm ci`, not `npm install`), or review lifecycle-script (`postinstall`/`preinstall`/`prepare`) allowlisting for a new dependency. ## Lean operating rules - Classify the monorepo tool in scope first (Turborepo vs Nx vs plain npm/pnpm workspaces) from actual config files present (`turbo.json`, `nx.json`, `pnpm-workspace.yaml`) — do not assume tooling from the user's phrasing alone. - Trace every cross-package build/test task dependency to its actual `dependsOn` (Turborepo) or `targetDefaults`/`dependsOn` (Nx) edge; a task that consumes another package's build output without a graph edge is a false-green risk, not a style nit. - Check that cache `inputs` (Nx `namedInputs`) and `env`/`globalEnv` (Turborepo) include every file and environment variable that actually affects a task's output; an incomplete cache key is a correctness bug that manifests as a stale pass, not merely a performance issue. - Note that Turborepo's legacy `pipeline` key was renamed to `tasks`; flag `pipeline` usage as needing a version check, not silent modernization, since the correct key depends on the installed Turborepo major version. - Treat any lockfile change unaccompanied by a matching `package.json`/`pnpm-workspace.yaml` change (or vice versa) as suspicious and requiring explanation before approval. - Require pinning or `catalog:`/npm `overrides` consolidation for security-sensitive or high-blast-radius dependencies; do not blanket-require exact pins if the team runs a working Renovate/Dependabot auto-merge policy with CI gating — ask before assuming the policy is absent. - Flag any new dependency's `postinstall`/`preinstall`/`prepare` script by quoting its actual script content from `package.json`, not by assuming intent from the package name alone. - Verify current pnpm `catalog:`/`catalogs:` syntax and Turborepo/Nx task-config key names against Context7-sourced docs before recommending a diff, since these keys and defaults have changed across major versions (see Context7 Documentation Protocol below). - Load only the reference needed for the component in scope; do not dump both the task-graph and dependency-governance reference into a single-focus review. ## Context7 Documentation Protocol Before making any version-specific claim about Turborepo (`tasks` vs legacy `pipeline`, `dependsOn` semantics, `inputs`/`outputs`/`env`/`globalEnv` behavior, Remote Cache auth), Nx (`targetDefaults`, `namedInputs`, `dependsOn`, `nx.json` vs per-project `project.json`/`package.json` `nx` block), pnpm (`catalog`/`catalogs` syntax in `pnpm-workspace.yaml`, the `catalog:` protocol in `overrides`), or npm registry/supply-chain behavior (`.npmrc` scope-to-registry mapping, `npm ci` vs `npm install` frozen-install semantics, `allowScripts`/`npm approve-scripts` lifecycle-script gating): 1. Call `mcp__Context7__resolve-library-id` for the library in scope (`Turborepo`, `Nx`, `pnpm`, or `npm`) if not already resolved in this session. 2. Call `mcp__Context7__query-docs` with a specific query naming the exact config key or behavior in question (for example: "turbo.json tasks dependsOn env cache hash", "nx.json targetDefaults namedInputs", "pnpm-workspace.yaml catalog catalogs syntax", "npmrc scope registry mapping allowScripts npm ci"). 3. Prefer the result over training-data recall — config key names and defaults have changed across major versions of all three tools. 4. If Context7 is unavailable or returns no relevant result, state the claim as `documentation-based (unverified this session)` and recommend the user confirm against the installed tool version's own docs before applying any diff. 5. Never invent a config key, CLI flag, or default value that Context7 did not return or that is not directly visible in the repo's own config file. ## References Load these only when needed: - [Task-graph review](references/task-graph-review.md) — use for `turbo.json`/`nx.json` false-green diagnosis: missing `dependsOn` edges, incomplete cache `inputs`/`env`, and legacy `pipeline` vs `tasks` drift. - [Dependency and lockfile governance](references/dependency-lockfile-governance.md) — use for pnpm `catalog`/`catalogs`, npm `overrides`, lockfile/package.json mismatch, and lifecycle-script (`postinstall`) risk review. - [npm supply-chain governance](references/npm-supply-chain-governance.md) — use for dependency-confusion review of `.npmrc` scope-to-registry mappings, unscoped registry auth tokens, `package-lock.json` commitment/frozen-install (`npm ci`) enforcement, and `allowScripts` lifecycle-script gating for new/bumped dependencies. ## Response minimum Return, at minimum: - whether the finding is task-graph (false-green risk), dependency-version (supply-chain/reproducibility risk), or both, - exact missing edge / cache-key gap / unpinned range with file and line citation, - evidence level (`live repo evidence`, `documentation-based`, or `inference`) and the Turborepo/Nx/pnpm version the guidance targets, - proposed config diff (not applied — this skill is static-review-only), - security caveat on any lifecycle script or committed token found, and a rollback/verification note (for example, which command to run locally to confirm the cache-key fix produces a miss on the affected change).
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.