dependency-health-loop
A configuration of loop-controller. That skill supplies the loop
machinery — primitive selection, the full guardrail stack, state
externalization. This skill supplies the three things specific to "keep deps
healthy": the per-pass recipe (audit → one safe bump → gate → PR), a
mechanical proof that combines a security/freshness audit with a green gate
after every update, and the HITL boundary that holds majors for a human.
Read loop-controller for the guardrails; they're inherited, not repeated
here.
Why disable-model-invocation: this loop edits the lockfile, commits, and
pushes branches on its own, on a clock. It is user-driven — you want to type
/loop 30m /dependency-health-loop (or /dependency-health-loop), not have
Claude silently start bumping packages because an advisory landed.
The 5-part contract
| Part |
This loop |
| trigger |
a sprint in flight (deps drifting / new advisories), scheduled via /loop 30m /dependency-health-loop, or an explicit /dependency-health-loop |
| action |
ONE pass: run the ecosystem audit → pick one safe update (a security fix first, else one over-stale patch/minor) → apply it → run the whole fix-until-green gate → open or update a PR. One bump per pass; re-audit the whole tree after |
| proof |
no known vulnerabilities AND no pinned dep stale beyond policy AND the gate exits green — all three observed in the same pass, from the audit report + gate exit codes. Default-FAIL: assume unhealthy until the audit proves otherwise |
| memory |
dep-health.md (audit snapshot, the bump→result log, deferred majors + why), the PR branch, the lockfile diff, and git history — durable across the session-scoped /loop fires |
| stop |
proof holds (clean audit + in-policy freshness + green gate) OR poll/iteration cap OR no-progress for 3 passes (same advisory unfixable within the reversible boundary) OR budget cap OR an HITL checkpoint is hit (a major bump, or a lockfile-semantics change) |
The proof: clean audit + in-policy freshness + green gate, default-FAIL
"Healthy" is not "the last bump installed." It is three conditions read
together in one pass: the ecosystem audit reports zero known
vulnerabilities at or above the policy severity, no pinned dependency is
stale beyond the freshness policy, and the full gate (fix-until-green's three
exit codes) is green after the update. Assume the tree is unhealthy until
a fresh audit proves all three — that is the default-FAIL stance, and it is why
every pass re-audits rather than trusting the last verdict. A loop that stops
after one bump ships a tree that picked up a new CVE and a red typecheck.
This is the only loop whose proof fuses a security/freshness audit with a
green gate after each bump — neither half alone is the proof. Name the
artifacts: the audit report (npm audit --json / pip-audit / cargo audit /
govulncheck) plus the gate's exit codes. Both must read clean in the same
pass. Per-ecosystem audit + freshness commands are in
references/ecosystems.md.
Step 1 — Audit, pick exactly one update
Run the ecosystem audit and the freshness check (the precise commands per stack
are in references/ecosystems.md). Then pick one target, in priority order:
- A security fix for the highest-severity known vulnerability (a patch/minor
that resolves an advisory).
- Else one over-stale dependency past the freshness policy — smallest safe
bump first (patch before minor).
One bump per pass. Batching destroys the signal about which change broke (or
fixed) the gate, and it makes the PR un-reviewable. If the only available fix for
an advisory is a major bump, that is an HITL checkpoint — do not apply it;
record it as a deferred major and surface it.
Step 2 — Apply, then verify with the whole gate
Apply the single bump, regenerate the lockfile, and re-run the entire
fix-until-green gate (test + lint + typecheck) from scratch — not just the
package's own tests. A bump that installs cleanly but reds typecheck has made the
tree worse, and only a full re-run catches it. Do not re-implement the gate —
invoke fix-until-green; this loop adds only the dependency discipline around it.
If the gate goes red and the fix is a mechanical adaptation to the new API,
fix-until-green resolves it; if it can't be made green within the bump, roll
the bump back (the lockfile diff is the undo) and record it as needing a human.
Step 3 — Open or update the PR, re-audit
On a green bump, commit a checkpoint naming the package and version, then open or
update a single dependency-health PR via git-pr. Then re-audit the whole
tree — the proof is about the whole, re-checked after every change. When the
audit is clean, freshness is in policy, and the gate is green together, the
loop's proof holds; report the audit report + gate output as evidence. The loop
prepares the PR for a human to merge — it never merges.
HITL is load-bearing for this loop
This loop runs only inside the reversible boundary unattended. The
irreversible or breaking actions are HITL checkpoints — never autonomous:
- A major version bump — pause, always. A breaking major is never merged
unattended; record it as a deferred major and surface it for a human, with the
changelog/migration link.
- Anything that changes lockfile semantics beyond a clean add/bump — a
resolution override, a peer-dependency force, a registry/source swap, removing
a transitive pin — pause. These change what resolves, not just a version.
- Merging — never. Reaching the proof means ready for the human to merge,
not "merge it." This loop does not auto-merge, full stop.
Within the boundary — a patch/minor security update or in-policy freshness bump
with a green gate, on its own branch/PR — it is AFK-safe. The full AFK-safe vs
pause table is in references/ecosystems.md.
Guardrails specific to this loop
Inherits the full stack from loop-controller → references/safety.md. The caps
this loop sets:
- Poll / iteration cap —
/loop's ~3-day session expiry is the outer bound;
set an inner per-run pass cap (default ~20) so a tree full of advisories
doesn't burn the window. Hitting it is a stop-and-escalate, not a license to
loosen the proof.
- No-progress detection — if the same advisory survives 3 consecutive
passes (only fixable by a major, or the bump can't be made green), stop and
surface it. Three passes on one advisory means it needs a human decision, not a
fourth attempt.
- Budget cap — a watch loop firing every 30 minutes adds up; enforce a
token/cost ceiling that terminates the loop (read from
.claude/profile.yaml
when present), not just warns.
- Never cheat the proof. Forbidden, each a finding: pinning around an
advisory with an audit-ignore/allowlist entry instead of fixing it, downgrading
the policy severity to clear the count, suppressing a deprecation rather than
resolving it, or merging to make the audit moot. A clean audit that came from
silencing the auditor is not health.
How this differs from its neighbors
This loop is not a one-shot dependency update, and it is not a re-skin of an
existing skill — it draws three explicit boundaries:
- vs a one-shot
npm update / dependabot bump: those apply versions; they do
not prove health. This loop's substance is the loop discipline — a
default-FAIL proof that fuses audit + freshness + a green gate, one bump per
pass with full re-verification, no-progress detection, scheduling, and an HITL
gate on majors. The bump is the easy part; the proof and the boundary are the
product.
- vs
security-agent: the security role authors the policy — which
severities block, which advisories are accepted, the SBOM/audit expectations.
This loop consumes that policy as its proof threshold and acts on a
cadence; it does not define security posture. On a vuln it can't resolve
within the reversible boundary, it escalates to the human (and, under an
orchestrated build, to the security role).
- vs
infrastructure-agent: infra owns the dependency manifests and
lockfiles as files. This loop proposes single, gated bumps as PRs for the
owner to merge — it never force-merges into infra-owned files, and majors route
to a human exactly because they may change runtime/build semantics infra owns.
- vs
fix-until-green: that loop's proof is three exit codes. This loop's
proof contains a green gate but is strictly larger (audit + freshness), and
it invokes fix-until-green to verify each bump rather than re-implementing
the gate.
Choosing the driver primitive
Per loop-controller Step 1, this is a watch/poll job — you wait for the
dependency tree and advisory feed to change on a cadence — so the primitive is
/loop (sprint cadence) or /schedule (a longer, e.g. nightly,
cadence), not /goal (which pushes to a finish line). The daily-driver
recipe:
/loop 30m /dependency-health-loop
The per-pass exit (this pass made one clean, reversible, gated bump and
re-audited) is provable from the audit report + gate exit codes, so a pass can
run unattended within the HITL boundary above. /loop's session-scope, ~3-day
expiry, and no-catch-up mechanics live in loop-controller's
references/primitives.md; don't re-document them.
Reference files
references/ecosystems.md — per-ecosystem audit + freshness + update commands
(npm/pnpm/yarn, pip/poetry/uv, cargo, go, bundler, composer), the
major-vs-minor HITL policy, the lockfile-safety rules (what's a clean bump vs a
semantics change), and the AFK-safe vs pause table.
1---2name: dependency-health-loop3description: Keep a project's dependencies healthy on a schedule: audit for known vulnerabilities and over-stale pins, apply ONE safe update (or a vuln fix) per pass, run the full gate to prove the update is non-breaking, and open or update a PR — looping on a sprint cadence with HITL on every major version bump and never auto-merging. Use when you want deps watched while you build, want security advisories acted on a cadence, want lockfiles kept fresh without breaking the build, or want a dependency audit run every 30 minutes during a sprint. Trigger on "audit my dependencies", "keep deps up to date", "dependency health", "check for vulnerable packages", "npm audit on a schedule", "pip-audit loop", "cargo audit loop", "update dependencies safely", "watch for CVEs", "keep the lockfile fresh", "dependabot-style loop", "bump deps and test". Routes major/breaking bumps to a human and never merges. A configuration of loop-controller.4---56# dependency-health-loop78> **A configuration of [`loop-controller`].** That skill supplies the loop9> machinery — primitive selection, the full guardrail stack, state10> externalization. This skill supplies the three things specific to "keep deps11> healthy": the **per-pass recipe** (audit → one safe bump → gate → PR), a12> **mechanical proof** that combines a security/freshness audit with a green gate13> after every update, and the **HITL boundary** that holds majors for a human.14> Read `loop-controller` for the guardrails; they're inherited, not repeated15> here.16>17> **Why `disable-model-invocation`:** this loop edits the lockfile, commits, and18> pushes branches on its own, on a clock. It is user-driven — you want to *type*19> `/loop 30m /dependency-health-loop` (or `/dependency-health-loop`), not have20> Claude silently start bumping packages because an advisory landed.2122## The 5-part contract2324| Part | This loop |25|---|---|26| **trigger** | a sprint in flight (deps drifting / new advisories), scheduled via `/loop 30m /dependency-health-loop`, or an explicit `/dependency-health-loop` |27| **action** | ONE pass: run the ecosystem audit → pick **one** safe update (a security fix first, else one over-stale patch/minor) → apply it → run the **whole** [`fix-until-green`] gate → open or update a PR. One bump per pass; re-audit the whole tree after |28| **proof** | **no known vulnerabilities AND no pinned dep stale beyond policy AND the gate exits green** — all three observed in the same pass, from the audit report + gate exit codes. Default-FAIL: assume **unhealthy** until the audit proves otherwise |29| **memory** | `dep-health.md` (audit snapshot, the bump→result log, deferred majors + why), the PR branch, the lockfile diff, and git history — durable across the session-scoped `/loop` fires |30| **stop** | proof holds (clean audit + in-policy freshness + green gate) **OR** poll/iteration cap **OR** no-progress for 3 passes (same advisory unfixable within the reversible boundary) **OR** budget cap **OR** an HITL checkpoint is hit (a major bump, or a lockfile-semantics change) |3132## The proof: clean audit + in-policy freshness + green gate, default-FAIL3334"Healthy" is not "the last bump installed." It is **three conditions read35together in one pass**: the ecosystem audit reports **zero known36vulnerabilities** at or above the policy severity, **no pinned dependency is37stale beyond the freshness policy**, and the full gate (`fix-until-green`'s three38exit codes) is **green after the update**. Assume the tree is **unhealthy** until39a fresh audit proves all three — that is the default-FAIL stance, and it is why40every pass *re-audits* rather than trusting the last verdict. A loop that stops41after one bump ships a tree that picked up a new CVE and a red typecheck.4243This is the only loop whose proof fuses a **security/freshness audit** with a44**green gate after each bump** — neither half alone is the proof. Name the45artifacts: the audit report (`npm audit --json` / `pip-audit` / `cargo audit` /46`govulncheck`) plus the gate's exit codes. Both must read clean *in the same47pass*. Per-ecosystem audit + freshness commands are in48`references/ecosystems.md`.4950## Step 1 — Audit, pick exactly one update5152Run the ecosystem audit and the freshness check (the precise commands per stack53are in `references/ecosystems.md`). Then pick **one** target, in priority order:54551. **A security fix** for the highest-severity known vulnerability (a patch/minor56 that resolves an advisory).572. Else **one over-stale dependency** past the freshness policy — smallest safe58 bump first (patch before minor).5960One bump per pass. Batching destroys the signal about which change broke (or61fixed) the gate, and it makes the PR un-reviewable. If the only available fix for62an advisory is a **major** bump, that is an HITL checkpoint — do not apply it;63record it as a deferred major and surface it.6465## Step 2 — Apply, then verify with the whole gate6667Apply the single bump, regenerate the lockfile, and re-run the **entire**68[`fix-until-green`] gate (test + lint + typecheck) from scratch — not just the69package's own tests. A bump that installs cleanly but reds typecheck has made the70tree worse, and only a full re-run catches it. **Do not re-implement the gate** —71invoke `fix-until-green`; this loop adds only the dependency discipline around it.72If the gate goes red and the fix is a mechanical adaptation to the new API,73`fix-until-green` resolves it; if it can't be made green within the bump, **roll74the bump back** (the lockfile diff is the undo) and record it as needing a human.7576## Step 3 — Open or update the PR, re-audit7778On a green bump, commit a checkpoint naming the package and version, then open or79update a single dependency-health PR via [`git-pr`]. Then **re-audit the whole80tree** — the proof is about the whole, re-checked after every change. When the81audit is clean, freshness is in policy, and the gate is green together, the82loop's proof holds; report the audit report + gate output as evidence. The loop83prepares the PR for a human to merge — it never merges.8485## HITL is load-bearing for this loop8687This loop runs **only inside the reversible boundary** unattended. The88irreversible or breaking actions are **HITL checkpoints — never autonomous**:8990- **A major version bump** — pause, always. A breaking major is never merged91 unattended; record it as a deferred major and surface it for a human, with the92 changelog/migration link.93- **Anything that changes lockfile semantics** beyond a clean add/bump — a94 resolution override, a peer-dependency force, a registry/source swap, removing95 a transitive pin — pause. These change *what resolves*, not just a version.96- **Merging** — never. Reaching the proof means *ready for the human to merge*,97 not "merge it." This loop does not auto-merge, full stop.9899Within the boundary — a patch/minor security update or in-policy freshness bump100with a green gate, on its own branch/PR — it is AFK-safe. The full AFK-safe vs101pause table is in `references/ecosystems.md`.102103## Guardrails specific to this loop104105Inherits the full stack from `loop-controller` → `references/safety.md`. The caps106this loop sets:107108- **Poll / iteration cap** — `/loop`'s ~3-day session expiry is the outer bound;109 set an inner per-run pass cap (default ~20) so a tree full of advisories110 doesn't burn the window. Hitting it is a *stop-and-escalate*, not a license to111 loosen the proof.112- **No-progress detection** — if the **same advisory** survives **3 consecutive113 passes** (only fixable by a major, or the bump can't be made green), stop and114 surface it. Three passes on one advisory means it needs a human decision, not a115 fourth attempt.116- **Budget cap** — a watch loop firing every 30 minutes adds up; enforce a117 token/cost ceiling that *terminates* the loop (read from `.claude/profile.yaml`118 when present), not just warns.119- **Never cheat the proof.** Forbidden, each a *finding*: pinning around an120 advisory with an audit-ignore/allowlist entry instead of fixing it, downgrading121 the policy severity to clear the count, suppressing a deprecation rather than122 resolving it, or merging to make the audit moot. A clean audit that came from123 silencing the auditor is not health.124125## How this differs from its neighbors126127This loop is **not** a one-shot dependency update, and it is not a re-skin of an128existing skill — it draws three explicit boundaries:129130- **vs a one-shot `npm update` / dependabot bump:** those apply versions; they do131 not *prove* health. This loop's substance is the **loop discipline** — a132 default-FAIL proof that fuses audit + freshness + a green gate, one bump per133 pass with full re-verification, no-progress detection, scheduling, and an HITL134 gate on majors. The bump is the easy part; the proof and the boundary are the135 product.136- **vs [`security-agent`]:** the security role authors the **policy** — which137 severities block, which advisories are accepted, the SBOM/audit expectations.138 This loop **consumes** that policy as its proof threshold and *acts on a139 cadence*; it does not define security posture. On a vuln it can't resolve140 within the reversible boundary, it escalates to the human (and, under an141 orchestrated build, to the security role).142- **vs [`infrastructure-agent`]:** infra owns the dependency manifests and143 lockfiles as files. This loop proposes single, gated bumps **as PRs** for the144 owner to merge — it never force-merges into infra-owned files, and majors route145 to a human exactly because they may change runtime/build semantics infra owns.146- **vs [`fix-until-green`]:** that loop's proof is three exit codes. This loop's147 proof *contains* a green gate but is strictly larger (audit + freshness), and148 it **invokes** `fix-until-green` to verify each bump rather than re-implementing149 the gate.150151## Choosing the driver primitive152153Per `loop-controller` Step 1, this is a **watch/poll** job — you wait for the154dependency tree and advisory feed to *change* on a cadence — so the primitive is155**`/loop`** (sprint cadence) or **`/schedule`** (a longer, e.g. nightly,156cadence), **not** `/goal` (which pushes to a finish line). The daily-driver157recipe:158159```160/loop 30m /dependency-health-loop161```162163The *per-pass* exit (this pass made one clean, reversible, gated bump and164re-audited) is provable from the audit report + gate exit codes, so a pass can165run unattended within the HITL boundary above. `/loop`'s session-scope, ~3-day166expiry, and no-catch-up mechanics live in `loop-controller`'s167`references/primitives.md`; don't re-document them.168169## Reference files170171- `references/ecosystems.md` — per-ecosystem audit + freshness + update commands172 (npm/pnpm/yarn, pip/poetry/uv, cargo, go, bundler, composer), the173 major-vs-minor HITL policy, the lockfile-safety rules (what's a clean bump vs a174 semantics change), and the AFK-safe vs pause table.175176[`loop-controller`]: ../loop-controller/SKILL.md177[`fix-until-green`]: ../fix-until-green/SKILL.md178[`security-agent`]: ../../roles/security-agent/SKILL.md179[`infrastructure-agent`]: ../../roles/infrastructure-agent/SKILL.md180[`git-pr`]: ../../git/git-pr/SKILL.md