Claude Skill

phx-deps-update

Bump outdated Hex deps — inventory, snapshot changelogs, update, fix

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download oliver-kriska-claude-elixir-phoenix-targets_amp_skills_phx-deps-update-9767a82.zip · 7 KB
Part of oliver-kriska/claude-elixir-phoenix — 93 skills

Install

skills CLI npx skills add https://github.com/oliver-kriska/claude-elixir-phoenix/tree/main/targets/amp/skills/phx-deps-update
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install oliver-kriska-claude-elixir-phoenix@llmmart
Git 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

  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 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 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 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 (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

  • references/update-mechanics.md — hex.outdated parsing, update vs unlock+get, majors, lock-diff
  • references/changelog-sources.md — hex.package diff, gh fallbacks, private orgs
  • references/coupled-groups.md — must-move-together groups + edge cases
  • 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.1 KB
    ---
    name: phx-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).
    ---
    
    # 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
       `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 `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 `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 (`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
    
    - `references/update-mechanics.md` — hex.outdated parsing, update vs unlock+get, majors, lock-diff
    - `references/changelog-sources.md` — hex.package diff, gh fallbacks, private orgs
    - `references/coupled-groups.md` — must-move-together groups + edge cases
    - `references/pr-strategy.md` — grouping rules, area buckets, PR template, scratch layout
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related