mit einem Klick
repomatic-deps
// Generate dependency graphs, audit pyproject.toml declarations against version policy, explore unused dependency APIs that could simplify code, and modernize code against the changelogs of upgraded dependencies.
// Generate dependency graphs, audit pyproject.toml declarations against version policy, explore unused dependency APIs that could simplify code, and modernize code against the changelogs of upgraded dependencies.
Orchestrate release preparation. Reconcile the changelog, code, and docs to the net release state, then commit, push, and babysit CI until the release PR is built and `main` is green. Stop before the merge. Review-gated in normal use, fully autonomous under `--dangerously-skip-permissions`.
Draft, validate, consolidate, and fix changelog entries.
Audit downstream repo alignment with upstream repomatic reference, covering workflows, configs, and conventions.
Bootstrap a repository with reusable workflows from kdeldycke/repomatic.
Optimize GitHub topics for discoverability by analyzing competition on topic pages.
Triage new issues and PRs on awesome-list repos by applying curation criteria distilled from past decisions.
| name | repomatic-deps |
| description | Generate dependency graphs, audit pyproject.toml declarations against version policy, explore unused dependency APIs that could simplify code, and modernize code against the changelogs of upgraded dependencies. |
| model | opus |
| disable-model-invocation | true |
| allowed-tools | Bash, Read, Grep, Glob, Agent, Edit, Write, WebFetch |
| argument-hint | [graph [--level N]|review [all|runtime|dev|policy]|explore [<package>]|modernize [<package>]] |
![ -f uv.lock ] && echo "uv.lock exists" || echo "No uv.lock found"
![ -f pyproject.toml ] && head -5 pyproject.toml || echo "No pyproject.toml found"
!grep -c '".*>=\|".*~=\|".*<\|".*==' pyproject.toml 2>/dev/null || echo "0"
![ -f repomatic/__init__.py ] && echo "CANONICAL_REPO" || echo "DOWNSTREAM"
You help users understand and maintain their project's dependencies. This skill has four modes: graph (visualize the resolved dependency tree), review (audit pyproject.toml declarations against version policy), explore (find unused dependency APIs that could simplify existing code), and modernize (refactor code to adopt features from recently-upgraded dependencies, applying the changes).
CANONICAL_REPO, use uv run repomatic.uvx -- repomatic.graph and review all.graph: Generate and analyze the dependency graph only.review [all|runtime|dev|policy]: Audit pyproject.toml declarations only.explore [<package>]: Search for unused dependency APIs that could simplify existing code.modernize [<package>]: Refactor code to adopt new features from upgraded dependencies, applying and test-gating each change.$ARGUMENTS starts with --level or a number, treat it as graph mode with those arguments.The autofix.yaml workflow's update-deps-graph job already regenerates the dependency graph on every push to main. This mode is useful for interactive analysis — understanding the graph, spotting concerns, or generating it before pushing.
<cmd> update-deps-graph.<cmd> update-deps-graph with no arguments.This is purely analytical work with no mechanical equivalent in CI.
all (default when no sub-argument): Run all checks below.runtime: Review [project].dependencies only.dev: Review [dependency-groups] and [project.optional-dependencies] only.policy: Print the policy summary without auditing.These conventions are derived from the pyproject.toml files across all kdeldycke/* repositories. User-facing documentation of the same content is in docs/dependencies.md.
[project].dependencies)>= (not ~= or ==). Relaxed lower bounds give packagers freedom to release security hotfixes without waiting for an upstream bump. Upper bounds are forbidden per https://iscinumpy.dev/post/bound-version-constraints/.# wcmatch 10.0 changed globbing semantics; our sync_gitignore() relies on
# the new symlink-aware matching behavior.
"wcmatch>=10",
A good floor comment answers: "if someone installed an older version, what would break and where?" If you cannot point to a concrete usage, the floor may be unnecessarily high.
Security fixes are also a valid floor bump reason. A CVE or advisory in an older version justifies raising the floor even when the API is unchanged. The comment should cite the CVE or advisory:
# requests 2.32.0 fixes CVE-2024-35195 (session credential leak on redirects).
"requests>=2.32",
requires-python metadata. If boltons>=20 works and boltons 25 merely adds Python 3.13 support, keep >=20 — the resolver handles it. Exception: when a dependency drops a Python version your project still supports (or your project drops one, aligning minimum requires-python), that alignment is a valid floor bump reason. The comment should state the version range alignment, not the Python support:
# boltons 25.0.0 dropped Python 3.9, matching our requires-python >= 3.10.
"boltons>=25",
"tomli>=2; python_version<'3.11'". When a dep has a version marker, the floor rationale must make sense for the Python versions where the dep is actually installed — not for versions excluded by the marker.[dependency-groups])[dependency-groups] (uv standard) over [project.optional-dependencies] for test, typing, and docs groups.>= is preferred for dev deps too, but ~= is acceptable when stricter pinning reduces CI randomness. If a package also appears in runtime deps, the dev entry must use the same specifier style. The relaxation is about specifier style (~= allowed), not about floor accuracy — dev dep floors still need to be grounded in actual API or compatibility requirements, not adoption timestamps.test, typing, docs (lowercase, alphabetical).typing group with stub-specific versions: "types-boltons>=25.0.0.20250822".<, <=, !=, ~= that implies an upper bound). The only exception is conditional markers like python_version<'3.11'."coverage[toml]>=7.11".format-json workflow normalizes layout automatically.Read the full pyproject.toml. For each dependency entry, check:
| Check | What to flag |
|---|---|
| Specifier style | ~=, ==, or upper bounds on runtime deps |
| Missing comment | No comment above the entry explaining the version floor |
| Weak comment | Comment cites Python version support instead of a concrete code dependency. Flag unless it documents a requires-python alignment or a Python version drop |
| Stale comment | Comment references a reason that no longer applies (e.g., the cited method was replaced, or the Python version was dropped from the support matrix) |
| Inflated floor | Floor higher than the oldest version providing the APIs actually used (see floor verification below) |
| Marker/rationale mismatch | Floor rationale contradicts the conditional marker (e.g., "Python 3.14 wheels" on a dep gated by python_version<'3.11' — that dep is never installed on 3.14) |
| Ordering | Dependencies not in alphabetical order |
| Group placement | Type stubs outside typing group, test deps outside test group |
| Section style | [project.optional-dependencies] used where [dependency-groups] would be appropriate |
| Bare dependency | No version specifier at all (e.g., "requests") |
| Conditional markers | Missing Python version marker for backport packages |
| Stale cooldown exceptions | exclude-newer-package entries in [tool.uv] for packages that no longer need them (see below) |
Comments and changelogs can lie; the codebase is the source of truth. For each dependency with a weak or suspicious comment, verify the floor against actual usage:
pip index versions <pkg> to see what exists on PyPI.uv lock after any floor change to verify the lock still resolves.backports-strenum, tomli, exceptiongroup) exist solely to provide a stdlib class to older Python versions. Their entire API is the backported class itself, available in all versions. The floor is typically >=1 (or the first release) unless a specific bug fix is needed for the Python versions where the dep is actually installed.python_version<'3.11' that has a floor set for a bug affecting Python <3.8.6 — if the project's requires-python is >=3.10, that bug is irrelevant and the floor can be lowered.pytest-randomly, pytest-github-actions-annotate-failures) have low effective floors — their basic functionality has been stable across major versions. Set the floor at the major version introducing the current plugin interface, not at the latest release.A floor bump is justified when a newer version of an existing dependency provides an API that replaces hand-rolled code in the project. This is the flip side of floor verification: instead of checking whether the floor is too high, check whether it could be raised to unlock a simplification.
A valid simplification bump must:
When explore mode identifies a candidate, the review output should include it as an Info-level suggestion with the current code, the replacement, and the version that introduced the API.
These comment patterns typically signal a floor set at adoption or auto-bump time, not at an API boundary:
requires-python drop alignment or a concrete build failure (missing wheels that cause install failures on that Python version), this is not a valid floor reason.~= -> >= conversion pipeline. A common inflation path: (a) dep added as ~=X.Y (latest at time), (b) Renovate bumps to ~=X.Z, (c) a bulk "relax requirements" commit converts all ~= to >=. Each step inflates the floor without API validation. Check git log for this pattern when a floor looks suspiciously high.exclude-newer-package cooldown auditThe [tool.uv] section may contain exclude-newer-package entries that exempt specific packages from the global exclude-newer cooldown window. These exceptions exist for a reason (typically: the package is developed in-repo or needs immediate updates), but they accumulate over time and may outlive their purpose.
For each exclude-newer-package entry, check:
[project].dependencies and all [dependency-groups], the exception is dead weight."0 day" override for an in-repo package makes sense. A "0 day" override for an external package that was temporarily pinned during a migration may no longer be needed.Flag stale or unjustified entries as warnings.
When the context shows DOWNSTREAM, also compare the dependency list against the canonical repomatic pyproject.toml (fetch with gh api repos/kdeldycke/repomatic/contents/pyproject.toml --jq '.content' | base64 -d) to identify:
Produce:
Search for unused APIs in existing dependencies that could replace hand-rolled code. This is purely analytical: it produces recommendations, not changes.
<package>: explore a single dependency (e.g., explore boltons).For each dependency in scope:
Catalog current usage. Grep the source tree (not tests) for all imports from the package. List every function, class, and constant actually used, with file locations.
Catalog available APIs. Using your knowledge of the library (and its docs if needed via WebFetch), list the public APIs the project does NOT currently use. Focus on utilities, helpers, and data structures: the kind of thing that replaces 3-10 lines of hand-rolled code.
Search for replacement candidates. For each unused API, grep the source tree for code patterns it could replace. Be specific about what constitutes a match:
| Library API | Pattern to search for |
|---|---|
boltons.iterutils.partition | Two complementary list comprehensions filtering the same iterable |
boltons.iterutils.first | next(iter(...), None) or seq[0] if seq else None on non-generator sequences |
boltons.iterutils.bucketize | Loops building a dict[K, list[V]] via setdefault(k, []).append(v) |
boltons.iterutils.chunked | Manual slice loops (for i in range(0, len(seq), n)) |
boltons.dictutils.subdict | {k: v for k, v in d.items() if k in keys} or if k not in exclude |
boltons.fileutils.atomic_save | Path.write_text() on files where partial writes would corrupt state |
packaging.specifiers.SpecifierSet | Manual version-range checks with </>/in loops over Version objects |
packaging.requirements.Requirement | Regex parsing of PEP 508 requirement strings |
packaging.markers.Marker | Regex parsing of PEP 508 environment markers (only when the public API exposes the needed structure) |
wcmatch.fnmatch / wcmatch.pathlib | stdlib fnmatch or pathlib.Path.glob() that would benefit from brace expansion, negation, or extended patterns |
pyproject_metadata.StandardMetadata properties | Manual toml_dict.get("project", {}).get("field") when a StandardMetadata instance is already available |
Verify each candidate. Read the actual code at each location. Discard false positives:
next() on a generator is idiomatic Python; first() adds a dependency import for no clarity gain.Path.write_text() is fine when the file is immediately committed by CI or when partial writes are harmless.TableFormat.GITHUB hardcoded in functions that generate markdown for PR bodies is correct: the --table-format CLI option is for terminal output only.glob.glob() with recursive=True is fine when no extended glob features (brace expansion, negation) are needed.packaging.markers.Marker only helps if the public API exposes the structure you need. Its primary public method is evaluate(), not AST inspection.Check version requirements. For each surviving candidate, determine which version of the dependency introduced the API. Compare against the current floor. If a bump is needed, it must satisfy the floor bump criteria.
Produce a table of findings:
| Dependency | Unused API | Location | Current code (summary) | Replacement | Version needed | Verdict |
|---|---|---|---|---|---|---|
| boltons | subdict | metadata.py:2232 | dict comprehension filtering by key set | subdict(metadata, keys) | any (available since 16.x) | Skip: one-liner, no clarity gain |
| pyproject-metadata | StandardMetadata.keywords | cli.py:2733 | toml.get("project", {}).get("keywords") | metadata.pyproject.keywords | current floor sufficient | Adopt: parsed object already available |
Only recommend changes where the replacement is a genuine simplification: fewer lines, better error handling, or elimination of a manual reimplementation.
Most dependencies are already well-used. Expect the majority of candidates to be discarded during verification. A run that produces zero recommendations is a valid outcome: it means the codebase is already leveraging its dependencies effectively.
Common false-positive patterns to reject early:
next(iter(x)) → first(x) adds an import for no clarity gain.atomic_save on files that are immediately git-committed or overwritten by CI.glob and wcmatch.glob serve different purposes; not every glob.glob() call needs extended syntax.re.match(r"extra\s*==\s*'([^']+)'", marker) is more direct than navigating a Marker object's internal structure.explore finds simplifications hiding in any installed dependency and only reports them. modernize is narrower and active: it works from the dependencies that changed version recently, reads what those versions added, and applies the resulting simplifications, gated by the test suite.
[!WARNING] This is the one mode that edits code on its own, and it acts on third-party changelogs it can misread. Every change must be behavior-preserving and verified against the local test suite before it stays. Run it where you can review the diff, and treat a failing test as a veto, never something to "fix" by loosening the test.
<package>: a single dependency (e.g., modernize click-extra).Find the version deltas. Determine which dependencies changed, and from and to which version, cheapest source first:
sync-uv-lock PR — its body lists every bump with its old and new version and a changelog or compare link per package. Find it with gh pr list --search 'head:sync-uv-lock' --state all --limit 1, then gh pr view <number> --json body.git tag --sort=-v:refname | head -1) and diff the lockfile against it (git diff <tag> -- uv.lock), reading the version pairs.pyproject.toml floor.Read each changelog for the delta. Fetch the release notes covering that version range from the link in the bump table (GitHub releases via gh api repos/{owner}/{repo}/releases, or WebFetch on the compare URL; the PyPI project page otherwise). Extract only what is new or changed in the range: added public APIs, fixed bugs our code works around, and deprecations of APIs we still call. Degrade gracefully: if WebFetch is unavailable and the package is not on GitHub, fall back to your own knowledge of the library's release history and lower your confidence accordingly.
Map deltas to our code. For each new or changed item, grep the source tree (not tests) for code it touches, reusing the candidate patterns and false-positive filters from Explore mode:
work around, TODO, and version-guarded branches.Apply one dependency at a time. Make the edits for a single package, keeping each behavior-preserving. If adopting an API needs a higher floor, raise it and rewrite the comment per Floor bumps to adopt new APIs, then run uv lock.
Verify before moving on. Run the project's tests, mypy, and ruff (the same fast local channel /babysit-ci and /repomatic-ship rely on). If anything fails, fix it within the same change or revert that package's edits. Only move to the next dependency once green. A change that cannot be made green is reverted, not forced.
Report. Summarize per dependency: the version delta, what was adopted or dropped, the files touched, and anything skipped with the reason. A dependency whose changelog offers nothing actionable is a valid no-op.
Suggest the user run:
/repomatic-deps review all to audit version floors and specifier policy./repomatic-deps modernize after a sync-uv-lock PR lands, to fold the freshly-upgraded dependencies' new features into the code./repomatic-audit for a comprehensive alignment check beyond dependencies.