dependency-currency
Periodic maintenance pass over a project's dependencies, tooling, pins and agent config: find what is unused, vulnerable, broken or misdescribed, upgrade what is safe, and name what needs a human. Use for 'update the dependencies', 'tooling update', 'are we current', 'check for outdated packages', a scheduled maintenance run, or before a release. Works in any project — Python, Node, or both — by reading the project's own AGENTS.md for its check commands rather than assuming a stack.
- License 许可
- Apache-2.0
- 最近更新
- 2026-09-29
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- dependency-currency
- description
- Periodic maintenance pass over a project's dependencies, tooling, pins and agent config: find what is unused, vulnerable, broken or misdescribed, upgrade what is safe, and name what needs a human. Use for 'update the dependencies', 'tooling update', 'are we current', 'check for outdated packages', a scheduled maintenance run, or before a release. Works in any project — Python, Node, or both — by reading the project's own AGENTS.md for its check commands rather than assuming a stack.
02 资源文件 9
- 脚本commands/forget.mjs1.3 KB
- 脚本commands/info.mjs650 B
- 脚本commands/init.mjs6.0 KB
- 脚本commands/list.mjs1.1 KB
- 脚本commands/migrate.mjs7.2 KB
- 脚本commands/resume.mjs949 B
- 脚本commands/save.mjs7.4 KB
- 脚本commands/shared.mjs14.9 KB
- 脚本hooks/session-start.mjs8.6 KB
03 SKILL.md 正文
Dependency currency — a maintenance pass that is not a bump list
<!-- agent-sync:start dependency-currency -->
Vendor-neutral and machine-wide: this lives in ~/.agents/skills/, which Codex,
Grok and Kilo read themselves; Claude Code gets a one-line pointer to it. It is written to be followed by any coding agent
— Codex, Grok, Claude, Kilo, Cursor, Gemini, Copilot — or by a person. It names
no tool of its own beyond the package managers a project already uses, and
every check command it runs is one the project already defines.
Start here: "what is outdated" is the wrong first question
In a maintained project, version currency is usually the least informative
signal: most packages are at or one patch behind latest, and the findings
that matter are dependencies nothing imports (which can carry every CVE the
audit reports), pin comments that no longer match reality, and lockfile
commands that silently remove packages. Leading with outdated produces a
list of patch bumps and misses all of that.
So the order is: unused → vulnerable → misdescribed → outdated. Currency is the last question, not the first.
0. Learn the project before touching it
Read the project's AGENTS.md (or its README.md where it has none) and find:
- The check commands. Never assume. Observed across three sibling repos:
one runs
ruff check src teststhenpytest; one runs a singlescripts/gate.shthat wraps import-linter and architecture tests; one runsuv run ruff check ., strict mypy, andcd ui && npm run typecheck. A skill that hardcodespytestis wrong in two of the three. - Which pins are deliberate. A pin with a comment is a decision someone made. You are going to verify that comment, not obey it and not ignore it.
- What the project says it does not check. Do not "fix" it by adding a check the project deliberately refused.
Record the baseline: run the project's checks before changing anything. A pass that cannot say "1041 tests passed before and after" cannot claim it changed nothing.
1. Unused — what is declared but never runs
The highest-value step and the one a bump list skips entirely.
For each declared dependency, ask whether anything actually reaches it:
grep -rn "<import-name>" --include=<source-glob> .
A hit count of zero is a candidate, not a verdict. Before removing anything, all three of these must hold:
- No direct import anywhere in source or tests.
- Nothing in the lockfile requires it. Read reverse-dependencies out of
the lock. This is not optional and it is the one that bites: in repo B the
same "zero direct imports" signal that correctly condemned five libraries
would also have removed
statsmodels, which is reached throughstatsforecastandarchand would have broken the ML package. - No dynamic reference. Check
importlib, plugin registries, entry points, and config files that name a module as a string. A grep forimportdoes not seeimportlib.import_module(name)or a"module:Class"entry in a TOML.
Removal needs a stronger proof than addition. Adding a dependency that turns out to be unnecessary wastes disk; removing one that turns out to be reachable breaks the product, often not at import time but on the one code path that uses it.
Two patterns worth hunting directly:
- A module whose name matches a dependency but does not import it. Someone
wrote the replacement and never removed the dep. Repo B had two:
experiment.pyhand-rolling whatmlflowwould do,tearsheet.pydelegating to an internal metrics module wherequantstatswould sit. - A dependency that no longer imports cleanly. If nothing imports it,
nothing has noticed it broke. Repo B's
vectorbtraisedValueErroron import against the resolved plotly — it would have failed the moment anyone tried to use it. Try importing each candidate before deciding.
2. Vulnerable — and where the vulnerability actually lives
Run the ecosystem's audit: pip-audit, npm audit, cargo audit, whatever
the project's language provides.
Then ask the question the audit does not: is the vulnerable code reachable?
A CVE in a library nothing imports is a declaration problem, not a security
problem, and the fix is removal rather than an upgrade. Repo B's 53
vulnerabilities were 51 in mlflow and 2 in pyarrow, where pyarrow was
only present as mlflow's transitive dependency, and nothing imported either.
Removing one declaration took the count to zero.
Report both numbers: how many vulnerabilities, and how many are reachable.
3. Misdescribed — where a comment disagrees with reality
Every pin comment is a claim, and claims decay. Verify them. This is where both passes found their sharpest results.
Repo A pinned onnxruntime-gpu>=1.20,<1.23 with a comment saying "1.23+ wants
CUDA 13". The real boundary was 1.27 — wrong by four minor versions, and
four versions of fixes were being refused for no reason. Verified two ways
rather than read: the package metadata showed 1.23–1.26 still requiring
nvidia-cudnn-cu12 and 1.27 switching to -cu13, and in a throwaway
environment 1.26 created a real GPU session and ran a kernel while 1.27 failed
at import with libcudart.so.13: cannot open shared object file.
Note the shape: the pin was real — there is a genuine wall — but it was in the wrong place. Neither "trust the comment" nor "delete the pin" was correct.
Also check documentation that points at a library. Repo B's docs listed a dependency as the way to do a thing; that library's author died in early 2024, it is unmaintained, and a maintained fork is the drop-in replacement. The doc would have sent the next person to a dead library.
Never bump a pin whose comment you have not verified. If you cannot verify it, say so and leave the pin alone.
4. Outdated — last, and read the right column
Only now ask what is behind.
Read the version numbers, not the exit code and not the "wanted" column.
npm outdated exits 0 and prints nothing when every dependency satisfies its
declared range — but a ^18 range is satisfied forever while React reaches
19. In repo B, react, typescript and tailwindcss all showed
Current == Wanted with Latest one or more majors ahead. The information was
there, in the Latest column; an agent that checked the exit code or diffed
Current against Wanted would have concluded "current" while five majors behind.
The equivalent for a lockfile-based resolver: run the upgrade in dry-run and read every line, because the destructive ones are formatted like the safe ones:
Resolved 60 packages
Remove coloredlogs v15.0.1 <- these
Update filelock v3.32.6 -> v4.0.0
Update onnxruntime-gpu v1.22.0 -> v1.26.0
Remove sympy v1.14.0 <- and these
Two specific traps, both observed:
- A "Update" line that goes backwards. Repo B saw
ccxt 4.5.76 -> 4.5.64presented among the updates — a downgrade wearing the word Update. Diff proposed against installed and flag any version that decreases, whatever the label next to it says. - A removal count above zero on an upgrade run is a stop-and-read, never a proceed. Repo A's plain sync would have removed 60 packages including the speech engine, the voice and the cryptography library; repo B's removed 92. Both commands "succeed" and leave a process that still starts, still serves, and has quietly lost a capability. That failure mode — green, running, silently degraded — is worse than a crash.
The pipe that manufactures a clean result
Worth its own rule, because it silently invalidates every check this skill runs, not just the dependency ones.
$? after a pipeline is the last command's status. So a check piped into
head, tail or grep — which is how an agent keeps output readable —
reports the filter's success and discards the tool's verdict. Measured on one
machine, three tools, same shape:
npm outdated >/dev/null -> 1 | … | head -3 -> 0
pytest -k nothing-matches -> 5 | … | tail -1 -> 0
ruff check <file-with-an-error> -> 1 | … | head -2 -> 0
All three are real failures reported as success. pip-audit, npm audit,
mypy and most linters signal the same way, so an agent that runs checks
through a pager and reads the status will pass a failing suite.
Use set -o pipefail, or do not read $? through a pipe at all — run the
command bare for its status and separately for its output.
This is how the npm finding above was originally misreported: the exit code
came back 0 through a | head, and that one-line summary was believed over
eleven rows of contradicting version output printed directly above it.
The general rule: when a one-line summary disagrees with the output above it, doubt the summary. A tool that prints eleven rows of versions and then reports clean is not clean. This is the same failure shape as a benchmark that could not have shown the problem being trusted because it returned — see step 6.
The silent case, which has no backstop
The rule above relies on there being output to disagree with. Usually there is, and it is what saves you: measured across pytest's failure modes, three of the four still print something a filter catches even though the exit code lied.
pytest <a failing test> bare 1 piped 0 prints "FAILED …" and "1 failed"
pytest <an import error> bare 2 piped 0 prints "ERROR <file>"
pytest <a bad path> bare 4 piped 0 prints "ERROR: file not found"
pytest -k <no match> bare 5 piped 0 prints NOTHING
The first three are survivable by accident. The fourth is not: no tests
were collected, so there are no failures to report, and a filter looking for
FAILED, ERROR, passed or failed emits zero lines.
A check that produced no output is not a check that passed. Blank is the most reassuring possible result and here it means the suite never ran.
This is a live risk for exactly this skill. A maintenance pass narrows to "just
the tests near what I changed" with a -k expression or a path — and a rename,
a moved file or a renamed test makes that selector match nothing. The run exits
5, the filtered output is empty, and it looks identical to success.
So for every check, assert on the positive evidence rather than the absence of negative evidence: a test count that is non-zero and matches expectation, a named summary line, an explicit exit code read without a pipe. "Nothing was reported" is not a result.
5. Upgrade what is safe; name what is not
Upgrade by default where it is genuinely safe: patch and minor bumps of libraries the tests actually exercise, with the project's checks green before and after.
Do not upgrade on a version number alone when any of these hold:
- It is load-bearing and native. GPU runtimes, CUDA/cuDNN stacks, compiled extensions, anything whose failure mode the test suite cannot see. Repo A's tests pass identically whether or not the GPU path works — a green suite is not evidence there.
- It is a major with breaking changes and the area has no test coverage. Repo B deliberately left React 18→19 and Tailwind 3→4 alone because that UI has no meaningful tests to catch a regression, and said so. A named, unupgraded major is a better outcome than a broken one you bumped.
- You have not verified the pin's comment. See step 3.
When you decline an upgrade, name it, say why, and say what would let it proceed — "needs a human clicking through the UI", "needs a CUDA 13 runtime on the machine". A declined upgrade with a stated unblock condition is useful; a silent omission is not.
If the repos disagree on a tool's version, do not "align" by downgrading the one that is ahead. A repo running a newer linter is not drifting, it is leading; the fix belongs in the laggards.
6. Measure, if you are changing a default
If the pass changes a behavioural default — a model, an algorithm, a compression level — measure it. And vary the thing you are judging.
Repo A changed a speech-to-text default and benchmarked it first on clean synthesised audio, which produced four near-identical transcripts and would have rejected the change. Only degrading the signal separated the models: at a 0 dB signal-to-noise ratio the word error rate was 43.2% against 28.9%. On clean audio the new default was marginally worse. The conditions the benchmark ran under decided the answer, and the first set of conditions was the wrong one.
State honestly what the measurement does not show. That same pass could not reproduce the original user complaint synthetically, and said so in the code rather than claiming a fix it had not demonstrated.
7. Check the agent-facing surface too
This is a tooling pass, not only a library pass. Also look at:
- Skills, hooks and MCP servers — are they current, and is any of them generated from a source file that should be edited instead? Editing a generated file is work that disappears on the next run.
- Install instructions that do not match reality. Repo A's README installed one extra while the working environment needed four; following the documented command produced a pipeline with no voice that stored transcripts in the clear.
- Name collisions. Repo A's project shares its name with an unrelated package on the public index, so the standard "what is outdated" command reported the project itself as outdated against a stranger's package.
What to say at the end
Lead with what was not a version bump. Then:
- Unused: what was removed, and the three checks that justified each.
- Vulnerable: how many, and how many reachable.
- Misdescribed: every pin comment or doc that disagreed with reality, with the evidence that settled it.
- Outdated: what moved, what did not, and the unblock condition for each thing that did not.
- Checks: the project's own commands, verbatim, before and after — and their real exit codes, not ones read through a pipe.
- For a human: the majors you deliberately left, and what they would need.
If the pass found nothing, say that plainly and say what you checked. "Already current" is a real and useful result — it is most of what both reference passes found — and it is only trustworthy if it names the ground it covered.
Report the mechanism, not only the conclusion
Every rule in this skill about pipes and exit codes exists because of how one finding travelled between two agents, and the method is worth more than the findings.
One reported a conclusion — "the UI is five majors behind" — with a mechanism attached: "the tool prints nothing and exits 0". The conclusion was true and could not be checked by inspection. The mechanism was false and testable in one command. Running it found that the tool prints eleven lines and exits 1; chasing that gap found the real cause, an exit code eaten by a pipe, which was corrupting every check both agents ran and which neither had noticed.
The mechanism is the falsifiable part of a claim. Report it even when the conclusion is what matters, because it is what lets someone else catch you. A conclusion alone is unfalsifiable and travels unchallenged.
And when you cannot reproduce something a peer reported, the useful move is neither "they are wrong" nor "I must be missing something". Treat the discrepancy as its own finding and chase it. Verify a peer's evidence exactly as you would your own — not from distrust, but because a rule carrying a wrong mechanism discredits its true half the first time someone tests it.
Finally: re-verify anything you have already reported as green, bare rather than piped. "I already said it passed" is not evidence. <!-- agent-sync:end -->
04 同仓库更多 Skills
train
Launch a new max_llm training run on both GPUs (RTX 4090 + RTX 3090 Ti) through torchrun DDP, resolving the TOML config by name or path. Use only when the user explicitly asks to start, launch or begin a run — "resume", "continue" or "keep training" mean the existing run; takes an optional config name or path.
serve-live
Before a person looks at a running service, make sure what is running IS the code in the checkout. Restart the services your change actually touched — not every service — and verify each is serving the current commit rather than merely answering; check the remaining dependencies are up without bouncing them. Use whenever you finish work a person will view in a browser or client, whenever you say a page or service is ready, after any commit that changes served code, and when asked to 'restart', 'refresh the services', 'is it live', or 'serve the latest'. Also covers waiting for lazy warm-up before calling a service ready.
review
Review a local change, branch, pull request, or stack for correctness, regressions, security, tests, and unnecessary complexity. Use when asked to review rather than implement.
regression-coverage
Reshape a slow or brittle test suite around observable user journeys and published contracts. Use when pruning tests, repairing weak smoke coverage, or addressing repeated feature regressions.
pr-babysit
Monitor selected pull requests and report or safely act on review, CI, mergeability, and stack state.
keel
Use a project's local keel code graph to assess change impact, find callers and reuse candidates, validate a plan, or check an edit. Applies only in a repository with a .keel/ directory; skip it for prose-only work.