Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agent/skills/uv-deps
90 changes: 62 additions & 28 deletions .agents/skills/js-deps/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
---
name: js-deps
description: >
Maintain JavaScript/Node.js packages through security audits or dependency updates on a dedicated branch.
Supports npm, yarn, pnpm, and bun. Use for: security audits, CVE fixes, vulnerability checks, dependency updates,
package upgrades, outdated packages, bump versions, fix npm vulnerabilities, modernize node_modules, or when user
types "/js-deps" with or without specific package names or glob patterns. Use "help" or "--help" to show options.
description: Maintain JavaScript/Node.js packages through security audits or dependency updates using an isolated git worktree. Supports npm, yarn, pnpm, and bun. Use for security audits, CVE fixes, vulnerability checks, dependency updates, package upgrades, outdated packages, bump versions, fix npm vulnerabilities, modernize node_modules, or when user types "/js-deps" with or without specific package names or glob patterns.
license: MIT
compatibility: Requires git, a JavaScript package manager (npm, yarn, pnpm, or bun), and network access to package registries
metadata:
author: Gregory Murray
repository: github.com/whatifwedigdeeper/agent-skills
version: "0.4"
version: "0.7"
---

# JS Deps
Expand All @@ -19,26 +15,38 @@ metadata:

Specific package names (e.g. `jest @types/jest`), `.` for all packages, or glob patterns (e.g. `@testing-library/*`).

If `$ARGUMENTS` is `help`, `--help`, `-h`, or `?`, skip the workflow and read [references/options.md](references/options.md).
If `$ARGUMENTS` is `help`, `--help`, `-h`, or `?`, skip the workflow and read [references/interactive-help.md](references/interactive-help.md).

## Workflow Selection

Based on user request:
- **Security audit** (audit, CVE, vulnerabilities, security): Read [references/audit-workflow.md](references/audit-workflow.md)
- **Dependency updates** (update, upgrade, latest, modernize): Read [references/update-workflow.md](references/update-workflow.md)
- **Ambiguous** (no clear intent, or invoked with no args and no context): Read [references/interactive-help.md](references/interactive-help.md) to present the interactive help flow.

If the user expresses version preferences (e.g., "only minor and patch", "skip major versions", "only critical CVEs"), apply the filters defined in [references/interactive-help.md](references/interactive-help.md) without requiring an explicit `--help` invocation.

## Shared Process

### 1. Create Branch
### 1. Create Worktree

Stash uncommitted changes and create a dedicated branch:
Create an isolated git worktree so the main working directory is never modified:
```bash
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BRANCH_NAME="js-deps-$TIMESTAMP"
git stash --include-untracked
git checkout -b "$BRANCH_NAME"
WORKTREE_PATH="${TMPDIR:-/tmp}/$BRANCH_NAME"
git worktree add "$WORKTREE_PATH" -b "$BRANCH_NAME"
```

If `git worktree add` fails (e.g., sandbox permission error), prompt the user:
> `git worktree` requires write access to `$TMPDIR`. Choose an option:
> 1. Add `$TMPDIR` to your sandbox allowlist in `settings.json` (recommended)
> 2. Fall back to branch+stash approach

**All subsequent steps operate within `$WORKTREE_PATH`.** Discovery, installs, edits, and commits all happen there. Paths like `cd <directory>` in reference files are relative to `$WORKTREE_PATH`.

`gh`, `git push`, and `git commit` require `dangerouslyDisableSandbox: true` (keyring access for auth).

### 2. Detect Package Manager

Detect from lock files and `package.json` `packageManager` field (which takes precedence). See [references/package-managers.md](references/package-managers.md) for detection logic and command mappings.
Expand All @@ -47,17 +55,21 @@ Detect from lock files and `package.json` `packageManager` field (which takes pr

Verify the package manager CLI is available and, for npm, that it can reach the registry. See [references/package-managers.md](references/package-managers.md) for manager-specific verification commands.

If verification fails, prompt user: "Cannot reach package registry. Sandbox may be blocking network access. To allow package manager commands in sandbox mode, update settings.json."
If verification fails, prompt user with a message appropriate to what was checked:
- **npm or pnpm** (registry connectivity verified): "Cannot reach package registry. Sandbox may be blocking network access. To allow package manager commands in sandbox mode, update settings.json."
- **yarn or bun** (CLI availability only): "Package manager CLI not found or not executable. Ensure yarn/bun is installed and available in PATH."

Do not proceed until verification passes.

### 4. Discover Package Locations

Find all `package.json` files excluding `node_modules`. Store results as an array of directories to process.
Find all `package.json` files within `$WORKTREE_PATH` excluding `node_modules`, `dist`, `.cache`, `coverage`, `.next`, and `.nuxt` directories. Store results as an array of directories to process.

### 5. Install Dependencies

Install dependencies in each discovered package directory so that `npm outdated` (and similar commands) can accurately compare installed versions against the registry. Without `node_modules`, exact-pinned packages (no `^` or `~`) won't appear in outdated reports.
**Skip this step for security audit workflows** — `$PM audit` reads from lock files and does not require `node_modules`.

For dependency update workflows only: install dependencies so that `$PM outdated` can accurately compare installed vs. registry versions. Without `node_modules`, exact-pinned packages (no `^` or `~`) won't appear in outdated reports. If `$ARGUMENTS` specifies particular packages (not `.`), only install in directories where those packages appear in `package.json`. Check `dependencies`, `devDependencies`, `optionalDependencies`, and `peerDependencies` fields when scanning for package presence. For glob arguments, expand against all four fields before filtering directories.

### 6. Identify Packages

Expand All @@ -67,39 +79,61 @@ Install dependencies in each discovered package directory so that `npm outdated`

### 7. Validate Changes

Check `package.json` scripts for available validation commands. Run available scripts using `$PM run <script>` in order (build, lint, test), continuing on failure to collect all errors. Skip any that don't exist.
Run validation **per directory** after each package update. Check `package.json` scripts and run available commands using `$PM run <script>` in order: build, lint, test. Skip any that don't exist.

**For audit workflows only:** if `node_modules` does not exist in the directory being validated (step 5 skips installation for audits), run `$PM install` before executing validation scripts. This is a validation-only install and does not affect the audit results already collected.

If validation fails, revert the failing package to its previous version before continuing with remaining packages:
- **Build failure** is a hard failure: revert the package before continuing.
- **Lint or test failure** is a soft failure: report it but continue with remaining packages.

Continue running all validators even on failure to collect the full error set before reporting.

If a build fails for a specific package, revert before continuing with remaining packages:
```bash
git checkout -- package.json package-lock.json # or the equivalent lock file
$PM install
# Replace <directory> with the actual path relative to $WORKTREE_PATH
DIR="$WORKTREE_PATH/<directory>"
# Revert only the dependency manifest and lock file — not the entire directory
git checkout -- "$DIR/package.json"
# Revert the lock file for the detected package manager:
git checkout -- "$DIR/package-lock.json" 2>/dev/null || \
git checkout -- "$DIR/yarn.lock" 2>/dev/null || \
git checkout -- "$DIR/pnpm-lock.yaml" 2>/dev/null || \
git checkout -- "$DIR/bun.lock" 2>/dev/null || \
git checkout -- "$DIR/bun.lockb" 2>/dev/null || true
cd "$DIR" && $PM install
```

### 8. Update Documentation for Major Version Changes

For major version upgrades (e.g., 18.x to 19.x):

1. Search for version references in markdown files
2. Update in: `CLAUDE.md`, `README.md`, `docs/*.md`
1. Search for version references using patterns like `grep -r "v<old-major>" --include="*.md"` across `CLAUDE.md`, `README.md`, `docs/*.md`
2. Also check the `engines` field in `package.json` (e.g., `"engines": { "node": ">=18" }`) and update if needed
3. Include changes in report/PR description

### 9. Cleanup
### 9. Commit, Push, and PR

Handled by the reference workflow. See the **On Success** section of [references/audit-workflow.md](references/audit-workflow.md) or [references/update-workflow.md](references/update-workflow.md) for commit message format, push command, and PR creation steps.

If a PR was created, do not delete the branch — it's needed for the open PR.
### 10. Cleanup

Remove the worktree. The main working directory was never modified, so no stash restore is needed.

```bash
git checkout -
git stash list | grep -q . && git stash pop
# Only delete branch if no PR was created
if ! gh pr view "$BRANCH_NAME" --json url > /dev/null 2>&1; then
git branch -d "$BRANCH_NAME"
git worktree remove "$WORKTREE_PATH" --force
# Only delete branch if no PR was created (requires dangerouslyDisableSandbox: true)
if [ -z "$(gh pr list --head "$BRANCH_NAME" --json url --jq '.[0].url' 2>/dev/null)" ]; then
git branch -D "$BRANCH_NAME"
fi
```

`--force` handles cases where the skill failed mid-run with uncommitted changes in the worktree.

## Edge Cases

- **Glob matches nothing**: Warn and list available packages
- **Unsupported package manager**: Prompt user for guidance
- **Peer dep conflicts after major upgrades**: When a plugin doesn't declare support for the new major version of its host (e.g., `eslint-plugin-react-hooks` not supporting eslint 10), add `"overrides"` to `package.json` rather than using `--legacy-peer-deps`. Example: `"overrides": { "eslint-plugin-react-hooks": { "eslint": "$eslint" } }`. The `$eslint` syntax references the version already declared in the package's own dependencies
- **Peer dep conflicts after major upgrades**: When a plugin doesn't declare support for the new major version of its host (e.g., `eslint-plugin-react-hooks` not supporting eslint 10), add an override rather than using `--legacy-peer-deps`. The field name and syntax differ by package manager: npm/bun use `"overrides": { "eslint-plugin-react-hooks": { "eslint": "$eslint" } }` (the `$<pkg>` shorthand is npm-specific); yarn uses `"resolutions"`; pnpm uses `"pnpm": { "overrides": ... }`
- **Lockfile sync**: After all package.json changes, run `$PM install` in every modified directory and commit lockfiles — CI tools like `npm ci` require exact sync between package.json and the lockfile
- **Verify devDependencies placement**: After bulk installs across directories, verify that linting/testing/build packages (eslint, typescript, vite, etc.) ended up in `devDependencies`, not `dependencies` — easy to misplace when running install commands across many directories
- **Monorepo workspace root**: If a discovered `package.json` has a `workspaces` field but no `dependencies` or `devDependencies`, it is a workspace root acting only as an orchestrator. Run `$PM audit` or `$PM outdated` from the root (which covers all workspaces) rather than processing member directories individually. For npm 7+, use `npm audit --workspaces` and `npm install --workspaces` to operate on all workspaces at once.
94 changes: 54 additions & 40 deletions .agents/skills/js-deps/references/audit-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@ Use the detected `$PM` package manager for all commands. See [package-managers.m

For each directory containing package.json:
```bash
cd <directory>
$PM audit --json > audit-report-<dir-name>.json
cd "$WORKTREE_PATH/<directory>"
AUDIT_JSON=$($PM audit --json 2>/dev/null)
# Write to temp file if you need to inspect: echo "$AUDIT_JSON" > "$TMPDIR/audit-report-<dir-name>.json"
```

Note: bun does not support audit. If using bun, skip audit and inform user.

**Note:** Installation (SKILL.md step 5) is not required before running `$PM audit` — the audit reads from lock files and package.json, not `node_modules`.

**Note for npm monorepos:** If the root `package.json` has a `workspaces` field, run `npm audit --workspaces` from the root instead of auditing member directories individually.

Collect all audit results into a consolidated report.

### Scope to Requested Packages

If `$ARGUMENTS` contains specific package names or glob patterns (not `.` or empty), filter the vulnerability list to only those packages before applying fixes.

### Categorize by Severity

Parse audit results for each directory:
Expand All @@ -29,67 +38,72 @@ Parse audit results for each directory:

### Determine Strategy

Per directory:
- **1-3 packages**: Update sequentially
- **4+ packages**: Use parallel Task subagents (2 packages per agent)
Always update packages **sequentially within a directory** to avoid lock file races — concurrent installs in the same directory will corrupt `package-lock.json` (or equivalent).

If multiple directories have vulnerabilities, process them in parallel using separate agents.
Parallelize **across directories** only: if multiple directories have vulnerabilities, launch a separate Task subagent (general-purpose, background) per directory. Each subagent handles package updates and validation for its directory only — **do not commit from subagents**. The main agent commits all changes after all subagents complete.

When consolidating results:
- Collect vulnerability counts (before/after), packages fixed, and validation results from each subagent
- Merge into a single report; if any subagent fails, still include partial results from the others
- If a subagent fails to fix a package, document it in the PR as a partial fix

### Update Packages

For each vulnerable package in each directory, use the appropriate install command from [package-managers.md](package-managers.md):
#### Preferred: Use audit fix (npm and pnpm 8+)

For npm, try automated fix first:
```bash
cd <directory>
$PM install <package>@latest # npm
$PM add <package>@latest # yarn, pnpm, bun
cd "$WORKTREE_PATH/<directory>"
npm audit fix
```
This handles transitive dependency chains automatically. Only proceed to manual updates below if `npm audit fix` reports remaining vulnerabilities.

Validate after each update per SKILL.md step 7.

### Post-Audit Scan
> **Note:** `npm audit fix` fixes all vulnerabilities regardless of `$ARGUMENTS` scope. If specific packages were requested, verify afterwards that only those packages were modified and revert any unintended changes using the revert block in SKILL.md step 7.

For each directory:
For pnpm 8+, automated fix is also available:
```bash
cd <directory>
$PM audit
cd "$WORKTREE_PATH/<directory>"
pnpm audit --fix
```

Compare before/after vulnerability counts per directory.

## Parallel Execution
For yarn and older pnpm, proceed directly to manual updates below.

### Per-Directory Parallelization
#### Fallback: Manual Updates

When multiple directories have vulnerabilities, launch separate Task subagents for each:

```
Task({
subagent_type: 'general-purpose',
prompt: 'Audit and fix vulnerabilities in <directory>...',
run_in_background: true
})
For each vulnerable package in each directory, use the appropriate install command from [package-managers.md](package-managers.md):
```bash
cd "$WORKTREE_PATH/<directory>"
# npm:
npm install <package>@<patched-version>
# yarn, pnpm, bun:
$PM add <package>@<patched-version>
```

### Per-Package Parallelization
Use the minimum patched version from the audit report's `fixAvailable.version` field. Only fall back to `@latest` if the audit report explicitly recommends it as the fix.

Within a directory with >3 vulnerable packages, split into groups:
Validate after each update per SKILL.md step 7.

```
Task({
subagent_type: 'general-purpose',
prompt: 'Update packages X, Y in <directory> with full validation...',
run_in_background: true
})
### Post-Audit Scan

For each directory:
```bash
cd "$WORKTREE_PATH/<directory>"
$PM audit
```

Collect results from all agents before generating final report.
Compare before/after vulnerability counts per directory.

## Handle Results

### On Success

1. Generate consolidated security report
2. Create commit with security fixes
2. Commit changes:
```bash
git -C "$WORKTREE_PATH" add -A
git -C "$WORKTREE_PATH" commit -m "fix: resolve security vulnerabilities"
# If commit fails due to GPG signing, retry with --no-gpg-sign
```
3. Push branch to remote:
```bash
git push -u origin "$BRANCH_NAME"
Expand All @@ -115,7 +129,7 @@ Collect results from all agents before generating final report.

Generated with [Claude Code](https://claude.com/claude-code)
PREOF
gh pr create --title "fix: Security audit fixes" --body-file "$BODY_FILE"
gh pr create --title "fix: resolve security vulnerabilities" --body-file "$BODY_FILE"
rm -f "$BODY_FILE"
```
5. Return the PR URL to the user
Expand All @@ -130,7 +144,7 @@ Collect results from all agents before generating final report.

When transitive dependencies have vulnerabilities that no direct dependency update can resolve:

1. Check if the project uses `audit-ci` or similar CI audit tools (look for `.auditconfig.json` or audit scripts in `package.json`)
1. Check if the project uses `audit-ci` or similar CI audit tools (look for `audit-ci.json`, `audit-ci.jsonc`, or audit scripts in `package.json`)
2. If so, add the advisory ID (e.g., `GHSA-xxxx-xxxx-xxxx`) to the `allowlist` array in each package's audit config
3. Document the upstream blockers in the PR description — list which packages hold the vulnerable transitive dependency and why no fix is available
4. Include the allowlist change in the same commit/PR as the fixable updates
Loading