node-deps-bumper
Use for dependency updates: bump npm/pnpm/yarn/bun packages, check outdated, or run taze.
Install
npx skills add https://github.com/PaulRBerg/agent-skills/tree/main/skills/node-deps-bumper
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install paulrberg-agent-skills@llmmart
git clone https://github.com/PaulRBerg/agent-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole paulrberg/agent-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Node Dependency Bumper
Use Taze to build one structured update plan, apply compatible ranged updates, and make major-version decisions as a batch.
Adding Dependencies
For a new package or CLI, use the repository's package manager and existing range convention instead of Taze. Installing
package@latest and retaining the package manager's resulting ^x.y.z range is allowed. Do not replace a caret range
with an exact version merely because it can admit future releases.
When an effective package-manager minimum-release-age policy exists, it provides the freshness boundary for candidate versions; read references/conditional-workflows.md. The committed lockfile and frozen deployment install provide reproducibility. Exact-pin only when the user or repository requires it, a known compatibility constraint justifies it, or the package will run without a committed lockfile and frozen install.
Multiple Repositories
When the user names two or more repositories or asks to sync or align dependencies across repositories, read references/multi-repo-alignment.md. It runs the Workflow below per repository and adds shared-target selection, one cross-repository major batch, and per-repository commits.
Workflow
Resolve the skill directory and save the helper plan from the target repository:
bash <skill-dir>/scripts/run-taze.sh --plan [--include package-a,package-b] > <taze-plan.json>The JSON plan classifies every discovered update as
apply,review-major,review, orskip-fixed. The helper detects monorepos, includes locked versions during scans, and mirrors Bun minimum-release-age settings. If the repository uses package-manager age gates or Bun catalogs, read references/conditional-workflows.md for that active branch only.If
--dry-runwas requested, present the plan and counts, then stop without changing manifests or lockfiles.Select every ranged minor/patch update marked
apply. Reuse explicit approval of package/version transitions; never auto-approve a major merely from its package name. Present unresolvedreview-majorand unknown updates in one decision batch with current version, target version, package role, and relevant migration/release notes. Apply only authorized majors.If nothing is selected, report the no-op and stop. If the root manifest uses Bun catalogs, preview the exact selected catalog transitions from the accepted plan. Pass only selected packages present in a catalog; skip the helper when that subset is empty:
uv run <skill-dir>/scripts/update-bun-catalogs.py \ --root <repo> --plan <taze-plan.json> --include package-a,package-bThe preview is read-only. Missing catalog entries, conflicting plan rows, unsupported versions, or a catalog value that no longer matches the plan fail before writes. The helper does not select upgrades.
Before the first manifest or lockfile write, select and run a baseline that can detect regressions from the chosen updates. Honor repository-required checks; otherwise select dependency-resolution, build, test, typecheck, lint, formatting, codegen, or invariant checks from the updated packages' actual consumers and risk. Use an aggregate suite for shared toolchain or runtime changes, not merely because it exists. Use frozen or non-writing modes where available, and record the exact commands and coverage for the post-bump rerun. Attribute every failure before deciding whether it blocks. Proceed when an unrelated pre-existing failure is reproducible, can be compared after the bump, and does not prevent dependency resolution or the checks needed to detect regressions; do not fix it as part of the bump. Dependency or peer-resolution conflicts, actionable unsafe behavior, or a baseline that cannot provide trustworthy before/after signal block the bump. In that case, stop with
### ⛔ Dependency bump blocked — baseline unusableand report the exact prerequisite and diagnostics without asking for redundant authorization. Informational notices such as unavoidable deprecations do not block.For Bun catalogs, apply the previewed transitions first: rerun
update-bun-catalogs.pywith the same plan and catalog include subset plus--write. It atomically updates every matching default/named catalog occurrence and preserves each existing^,~, or empty prefix. Apply before Taze, whose native catalog writes would make this plan stale.Then write all selected Taze updates in one command:
bash <skill-dir>/scripts/run-taze.sh --write --include package-a,package-bRun
niso the repository's package manager updates its lockfile.Inspect the manifest and lockfile diff. Rerun the exact baseline commands, plus the narrowest checks that exercise the updated dependencies and any required migrations. Treat every newly introduced error, type issue, check failure (including tests, builds, lint, formatting, codegen, and repository invariants), dependency or peer-resolution conflict, and actionable compatibility or safety warning as caused by the bump unless evidence shows otherwise.
Fix every issue caused by the bump, including required source or configuration migrations, while preserving intended behavior. Do not suppress diagnostics, weaken validation, or change expected behavior merely to make checks pass. After each fix, rerun checks whose inputs or behavior changed. At completion, require valid results for the entire recorded suite against the final state, with no new failures against the accepted baseline; reuse passing results whose inputs remain unchanged. Only unrelated pre-existing failures meeting step 5's comparison requirements may remain. If no clear safe fix exists within the task's authority, stop with
### ⚠️ Dependency regression decision required. Present all such issues in one table with the evidence, affected locations, fix and revert options, and likely effects. Do not report completion until the user chooses, the fix is applied or the offending update is reverted, the lockfile is regenerated, and the complete suite meets that same baseline-comparison requirement.
User-Facing Output
Present plans as ### 📦 Dependency plan with counts and a compact table:
| ID | Plan value | Decision | Package | Current → target | Type | Notes |
|---|
Use the plan's exact apply, review-major, review, and skip-fixed values alongside plain-language decisions.
Assign stable IDs to rows needing a choice so the user can answer once. Use ### 🔎 Dry run — no files written for a
preview and ### ✅ No selected updates for a no-op.
Finish applied work with ### 🏁 Dependencies updated, a tree of changed manifests/lockfiles, and
### 🧪 Verification. Use ### ⚠️ Remaining review only for non-blocking informational matters, never for an
unresolved issue caused by the bump. Keep helper JSON, package/version strings, commands, and diagnostics exact and
undecorated.
Invariants
- Fixed versions and non-semver protocols remain unchanged unless the user explicitly asks otherwise.
- Caret ranges are valid for new dependencies and CLIs; do not describe them as unreproducible when a committed lockfile and frozen install control resolution.
- Package arguments constrain both scan and write phases.
- The same maturity-period policy applies to scan and write.
- Bun catalog preview and write use the same accepted Taze plan and selected package set; stale plans never write.
- Do not infer compatibility from SemVer alone when repository evidence, peer ranges, or release notes indicate otherwise.
Completion requires an attributed, comparison-safe pre-write baseline, a reviewed plan, the retained selected updates, a regenerated lockfile, no new failure in the recorded suite or dependency-specific checks, and no unresolved issue caused by the bump. Dry-run completion requires the structured plan and zero writes.
Files (agent-skills)
-
agents
-
openai.yaml 42 B
policy: allow_implicit_invocation: true
-
-
references
-
conditional-workflows.md 3.5 KB
# Conditional Workflows Both sections below are gated: read only when the triggering condition in `SKILL.md` is met. ## Minimum Release Age Mode Use this mode for projects that configure a package-manager minimum-age policy. Discover the effective project and user-level package-manager configuration. A configured age gate makes `@latest` a valid initial selector and `^x.y.z` a valid retained manifest range: the installer filters out releases inside the cooldown. Do not exact-pin solely to impose the same freshness boundary; Bun still age-gates exact requests but they bypass its rapid-release stability check. The age gate is not a reproducibility mechanism; commit the lockfile and use frozen installs in deployment. If the deployment resolves dependencies without that lockfile, require the same effective age policy there or exact-pin for that workflow. Taze calls this `maturityPeriod`: - `--maturity-period [days]` filters out package versions newer than the given number of days - `--maturity-period-exclude <packages>` excludes packages from that filter, when supported by the installed Taze version ```bash # 7-day cooldown taze major -r --maturity-period 7 ``` For Bun `minimumReleaseAge`, convert seconds to whole days using a ceiling division. Example: `604800` seconds becomes `--maturity-period 7`. If the configured seconds are not a whole number of days, round up so Taze is not weaker than the package manager policy. Taze v19.13.0+ auto-infers maturity periods from pnpm and Yarn workspace config, but not from Bun `bunfig.toml`. For Bun projects, pass `--maturity-period` explicitly. For Bun lockfile projects, `run-taze.sh` uses Python 3.11+ through `uv` to parse the global `.bunfig.toml` under `$XDG_CONFIG_HOME` (or `$HOME` when unset), then overlay project `[install]` keys. Multiline exclusion arrays are supported; an explicit local `0` or empty array overrides the inherited value. See [Bun configuration](https://bun.sh/docs/runtime/bunfig#global-vs-local). Verify the installer actually enforces inherited age settings; if it ignores them, preserve the policy in project configuration before installing. When the package manager config has an exclude list, pass matching Taze excludes if available: ```bash taze major -r --maturity-period 7 --maturity-period-exclude react,webpack ``` `run-taze.sh` adds these flags itself for Bun lockfile projects and rejects extra options; Taze infers them for pnpm and Yarn. Append the same maturity flags only to direct Taze scan and write commands. After Taze writes manifests, run the project package manager install as usual; the package manager remains the final enforcement layer for direct and transitive resolution. ## Update Bun Catalogs When the root `package.json` contains `catalog` / `catalogs` at the top level or under `workspaces`, use `scripts/update-bun-catalogs.py` with the saved Taze plan and the selected packages present in catalogs. Skip this helper if that subset is empty. Preview before manifest writes; after the baseline passes, rerun the same command with `--write`, then perform the selected Taze write and regenerate the lockfile. Taze can update Bun catalogs natively, so running it first would invalidate the helper's saved plan. The helper owns default/named catalog discovery, multiple occurrences, prefix preservation, stale-plan validation, and atomic replacement. The agent owns which upgrades are accepted and whether a major migration is compatible. Do not manually reproduce the catalog transition or weaken a helper failure. -
multi-repo-alignment.md 3.6 KB
# Multi-Repository Alignment Use when the user names two or more repositories or asks to sync or align dependency versions across repositories. Each repository keeps its own package manager, range style, age policy, and validation suite; alignment only chooses shared targets. Treat an argument that resolves to a directory containing `package.json` as a repository root. ## 1. Plan Every Repository Save one plan per repository: ```sh bash <skill-dir>/scripts/run-taze.sh --plan <repo> > <repo-plan.json> ``` The helper applies that repository's own minimum-release-age policy, so each row's `available` is the newest version that repository admits. Plans list only packages with updates; also record every direct dependency and catalog entry from each manifest so packages that are already current still count as shared. ## 2. Respect Holds Search each repository for documented holds: `overrides`, `resolutions`, or `pnpm.overrides` entries; exact pins; and guidance, changelog, or code comments explaining a pin or version cap. A hold caps that repository's ceiling or excludes the package; never override it silently. Report every hold with its source and effect on the shared target. ## 3. Choose One Shared Target For every package declared directly in two or more repositories: - A repository's ceiling is its plan row's `available`, or its current version when its plan has no row. - The shared target is the highest version every repository's plan allows: the lowest ceiling. - Never move a repository below its current version. When one repository is already ahead of another's ceiling, leave the package unaligned and report why. - Fixed versions and non-semver protocols stay unchanged unless the user asks otherwise. Packages found in only one repository follow that repository's plan exactly as in the single-repository workflow. ## 4. Review Majors Once Present every shared target that crosses a major version in any repository, plus every `review` or unknown row, in one cross-repository decision batch: package, per-repository current → target, package role, and migration notes. Reuse explicit approval of those transitions and ask only about unresolved rows; never infer major approval from a package name. A declined major leaves that package unchanged everywhere. ## 5. Apply Per Repository Run the single-repository baseline (Workflow step 5) in each repository before its first write. Then, from each root: - Bun catalogs: change catalog definitions, never `catalog:` references in workspace manifests. When the shared target differs from the plan's `available`, set that row's `available` to the target in a copy of the saved plan, then run `update-bun-catalogs.py` preview and `--write` with that copy. - Other direct dependencies: when the target equals the plan's `available`, write with `run-taze.sh --write --include <packages>`. Otherwise install the target through the repository's package manager in the same dependency section with the existing range prefix (for example `bun add -d pkg@^x.y.z`). - Regenerate the lockfile with the repository's package manager. ## 6. Validate and Commit Per Repository Rerun each repository's recorded baseline plus the narrowest checks that exercise the updated packages, fixing or escalating regressions exactly as in Workflow steps 8-9. Commit each repository separately with only its manifest, catalog, lockfile, and required migration changes, following that repository's commit conventions. ## Report Use one table: package, shared target, per-repository current → new, and notes. List unaligned packages, declined majors, and holds with their sources, then per-repository verification and commit receipts.
-
-
scripts
-
bun-maturity.py 2 KB
#!/usr/bin/env python3 # /// script # requires-python = ">=3.11" # /// """Print Taze arguments for the inherited Bun release-age policy.""" import argparse import os import sys import tomllib from pathlib import Path def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--exclude-supported", action="store_true") args = parser.parse_args() global_root = os.environ.get("XDG_CONFIG_HOME") or os.environ.get("HOME") configs = [Path(global_root) / ".bunfig.toml"] if global_root else [] configs.append(Path("bunfig.toml")) policy = {} for config in configs: try: with config.open("rb") as source: install = tomllib.load(source).get("install", {}) except FileNotFoundError: continue except (OSError, ValueError): # Configs can contain registry credentials; do not echo parser input. raise ValueError(f"cannot read Bun configuration: {config}") from None if not isinstance(install, dict): raise ValueError(f"expected an install table in {config}") policy.update(install) age = policy.get("minimumReleaseAge", 0) excludes = policy.get("minimumReleaseAgeExcludes", []) if type(age) is not int or age < 0: raise ValueError("minimumReleaseAge must be a nonnegative integer") if not isinstance(excludes, list) or any( not isinstance(name, str) or not name or any(c in name for c in ",\r\n") for name in excludes ): raise ValueError("minimumReleaseAgeExcludes must be an array of package names") if age: print("--maturity-period") print((age + 86399) // 86400) if excludes and args.exclude_supported: print("--maturity-period-exclude") print(",".join(excludes)) if __name__ == "__main__": try: main() except ValueError as error: print(f"ERROR: {error}", file=sys.stderr) sys.exit(1) -
parse-taze-plan.py 2.8 KB
#!/usr/bin/env -S uv run --script """Convert stable, no-color Taze output into a machine-readable update plan.""" from __future__ import annotations import argparse import json import re from pathlib import Path ANSI_RE = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]") VERSION_RE = re.compile(r"^(?:workspace:)?[~^<>=v]*([0-9]+)\.([0-9]+)\.([0-9]+)(?:[-+][0-9A-Za-z.-]+)?$") def version_parts(value: str) -> tuple[int, int, int] | None: match = VERSION_RE.match(value) return tuple(map(int, match.groups())) if match else None def classify(current: str, available: str) -> str: old = version_parts(current) new = version_parts(available) if old is None or new is None: return "unknown" if old[0] != new[0]: return "major" if old[1] != new[1]: return "minor" return "patch" def parse(text: str) -> list[dict[str, str]]: entries: list[dict[str, str]] = [] for raw_line in ANSI_RE.sub("", text).splitlines(): if "→" not in raw_line: continue left, right = raw_line.split("→", 1) left_tokens = left.split() right_tokens = right.split() if not left_tokens or not right_tokens: continue available = right_tokens[0] current_index = next( (index for index in range(len(left_tokens) - 1, -1, -1) if version_parts(left_tokens[index]) is not None), None, ) if current_index is None or version_parts(available) is None: continue current = left_tokens[current_index] package = left_tokens[0] update_type = classify(current, available) fixed = not current.startswith(("^", "~", ">", "<", "=")) if fixed: action = "skip-fixed" elif update_type == "major": action = "review-major" elif update_type in {"minor", "patch"}: action = "apply" else: action = "review" entries.append( { "package": package, "current": current, "available": available, "type": update_type, "action": action, } ) priority = {"review-major": 0, "review": 1, "apply": 2, "skip-fixed": 3} return sorted(entries, key=lambda item: (priority[item["action"]], item["package"])) def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--input", required=True, type=Path) args = parser.parse_args() entries = parse(args.input.read_text(encoding="utf-8", errors="replace")) counts = {key: sum(item["type"] == key for item in entries) for key in ("major", "minor", "patch", "unknown")} print(json.dumps({"updates": entries, "counts": counts, "total": len(entries)}, indent=2, sort_keys=True)) return 0 if __name__ == "__main__": raise SystemExit(main()) -
run-taze.sh 3.5 KB
#!/usr/bin/env bash # run-taze.sh - Run taze in non-interactive mode # # Usage: run-taze.sh [--include pkg1,pkg2] [--concurrency n] [--plan|--write] [path] # # Automatically detects monorepo projects (workspaces in package.json # or pnpm-workspace.yaml) and enables recursive mode. # # Bun projects with local or global minimumReleaseAge get matching Taze # maturity-period flags. Taze does not auto-infer Bun's age gate. # # Exit codes: # 0 - Success (updates displayed) # 1 - taze not installed # 2 - No package.json found # 64 - Usage error set -euo pipefail include="" concurrency="" write=false plan=false target_dir="." script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" while [[ $# -gt 0 ]]; do case "$1" in --include) include="${2:?ERROR: --include requires a value}" shift 2 ;; --include=*) include="${1#*=}" shift ;; --concurrency) concurrency="${2:?ERROR: --concurrency requires a value}" shift 2 ;; --concurrency=*) concurrency="${1#*=}" shift ;; --write) write=true shift ;; --plan) plan=true shift ;; -*) echo "ERROR: Unknown option: $1" >&2 exit 64 ;; *) if [[ "$target_dir" != "." ]]; then echo "ERROR: Only one target path is supported" >&2 exit 64 fi target_dir="$1" shift ;; esac done # Check for package.json if [[ ! -f "$target_dir/package.json" ]]; then echo "ERROR: No package.json found in $target_dir" >&2 exit 2 fi cd "$target_dir" # Refresh registry metadata so cached scans cannot hide newly published releases. taze_args=(major --force) # Auto-detect monorepo if grep -q '"workspaces"' package.json 2>/dev/null || [[ -f pnpm-workspace.yaml ]]; then taze_args+=("-r") fi # Check taze availability if ! command -v taze &>/dev/null; then cat >&2 <<'EOF' ERROR: taze CLI is not installed. Install taze globally: npm install -g taze Or run via bunx: bunx taze Documentation: https://github.com/antfu-collective/taze EOF exit 1 fi if [[ -n "$include" ]]; then taze_args+=("--include" "$include") fi if [[ -n "$concurrency" ]]; then taze_args+=("--concurrency" "$concurrency") fi if [[ "$write" == true && -z "$include" ]]; then echo "ERROR: --write requires --include with the selected package list" >&2 exit 64 fi if [[ "$write" == true && "$plan" == true ]]; then echo "ERROR: --plan and --write are mutually exclusive" >&2 exit 64 fi # Mirror Bun's delayed-resolution policy for direct dependency candidates. # bunfig.toml stores seconds; Taze expects whole days. if [[ -f bun.lock || -f bun.lockb ]]; then policy_args=() if taze --help 2>/dev/null | grep -q -- '--maturity-period-exclude'; then policy_args+=("--exclude-supported") fi # Parse TOML rather than lines: lists may span lines and local keys override global keys. maturity_args="$(uv run "$script_dir/bun-maturity.py" ${policy_args[@]+"${policy_args[@]}"})" if [[ -n "$maturity_args" ]]; then while IFS= read -r argument; do taze_args+=("$argument") done <<<"$maturity_args" fi fi if [[ "$write" == true ]]; then taze_args+=("--write") else # Scan all available updates, including fixed versions (no ^ or ~). taze_args+=("--include-locked") fi if [[ "$plan" == true ]]; then plan_output="$(mktemp "${TMPDIR:-/tmp}/taze-plan.XXXXXX")" trap 'rm -f "$plan_output"' EXIT NO_COLOR=1 taze "${taze_args[@]}" --no-group --no-timediff --no-nodecompat --sort name-asc >"$plan_output" 2>&1 uv run "$script_dir/parse-taze-plan.py" --input "$plan_output" else taze "${taze_args[@]}" 2>&1 fi -
update-bun-catalogs.py 6.2 KB
#!/usr/bin/env python3 """Preview or atomically apply selected Taze updates to Bun catalogs.""" from __future__ import annotations import argparse import json import os import re import sys import tempfile from pathlib import Path from typing import Any VERSION_RE = re.compile(r"^(?P<prefix>\^|~)?(?P<version>\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$") class CatalogError(ValueError): pass def parse_version(value: Any, context: str) -> tuple[str, str]: if not isinstance(value, str) or not (match := VERSION_RE.fullmatch(value)): raise CatalogError(f"unsupported version in {context}: {value!r}") return match.group("prefix") or "", match.group("version") def selected_names(values: list[str]) -> list[str]: names: list[str] = [] for value in values: names.extend(item.strip() for item in value.split(",") if item.strip()) if not names: raise CatalogError("--include must select at least one package") return list(dict.fromkeys(names)) def plan_updates(plan: dict[str, Any], names: list[str]) -> dict[str, dict[str, Any]]: updates = plan.get("updates") if not isinstance(updates, list): raise CatalogError("plan updates must be an array") selected: dict[str, dict[str, Any]] = {} for name in names: matches = [entry for entry in updates if isinstance(entry, dict) and entry.get("package") == name] if not matches: raise CatalogError(f"selected package is missing from plan: {name}") normalized = {(entry.get("current"), entry.get("available")) for entry in matches} if len(normalized) != 1: raise CatalogError(f"ambiguous plan entries for package: {name}") entry = matches[0] parse_version(entry.get("current"), f"plan current for {name}") parse_version(entry.get("available"), f"plan available for {name}") selected[name] = entry return selected def catalog_maps(document: dict[str, Any]) -> list[tuple[str, dict[str, Any]]]: workspaces = document.get("workspaces") if isinstance(workspaces, dict) and ("catalog" in workspaces or "catalogs" in workspaces): location = workspaces label = "workspaces." elif "catalog" in document or "catalogs" in document: location = document label = "" else: raise CatalogError("package.json has no Bun catalog configuration") maps: list[tuple[str, dict[str, Any]]] = [] default = location.get("catalog") if default is not None: if not isinstance(default, dict): raise CatalogError(f"{label}catalog must be an object") maps.append(("default", default)) named = location.get("catalogs") if named is not None: if not isinstance(named, dict): raise CatalogError(f"{label}catalogs must be an object") for catalog_name, catalog in sorted(named.items()): if not isinstance(catalog, dict): raise CatalogError(f"{label}catalogs.{catalog_name} must be an object") maps.append((catalog_name, catalog)) return maps def update_document(document: dict[str, Any], selected: dict[str, dict[str, Any]]) -> list[dict[str, str]]: maps = catalog_maps(document) changes: list[dict[str, str]] = [] for package, entry in selected.items(): plan_prefix, plan_current = parse_version(entry["current"], f"plan current for {package}") _available_prefix, available = parse_version(entry["available"], f"plan available for {package}") occurrences = [(catalog_name, catalog) for catalog_name, catalog in maps if package in catalog] if not occurrences: raise CatalogError(f"selected package is missing from Bun catalogs: {package}") for catalog_name, catalog in occurrences: prefix, current = parse_version(catalog[package], f"catalog {catalog_name} entry for {package}") if current != plan_current: raise CatalogError( f"stale plan for {package} in catalog {catalog_name}: catalog has {current}, plan has {plan_current}" ) if plan_prefix and prefix != plan_prefix: raise CatalogError( f"stale plan prefix for {package} in catalog {catalog_name}: catalog has {prefix!r}, plan has {plan_prefix!r}" ) replacement = f"{prefix}{available}" changes.append({"package": package, "catalog": catalog_name, "from": catalog[package], "to": replacement}) catalog[package] = replacement return changes def atomic_write(path: Path, document: dict[str, Any]) -> None: encoded = (json.dumps(document, indent=2, ensure_ascii=False) + "\n").encode() descriptor, temporary_name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent) temporary = Path(temporary_name) try: with os.fdopen(descriptor, "wb") as handle: handle.write(encoded) handle.flush() os.fsync(handle.fileno()) os.chmod(temporary, path.stat().st_mode) os.replace(temporary, path) finally: if temporary.exists(): temporary.unlink() def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--root", required=True, type=Path) parser.add_argument("--plan", required=True, type=Path) parser.add_argument("--include", required=True, action="append") parser.add_argument("--write", action="store_true") args = parser.parse_args() package_path = args.root if args.root.name == "package.json" else args.root / "package.json" try: names = selected_names(args.include) document = json.loads(package_path.read_text(encoding="utf-8")) plan = json.loads(args.plan.read_text(encoding="utf-8")) changes = update_document(document, plan_updates(plan, names)) if args.write: atomic_write(package_path, document) except (OSError, json.JSONDecodeError, CatalogError) as exc: print(f"ERROR: {exc}", file=sys.stderr) return 64 print(json.dumps({"schemaVersion": 1, "wrote": args.write, "file": str(package_path), "changes": changes}, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())
-
-
SKILL.md 8.3 KB
--- argument-hint: "[--dry-run] [package ...] [repo-path ...]" effort: medium model: sonnet name: node-deps-bumper description: "Use for dependency updates: bump npm/pnpm/yarn/bun packages, check outdated, or run taze." --- # Node Dependency Bumper Use Taze to build one structured update plan, apply compatible ranged updates, and make major-version decisions as a batch. ## Adding Dependencies For a new package or CLI, use the repository's package manager and existing range convention instead of Taze. Installing `package@latest` and retaining the package manager's resulting `^x.y.z` range is allowed. Do not replace a caret range with an exact version merely because it can admit future releases. When an effective package-manager minimum-release-age policy exists, it provides the freshness boundary for candidate versions; read [references/conditional-workflows.md](references/conditional-workflows.md). The committed lockfile and frozen deployment install provide reproducibility. Exact-pin only when the user or repository requires it, a known compatibility constraint justifies it, or the package will run without a committed lockfile and frozen install. ## Multiple Repositories When the user names two or more repositories or asks to sync or align dependencies across repositories, read [references/multi-repo-alignment.md](references/multi-repo-alignment.md). It runs the Workflow below per repository and adds shared-target selection, one cross-repository major batch, and per-repository commits. ## Workflow 1. Resolve the skill directory and save the helper plan from the target repository: ```sh bash <skill-dir>/scripts/run-taze.sh --plan [--include package-a,package-b] > <taze-plan.json> ``` The JSON plan classifies every discovered update as `apply`, `review-major`, `review`, or `skip-fixed`. The helper detects monorepos, includes locked versions during scans, and mirrors Bun minimum-release-age settings. If the repository uses package-manager age gates or Bun catalogs, read [references/conditional-workflows.md](references/conditional-workflows.md) for that active branch only. 2. If `--dry-run` was requested, present the plan and counts, then stop without changing manifests or lockfiles. 3. Select every ranged minor/patch update marked `apply`. Reuse explicit approval of package/version transitions; never auto-approve a major merely from its package name. Present unresolved `review-major` and unknown updates in one decision batch with current version, target version, package role, and relevant migration/release notes. Apply only authorized majors. 4. If nothing is selected, report the no-op and stop. If the root manifest uses Bun catalogs, preview the exact selected catalog transitions from the accepted plan. Pass only selected packages present in a catalog; skip the helper when that subset is empty: ```sh uv run <skill-dir>/scripts/update-bun-catalogs.py \ --root <repo> --plan <taze-plan.json> --include package-a,package-b ``` The preview is read-only. Missing catalog entries, conflicting plan rows, unsupported versions, or a catalog value that no longer matches the plan fail before writes. The helper does not select upgrades. 5. Before the first manifest or lockfile write, select and run a baseline that can detect regressions from the chosen updates. Honor repository-required checks; otherwise select dependency-resolution, build, test, typecheck, lint, formatting, codegen, or invariant checks from the updated packages' actual consumers and risk. Use an aggregate suite for shared toolchain or runtime changes, not merely because it exists. Use frozen or non-writing modes where available, and record the exact commands and coverage for the post-bump rerun. Attribute every failure before deciding whether it blocks. Proceed when an unrelated pre-existing failure is reproducible, can be compared after the bump, and does not prevent dependency resolution or the checks needed to detect regressions; do not fix it as part of the bump. Dependency or peer-resolution conflicts, actionable unsafe behavior, or a baseline that cannot provide trustworthy before/after signal block the bump. In that case, stop with `### ⛔ Dependency bump blocked — baseline unusable` and report the exact prerequisite and diagnostics without asking for redundant authorization. Informational notices such as unavoidable deprecations do not block. 6. For Bun catalogs, apply the previewed transitions first: rerun `update-bun-catalogs.py` with the same plan and catalog include subset plus `--write`. It atomically updates every matching default/named catalog occurrence and preserves each existing `^`, `~`, or empty prefix. Apply before Taze, whose native catalog writes would make this plan stale. Then write all selected Taze updates in one command: ```sh bash <skill-dir>/scripts/run-taze.sh --write --include package-a,package-b ``` 7. Run `ni` so the repository's package manager updates its lockfile. 8. Inspect the manifest and lockfile diff. Rerun the exact baseline commands, plus the narrowest checks that exercise the updated dependencies and any required migrations. Treat every newly introduced error, type issue, check failure (including tests, builds, lint, formatting, codegen, and repository invariants), dependency or peer-resolution conflict, and actionable compatibility or safety warning as caused by the bump unless evidence shows otherwise. 9. Fix every issue caused by the bump, including required source or configuration migrations, while preserving intended behavior. Do not suppress diagnostics, weaken validation, or change expected behavior merely to make checks pass. After each fix, rerun checks whose inputs or behavior changed. At completion, require valid results for the entire recorded suite against the final state, with no new failures against the accepted baseline; reuse passing results whose inputs remain unchanged. Only unrelated pre-existing failures meeting step 5's comparison requirements may remain. If no clear safe fix exists within the task's authority, stop with `### ⚠️ Dependency regression decision required`. Present all such issues in one table with the evidence, affected locations, fix and revert options, and likely effects. Do not report completion until the user chooses, the fix is applied or the offending update is reverted, the lockfile is regenerated, and the complete suite meets that same baseline-comparison requirement. ## User-Facing Output Present plans as `### 📦 Dependency plan` with counts and a compact table: | ID | Plan value | Decision | Package | Current → target | Type | Notes | | --- | ---------- | -------- | ------- | ---------------- | ---- | ----- | Use the plan's exact `apply`, `review-major`, `review`, and `skip-fixed` values alongside plain-language decisions. Assign stable IDs to rows needing a choice so the user can answer once. Use `### 🔎 Dry run — no files written` for a preview and `### ✅ No selected updates` for a no-op. Finish applied work with `### 🏁 Dependencies updated`, a tree of changed manifests/lockfiles, and `### 🧪 Verification`. Use `### ⚠️ Remaining review` only for non-blocking informational matters, never for an unresolved issue caused by the bump. Keep helper JSON, package/version strings, commands, and diagnostics exact and undecorated. ## Invariants - Fixed versions and non-semver protocols remain unchanged unless the user explicitly asks otherwise. - Caret ranges are valid for new dependencies and CLIs; do not describe them as unreproducible when a committed lockfile and frozen install control resolution. - Package arguments constrain both scan and write phases. - The same maturity-period policy applies to scan and write. - Bun catalog preview and write use the same accepted Taze plan and selected package set; stale plans never write. - Do not infer compatibility from SemVer alone when repository evidence, peer ranges, or release notes indicate otherwise. Completion requires an attributed, comparison-safe pre-write baseline, a reviewed plan, the retained selected updates, a regenerated lockfile, no new failure in the recorded suite or dependency-specific checks, and no unresolved issue caused by the bump. Dry-run completion requires the structured plan and zero writes.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.