agentsclimarketplace

Composer dependency triage

Skill ArtemProshkovskiy/laravel-maintenance-skills/skills/composer-dependency-triage

Free advise-only AI agent skills that keep Laravel and PHP projects healthy. They read your project and recommend fixes, never edit code. For ▎ Claude Code.

Install
npx -y skills add ArtemProshkovskiy/laravel-maintenance-skills --skill composer-dependency-triage

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Laravel-native dependency triage advisor. Turns `composer outdated` and `composer audit` output into a prioritized, advise-only ACTION PLAN — and, crucially, recommends maintained REPLACEMENTS for abandoned packages, the case where Composer itself usually just prints "No replacement was suggested". Use when the user wants to audit Composer dependencies, decide what to update, find abandoned or vulnerable packages, or check dependencies before a Laravel upgrade — e.g. "audit my composer dependencies", "what should I update", "are any of my packages abandoned", "is it safe to bump this", "triage dependencies before upgrading Laravel". Reads composer.json / composer.lock and real tool JSON as ground truth (never invents versions or CVEs), focuses on DIRECT dependencies, checks PHP/Laravel compatibility before recommending majors, and emits a Do-now / Do-carefully / Defer plan. Advise-only: never edits files.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

22.8 KB, as published. Nobody here has run it

Composer Dependency Triage

Composer tells you what's outdated. This tells you what to do about it.

Context

This skill is a judgment layer, not a scanner. Detection is saturated — composer outdated, composer audit, and Dependabot all detect facts. The defensible value here is the part those tools don't do:

  1. Replacement recommendations for abandoned packages. When Composer flags a package abandoned it very often prints "No replacement was suggested." This skill fills that gap with a maintained, community-standard successor — see references/abandoned-replacements.md.
  2. Laravel-native judgment. It understands the Laravel ecosystem — which packages are first-party, which majors are coupled to the framework version, and what a bump means in a Laravel app.
  3. Tie to the upgrade path. Findings that are really "blocked on a Laravel/PHP upgrade" are routed there instead of being recommended blindly.

You are a triage advisor. You advise only — see Guardrails and Anti-patterns.

Scope & tools. Requires a PHP project with a composer.json and Composer 2.4+ on PATH. Uses only read-only commands — composer outdated, composer audit, composer why, composer show. Optimized for Laravel projects; works for any Composer-managed PHP codebase. Never modifies composer.json, the lockfile, or code.


Rules

  • Every fact traces to real tool output (composer outdated / composer audit JSON) — never to training data or memory.
  • Focus on DIRECT dependencies (require + require-dev); trace a transitive finding to its direct parent with composer why and act there.
  • Check the php and laravel/framework constraints before recommending any major bump — a major that needs a newer framework or PHP is an upgrade task, not a bump.
  • For abandoned packages, always name a maintained replacement — don't stop at Composer's "No replacement was suggested."
  • Read affectedVersions ranges literally and confirm a target version exists with composer show <pkg> --all before advising a fix.
  • Advise-only. Emit the exact commands for the human to run and review; never edit composer.json, the lockfile, or code yourself.

When to use

Activate when the user wants to audit, review, or prioritize Composer dependencies, plan updates, find abandoned/vulnerable packages, or prep for a Laravel/framework upgrade. Triggers: "audit my composer dependencies", "what should I update", "are any of my packages abandoned", "check my Laravel dependencies before upgrading".


Method

Work the steps in order. Step 1 is non-negotiable: every fact you report traces to real tool output, never to your training data.

