Remove a dependency identified by uzomuzo diet — analysis + issue (default) or direct PR
Installation
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Remove a dependency identified by uzomuzo diet — analysis + issue (default) or direct PR
argument-hint
<module path or PURL> [--pr] [--repo owner/repo]
Diet Remove: $ARGUMENTS
Analyze and plan the removal of dependency $ARGUMENTS, then take action.
Mode selection
Default (Issue mode): Run Phase 1 analysis, then file a GitHub Issue with the findings and proposed migration plan. Appropriate for external OSS contributions and large projects where you don't own the build environment.
--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:
If --pr is present → PR mode (direct implementation)
If --repo owner/repo is present → target that repository for the issue
Otherwise → Issue mode (default)
When 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.
Ecosystem detection
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.
GitHub Actions detection
If the target PURL starts with pkg:githubactions/ or is a GitHub Action name (owner/action):
Assess impact: Count workflow files and jobs affected. Note whether release-critical pipelines are involved.
Find replacement: Check if an official replacement, maintained fork, or first-party alternative exists (e.g., tibdex/github-app-token → actions/create-github-app-token).
Pin strategy: Replacement should use SHA pins with version comments (e.g., 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.
Phase 1: Pre-flight checks
Before writing any code, run through these checks:
1. Will it actually disappear?
Check the STAYS column in the uzomuzo diet output (or stays_as_indirect in JSON):
STAYS = - → Removing this dep fully removes it from the dependency tree. Go ahead.
STAYS = 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"
2. What's the replacement?
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.
3. Are there hidden complications?
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:
Side-effect import (import _ "pkg", import 'pkg', require('pkg') without assignment)
Database / driver registration (blank import or conditional require())
Config-driven plugin (eslint, tailwind, babel, postcss — referenced in config files, not imports)
Framework DI / decorator (@Entity, @Autowired, extends Framework, Ember DI)
Annotation-only usage (Java: @NotNull, @JsonProperty — the annotation is the usage)
Type-only / constant-only package (imported for types or constants, zero function calls)
Delegated composition (called indirectly through SDK wrappers or framework context objects)
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.
5. SBOM tool awareness
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.
Issue mode (default): File a GitHub Issue
Step 0: Duplicate check (MANDATORY)
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 } } } }'
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.
Step 1: File the issue or discussion
After completing Phase 1, stop and file an issue instead of implementing. This is the default because:
External contributors cannot run CI or regenerate lockfiles
Maintainers need context to evaluate the change
Large monorepos have project-specific build/test requirements
Issue template
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?}
Choosing the right channel
Before filing, check the target repository's issue templates:
Run ls <repo>/.github/ISSUE_TEMPLATE/ or check config.yml for blank_issues_enabled
If blank_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 } } }'
If blank issues are enabled or a "feature request" template exists, use gh issue create
After filing
Do not proceed to implementation. The issue/discussion is the deliverable.
Follow-up guidance:
If no maintainer response after 2 weeks, add a polite ping comment.
If the maintainer responds with "PR welcome", you may re-run with --pr to implement (but check if you can reproduce CI locally first).
If the same dependency is EOL across multiple projects (e.g., mitchellh/* packages), cross-reference the issues in each body so maintainers see the ecosystem-wide pattern.
If the dependency would benefit from structural reform rather than individual removal (e.g., a framework that pulls in many EOL deps), mention this in the Notes section — the insight is valuable even if you can't implement it yourself.
If --pr was specified, skip this section and continue to Phase 1.5 below.
PR mode (--pr): Direct implementation
The following phases apply only in PR mode. Use this when you own the project.
Phase 1.5: Test coverage check — before you touch anything
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
If tests exist: You're safe to proceed
The existing tests define the expected behavior. After replacement, run them — if they pass, the replacement is correct.
If tests DON'T exist: Write tests FIRST, before changing anything
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.
Identify the contract: What does the code do with this dependency? What are the inputs and outputs?
Write tests that capture current behavior:
Normal cases (happy path)
Edge cases specific to the dependency's behavior (e.g., how does it handle nil? empty input? unicode?)
Error cases (what happens when the dependency returns an error?)
Run the tests against the current code — they must pass before you change anything
Then proceed to Phase 2
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 framework peels: Build a regression harness
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).
For each site, replace the API call with the stdlib equivalent
Update imports
If the replacement API has different error handling or return types, adapt the call site
For self-implementation:
Write the replacement in the same package that uses it (don't create a shared utility for a single use site)
Keep it minimal — match only the API surface actually used, not the full library
Write tests that cover the same behavior as the original
For submodule isolation:
Create contrib/<tool>/go.mod with its own module path
Move the relevant code under contrib/<tool>/
Watch for imports back to the root module (especially version.go, config packages)
Add go.work if needed for local development
For framework peel:
Identify which specific functions you actually call through the framework
Call them directly, bypassing the framework's registration/discovery layer
This is the highest-effort strategy but has the highest payoff
Build a comprehensive regression test BEFORE starting (golden files, A/B comparison)
Step 2: Handle edge cases
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)
Non-error types passed to %w (needs %v)
Existing bugs hidden by the old library's lax type checking
Check for behavior differences in the stdlib equivalent:
net/smtp is frozen but not deprecated — safe to use
IPv4 network/broadcast address handling differs between libraries
tls.Dial + smtp.NewClient is not the same as a library's DialTLS
Linter 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.
Step 3: Update dependency manifest
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.
Phase 3: Verification
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.
Additional verification for high-impact removals:
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.
Phase 4: Commit
Create a focused commit with clear metrics:
fix: remove {dependency} — replace with {replacement}
{What was done and why}
Before: {N} direct deps, go.sum {N} lines
After: {N} direct deps, go.sum {N} lines
Binary: {size before} → {size after} ({reduction}%)
Phase 5: Upstream Diet — when the dep stays as indirect
If uzomuzo diet showed STAYS = yes for this dependency (or you see it still in go.sum / lockfile after removal), it remains as an indirect dependency.
The diet detailed output's IndirectVia field already tells you exactly which direct deps pull it in. Use this to plan upstream removals.
Is the upstream under your control?
If the upstream is your organization's repo (e.g., your company's other OSS projects):
Submit the same removal PR to that upstream repo
Wait for it to be merged and tagged
Update your go.mod to the new version: go get -u <upstream>@latest
Run go mod tidy — now it should disappear from go.sum
Real example: Removing go-homedir from vuls required PRs to 6 upstream repos (go-exploitdb, go-kev, gost, go-cti, go-msfdb, go-cve-dictionary). All used the identical os.UserHomeDir() replacement. Only after all 6 were merged and tagged did go-homedir finally leave vuls's go.sum.
If the upstream is a third-party project you don't control:
You can submit a PR, but you can't control the timeline
In the meantime, the removal from your direct deps still has value:
Version management is delegated to the upstream
Your code no longer calls the dependency directly
You're ready for instant cleanup when the upstream removes it
Note this in the commit message: "Direct usage removed. Remains as indirect via X."
Don't stop at one repo
If you manage multiple repos that share the same dependency, plan the removal across all of them:
Week 1: Remove from leaf repos (no downstream dependents)
Week 2: Tag releases of leaf repos
Week 3: Update parent repos to new versions, remove there too
Week 4: Tag releases, final go mod tidy in the top-level repo
Phase 6: Step back — individual removal vs structural reform
After removing a few dependencies individually, check the cumulative effect:
# Compare with the original baselinewc -l go.sum # did it actually shrink meaningfully?
go build -o /tmp/bin . && ls -la /tmp/bin # did binary size change?
The "9 deps removed, binary unchanged" lesson
In the vuls Code Diet project, removing 9 individual dependencies barely changed the binary size. The dependencies were gone from go.mod, but their code was still pulled in transitively through a framework layer (Trivy's fanal).
The breakthrough came from removing the framework itself — replacing the fanal analyzer registration with direct parser calls. That single change dropped the scanner binary from 106.6 MB to 34.1 MB (-68%).
When to shift from individual removal to structural reform
Signal
Action
go.sum keeps getting longer despite removals
Check for a framework pulling everything back in
Binary size doesn't change after 3+ removals
Look for a shared layer that imports the deps transitively
Multiple deps serve the same framework
Remove the framework, not the individual deps
diet shows many deps with 0 ONLY-VIA-THIS
They're all shared through a common layer — peel the layer
Structural reform approaches:
Framework peel: Call specific functions directly instead of going through a plugin/registration layer
Reporter/plugin extraction: Move optional integrations to submodules with their own go.mod
Binary split: Separate CLI subcommands into separate binaries (git-style delegation — like uzomuzo-diet itself)
Common patterns (from real removals)
Dependency
Replacement
Effort
Surprise
mitchellh/go-homedir
os.UserHomeDir()
15 min
None — simplest possible removal
rifflock/lfshook
20-line logrus.Hook impl
30 min
Test was 131 lines (file I/O)
samber/lo (UniqBy)
Per-package uniqBy helper
30 min
Better to duplicate than abstract
golang.org/x/oauth2
10-line bearerTransport
30 min
Just a RoundTripper wrapper
golang.org/x/xerrors
fmt.Errorf + errors
2 hr
10 edge cases in 788 call sites, found existing bug
gosnmp/gosnmp
Submodule contrib/snmp2cpe/
1 hr
version.go imported root module
emersion/go-smtp
net/smtp + LOGIN auth
1.5 hr
TLS connection setup differs
c-robinson/iplib
net/netip CIDR enumeration
1 hr
IPv4 network/broadcast edge cases
Trivy fanal framework
Direct parser calls
2 days
-68% binary size, found pnpm bug
Language coverage: PR mode commands and common patterns are currently Go-centric, built from real removal experience. Issue mode and IBNC checks are language-agnostic. PRs adding equivalent PR-mode guidance for npm, pip, Maven, or other ecosystems (backed by actual removal data) are welcome.
Primary Source Links
All generated content (commit messages, issues, PRs, discussions) MUST include primary source links so reviewers can verify claims independently.
Required links
Claim
Required link
"Dependency X is Archived/Deprecated"
Registry page (npm/pkg.go.dev/PyPI/Maven Central) showing the status