Remove a dependency identified by uzomuzo diet — analysis + issue (default) or direct PR
Analyze and plan the removal of dependency $ARGUMENTS, then take action.
--pr (PR mode): Run the full removal lifecycle locally: analysis → replacement → verification → commit. Use this only when you own the project and can run build/test locally.Parse $ARGUMENTS for flags:
--pr is present → PR mode (direct implementation)--repo owner/repo is present → target that repository for the issueWhen to use: After /diet-evaluate-removal confirms the dependency is worth removing, or when uzomuzo diet ranks it as trivial/easy.
Safety principle: Every removal must pass build + vet + test before committing. If any step fails, stop and diagnose — don't force it.
Detect the ecosystem from the PURL scheme or module path (pkg:golang/ → Go, pkg:npm/ → npm, pkg:pypi/ → Python, pkg:maven/ → Maven, pkg:githubactions/ → GitHub Actions).
If the ecosystem is not Go, display this notice to the user before proceeding:
Note: This skill's PR-mode commands and common patterns are optimized for Go. Issue mode and IBNC safety checks work for any language, but ecosystem-specific PR-mode guidance (verification commands, edge cases, lockfile handling) is still being developed for {ecosystem}. If you discover surprises or improvements during this removal, please contribute them via an issue or PR to future-architect/uzomuzo-oss.
Then continue with the rest of the flow — the analysis, IBNC checks, and issue template are language-agnostic.
If the target PURL starts with pkg:githubactions/ or is a GitHub Action name (owner/action):
grep -rn "{action-name}" .github/workflows/ --include="*.yml" --include="*.yaml"tibdex/github-app-token → actions/create-github-app-token).uses: actions/checkout@<sha> # v4.2.0).The rest of the flow (duplicate check, issue template, etc.) proceeds identically. In the Usage breakdown table, list workflow files instead of source files.
Before writing any code, run through these checks:
Check the STAYS column in the uzomuzo diet output (or stays_as_indirect in JSON):
- → Removing this dep fully removes it from the dependency tree. Go ahead.yes → Another direct dep depends on this transitively. It will remain as an indirect dependency after removal. Still worth doing (version management delegation, future removal readiness), but set expectations: it won't leave go.sum / lockfile.In detailed output, the IndirectVia field shows exactly which direct deps pull it in transitively. These are the upstream targets for Phase 5 (Upstream Diet).
If diet output is not available, you can verify manually:
# Go
go mod why -m $ARGUMENTS
# npm
npm ls $ARGUMENTS
# pip
pip show $ARGUMENTS | grep "Required-by"
Determine the replacement strategy. In order of preference:
| Strategy | When to use | Example |
|---|---|---|
| Delete | Unused (0 imports) | Remove from go.mod/package.json, run tidy |
| Standard library | stdlib equivalent exists | go-homedir → os.UserHomeDir() |
| Consolidate | Another dep already does this | Two JSON libs → keep one |
| Self-implement | Small, non-crypto, well-defined API | lfshook → 20-line logrus.Hook impl |
| Submodule isolate | Used only in one subcommand/tool | gosnmp → contrib/snmp2cpe/go.mod |
| Framework peel | Dep comes via a framework you don't fully need | Trivy fanal → direct parser calls |
NEVER self-implement: crypto, TLS, protocol negotiation, auth token handling, or anything where subtle bugs create security vulnerabilities.
Check these before starting:
API leakage: Does this dependency's types appear in exported identifiers? If yes, removal is a breaking change — needs major version bump or deprecation period.
# Go: search for exported identifiers using the dep's types
grep -rn "func.*$ARGUMENTS\|type.*$ARGUMENTS" --include="*.go" | grep -v _test.go | grep "^[A-Z]"
Build tags: Is the import behind a build tag? (e.g., //go:build jsoniter)
If yes, the dep may not affect default builds — consider just deleting the tagged file.
Generated code: Files with // Code generated headers are trivially migrated
by re-running the generator with the replacement tool.
Blank / side-effect imports: See the IBNC checklist in step 4 below.
If the dependency shows 0 call sites but >0 import files, it may still be required. Verify it is not:
import _ "pkg", import 'pkg', require('pkg') without assignment)require())@Entity, @Autowired, extends Framework, Ember DI)@NotNull, @JsonProperty — the annotation is the usage)If any apply, the dependency is not safe to remove even if call-site analysis shows 0 calls. See docs/ibnc-patterns.md for the full pattern taxonomy with evidence from 79+ OSS projects.
Note which SBOM tool (trivy/syft/cdxgen) and version generated the dependency data. Tool choice can produce 10-20x variance in dependency counts for the same project, affecting which dependencies appear and their coupling scores. If the dep count seems unexpectedly low, cross-check with a different tool.
Before filing anything, search for existing issues and discussions. GitHub search is word-level tokenized — not semantic — so run multiple queries with different phrasings to reduce false negatives:
# Search by package name (exact)
gh search issues "{dependency}" --repo {owner/repo} --limit 10
# Search by replacement package name
gh search issues "{replacement}" --repo {owner/repo} --limit 10
# Search by keywords describing the change
gh search issues "replace deprecated {short-name}" --repo {owner/repo} --limit 10
# Search discussions (same queries)
gh api graphql -f query='{ search(query: "repo:{owner/repo} {dependency} type:discussion", type: DISCUSSION, first: 10) { nodes { ... on Discussion { title url } } } }'
Example for @vercel/kv:
gh search issues "@vercel/kv" --repo vercel/next.js --limit 10
gh search issues "@upstash/redis" --repo vercel/next.js --limit 10
gh search issues "replace deprecated kv" --repo vercel/next.js --limit 10
Post-filter: GitHub fuzzy search can return false positives. Verify that each hit is actually about the same dependency removal — not just a mention in passing.
If a matching issue/discussion already exists, do not file a duplicate. Instead, add a comment with any new analysis (e.g., impact data from diet) and stop.
After completing Phase 1, stop and file an issue instead of implementing. This is the default because:
Use gh issue create with the following structure:
Title: dep: replace EOL {dependency} with {replacement}
Body:
## Problem
`{dependency}` is {lifecycle status}.
{1-2 sentences on why this matters — security risk, no more patches, etc.}
## Impact analysis
- **Detected by**: [uzomuzo diet](https://github.com/future-architect/uzomuzo-oss) with {sbom-tool} {version}
- **Files**: {N} files import this dependency
- **Call sites**: {N} calls across {N} APIs
- **Exclusive transitive deps**: {N} (removed together)
- **Stays as indirect**: {yes/no}
- **Difficulty**: {trivial/easy/moderate/hard}
### Usage breakdown
| File | Usage | Category |
|------|-------|----------|
{table of files and how they use the dependency}
## Proposed replacement
{replacement} — {why this is the right alternative}
### API mapping
| Current | Replacement |
|---------|-------------|
{API-level migration table}
### Environment variable changes
{any env var renames needed, or "None"}
## False-positive risk
{If the dependency matches an IBNC pattern (side-effect import, config-driven plugin, framework DI, etc.), note it here. If none apply, write "None — all usage is via direct function calls."}
## Cross-project context
{If the same dependency is known to be EOL/archived in other major OSS projects, note it here. E.g., "mitchellh/go-homedir is archived and also affects Trivy, Terraform, Vault, and MinIO." If no cross-project data is available, write "No cross-project data available."}
## Notes
- {any hidden complications from Phase 1 step 3}
- {API leakage? build tags? generated code?}
Before filing, check the target repository's issue templates:
ls <repo>/.github/ISSUE_TEMPLATE/ or check config.yml for blank_issues_enabledblank_issues_enabled: false and only bug/docs templates exist, the project likely uses Discussions for proposals. File in the Ideas category instead:
# Use GitHub Discussions when issues require a specific template
gh api graphql -f query='mutation { createDiscussion(input: { repositoryId: "...", categoryId: "...", title: "...", body: "..." }) { discussion { url } } }'
gh issue createDo not proceed to implementation. The issue/discussion is the deliverable.
Follow-up guidance:
--pr to implement (but check if you can reproduce CI locally first).If --pr was specified, skip this section and continue to Phase 1.5 below.
--pr): Direct implementationThe following phases apply only in PR mode. Use this when you own the project.
Before writing any replacement code, check if the code that uses this dependency has tests.
# Find all files importing the dependency (production code only)
grep -rn "$ARGUMENTS" --include="*.go" -l | grep -v _test.go
# For each file, check if a corresponding test file exists
# e.g., reporter/email.go → reporter/email_test.go
The existing tests define the expected behavior. After replacement, run them — if they pass, the replacement is correct.
This is the most important step in the entire process. Write tests against the current (working) implementation before replacing it. This gives you a safety net that catches behavior differences in the replacement.
Why before, not after? If you write tests after replacing the code, you're only testing that your new code does what you think it should do — not what the old code actually did. Behavior differences slip through.
Real example: c-robinson/iplib handled IPv4 /31 and /32 CIDR prefixes differently from net/netip. If tests had been written after the replacement, the edge case would have been missed because the test would match the new (wrong) behavior.
For high-impact removals (framework replacement, parser rewrite), unit tests aren't enough. Build a comparison harness:
# Build before and after binaries
git stash && go build -o /tmp/before ./cmd/... && git stash pop
go build -o /tmp/after ./cmd/...
# Run both against real-world inputs and diff the output
/tmp/before < input.json > /tmp/out-before.json
/tmp/after < input.json > /tmp/out-after.json
diff /tmp/out-before.json /tmp/out-after.json
The vuls fanal framework removal used this approach: 17 real OSS lockfiles, 7,198 libraries compared — found 1 legitimate difference (a pnpm bug fix).
Based on the strategy from Phase 1:
For stdlib replacement:
grep -rn "$ARGUMENTS" --include="*.go" | grep -v _test.goFor self-implementation:
For submodule isolation:
contrib/<tool>/go.mod with its own module pathcontrib/<tool>/version.go, config packages)go.work if needed for local developmentFor framework peel:
Lessons learned from real dependency removals:
Mechanical replacements aren't fully mechanical. xerrors → fmt.Errorf looked like a sed job, but 10 of 788 call sites had edge cases:
[]error passed to %w (needs errors.Join)%w (needs %v)Check for behavior differences in the stdlib equivalent:
net/smtp is frozen but not deprecated — safe to usetls.Dial + smtp.NewClient is not the same as a library's DialTLSLinter rules may change: Switching from xerrors.New to errors.New may trigger revive rules about error message capitalization that the old library was exempt from.
For Go:
# Remove the direct dependency
go mod edit -droprequire $ARGUMENTS
go mod tidy
For npm: Remove from package.json, run npm install or equivalent.
For Python: Remove from requirements.txt / pyproject.toml, run pip install.
For Maven: Remove from pom.xml.
Monorepo warning: In monorepos with workspace-managed lockfiles (pnpm, yarn workspaces, Gradle multi-module), the lockfile must be regenerated in the monorepo environment. If you cannot run the package manager locally, use Issue mode instead of PR mode — the next.js @vercel/kv removal failed CI entirely because pnpm-lock.yaml was not regenerated.
All three must pass. No exceptions.
# 1. Build
go build ./...
# 2. Static analysis
go vet ./...
# 3. Tests
go test ./... -count=1
For non-Go projects, run the equivalent build + lint + test pipeline.
Check go.sum reduction:
wc -l go.sum # compare with before
If go.sum didn't shrink, the dependency remains as indirect.
Check binary size (for compiled languages):
go build -o /tmp/binary-after ./cmd/...
ls -la /tmp/binary-after
For framework peels: A/B comparison Build both old and new versions, run them against real-world inputs, diff the outputs. This catches subtle behavior changes that unit tests miss.
Create a focused commit with clear metrics: