# Composer Dependency Triage

> >-

- **Type:** Skill
- **Install:** `agentstack add skill-artemproshkovskiy-laravel-maintenance-skills-composer-dependency-triage`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [ArtemProshkovskiy](https://agentstack.voostack.com/s/artemproshkovskiy)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [ArtemProshkovskiy](https://github.com/ArtemProshkovskiy)
- **Source:** https://github.com/ArtemProshkovskiy/laravel-maintenance-skills/tree/main/skills/composer-dependency-triage
- **Website:** https://ArtemProshkovskiy.github.io/laravel-maintenance-skills/

## Install

```sh
agentstack add skill-artemproshkovskiy-laravel-maintenance-skills-composer-dependency-triage
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# 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`](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](#guardrails)
and [Anti-patterns](#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  --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):
   ```bash
   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](#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 ` 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:

| Class | Signal (from real output) | Severity |
|-------|---------------------------|----------|
| 🔴 **Vulnerable** | listed in `composer audit` with ≥1 advisory/CVE | Critical |
| 🟠 **Abandoned** | Packagist `abandoned` flag, archived repo, or long-stale (note the date) | High |
| 🟡 **Outdated — major** | new **major** available (X bump) | Medium, risky |
| 🟢 **Outdated — minor/patch** | new minor/patch (y/z bump) | Low, usually safe |
| ✅ **Healthy** | latest, not abandoned, no advisories | None |

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, --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 `` is a no-op**: it cannot move past the pin.
   The remediation is to **change the constraint**, so advise
   `composer require ":"` (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 ` 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`](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](#confidence--honesty)).

### 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`](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  --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 `, or `composer require ":"` 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  --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 ":"` — 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 ":" --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`](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-.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](#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 ( --all` and check
  the published version list. An advisory ` --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 ` for an exact-pinned dependency** — it's a
  no-op that can't pass the pin. Advise `composer require ":"` 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

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [ArtemProshkovskiy](https://github.com/ArtemProshkovskiy)
- **Source:** [ArtemProshkovskiy/laravel-maintenance-skills](https://github.com/ArtemProshkovskiy/laravel-maintenance-skills)
- **License:** MIT
- **Homepage:** https://ArtemProshkovskiy.github.io/laravel-maintenance-skills/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-artemproshkovskiy-laravel-maintenance-skills-composer-dependency-triage
- Seller: https://agentstack.voostack.com/s/artemproshkovskiy
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