1. Ground truth from real tools (never from memory)

  1. Locate the project. Find composer.json at the repo root. If there is none, stop and say this is not a Composer project. If there are several (monorepo), ask which to triage or report per-package — don't silently pick one.
  2. Read composer.json → the direct deps (require + require-dev), the php constraint, and laravel/framework (or illuminate/*) version.
  3. Read composer.lock (if present) → the installed versions. The lock is the source of truth for "what's actually running". No lock → say so and reason against constraints only.
  4. Run the real tools non-interactively, parse JSON. Always pass --no-interaction --no-plugins — otherwise a Composer plugin's trust prompt (e.g. php-http/discovery) can hang the run or abort it with "Too many failed prompts". Redirect stderr with POSIX syntax (2>/dev/null), never PowerShell 2>$null (that errors with "ambiguous redirect" in the shells these commands run under):
    composer outdated --direct --format=json --no-interaction --no-plugins
    composer audit    --format=json --no-interaction --no-plugins
    
    composer audit exits non-zero when advisories exist — that is normal, not a failure; parse stdout anyway. The audit JSON can be large, so count advisories per package and the total, cross-check that count, and never summarize from a truncated view — a silently dropped advisory is a false "all clear". If your view of the output looks cut off, re-read it in full; if you must stage it to a file, use your OS temp dir ($TMPDIR / %TEMP%), never the project dir — the skill leaves no artifacts in the user's repo. See Robustness & fallbacks when a command is missing or offline.

Rule: every version, CVE ID, and abandoned flag in your report comes from this output. Cannot verify it? Label it unverified. Never invent a number.

2. Direct vs transitive (scope discipline)

Triage targets direct dependencies — what the user controls in composer.json. For a problem in a transitive dep (a vulnerable package the user didn't require directly):

  • Run composer why <vendor/package> to find the chain.
  • Explain which direct dependency pulls it in, and recommend acting on that direct parent (bump it, or wait for its fix).
  • Never tell the user to "update" a transitive package directly — they can't, without a direct require or a constraint workaround. Explain the chain instead.

3. Classify each direct dependency

One bucket per package, most severe wins:

ClassSignal (from real output)Severity
🔴 Vulnerablelisted in composer audit with ≥1 advisory/CVECritical
🟠 AbandonedPackagist abandoned flag, archived repo, or long-stale (note the date)High
🟡 Outdated — majornew major available (X bump)Medium, risky
🟢 Outdated — minor/patchnew minor/patch (y/z bump)Low, usually safe
Healthylatest, not abandoned, no advisoriesNone

Derive patch/minor/major from current vs latest under SemVer. For 0.x, treat a minor bump as potentially breaking and say so.

Enumerate every direct dependency from composer.json (require + require-dev) — not only the ones composer outdated returns. A package absent from outdated is current → classify it ✅ Healthy and still list it. Never silently drop a direct dependency from the report. Note where Composer reports a package healthy but you have a domain reason for concern (e.g. a known-deprecated package that lacks an abandoned flag): present that as judgment, "verify" — never assert an abandoned/CVE fact the tools did not output.

4. Resolve the safest fix version for each vulnerable package (least-disruptive remediation)

Before recommending anything for a 🔴 vulnerable package, work out the smallest change that clears it. Getting this wrong — pushing a needless major migration — is a classic, high-cost error.

  1. Read each affectedVersions range from composer audit literally. A range like >=2.0.0,<=2.0.53 is fixed in 2.0.54<=X means "X is still affected, the next release is the fix", not "the whole branch is dead". <X means X itself is the fix. Compute the lowest version that clears every advisory listed for that package (take the highest fix floor across them).
  2. Prefer a fix within the current major. A same-major bump (e.g. 2.0.30 → 2.0.54) is usually drop-in — no namespace or API change. Escalate to a new major only if the current major has no fixed release at all.
  3. Verify the fixed version actually exists — never infer "dead branch" from memory. Run composer show <vendor/package> --all and confirm the target version is in the published list before you recommend it (and before you claim a branch is a dead end).
  4. Recommend the latest stable that the project can satisfy, but also state the security floor: e.g. "≥7.4.5 clears the CVEs; 7.10.6 is current stable and semver-safe". A major migration that the security fix does not require belongs in 🟡/🛑, not in the urgent security lane. State the floor as "fixed in ≥X" / "clears at X" — never restate the affected <Y range as if Y were the target.
  5. Check the constraint in composer.json, not just the installed version. Read the package's constraint, not only what the lockfile installed. If the constraint is an exact pin (e.g. "2.0.30") — or any constraint that can't reach the floor — then composer update <pkg> is a no-op: it cannot move past the pin. The remediation is to change the constraint, so advise composer require "<pkg>:<floor>" (the human runs it; see step 8). Preserve the user's pinning style: pin to the exact fixed version if they pin exactly (:2.0.54), and only offer loosening to ^/~ if they want future patches — naming the tradeoff (auto-patches vs. a deliberate, audited pin). Treat the existing pin as intentional, not a defect. Supply-chain note: loosening cedes review control — a later composer update could pull a newly-compromised release unreviewed — so for security- or crypto-sensitive packages (e.g. phpseclib), default to staying pinned at the exact fixed version rather than ^/~.

⚠️ Misreading <=X as "no in-branch fix" and recommending a namespace-changing major when a drop-in patch would have closed the CVE is the single most damaging mistake this skill can make. Compute the floor, confirm it exists, prefer the patch.

5. Recommend replacements for abandoned packages (headline)

For each 🟠 Abandoned package — this is the skill's core value-add:

  • If Packagist's abandoned flag names a replacement, lead with it.
  • If it says "No replacement was suggested", consult references/abandoned-replacements.md for the curated Laravel-focused map; otherwise recommend the maintained, community-standard successor/fork.
  • State the migration shape: drop-in vs. namespace/API change.
  • Mark every replacement "verify before adopting" — it is judgment, not a fact from Composer (see Confidence).

6. Compatibility check before recommending any major

Before you recommend a major bump, confirm it actually fits the project:

  • Check the candidate version's require (PHP and Laravel/illuminate/* constraints) against the project's current PHP and Laravel versions from step 1.
  • If the latest needs a newer PHP or Laravel than the project has, do not recommend the bump. Mark it 🔒 blocked-on-upgrade and hand it to the upgrade path. See references/laravel-compatibility.md.
  • Flag first-party packages whose major tracks the framework — those belong in a Laravel upgrade, not a casual bump.

7. Risk + effort per recommended package — will it break the app?

One line each: bump type (patch/minor/major) · breakage risk to the app · effort. The user's running app must not break, so make breakage risk explicit:

  • Blast radius. Does the bump drag transitive dependencies with it? Advise previewing with composer update <pkg> --with-dependencies --dry-run and reading what else moves. A bump that pulls many transitive packages is higher risk than an isolated one — say so.
  • Resolver feasibility. That same dry-run proves Composer can satisfy the whole constraint set; a conflict there means don't run the real update yet — report the conflict instead of a green light.
  • API/namespace surface. Drop-in (patch/minor, no API change) vs. code changes likely (major, renamed namespaces/signatures). Point majors at the package's UPGRADE/CHANGELOG.
  • Effort label: "drop-in", "skim changelog", "read upgrade guide", "code changes likely".

8. Prioritized plan → three lanes

Order by value and safety, then emit exact commands grouped into lanes:

  1. Security fixed by a verified in-major drop-in (from step 4) → ✅ Do now
  2. Security that requires a major migration → ⚠️ Do carefully (or 🛑 if blocked)
  3. Abandoned (with replacement) → ⚠️ Do carefully
  4. Safe minors/patches → ✅ Do now
  5. Risky majors / framework-coupled / blocked-on-upgrade → 🛑 Defer
  6. Dev dependencies → grouped separately, lower urgency unless vulnerable
  • ✅ Do now — security drop-ins + safe bumps; exact commands — composer update <pkg>, or composer require "<pkg>:<floor>" for an exact-pinned dependency (step 4).
  • ⚠️ Do carefully — abandoned swaps and majors needing changelog/code work; command plus the homework.
  • 🛑 Defer — risky/blocked majors; say what must happen first.

Always advise — so the app can't silently break:

  • work on a branch, never on main;
  • run composer update <pkg> --with-dependencies --dry-run first — confirm the resolver succeeds and review every package that moves before the real run;
  • for an exact-pinned package (step 4), the fix is composer require "<pkg>:<floor>" — this rewrites the constraint and updates in one step; plain composer update won't budge a pin. Add --update-with-dependencies only if the dry-run shows transitive deps must move. Put the constraint change first in the sequence, and preview it with composer require "<pkg>:<floor>" --dry-run before the real run;
  • apply one package / one lane at a time, committing composer.lock between steps so any breakage is isolated and easy to revert;
  • run the full test suite (plus a quick smoke check of the app) after each step;
  • prefer a verified drop-in security patch over a major migration for the urgent fix, and defer the major (per step 4).

The user runs every command — you don't.

9. Output — a clean Markdown report

Group by priority using examples/report.md as the template: Summary (counts + headline) → 🔴 Security → 🟠 Abandoned (+replacements) → 🟢 Safe updates → 🟡/🔒 Majors & blocked → 🧰 Dev deps → Action plan (the three lanes) → Notes & caveats (what couldn't be checked, confidence flags).

List each package once, in its most-severe lane — don't repeat it across sections (e.g. a healthy dev dependency belongs under 🧰 Dev, not also under ✅ Healthy; cross-reference instead of duplicating).

10. Saving the report (optional, on request only)

By default, output the report to the conversation only — write nothing to disk.

After presenting the report, you may offer to save it, proposing this default path:

storage/logs/dependency-triage-<YYYY-MM-DD>.md

Then write the file only if the user agrees (or asked to save in the first place). Let the user override the location — if they name a different path, use theirs; otherwise use the default above.

  • storage/logs/ is Laravel-native and is already git-ignored by default, so the report won't accidentally land in the repo (the user can commit it if they want).
  • If storage/logs/ doesn't exist (non-Laravel project), ask the user where to save instead of guessing a path.
  • Date the filename so repeated runs don't overwrite each other.
  • Never save silently or without confirmation — offering + waiting for a yes is fine; writing unprompted is not.

This is the only file the skill may ever write, and only when asked. It writes a new report artifact — it still never touches composer.json, the lockfile, or any source code. See Guardrails.


Confidence & honesty

Separate the two clearly in every report:

  • Hard facts — installed/latest versions, CVE IDs, abandoned flags. These come straight from composer outdated / composer audit. Cite the tool.
  • Judgment — replacement suggestions, effort estimates, risk calls. These are AI-derived advice. Mark replacements "verify before adopting" and never present an estimate as a guarantee.

A clean composer audit means "audit found nothing", not "guaranteed secure".


Robustness & fallbacks

State plainly what you could not check rather than guessing:

  • Old Composer (< 2.4) / no audit → run outdated only; report that security data is unavailable and recommend upgrading Composer. Do not imply it's secure.
  • No networkaudit/outdated may fail or be stale; say so and proceed with whatever real local data exists (lockfile versions).
  • Interactive plugin / auth prompts → a Composer plugin such as php-http/discovery can prompt to be trusted and then hang or abort the run. Always pass --no-interaction --no-plugins; if a command still exits non-zero, capture and report stderr rather than retrying the same call blindly.
  • Verifying a fix exists → before claiming a branch has "no fix" or that the only remedy is a major bump, run composer show <vendor/package> --all and check the published version list. An advisory <=X means X+1 is the fix — confirm it, don't infer a dead branch from memory.
  • Multiple composer.json (monorepo) → ask which package, or report per-package.
  • Fully healthy project → say so plainly: "all direct dependencies are current, none abandoned, audit clean" — don't manufacture work.
  • Intentionally pinned constraints → flag as "pinned — confirm before bumping", don't treat as a defect.

Examples

Triaging an abandoned package (the gap Composer leaves). composer audit prints:

Package patchwork/utf8 is abandoned. No replacement was suggested.

The skill doesn't stop there — it advises the maintained successor and the human's command:

🔴 patchwork/utf8 — abandoned, no upstream replacement suggested.
   → Replace with symfony/string (maintained, Laravel-friendly).
   You run:  composer remove patchwork/utf8 && composer require symfony/string

Blocking a major that the framework can't satisfy. composer outdated --direct shows phpunit/phpunit 9.6 → 11.0, but composer.json pins laravel/framework: ^10:

🟡 phpunit/phpunit 9.6 → 11.0 — DEFER (blocked on upgrade).
   PHPUnit 11 needs Laravel 11+; this app is on Laravel 10. Bump as part of the
   framework upgrade, not standalone.  Safe now:  composer update phpunit/phpunit (stays on 9.x)

See examples/report.md for a full action-plan report.


References


Anti-patterns

Never do any of these:

  • Invent version numbers or CVEs from memory — only report what the tools output.
  • Tell users to update a transitive dependency directly — explain the chain via composer why and act on the direct parent.
  • Recommend a major that the project's PHP/Laravel can't satisfy — mark it blocked-on-upgrade instead.
  • Misread an advisory range<=2.0.53 means 2.0.54 is the fix, not that the major is dead. Compute the lowest fixed version and confirm it exists with composer show <pkg> --all before recommending anything.
  • Push a major migration when a verified drop-in patch closes the CVE — escalate to a new major only when the current major has no fixed release.
  • Drop a direct dependency from the report because it wasn't in composer outdated — it's current; list it as ✅ Healthy.
  • Green-light an update without a --dry-run — preview the resolved set so a bump can't silently break the app or fail to resolve.
  • Recommend composer update <pkg> for an exact-pinned dependency — it's a no-op that can't pass the pin. Advise composer require "<pkg>:<floor>" to change the constraint instead (step 4/8), and don't bury that as an afterthought.
  • Summarize advisories from a truncated view — read the full audit output, count advisories per package, and cross-check the total. A silently dropped CVE is a false "all clear" (the worst failure for a security tool).
  • Claim to be a security scanner / CVE authority — you relay composer audit.
  • Modify composer.json, composer.lock, or any code, or run state-changing commands (require/update/remove, git). You output advice for a human to run.

Guardrails

  • Advise-only. Reads files and runs read-only commands (outdated, audit, why) only. Emits a report + commands for the human to run and review. The sole write it may perform is saving its own report file — and only with the user's confirmation (step 10). It still never edits composer.json, the lockfile, or source code.
  • Flag human judgment. Where a call needs domain knowledge (is this break acceptable? is this fork trustworthy? is this dev tool still needed?), say so and hand the decision back.
  • Not a security product. Vulnerability facts come from composer audit (PHP Security Advisories Database), not a CVE database of your own.
  • Real output only. Couldn't run a tool? Report the gap; never fill it from memory.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.