[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CR3ZEQJWV8GSAP3VT8REBM":3,"skill-same:01M6CR3ZEQJWV8GSAP3VT8REBM":104},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":44,"commitSha":45,"skillDirectory":46,"forksCount":14,"repositoryHtmlUrl":47,"repositoryHomepageUrl":6,"ownerHtmlUrl":48,"ownerBlogUrl":49,"ownerDescription":50,"updatedAt":51,"install":52,"resources":65},"01M6CR3ZEQJWV8GSAP3VT8REBM","dependency-currency",null,"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.","maxjbarfuss\u002Fmax_llm","maxjbarfuss","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F13053317?v=4","01M6CR3EGHV8C2C90PGAQEXZ27","Max Barfuss","01M6CR3EN0FXE8RZXAXCT7DWJG",0,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fmaxjbarfuss\u002Fmax_llm\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fdependency-currency\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"dependency currency","dependency","check","upgrade","dependencies","maintenance","tooling","periodic","use","broken","python","find","agent config","node","stack","release","update","run","packages","config","2026-11-10 10:12:30",false,"---\nname: dependency-currency\ndescription: \"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.\"\n---\n\n# Dependency currency — a maintenance pass that is not a bump list\n\n\u003C!-- agent-sync:start dependency-currency -->\n*Vendor-neutral and machine-wide: this lives in `~\u002F.agents\u002Fskills\u002F`, which Codex,\nGrok and Kilo read themselves; Claude Code gets a one-line pointer to it. It is written to be followed by any coding agent\n— Codex, Grok, Claude, Kilo, Cursor, Gemini, Copilot — or by a person. It names\nno tool of its own beyond the package managers a project already uses, and\nevery check command it runs is one the project already defines.*\n\n## Start here: \"what is outdated\" is the wrong first question\n\nIn a maintained project, **version currency is usually the least informative\nsignal**: most packages are at or one patch behind latest, and the findings\nthat matter are dependencies nothing imports (which can carry every CVE the\naudit reports), pin comments that no longer match reality, and lockfile\ncommands that silently remove packages. Leading with `outdated` produces a\nlist of patch bumps and misses all of that.\n\n**So the order is: unused → vulnerable → misdescribed → outdated.** Currency is\nthe last question, not the first.\n\n## 0. Learn the project before touching it\n\nRead the project's `AGENTS.md` (or its `README.md` where it has none) and find:\n\n- **The check commands.** Never assume. Observed across three sibling repos:\n  one runs `ruff check src tests` then `pytest`; one runs a single\n  `scripts\u002Fgate.sh` that wraps import-linter and architecture tests; one runs\n  `uv run ruff check .`, strict mypy, and `cd ui && npm run typecheck`. A skill\n  that hardcodes `pytest` is wrong in two of the three.\n- **Which pins are deliberate.** A pin with a comment is a decision someone\n  made. You are going to verify that comment, not obey it and not ignore it.\n- **What the project says it does not check.** Do not \"fix\" it by adding a\n  check the project deliberately refused.\n\nRecord the baseline: run the project's checks **before** changing anything. A\npass that cannot say \"1041 tests passed before and after\" cannot claim it\nchanged nothing.\n\n## 1. Unused — what is declared but never runs\n\nThe highest-value step and the one a bump list skips entirely.\n\nFor each declared dependency, ask whether anything actually reaches it:\n\n```\ngrep -rn \"\u003Cimport-name>\" --include=\u003Csource-glob> .\n```\n\nA hit count of zero is a **candidate**, not a verdict. Before removing\nanything, all three of these must hold:\n\n1. **No direct import** anywhere in source or tests.\n2. **Nothing in the lockfile requires it.** Read reverse-dependencies out of\n   the lock. This is not optional and it is the one that bites: in repo B the\n   same \"zero direct imports\" signal that correctly condemned five libraries\n   would also have removed `statsmodels`, which is reached through\n   `statsforecast` and `arch` and would have broken the ML package.\n3. **No dynamic reference.** Check `importlib`, plugin registries, entry\n   points, and config files that name a module as a string. A grep for\n   `import` does not see `importlib.import_module(name)` or a\n   `\"module:Class\"` entry in a TOML.\n\n**Removal needs a stronger proof than addition.** Adding a dependency that\nturns out to be unnecessary wastes disk; removing one that turns out to be\nreachable breaks the product, often not at import time but on the one code\npath that uses it.\n\nTwo patterns worth hunting directly:\n\n- **A module whose name matches a dependency but does not import it.** Someone\n  wrote the replacement and never removed the dep. Repo B had two:\n  `experiment.py` hand-rolling what `mlflow` would do, `tearsheet.py`\n  delegating to an internal metrics module where `quantstats` would sit.\n- **A dependency that no longer imports cleanly.** If nothing imports it,\n  nothing has noticed it broke. Repo B's `vectorbt` raised `ValueError` on\n  import against the resolved plotly — it would have failed the moment anyone\n  tried to use it. Try importing each candidate before deciding.\n\n## 2. Vulnerable — and where the vulnerability actually lives\n\nRun the ecosystem's audit: `pip-audit`, `npm audit`, `cargo audit`, whatever\nthe project's language provides.\n\nThen ask the question the audit does not: **is the vulnerable code reachable?**\nA CVE in a library nothing imports is a declaration problem, not a security\nproblem, and the fix is removal rather than an upgrade. Repo B's 53\nvulnerabilities were 51 in `mlflow` and 2 in `pyarrow`, where `pyarrow` was\nonly present as `mlflow`'s transitive dependency, and nothing imported either.\nRemoving one declaration took the count to zero.\n\nReport both numbers: how many vulnerabilities, and how many are reachable.\n\n## 3. Misdescribed — where a comment disagrees with reality\n\n**Every pin comment is a claim, and claims decay.** Verify them. This is where\nboth passes found their sharpest results.\n\nRepo A pinned `onnxruntime-gpu>=1.20,\u003C1.23` with a comment saying \"1.23+ wants\nCUDA 13\". The real boundary was **1.27** — wrong by four minor versions, and\nfour versions of fixes were being refused for no reason. Verified two ways\nrather than read: the package metadata showed 1.23–1.26 still requiring\n`nvidia-cudnn-cu12` and 1.27 switching to `-cu13`, and in a throwaway\nenvironment 1.26 created a real GPU session and ran a kernel while 1.27 failed\nat import with `libcudart.so.13: cannot open shared object file`.\n\nNote the shape: the pin was *real* — there is a genuine wall — but it was in\nthe wrong place. Neither \"trust the comment\" nor \"delete the pin\" was correct.\n\nAlso check **documentation that points at a library**. Repo B's docs listed a\ndependency as the way to do a thing; that library's author died in early 2024,\nit is unmaintained, and a maintained fork is the drop-in replacement. The doc\nwould have sent the next person to a dead library.\n\n**Never bump a pin whose comment you have not verified.** If you cannot verify\nit, say so and leave the pin alone.\n\n## 4. Outdated — last, and read the right column\n\nOnly now ask what is behind.\n\n**Read the version numbers, not the exit code and not the \"wanted\" column.**\n`npm outdated` exits 0 and prints nothing when every dependency satisfies its\ndeclared range — but a `^18` range is satisfied forever while React reaches\n19. In repo B, `react`, `typescript` and `tailwindcss` all showed\nCurrent == Wanted with Latest one or more majors ahead. The information was\nthere, in the `Latest` column; an agent that checked the exit code or diffed\nCurrent against Wanted would have concluded \"current\" while five majors behind.\n\nThe equivalent for a lockfile-based resolver: run the upgrade in dry-run and\n**read every line**, because the destructive ones are formatted like the safe\nones:\n\n```\nResolved 60 packages\nRemove coloredlogs v15.0.1            \u003C- these\nUpdate filelock v3.32.6 -> v4.0.0\nUpdate onnxruntime-gpu v1.22.0 -> v1.26.0\nRemove sympy v1.14.0                  \u003C- and these\n```\n\nTwo specific traps, both observed:\n\n- **A \"Update\" line that goes backwards.** Repo B saw\n  `ccxt 4.5.76 -> 4.5.64` presented among the updates — a downgrade wearing\n  the word Update. **Diff proposed against installed and flag any version that\n  decreases**, whatever the label next to it says.\n- **A removal count above zero on an upgrade run is a stop-and-read**, never a\n  proceed. Repo A's plain sync would have removed 60 packages including the\n  speech engine, the voice and the cryptography library; repo B's removed 92.\n  Both commands \"succeed\" and leave a process that still starts, still serves,\n  and has quietly lost a capability. That failure mode — green, running,\n  silently degraded — is worse than a crash.\n\n### The pipe that manufactures a clean result\n\nWorth its own rule, because it silently invalidates **every** check this skill\nruns, not just the dependency ones.\n\n`$?` after a pipeline is the *last* command's status. So a check piped into\n`head`, `tail` or `grep` — which is how an agent keeps output readable —\nreports the filter's success and discards the tool's verdict. Measured on one\nmachine, three tools, same shape:\n\n```\nnpm outdated  >\u002Fdev\u002Fnull           -> 1     |  … | head -3  -> 0\npytest -k nothing-matches          -> 5     |  … | tail -1  -> 0\nruff check \u003Cfile-with-an-error>    -> 1     |  … | head -2  -> 0\n```\n\nAll three are real failures reported as success. `pip-audit`, `npm audit`,\n`mypy` and most linters signal the same way, so an agent that runs checks\nthrough a pager and reads the status will pass a failing suite.\n\nUse `set -o pipefail`, or do not read `$?` through a pipe at all — run the\ncommand bare for its status and separately for its output.\n\nThis is how the npm finding above was originally misreported: the exit code\ncame back `0` through a `| head`, and that one-line summary was believed over\neleven rows of contradicting version output printed directly above it.\n\n**The general rule: when a one-line summary disagrees with the output above\nit, doubt the summary.** A tool that prints eleven rows of versions and then\nreports clean is not clean. This is the same failure shape as a benchmark that\ncould not have shown the problem being trusted because it returned — see\nstep 6.\n\n### The silent case, which has no backstop\n\nThe rule above relies on there being output to disagree with. Usually there\nis, and it is what saves you: measured across pytest's failure modes, three of\nthe four still print something a filter catches even though the exit code\nlied.\n\n```\npytest \u003Ca failing test>   bare 1  piped 0   prints \"FAILED …\" and \"1 failed\"\npytest \u003Can import error>  bare 2  piped 0   prints \"ERROR \u003Cfile>\"\npytest \u003Ca bad path>       bare 4  piped 0   prints \"ERROR: file not found\"\npytest -k \u003Cno match>      bare 5  piped 0   prints NOTHING\n```\n\nThe first three are survivable **by accident**. The fourth is not: no tests\nwere collected, so there are no failures to report, and a filter looking for\n`FAILED`, `ERROR`, `passed` or `failed` emits zero lines.\n\n**A check that produced no output is not a check that passed.** Blank is the\nmost reassuring possible result and here it means the suite never ran.\n\nThis is a live risk for exactly this skill. A maintenance pass narrows to \"just\nthe tests near what I changed\" with a `-k` expression or a path — and a rename,\na moved file or a renamed test makes that selector match nothing. The run exits\n5, the filtered output is empty, and it looks identical to success.\n\nSo for every check, assert on the **positive** evidence rather than the absence\nof negative evidence: a test count that is non-zero and matches expectation, a\nnamed summary line, an explicit exit code read without a pipe. \"Nothing was\nreported\" is not a result.\n\n## 5. Upgrade what is safe; name what is not\n\nUpgrade by default where it is genuinely safe: patch and minor bumps of\nlibraries the tests actually exercise, with the project's checks green before\nand after.\n\nDo **not** upgrade on a version number alone when any of these hold:\n\n- **It is load-bearing and native.** GPU runtimes, CUDA\u002FcuDNN stacks, compiled\n  extensions, anything whose failure mode the test suite cannot see. Repo A's\n  tests pass identically whether or not the GPU path works — a green suite is\n  not evidence there.\n- **It is a major with breaking changes and the area has no test coverage.**\n  Repo B deliberately left React 18→19 and Tailwind 3→4 alone because that UI\n  has no meaningful tests to catch a regression, and said so. **A named,\n  unupgraded major is a better outcome than a broken one you bumped.**\n- **You have not verified the pin's comment.** See step 3.\n\nWhen you decline an upgrade, name it, say why, and say what would let it\nproceed — \"needs a human clicking through the UI\", \"needs a CUDA 13 runtime on\nthe machine\". A declined upgrade with a stated unblock condition is useful; a\nsilent omission is not.\n\n**If the repos disagree on a tool's version, do not \"align\" by downgrading the\none that is ahead.** A repo running a newer linter is not drifting, it is\nleading; the fix belongs in the laggards.\n\n## 6. Measure, if you are changing a default\n\nIf the pass changes a behavioural default — a model, an algorithm, a\ncompression level — measure it. And **vary the thing you are judging**.\n\nRepo A changed a speech-to-text default and benchmarked it first on clean\nsynthesised audio, which produced four near-identical transcripts and would\nhave rejected the change. Only degrading the signal separated the models: at a\n0 dB signal-to-noise ratio the word error rate was 43.2% against 28.9%. On\nclean audio the new default was *marginally worse*. The conditions the\nbenchmark ran under decided the answer, and the first set of conditions was\nthe wrong one.\n\nState honestly what the measurement does **not** show. That same pass could\nnot reproduce the original user complaint synthetically, and said so in the\ncode rather than claiming a fix it had not demonstrated.\n\n## 7. Check the agent-facing surface too\n\nThis is a tooling pass, not only a library pass. Also look at:\n\n- **Skills, hooks and MCP servers** — are they current, and is any of them\n  generated from a source file that should be edited instead? Editing a\n  generated file is work that disappears on the next run.\n- **Install instructions that do not match reality.** Repo A's README\n  installed one extra while the working environment needed four; following the\n  documented command produced a pipeline with no voice that stored transcripts\n  in the clear.\n- **Name collisions.** Repo A's project shares its name with an unrelated\n  package on the public index, so the standard \"what is outdated\" command\n  reported the project itself as outdated against a stranger's package.\n\n## What to say at the end\n\nLead with what was **not** a version bump. Then:\n\n- **Unused**: what was removed, and the three checks that justified each.\n- **Vulnerable**: how many, and how many reachable.\n- **Misdescribed**: every pin comment or doc that disagreed with reality, with\n  the evidence that settled it.\n- **Outdated**: what moved, what did not, and the unblock condition for each\n  thing that did not.\n- **Checks**: the project's own commands, verbatim, before and after — and\n  their real exit codes, not ones read through a pipe.\n- **For a human**: the majors you deliberately left, and what they would need.\n\nIf the pass found nothing, say that plainly and say what you checked. \"Already\ncurrent\" is a real and useful result — it is most of what both reference passes\nfound — and it is only trustworthy if it names the ground it covered.\n\n## Report the mechanism, not only the conclusion\n\nEvery rule in this skill about pipes and exit codes exists because of how one\nfinding travelled between two agents, and the method is worth more than the\nfindings.\n\nOne reported a conclusion — \"the UI is five majors behind\" — with a mechanism\nattached: \"the tool prints nothing and exits 0\". The conclusion was true and\ncould not be checked by inspection. The mechanism was false and testable in one\ncommand. Running it found that the tool prints eleven lines and exits 1; chasing\n*that* gap found the real cause, an exit code eaten by a pipe, which was\ncorrupting every check both agents ran and which neither had noticed.\n\n**The mechanism is the falsifiable part of a claim.** Report it even when the\nconclusion is what matters, because it is what lets someone else catch you. A\nconclusion alone is unfalsifiable and travels unchallenged.\n\nAnd when you cannot reproduce something a peer reported, the useful move is\nneither \"they are wrong\" nor \"I must be missing something\". **Treat the\ndiscrepancy as its own finding and chase it.** Verify a peer's evidence exactly\nas you would your own — not from distrust, but because a rule carrying a wrong\nmechanism discredits its true half the first time someone tests it.\n\nFinally: re-verify anything you have already reported as green, bare rather\nthan piped. \"I already said it passed\" is not evidence.\n\u003C!-- agent-sync:end -->\n","# Dependency currency — a maintenance pass that is not a bump list\n\n\u003C!-- agent-sync:start dependency-currency -->\n*Vendor-neutral and machine-wide: this lives in `~\u002F.agents\u002Fskills\u002F`, which Codex,\nGrok and Kilo read themselves; Claude Code gets a one-line pointer to it. It is written to be followed by any coding agent\n— Codex, Grok, Claude, Kilo, Cursor, Gemini, Copilot — or by a person. It names\nno tool of its own beyond the package managers a project already uses, and\nevery check command it runs is one the project already defines.*\n\n## Start here: \"what is outdated\" is the wrong first question\n\nIn a maintained project, **version currency is usually the least informative\nsignal**: most packages are at or one patch behind latest, and the findings\nthat matter are dependencies nothing imports (which can carry every CVE the\naudit reports), pin comments that no longer match reality, and lockfile\ncommands that silently remove packages. Leading with `outdated` produces a\nlist of patch bumps and misses all of that.\n\n**So the order is: unused → vulnerable → misdescribed → outdated.** Currency is\nthe last question, not the first.\n\n## 0. Learn the project before touching it\n\nRead the project's `AGENTS.md` (or its `README.md` where it has none) and find:\n\n- **The check commands.** Never assume. Observed across three sibling repos:\n  one runs `ruff check src tests` then `pytest`; one runs a single\n  `scripts\u002Fgate.sh` that wraps import-linter and architecture tests; one runs\n  `uv run ruff check .`, strict mypy, and `cd ui && npm run typecheck`. A skill\n  that hardcodes `pytest` is wrong in two of the three.\n- **Which pins are deliberate.** A pin with a comment is a decision someone\n  made. You are going to verify that comment, not obey it and not ignore it.\n- **What the project says it does not check.** Do not \"fix\" it by adding a\n  check the project deliberately refused.\n\nRecord the baseline: run the project's checks **before** changing anything. A\npass that cannot say \"1041 tests passed before and after\" cannot claim it\nchanged nothing.\n\n## 1. Unused — what is declared but never runs\n\nThe highest-value step and the one a bump list skips entirely.\n\nFor each declared dependency, ask whether anything actually reaches it:\n\n```\ngrep -rn \"\u003Cimport-name>\" --include=\u003Csource-glob> .\n```\n\nA hit count of zero is a **candidate**, not a verdict. Before removing\nanything, all three of these must hold:\n\n1. **No direct import** anywhere in source or tests.\n2. **Nothing in the lockfile requires it.** Read reverse-dependencies out of\n   the lock. This is not optional and it is the one that bites: in repo B the\n   same \"zero direct imports\" signal that correctly condemned five libraries\n   would also have removed `statsmodels`, which is reached through\n   `statsforecast` and `arch` and would have broken the ML package.\n3. **No dynamic reference.** Check `importlib`, plugin registries, entry\n   points, and config files that name a module as a string. A grep for\n   `import` does not see `importlib.import_module(name)` or a\n   `\"module:Class\"` entry in a TOML.\n\n**Removal needs a stronger proof than addition.** Adding a dependency that\nturns out to be unnecessary wastes disk; removing one that turns out to be\nreachable breaks the product, often not at import time but on the one code\npath that uses it.\n\nTwo patterns worth hunting directly:\n\n- **A module whose name matches a dependency but does not import it.** Someone\n  wrote the replacement and never removed the dep. Repo B had two:\n  `experiment.py` hand-rolling what `mlflow` would do, `tearsheet.py`\n  delegating to an internal metrics module where `quantstats` would sit.\n- **A dependency that no longer imports cleanly.** If nothing imports it,\n  nothing has noticed it broke. Repo B's `vectorbt` raised `ValueError` on\n  import against the resolved plotly — it would have failed the moment anyone\n  tried to use it. Try importing each candidate before deciding.\n\n## 2. Vulnerable — and where the vulnerability actually lives\n\nRun the ecosystem's audit: `pip-audit`, `npm audit`, `cargo audit`, whatever\nthe project's language provides.\n\nThen ask the question the audit does not: **is the vulnerable code reachable?**\nA CVE in a library nothing imports is a declaration problem, not a security\nproblem, and the fix is removal rather than an upgrade. Repo B's 53\nvulnerabilities were 51 in `mlflow` and 2 in `pyarrow`, where `pyarrow` was\nonly present as `mlflow`'s transitive dependency, and nothing imported either.\nRemoving one declaration took the count to zero.\n\nReport both numbers: how many vulnerabilities, and how many are reachable.\n\n## 3. Misdescribed — where a comment disagrees with reality\n\n**Every pin comment is a claim, and claims decay.** Verify them. This is where\nboth passes found their sharpest results.\n\nRepo A pinned `onnxruntime-gpu>=1.20,\u003C1.23` with a comment saying \"1.23+ wants\nCUDA 13\". The real boundary was **1.27** — wrong by four minor versions, and\nfour versions of fixes were being refused for no reason. Verified two ways\nrather than read: the package metadata showed 1.23–1.26 still requiring\n`nvidia-cudnn-cu12` and 1.27 switching to `-cu13`, and in a throwaway\nenvironment 1.26 created a real GPU session and ran a kernel while 1.27 failed\nat import with `libcudart.so.13: cannot open shared object file`.\n\nNote the shape: the pin was *real* — there is a genuine wall — but it was in\nthe wrong place. Neither \"trust the comment\" nor \"delete the pin\" was correct.\n\nAlso check **documentation that points at a library**. Repo B's docs listed a\ndependency as the way to do a thing; that library's author died in early 2024,\nit is unmaintained, and a maintained fork is the drop-in replacement. The doc\nwould have sent the next person to a dead library.\n\n**Never bump a pin whose comment you have not verified.** If you cannot verify\nit, say so and leave the pin alone.\n\n## 4. Outdated — last, and read the right column\n\nOnly now ask what is behind.\n\n**Read the version numbers, not the exit code and not the \"wanted\" column.**\n`npm outdated` exits 0 and prints nothing when every dependency satisfies its\ndeclared range — but a `^18` range is satisfied forever while React reaches\n19. In repo B, `react`, `typescript` and `tailwindcss` all showed\nCurrent == Wanted with Latest one or more majors ahead. The information was\nthere, in the `Latest` column; an agent that checked the exit code or diffed\nCurrent against Wanted would have concluded \"current\" while five majors behind.\n\nThe equivalent for a lockfile-based resolver: run the upgrade in dry-run and\n**read every line**, because the destructive ones are formatted like the safe\nones:\n\n```\nResolved 60 packages\nRemove coloredlogs v15.0.1            \u003C- these\nUpdate filelock v3.32.6 -> v4.0.0\nUpdate onnxruntime-gpu v1.22.0 -> v1.26.0\nRemove sympy v1.14.0                  \u003C- and these\n```\n\nTwo specific traps, both observed:\n\n- **A \"Update\" line that goes backwards.** Repo B saw\n  `ccxt 4.5.76 -> 4.5.64` presented among the updates — a downgrade wearing\n  the word Update. **Diff proposed against installed and flag any version that\n  decreases**, whatever the label next to it says.\n- **A removal count above zero on an upgrade run is a stop-and-read**, never a\n  proceed. Repo A's plain sync would have removed 60 packages including the\n  speech engine, the voice and the cryptography library; repo B's removed 92.\n  Both commands \"succeed\" and leave a process that still starts, still serves,\n  and has quietly lost a capability. That failure mode — green, running,\n  silently degraded — is worse than a crash.\n\n### The pipe that manufactures a clean result\n\nWorth its own rule, because it silently invalidates **every** check this skill\nruns, not just the dependency ones.\n\n`$?` after a pipeline is the *last* command's status. So a check piped into\n`head`, `tail` or `grep` — which is how an agent keeps output readable —\nreports the filter's success and discards the tool's verdict. Measured on one\nmachine, three tools, same shape:\n\n```\nnpm outdated  >\u002Fdev\u002Fnull           -> 1     |  … | head -3  -> 0\npytest -k nothing-matches          -> 5     |  … | tail -1  -> 0\nruff check \u003Cfile-with-an-error>    -> 1     |  … | head -2  -> 0\n```\n\nAll three are real failures reported as success. `pip-audit`, `npm audit`,\n`mypy` and most linters signal the same way, so an agent that runs checks\nthrough a pager and reads the status will pass a failing suite.\n\nUse `set -o pipefail`, or do not read `$?` through a pipe at all — run the\ncommand bare for its status and separately for its output.\n\nThis is how the npm finding above was originally misreported: the exit code\ncame back `0` through a `| head`, and that one-line summary was believed over\neleven rows of contradicting version output printed directly above it.\n\n**The general rule: when a one-line summary disagrees with the output above\nit, doubt the summary.** A tool that prints eleven rows of versions and then\nreports clean is not clean. This is the same failure shape as a benchmark that\ncould not have shown the problem being trusted because it returned — see\nstep 6.\n\n### The silent case, which has no backstop\n\nThe rule above relies on there being output to disagree with. Usually there\nis, and it is what saves you: measured across pytest's failure modes, three of\nthe four still print something a filter catches even though the exit code\nlied.\n\n```\npytest \u003Ca failing test>   bare 1  piped 0   prints \"FAILED …\" and \"1 failed\"\npytest \u003Can import error>  bare 2  piped 0   prints \"ERROR \u003Cfile>\"\npytest \u003Ca bad path>       bare 4  piped 0   prints \"ERROR: file not found\"\npytest -k \u003Cno match>      bare 5  piped 0   prints NOTHING\n```\n\nThe first three are survivable **by accident**. The fourth is not: no tests\nwere collected, so there are no failures to report, and a filter looking for\n`FAILED`, `ERROR`, `passed` or `failed` emits zero lines.\n\n**A check that produced no output is not a check that passed.** Blank is the\nmost reassuring possible result and here it means the suite never ran.\n\nThis is a live risk for exactly this skill. A maintenance pass narrows to \"just\nthe tests near what I changed\" with a `-k` expression or a path — and a rename,\na moved file or a renamed test makes that selector match nothing. The run exits\n5, the filtered output is empty, and it looks identical to success.\n\nSo for every check, assert on the **positive** evidence rather than the absence\nof negative evidence: a test count that is non-zero and matches expectation, a\nnamed summary line, an explicit exit code read without a pipe. \"Nothing was\nreported\" is not a result.\n\n## 5. Upgrade what is safe; name what is not\n\nUpgrade by default where it is genuinely safe: patch and minor bumps of\nlibraries the tests actually exercise, with the project's checks green before\nand after.\n\nDo **not** upgrade on a version number alone when any of these hold:\n\n- **It is load-bearing and native.** GPU runtimes, CUDA\u002FcuDNN stacks, compiled\n  extensions, anything whose failure mode the test suite cannot see. Repo A's\n  tests pass identically whether or not the GPU path works — a green suite is\n  not evidence there.\n- **It is a major with breaking changes and the area has no test coverage.**\n  Repo B deliberately left React 18→19 and Tailwind 3→4 alone because that UI\n  has no meaningful tests to catch a regression, and said so. **A named,\n  unupgraded major is a better outcome than a broken one you bumped.**\n- **You have not verified the pin's comment.** See step 3.\n\nWhen you decline an upgrade, name it, say why, and say what would let it\nproceed — \"needs a human clicking through the UI\", \"needs a CUDA 13 runtime on\nthe machine\". A declined upgrade with a stated unblock condition is useful; a\nsilent omission is not.\n\n**If the repos disagree on a tool's version, do not \"align\" by downgrading the\none that is ahead.** A repo running a newer linter is not drifting, it is\nleading; the fix belongs in the laggards.\n\n## 6. Measure, if you are changing a default\n\nIf the pass changes a behavioural default — a model, an algorithm, a\ncompression level — measure it. And **vary the thing you are judging**.\n\nRepo A changed a speech-to-text default and benchmarked it first on clean\nsynthesised audio, which produced four near-identical transcripts and would\nhave rejected the change. Only degrading the signal separated the models: at a\n0 dB signal-to-noise ratio the word error rate was 43.2% against 28.9%. On\nclean audio the new default was *marginally worse*. The conditions the\nbenchmark ran under decided the answer, and the first set of conditions was\nthe wrong one.\n\nState honestly what the measurement does **not** show. That same pass could\nnot reproduce the original user complaint synthetically, and said so in the\ncode rather than claiming a fix it had not demonstrated.\n\n## 7. Check the agent-facing surface too\n\nThis is a tooling pass, not only a library pass. Also look at:\n\n- **Skills, hooks and MCP servers** — are they current, and is any of them\n  generated from a source file that should be edited instead? Editing a\n  generated file is work that disappears on the next run.\n- **Install instructions that do not match reality.** Repo A's README\n  installed one extra while the working environment needed four; following the\n  documented command produced a pipeline with no voice that stored transcripts\n  in the clear.\n- **Name collisions.** Repo A's project shares its name with an unrelated\n  package on the public index, so the standard \"what is outdated\" command\n  reported the project itself as outdated against a stranger's package.\n\n## What to say at the end\n\nLead with what was **not** a version bump. Then:\n\n- **Unused**: what was removed, and the three checks that justified each.\n- **Vulnerable**: how many, and how many reachable.\n- **Misdescribed**: every pin comment or doc that disagreed with reality, with\n  the evidence that settled it.\n- **Outdated**: what moved, what did not, and the unblock condition for each\n  thing that did not.\n- **Checks**: the project's own commands, verbatim, before and after — and\n  their real exit codes, not ones read through a pipe.\n- **For a human**: the majors you deliberately left, and what they would need.\n\nIf the pass found nothing, say that plainly and say what you checked. \"Already\ncurrent\" is a real and useful result — it is most of what both reference passes\nfound — and it is only trustworthy if it names the ground it covered.\n\n## Report the mechanism, not only the conclusion\n\nEvery rule in this skill about pipes and exit codes exists because of how one\nfinding travelled between two agents, and the method is worth more than the\nfindings.\n\nOne reported a conclusion — \"the UI is five majors behind\" — with a mechanism\nattached: \"the tool prints nothing and exits 0\". The conclusion was true and\ncould not be checked by inspection. The mechanism was false and testable in one\ncommand. Running it found that the tool prints eleven lines and exits 1; chasing\n*that* gap found the real cause, an exit code eaten by a pipe, which was\ncorrupting every check both agents ran and which neither had noticed.\n\n**The mechanism is the falsifiable part of a claim.** Report it even when the\nconclusion is what matters, because it is what lets someone else catch you. A\nconclusion alone is unfalsifiable and travels unchallenged.\n\nAnd when you cannot reproduce something a peer reported, the useful move is\nneither \"they are wrong\" nor \"I must be missing something\". **Treat the\ndiscrepancy as its own finding and chase it.** Verify a peer's evidence exactly\nas you would your own — not from distrust, but because a rule carrying a wrong\nmechanism discredits its true half the first time someone tests it.\n\nFinally: re-verify anything you have already reported as green, bare rather\nthan piped. \"I already said it passed\" is not evidence.\n\u003C!-- agent-sync:end -->\n",{"name":5,"description":7},"main","16426a8647c4bf341798845ddf222a5b8c1ac861",".agents\u002Fskills\u002Fdependency-currency","https:\u002F\u002Fgithub.com\u002Fmaxjbarfuss\u002Fmax_llm","https:\u002F\u002Fgithub.com\u002Fmaxjbarfuss","https:\u002F\u002Fwww.altavega.io","Software Engineer, AI, Robotics, Systems, Full-Stack","2026-09-29 21:59:21",{"source":8,"skillName":5,"commands":53,"prompt":63,"zipAvailable":64},[54,57,60],{"tool":55,"command":56},"npx","npx skills add maxjbarfuss\u002Fmax_llm --skill dependency-currency",{"tool":58,"command":59},"bunx","bunx skills add maxjbarfuss\u002Fmax_llm --skill dependency-currency",{"tool":61,"command":62},"pnpm","pnpm dlx skills add maxjbarfuss\u002Fmax_llm --skill dependency-currency","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmaxjbarfuss\u002Fmax_llm\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fdependency-currency\u002FSKILL.md",true,[66,72,76,80,84,88,92,96,100],{"path":67,"resourceType":68,"resourceTypeDesc":69,"sizeBytes":70,"sourceHtmlUrl":71},"commands\u002Fforget.mjs","SCRIPT","脚本",1304,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FKnowledge-and-Productivity\u002Fck\u002Fcommands\u002Fforget.mjs",{"path":73,"resourceType":68,"resourceTypeDesc":69,"sizeBytes":74,"sourceHtmlUrl":75},"commands\u002Finfo.mjs",650,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FKnowledge-and-Productivity\u002Fck\u002Fcommands\u002Finfo.mjs",{"path":77,"resourceType":68,"resourceTypeDesc":69,"sizeBytes":78,"sourceHtmlUrl":79},"commands\u002Finit.mjs",6146,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FKnowledge-and-Productivity\u002Fck\u002Fcommands\u002Finit.mjs",{"path":81,"resourceType":68,"resourceTypeDesc":69,"sizeBytes":82,"sourceHtmlUrl":83},"commands\u002Flist.mjs",1092,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FKnowledge-and-Productivity\u002Fck\u002Fcommands\u002Flist.mjs",{"path":85,"resourceType":68,"resourceTypeDesc":69,"sizeBytes":86,"sourceHtmlUrl":87},"commands\u002Fmigrate.mjs",7416,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FKnowledge-and-Productivity\u002Fck\u002Fcommands\u002Fmigrate.mjs",{"path":89,"resourceType":68,"resourceTypeDesc":69,"sizeBytes":90,"sourceHtmlUrl":91},"commands\u002Fresume.mjs",949,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FKnowledge-and-Productivity\u002Fck\u002Fcommands\u002Fresume.mjs",{"path":93,"resourceType":68,"resourceTypeDesc":69,"sizeBytes":94,"sourceHtmlUrl":95},"commands\u002Fsave.mjs",7574,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FKnowledge-and-Productivity\u002Fck\u002Fcommands\u002Fsave.mjs",{"path":97,"resourceType":68,"resourceTypeDesc":69,"sizeBytes":98,"sourceHtmlUrl":99},"commands\u002Fshared.mjs",15267,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FKnowledge-and-Productivity\u002Fck\u002Fcommands\u002Fshared.mjs",{"path":101,"resourceType":68,"resourceTypeDesc":69,"sizeBytes":102,"sourceHtmlUrl":103},"hooks\u002Fsession-start.mjs",8835,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FKnowledge-and-Productivity\u002Fck\u002Fhooks\u002Fsession-start.mjs",[105,120,144,157,171,186],{"skillNo":106,"name":107,"nameZh":6,"description":108,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":109,"categories":110,"keywords":111,"publishedAt":39},"01M6CR6B20826Y0PPVXJQ2D0QJ","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.","https:\u002F\u002Fgithub.com\u002Fmaxjbarfuss\u002Fmax_llm\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Ftrain\u002FSKILL.md",[],[107,38,112,113,114,27,115,116,117,36,118,119],"max","resume","only","user","continue","start","training","launch",{"skillNo":121,"name":122,"nameZh":6,"description":123,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":124,"categories":125,"keywords":126,"publishedAt":39},"01M6CR67YE4MW1R46KB8NV85ZT","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.","https:\u002F\u002Fgithub.com\u002Fmaxjbarfuss\u002Fmax_llm\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fserve-live\u002FSKILL.md",[],[127,128,129,130,131,132,133,21,134,135,136,137,27,23,138,139,140,141,142,143],"serve live","services","verify","page","browser","code","work","commit","change","view","refresh","calling","restart","say","finish","up","ready",{"skillNo":145,"name":146,"nameZh":6,"description":147,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":148,"categories":149,"keywords":150,"publishedAt":39},"01M6CR665ZD6GG1SQKD0XS4GJS","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.","https:\u002F\u002Fgithub.com\u002Fmaxjbarfuss\u002Fmax_llm\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Freview\u002FSKILL.md",[],[146,151,135,27,33,152,153,154,155,156],"complexity","pull","pull request","implement","security","branch",{"skillNo":158,"name":159,"nameZh":6,"description":160,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":161,"categories":162,"keywords":163,"publishedAt":39},"01M6CR64DYZJFD4JM1QCKMCGFN","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.","https:\u002F\u002Fgithub.com\u002Fmaxjbarfuss\u002Fmax_llm\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fregression-coverage\u002FSKILL.md",[],[164,165,166,167,168,169,27,115,170],"regression","coverage","regression coverage","smoke","contracts","test suite","test",{"skillNo":172,"name":173,"nameZh":6,"description":174,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":175,"categories":176,"keywords":177,"publishedAt":39},"01M6CR62A9C4BB3YR6XZPVBKY3","pr-babysit","Monitor selected pull requests and report or safely act on review, CI, mergeability, and stack state.","https:\u002F\u002Fgithub.com\u002Fmaxjbarfuss\u002Fmax_llm\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fpr-babysit\u002FSKILL.md",[],[178,179,180,181,182,146,183,33,184,152,185],"pr babysit","babysit","pr","monitor","pull requests","report","ci","state",{"skillNo":187,"name":188,"nameZh":6,"description":189,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":190,"categories":191,"keywords":192,"publishedAt":39},"01M6CR5ZWFQFGJ6QY17DN0Q3NF","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\u002F directory; skip it for prose-only work.","https:\u002F\u002Fgithub.com\u002Fmaxjbarfuss\u002Fmax_llm\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fkeel\u002FSKILL.md",[],[188,30,193,114,27,194,21,133,195,196,197,132,198,135,199],"prose","edit","plan","repository","graph","validate","directory"]