deps-update
Bump outdated Hex deps — inventory, snapshot changelogs, update, fix breaks, split reviewable PRs (patches bundled, majors solo). Use to upgrade/bump Elixir dependencies or when versions fall behind. NOT for deps.get failures (/phx:investigate).
Install
npx skills add https://github.com/oliver-kriska/claude-elixir-phoenix/tree/main/plugins/elixir-phoenix/skills/deps-update
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install oliver-kriska-claude-elixir-phoenix@llmmart
git clone https://github.com/oliver-kriska/claude-elixir-phoenix.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole oliver-kriska/claude-elixir-phoenix collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Dependency Update (Freshness)
Inventory → update → fix breaks → grouped PRs. This is the only MUTATING
deps skill: it edits mix.exs, mix.lock, and source. Security scanning
stays in /phx:deps-audit; the vet ledger stays in /phx:deps-vet.
Usage
/phx:deps-update # inventory + interactive scope pick
/phx:deps-update --scope patch # bundle all patch bumps, one PR
/phx:deps-update --pkg phoenix_live_view # one package (+ coupled group)
/phx:deps-update --dry-run # inventory only, no changes
Iron Laws
- NEVER cross a major version without an explicit
mix.exsedit —mix deps.updatestays within requirements. Edit the constraint first; addoverride: trueonly whenmix hex.outdated <pkg>shows a transitive consumer blocking. One major per PR - ALWAYS snapshot the changelog delta BEFORE updating — capture
deps/<pkg>/CHANGELOG.md, then delta viamix hex.package diff. Never update blind - NEVER claim an update is safe without verification — run
/phx:verify(compile --warnings-as-errors + test). "Compiles" ≠ "works" - ALWAYS move coupled packages together — Phoenix core, Ecto, Ash,
Oban, telemetry families update in the SAME step/commit (see
${CLAUDE_SKILL_DIR}/references/coupled-groups.md) - NEVER commit a partial bump —
mix.lock+mix.exsedits + (for Phoenix-family)assets/package-lock.jsonin ONE commit - HAND OFF security to
/phx:deps-audit— run it on the lock diff before any PR; don't reimplement audit rules hex.outdatedexit 1 is normal — it means "deps are outdated", not failure. Capture with|| true
Workflow
Phase 0: Discover
Read mix.exs: deps list, umbrella (apps_path:), git/path deps, private
orgs (organization:/repo: in tuples), Phoenix/Ash presence. Create
scratch dir .claude/deps-update/{YYYY-MM-DD}/.
Phase 1: Inventory
mix hex.outdated --all || true — parse the text table (no JSON exists;
see ${CLAUDE_SKILL_DIR}/references/update-mechanics.md). Classify each
row patch/minor/major by semver delta; Update not possible = blocked
major (mix.exs constraint). Write inventory.md to scratch. Render
grouped table: Patch / Minor / Major / Blocked / Git-deps (manual).
--dry-run stops here.
Phase 2: Scope (AskUserQuestion)
Present groups with counts and risk. Default recommendation: "Patches (N)
— low risk, bundle into one PR". --scope/--pkg flags skip the prompt.
When ≥2 members of a coupled group are outdated, force them into one step
even under a narrower scope.
Phase 3: Per-Package Update Loop
For each selected package, in coupled-group order:
- Snapshot
deps/<pkg>/CHANGELOG.md→scratch/before/ - Update — patch/minor:
mix deps.update <pkg> [coupled...]; major: editmix.exsconstraint (+override: trueif needed), thenmix deps.update <pkg> git diff mix.lock→ the REAL{pkg, old, new}set (hex.outdated says what could change; the lock diff says what did)- Changelog delta:
mix hex.package diff <pkg> <old>..<new>— keep the CHANGELOG hunk. Empty →gh api repos/{o}/{r}/releasesfallback → compare-URL note (see${CLAUDE_SKILL_DIR}/references/changelog-sources.md) - Write
scratch/{pkg}-{old}-{new}.md - Phoenix-family in the diff +
assets/package.jsonexists →npm install --prefix assets, stageassets/package-lock.jsonwith the same commit
Phase 4: Verify
Run /phx:verify. On failure → Phase 5; else Phase 6.
Phase 5: Breaking-Change Fixes
Read the changelog deltas for "breaking"/"removed"/"deprecated" + the compile/test errors. Fix source (apply the sibling-file check). Re-verify.
Phase 6: Security Handoff
Run /phx:deps-audit on the working mix.lock diff (its Mode B default).
BLOCK findings → surface and offer /phx:deps-vet <pkg> <ver> for
accepted risks. Never skip this before a PR.
Phase 7: Group, Commit, PR
Apply the splitting strategy (${CLAUDE_SKILL_DIR}/references/pr-strategy.md):
patches bundled, minors by area, majors solo, coupled groups always
together. PR bodies cite the changelog excerpt, the
https://diff.hex.pm/diff/<pkg>/<old>..<new> link, verification result,
and the deps-audit risk band. Stage lock + mix.exs + package-lock together.
Integration
/phx:deps-update (mutating) → /phx:deps-audit (security, Mode B)
│ │ BLOCK → /phx:deps-vet (ledger)
└→ /phx:verify (gate) → grouped commits / PRs
References
${CLAUDE_SKILL_DIR}/references/update-mechanics.md— hex.outdated parsing, update vs unlock+get, majors, lock-diff${CLAUDE_SKILL_DIR}/references/changelog-sources.md— hex.package diff, gh fallbacks, private orgs${CLAUDE_SKILL_DIR}/references/coupled-groups.md— must-move-together groups + edge cases${CLAUDE_SKILL_DIR}/references/pr-strategy.md— grouping rules, area buckets, PR template, scratch layout
Files (claude-elixir-phoenix)
-
references
-
changelog-sources.md 2 KB
# Changelog Delta Sources (priority order) ## 1. `mix hex.package diff <pkg> <v1>..<v2>` — PRIMARY Built into Hex. Fetches both tarballs, unpacks, runs `git diff --no-index`. The `CHANGELOG.md` hunk is the delta you want — no network parsing: ``` diff --git jason-1.4.4/CHANGELOG.md jason-1.4.5/CHANGELOG.md +## 1.4.5 (05.05.2026) +* Add support for Decimal 3.0 ``` Filter to the hunk whose path matches `CHANGELOG` (e.g. `awk '/^diff --git/{keep=/CHANGELOG/} keep'`). Works off the hex registry — no GitHub dependency. Network cost: two tarballs per package; for large `--scope all` runs, diff sequentially rather than in parallel. ## 2. `deps/<pkg>/CHANGELOG.md` — the BEFORE snapshot After `mix deps.get`, the currently-locked changelog is on disk. Snapshot it to scratch BEFORE updating (Iron Law 2). Most packages ship one, but it is NOT guaranteed — commercial/private packages often omit it. ## 3. GitHub releases fallback When the diff has no CHANGELOG hunk, derive `owner/repo` from `mix hex.info <pkg>`'s GitHub link: ```bash gh api repos/{owner}/{repo}/releases \ --jq '.[] | select(.tag_name | test("v?1\\.4\\.5$")) | .body' ``` No releases either → `gh api repos/{owner}/{repo}/tags` (names only) and link the compare URL: `github.com/{owner}/{repo}/compare/v{old}...v{new}`. ## 4. diff.hex.pm — for PR bodies only `https://diff.hex.pm/diff/<pkg>/<v1>..<v2>` renders the same diff as source 1. Use as a clickable link in PR bodies, never as a parse target. ## Private / organization packages Deps with `organization:`/`repo:` in the tuple need prior auth: `mix hex.organization auth <org>`. On a 401, tell the user to run that command themselves — never prompt for or store keys. Private packages may publish release notes outside hex.pm; accept a user-supplied URL and WebFetch the relevant section — no vendor names hardcoded. ## Nothing found Note in the per-package file: "no changelog available; review the diff at {diff.hex.pm URL}" — and lean on `/phx:deps-audit`'s diff scan for safety. -
coupled-groups.md 2.7 KB
# Coupled Package Groups + Edge Cases ## Must-move-together groups When ≥2 members of a group appear in the outdated set, force them into ONE update step + commit — even if the user picked a narrower scope. If only one member is outdated but a sibling pins it, `mix hex.outdated <pkg>` surfaces the constraint. | Group | Packages | Why | |-------|----------|-----| | Phoenix core | `phoenix`, `phoenix_html`, `phoenix_live_view`, `phoenix_live_dashboard`, `phoenix_ecto` | Shared protocols/JS; LV pins a phoenix range. Mismatch = compile/runtime errors. Triggers the JS-sync step | | LV satellites | `phoenix_live_view` + LV-component libs present (`live_select`, `salad_ui`, ...) | They pin an LV version range | | Ecto | `ecto`, `ecto_sql`, `postgrex` (+ `myxql`/`tds`) | `ecto_sql` pins `ecto`; the adapter pins `ecto_sql` | | Ash | `ash`, `ash_postgres`, `ash_phoenix`, `ash_sql`, `ash_oban`, `ash_authentication` | Tight inter-version pinning; bump as a set | | Telemetry | `telemetry`, `telemetry_metrics`, `telemetry_poller` | Shared core version | | OpenTelemetry | `opentelemetry`, `opentelemetry_api`, `opentelemetry_exporter`, instrumentation libs | API/SDK lockstep | | Oban | `oban`, `oban_pro`, `oban_web` | Pro/Web pin an `oban` range; Pro/Web are private (org auth + off-hex notes) | | Absinthe | `absinthe`, `absinthe_plug`, `absinthe_phoenix` | Plug/Phoenix pin core | | Asset installers | `tailwind`, `esbuild` | Installer bump may need a version bump in `config/config.exs` | ## Phoenix/JS coupling A Phoenix-family bump with `assets/package.json` present requires: `npm install --prefix assets` and staging `assets/package-lock.json` in the SAME commit as `mix.lock` (Iron Law 5). The JS packages track the hex versions (`file:../deps/phoenix` references). ## Edge cases | Case | Detection | Handling | |------|-----------|----------| | Umbrella | `apps_path:` in mix.exs | `mix hex.outdated` from root iterates `apps/*`; updates apply at the root lock; snapshot changelogs from root `deps/` | | Git deps | `git:` in tuple | Skipped by hex.outdated. Update via `mix deps.update <pkg>` (re-resolves ref); no hex changelog — link the git compare URL; flag "git dep — manual ref review" in inventory | | Path deps | `path:` in tuple | Local, never outdated — exclude from inventory | | Private orgs | `organization:`/`repo:` in tuple | Needs prior `mix hex.organization auth <org>`; on 401 tell the user, never handle keys | | Blocked major | Status `Update not possible` | Edit mix.exs constraint; `override: true` only if a transitive consumer blocks (per-package hex.outdated table); one per PR | | Greenfield (<10 .ex files) | file count | Skip area bucketing — bundle everything; coupled groups still apply | -
pr-strategy.md 2.1 KB
# PR Splitting Strategy + Scratch Layout ## Default grouping (override with `--pr-per`) | Update class | Default grouping | Rationale | |--------------|------------------|-----------| | Patch (x.y.Z) | ONE bundled PR ("Bump N patch deps") | Low risk; reviewer skims the lock diff | | Minor (x.Y.z) | Grouped by area | Back-compatible features; area grouping keeps review coherent | | Major (X.y.z) | ONE PR each, changelog excerpt in body | Breaking; each needs focused review and its own revert unit | | Coupled group | ONE PR for the whole group, regardless of class | Must move together (Iron Law 4) | `--pr-per area` (default) · `--pr-per major` (majors separate, all minors bundled) · `--pr-per none` (single branch, no split — solo repos). ## Area buckets (heuristic by package name) `web` (phoenix*, plug*, bandit, cowboy) · `data` (ecto*, postgrex, decimal) · `json` (jason, poison) · `test` (`:only` test — ex_machina, mox, wallaby) · `obs` (telemetry*, opentelemetry*) · `bg` (oban*) · `auth` (guardian, bcrypt*, argon2*) · `misc` (rest). ## PR body template Built from the scratch changelog deltas: ```markdown ## Dependency update: <pkg> <old> → <new> [<class>] <changelog delta excerpt — the CHANGELOG hunk from hex.package diff> - Full diff: https://diff.hex.pm/diff/<pkg>/<old>..<new> - Verification: mix compile + mix test PASS - Security: /phx:deps-audit risk band <band> ``` ## Commit discipline Per commit: `mix.lock` + any `mix.exs` edit + (Phoenix-family) `assets/package-lock.json` — never a lock alone (Iron Law 5). Stage specific files; never `git add -A`. ## Scratch layout `.claude/deps-update/{YYYY-MM-DD}/` — per-run, never committed: ``` inventory.md # parsed hex.outdated table, classified before/<pkg>-CHANGELOG.md # snapshot of the current changelog (pre-update) <pkg>-<old>-<new>.md # per-package changelog delta + notes lock-diff.patch # git diff mix.lock for the whole run pr-plan.md # grouping decisions + PR bodies ``` PR bodies are the only content that leaves the dir (into `gh pr create`). -
update-mechanics.md 2.5 KB
# Update Mechanics Verified against Hex 2.4.2 / Elixir 1.20 (2026-06). ## Parsing `mix hex.outdated` There is NO JSON output — parse the fixed-width text table (split on 2+ spaces), tolerating the trailing "Run `mix hex.outdated APP`..." footer: ``` Dependency Only Current Latest Status decimal 2.4.1 3.1.1 Update not possible jason 1.4.5 1.4.5 Up-to-date ``` - **Exit 1 = some dep is outdated. This is the NORMAL signal, not an error** — always `|| true`. `--within-requirements` flips exit semantics to in-range updates only. - `--all` includes transitive deps; `--pre` includes pre-releases; `--only <env>` filters by `:only`. - `Update not possible` = newer version exists but the `mix.exs` requirement blocks it — the blocked-major signal. Classify by semver delta Current→Latest: patch (x.y.Z), minor (x.Y.z), major (X.y.z). ## Why is a bump blocked? (per-package mode) ``` $ mix hex.outdated decimal There is newer version of the dependency available 3.1.1 > 2.4.1! Source Requirement Up-to-date mix.exs ~> 2.0 No jason ~> 1.0 or ~> 2.0 or ~> 3.0 Yes ``` This shows WHICH constraint to edit (`mix.exs`) AND whether transitive consumers already allow the new major. If a consumer row says `No`, you need `override: true` on the `mix.exs` dep tuple; if all consumers say `Yes`, a plain constraint edit suffices. ## Update commands | Goal | Command | |------|---------| | Named deps + their children, within requirements | `mix deps.update <pkg> [<pkg2>...]` | | Single dep, NO children | `mix deps.unlock <pkg> && mix deps.get` | | Everything (destructive) | `mix deps.update --all` | | Cross a major | Edit `mix.exs` constraint FIRST, then `mix deps.update <pkg>` | `mix deps.update` can never cross a constraint boundary — a blocked major always needs the `mix.exs` edit first. ## Reading the lock diff (authoritative result) `mix.lock` is a map literal, one line per package: ```elixir "jason": {:hex, :jason, "1.4.5", "<hash>", [:mix], [<deps>], "hexpm", "<hash>"}, ``` After every update step, `git diff mix.lock` and parse element 3 (the version string) of old vs new lines to build the real `{pkg, old, new}` set. `hex.outdated` says what COULD change; the lock diff says what DID — transitive bumps appear here that the inventory never listed. ## Release metadata - `mix hex.info <pkg>` — locked version, recent releases with dates, GitHub link (the source for `gh` fallbacks) - `mix hex.info <pkg> <version>` — release date, deps, publisher
-
-
SKILL.md 5.3 KB
--- name: deps-update description: Bump outdated Hex deps — inventory, snapshot changelogs, update, fix breaks, split reviewable PRs (patches bundled, majors solo). Use to upgrade/bump Elixir dependencies or when versions fall behind. NOT for deps.get failures (/phx:investigate). effort: high argument-hint: "[--scope patch|minor|major|all] [--pkg <name>...] [--pr-per major|area|none] [--dry-run]" --- # Dependency Update (Freshness) Inventory → update → fix breaks → grouped PRs. This is the only MUTATING deps skill: it edits `mix.exs`, `mix.lock`, and source. Security scanning stays in `/phx:deps-audit`; the vet ledger stays in `/phx:deps-vet`. ## Usage ``` /phx:deps-update # inventory + interactive scope pick /phx:deps-update --scope patch # bundle all patch bumps, one PR /phx:deps-update --pkg phoenix_live_view # one package (+ coupled group) /phx:deps-update --dry-run # inventory only, no changes ``` ## Iron Laws 1. **NEVER cross a major version without an explicit `mix.exs` edit** — `mix deps.update` stays within requirements. Edit the constraint first; add `override: true` only when `mix hex.outdated <pkg>` shows a transitive consumer blocking. One major per PR 2. **ALWAYS snapshot the changelog delta BEFORE updating** — capture `deps/<pkg>/CHANGELOG.md`, then delta via `mix hex.package diff`. Never update blind 3. **NEVER claim an update is safe without verification** — run `/phx:verify` (compile --warnings-as-errors + test). "Compiles" ≠ "works" 4. **ALWAYS move coupled packages together** — Phoenix core, Ecto, Ash, Oban, telemetry families update in the SAME step/commit (see `${CLAUDE_SKILL_DIR}/references/coupled-groups.md`) 5. **NEVER commit a partial bump** — `mix.lock` + `mix.exs` edits + (for Phoenix-family) `assets/package-lock.json` in ONE commit 6. **HAND OFF security to `/phx:deps-audit`** — run it on the lock diff before any PR; don't reimplement audit rules 7. **`hex.outdated` exit 1 is normal** — it means "deps are outdated", not failure. Capture with `|| true` ## Workflow ### Phase 0: Discover Read `mix.exs`: deps list, umbrella (`apps_path:`), git/path deps, private orgs (`organization:`/`repo:` in tuples), Phoenix/Ash presence. Create scratch dir `.claude/deps-update/{YYYY-MM-DD}/`. ### Phase 1: Inventory `mix hex.outdated --all || true` — parse the text table (no JSON exists; see `${CLAUDE_SKILL_DIR}/references/update-mechanics.md`). Classify each row patch/minor/major by semver delta; `Update not possible` = blocked major (mix.exs constraint). Write `inventory.md` to scratch. Render grouped table: Patch / Minor / Major / Blocked / Git-deps (manual). `--dry-run` stops here. ### Phase 2: Scope (AskUserQuestion) Present groups with counts and risk. Default recommendation: "Patches (N) — low risk, bundle into one PR". `--scope`/`--pkg` flags skip the prompt. When ≥2 members of a coupled group are outdated, force them into one step even under a narrower scope. ### Phase 3: Per-Package Update Loop For each selected package, in coupled-group order: 1. Snapshot `deps/<pkg>/CHANGELOG.md` → `scratch/before/` 2. Update — patch/minor: `mix deps.update <pkg> [coupled...]`; major: edit `mix.exs` constraint (+ `override: true` if needed), then `mix deps.update <pkg>` 3. `git diff mix.lock` → the REAL `{pkg, old, new}` set (hex.outdated says what could change; the lock diff says what did) 4. Changelog delta: `mix hex.package diff <pkg> <old>..<new>` — keep the CHANGELOG hunk. Empty → `gh api repos/{o}/{r}/releases` fallback → compare-URL note (see `${CLAUDE_SKILL_DIR}/references/changelog-sources.md`) 5. Write `scratch/{pkg}-{old}-{new}.md` 6. Phoenix-family in the diff + `assets/package.json` exists → `npm install --prefix assets`, stage `assets/package-lock.json` with the same commit ### Phase 4: Verify Run `/phx:verify`. On failure → Phase 5; else Phase 6. ### Phase 5: Breaking-Change Fixes Read the changelog deltas for "breaking"/"removed"/"deprecated" + the compile/test errors. Fix source (apply the sibling-file check). Re-verify. ### Phase 6: Security Handoff Run `/phx:deps-audit` on the working `mix.lock` diff (its Mode B default). BLOCK findings → surface and offer `/phx:deps-vet <pkg> <ver>` for accepted risks. Never skip this before a PR. ### Phase 7: Group, Commit, PR Apply the splitting strategy (`${CLAUDE_SKILL_DIR}/references/pr-strategy.md`): patches bundled, minors by area, majors solo, coupled groups always together. PR bodies cite the changelog excerpt, the `https://diff.hex.pm/diff/<pkg>/<old>..<new>` link, verification result, and the deps-audit risk band. Stage lock + mix.exs + package-lock together. ## Integration ```text /phx:deps-update (mutating) → /phx:deps-audit (security, Mode B) │ │ BLOCK → /phx:deps-vet (ledger) └→ /phx:verify (gate) → grouped commits / PRs ``` ## References - `${CLAUDE_SKILL_DIR}/references/update-mechanics.md` — hex.outdated parsing, update vs unlock+get, majors, lock-diff - `${CLAUDE_SKILL_DIR}/references/changelog-sources.md` — hex.package diff, gh fallbacks, private orgs - `${CLAUDE_SKILL_DIR}/references/coupled-groups.md` — must-move-together groups + edge cases - `${CLAUDE_SKILL_DIR}/references/pr-strategy.md` — grouping rules, area buckets, PR template, scratch layout
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.