Code review culture took a decade to establish in most engineering organizations. Before it was the norm, teams shipped code that no other human had read. That seemed efficient until they learned what it cost: bugs that a second pair of eyes would have caught in five minutes, turning into incidents that took two days to debug.
Dependency management is at the same inflection point today. Most teams review the code. Almost none of them review what the code depends on with the same rigor.
The gap between "we reviewed the diff" and "we understand what this diff actually added to our dependency graph" is where the 3am incidents live.
What dependency hygiene actually means in practice
It's not about being paranoid. It's not about running a security scan once a quarter and filing the report. It's about treating changes to your dependency graph with the same intentionality as changes to the code itself — because the dependency graph is part of your codebase, and it breaks in all the same ways, just less visibly.
Concretely, dependency hygiene on a team that has it looks like:
- Adding a new direct dependency is a deliberate decision with a stated rationale — "we're adding
date-fns@3because we need timezone-aware date parsing in the billing module and this is the best-maintained option" — notnpm install whateverbecause it was the first Stack Overflow result - Upgrading a major version means looking at the full transitive delta in the lockfile diff, not just running tests and seeing if CI turns green
- The allowed license list is an actual config file committed to the repository root, not institutional knowledge that lives in one senior engineer's head and changes when they leave
- PR reviewers check maintainer health scores for new packages that touch security-critical paths — auth, input parsing, crypto — before approving, not as a separate process but as part of the PR annotation already visible in the diff
- Packages with declining maintainer health scores are tracked as technical debt with the same visibility as deprecated internal APIs
None of this requires heroic effort or significant process overhead. It requires making the right information available at the right moment — which is a tooling problem, not a discipline problem.
How code review culture actually happened
Code review didn't become universal because someone wrote a policy document that said "all code must be reviewed before merging." Policy documents get read once at onboarding and forgotten. Code review became universal because GitHub pull requests made "get a review before merging" the obvious path through the workflow, not the bureaucratic one. Requiring CI to pass before merge made running tests a non-negotiable step without anyone having to personally enforce it.
Dependency hygiene follows the same adoption pattern. You can write engineering guidelines that say "engineers should check dep health before adding packages." Or you can make dep health information appear automatically as an inline annotation on the PR diff, with no additional steps required from the reviewer. The second approach works for the same reason code review works: it eliminates friction from the correct behavior and adds friction to skipping it.
The teams we've spoken with who have high dep hygiene consistently attribute it to the tooling, not to unusually disciplined engineers. When the information is there and it's inconvenient to ignore, most engineers don't ignore it.
The PR review is where intervention is cheapest
When an engineer adds "new-package": "^2.3.1" to package.json and opens a PR, the reviewer has perfect context to evaluate that decision: they know why the change was made, the diff is in front of them, and any problem with the dep is a five-minute fix in the current PR — not a Jira ticket that gets triaged next sprint.
To make a good evaluation at that moment, the reviewer needs information that isn't in the diff itself: What does new-package depend on transitively? What's its maintainer health score? Does it introduce any license policy violations? Does it create a peer dep conflict with anything already in the resolved graph? Is the package in the team's blocked list for any historical reason?
If getting that information requires leaving the PR review and opening a separate tool, most reviewers won't do it — not because they're negligent, but because the friction is real and the immediate urgency isn't visible. If the information is annotated inline on the specific diff line that introduced the package, before the reviewer even scrolls to the comment panel, it gets read every time. It becomes part of the review, not a parallel task competing with it.
What the policy config needs to specify
Dependency hygiene requires a codified policy. Not institutional knowledge ("we generally use MIT-compatible packages"), but a file in the repository that states exactly what's acceptable — because institutional knowledge changes when engineers change jobs, and the new engineer who doesn't know the unwritten rules will make a different call than the staff engineer who's been there for four years.
A minimal depswright.yml covers the core cases:
schema_version: 2
policy:
license:
allow: [MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC]
deny: [AGPL-3.0, AGPL-3.0-only, GPL-2.0, GPL-3.0, SSPL-1.0]
on_deny: fail
on_unknown: warn
maintainer:
min_health_score: 30
on_below_threshold: warn
security_critical_paths:
- src/auth/**
- src/api/middleware/**
security_critical_min_score: 60
on_security_critical_below: fail
packages:
blocked:
- event-stream # supply chain incident, Nov 2018
- node-ipc # malicious payload, Mar 2022
A few details worth calling out. The security_critical_paths block lets you apply stricter maintainer health requirements to packages that load anywhere in your auth or API middleware paths. A health score of 30 is an acceptable threshold for a logging utility. It's not acceptable for anything that validates tokens or parses user input. The policy config lets you express that distinction explicitly rather than relying on each reviewer's judgment call in the moment.
The blocked packages list deserves explicit maintenance. Some packages have documented supply chain incidents. Others have maintainers whose past choices your team has decided to treat as permanent red flags regardless of subsequent behavior. An explicit list with a comment explaining why each package is blocked means the next engineer who considers adding one gets a clear signal with context — rather than running into an unexplained CI failure and spending 20 minutes figuring out why.
The cultural shift that actually happens
Teams with PR-level dep hygiene automation consistently describe the same outcome: dependency issues stop being incidents and start being pull request conversations. The volume of surprises drops. When something does slip through — and occasionally something will, especially via a lockfile change that didn't trigger a full graph re-analysis — it's smaller and caught earlier in the release cycle than it would have been otherwise.
The cultural change isn't "engineers now care more about dependencies." Engineers cared before — they just didn't have the information at the moment when caring was actionable. The change is "dependency information is available at the moment when acting on it is cheap."
This is the exact parallel to linting and type-checking: nobody runs eslint manually before committing. It runs in CI. Its findings appear in the PR. The behavior it enforces becomes habitual without anyone having to remember to enforce it. The right behavior becomes the path of least resistance, not a discipline exercise. Dependency hygiene works the same way when the tooling makes it work that way — not before.
Depswright annotates every PR that touches your lockfile with the full dependency context. Try it with your team.