Skip to content
Skillv1.0.0

commander-update-orchestrator

Use when a Commander update command (`/experiments:commander-update-{patch,minor,major,engines}` and their deep variants) needs to apply npm dependency or toolchain-engine updates across every project

by pabloimrik17(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from pabloimrik17/monolab (claude-plugins/experiments/skills/commander-update-orchestrator/SKILL.md). Install upstream with npx skills add pabloimrik17/monolab --skill commander-update-orchestrator. Copyright stays with the author.

commander-update-orchestrator

Cross-project npm-update orchestration. Parameterized by level / target, so every commander:update-* command (and the deep variants) drops into the same plumbing without re-implementing fan-out / fan-in.

When to use

  • Invoked by /experiments:commander-update-patch with level=patch, target=patch (shallow mode, default).
  • Invoked by /experiments:commander-update-deep-patch with level=patch, target=patch, mode=deep (deep mode, MON-199).
  • commander-update-{minor,major,engines} and commander-update-deep-{minor,major,engines} wire the matching level/target/mode trio. At level=engines the scan/apply steps route to the engine toolchain skills — see "Level-conditional routing: level=engines".
  • Composes with parallel-research-workflow (in cross-project mode) for the deep-mode research insertion (Step 6.5).

Never invoke directly from the user side. The skill is meant for command-layer composition.

Artifact glossary + main-window context diet. dossier.md is the global research document (formerly plan.md; no artifact is named plan.md), changeset.md is the per-project concrete apply plan written by an apply teammate, and Claude Code plan mode is the harness feature (used only as the changeset gate's review UI — see Step 10b). The orchestrator's main conversation holds only paths and small status digests (target ≤ ~30 lines each; structured tables such as the bump set may exceed the target but stay bounded digests, never full artifact bodies). It SHALL NOT load changelog bodies, per-group research files, or the dossier body into its own context, and ncu/install output SHALL NOT stream verbatim into the conversation (on-disk logs + digest + bounded tail-on-failure only, per apply-npm-updates).

Inputs

Field Type Required Notes
level string yes One of patch, minor, major, engines. Passed verbatim to experiments:scan-npm-updates.
target string yes One of patch, minor, major, engines. Passed verbatim to ncu --target. Matches level for the four shipped shallow commands and four future deep commands.
mode string no One of shallow, deep. Default shallow. Selects the deep-research path (cross-project changelog research inserted at Step 6.5 + per-project changeset gate round at apply time). The shallow path is byte-equivalent across mode: "shallow" and an absent mode input. See "Mode-conditional behavior" below.
overrideRegistryPath string no Repo-relative path to a pkg-upgrade-overrides.yaml file. Default: claude-plugins/experiments/skills/scan-npm-updates/data/pkg-upgrade-overrides.yaml. Identical default in both modes.
projectsFilter string[] no Project names to operate on. When set, the project picker is skipped. When unset, the multi-select picker is raised (Step 3).

Input validation

Reject before any side effect:

  • Unknown level: abort with Error: invalid level "<value>". Expected patch|minor|major|engines.
  • Unknown target: abort with Error: invalid target "<value>". Expected patch|minor|major|engines.
  • Unknown mode: abort with Error: invalid mode "<value>". Expected shallow|deep.

Mode-conditional behavior

This skill ships two execution paths selected by the mode input. The shallow path is byte-equivalent to MON-194's shipped contract; the deep path layers research + a per-project changeset gate round on top.

Step / Concern mode === "shallow" (default) mode === "deep"
Steps 1, 2, 3, 4, 5, 6, 8 Identical (shared plumbing). Identical (shared plumbing).
Step 6.5 (cross-project research) SHALL NOT execute. No plan-dir is created. Fires. Composes group-packages-for-research + parallel-research-workflow (cross-project mode). Produces <plan-dir>/dossier.md (synthesized by the workflow's teammate + checks).
Step 7 (gate rendering) Generates the bump-set table inline from CrossProjectPlan. Dossier gate rendering: references <plan-dir>/dossier.md by path + a bounded digest; appends orchestrator-owned drift sections (Warnings, scan-failed, path-missing).
Step 9 (gate) Three options: apply-all, pick-subset, cancel. Four options: apply-all, apply-bumps-only, pick-subset, cancel. pick-subset accepts both package names AND improvement-bullet titles.
Step 10 (apply) Single per-project bumps loop (10.1–10.6). Splits into Step 10a (bumps loop, identical mechanics; failure pauses for a stop/continue decision), Step 10b (per-project changeset gate round), Step 10c (end-of-flow cleanup).
Step 11 (summary) H1 ## commander-update-<level> summary ## commander-update-deep-<level> summary
Step 11 (summary) sections Shallow set (Applied / Failed / Pending / Skipped-by-{path,scan,user,policy,override} / Warnings). Shallow set PLUS Applied improvements, Skipped improvements, Inapplicable improvements, Skipped or unavailable groups. Conditional Review dossier.md bullet on keep-plan.

The shallow path SHALL NOT execute Step 6.5, SHALL NOT run any changeset gate round at apply time, SHALL NOT invoke the workflow's end-of-flow cleanup, and SHALL NOT render deep-mode summary sections. The deep-mode insertions are local to Steps 6.5, 7, 9, 10a/10b/10c, and 11 — Steps 1, 2, 3, 4, 5, 6, and 8 behave identically across modes (in particular, Step 8 override registry consultation is shared verbatim — Decision 5 in design.md).

Level-conditional routing: level=engines (toolchain bump)

engines is not an ncu dependency level. When level === "engines" (and target === "engines"), the orchestrator swaps the per-project scan and apply steps to the engine toolchain skills and aligns cross-project on the engine version, while reusing the rest of the cross-project skeleton unchanged (project resolution Steps 1–3, subset selection, plan rendering Step 7, sequential apply with stop-on-fail Step 10, aggregated summary Step 11). For level ∈ {patch, minor, major} this entire section is inert — those levels use scan-npm-updates / apply-npm-updates exactly as before (design D6, additive delta).

The engines branch overrides exactly these surfaces:

  • Step 4 — scan. The per-project scan agent invokes detect-toolchain-surfaces (capability engine-surface-scanning) instead of scan-npm-updates. Each agent returns that skill's EngineSurfaceInventory JSON verbatim (or {"_error": "<string>"} on a precondition abort). The CWD + parse-and-tag plumbing of Step 4 is unchanged; only the skill name in the agent prompt changes.
  • Steps 5–6 — aggregation + alignment. Aggregate per-engine across projects instead of per-package. Cross-project alignment is on the resolved engine version: resolve one target per engine once (Node → latest LTS; pnpm/npm/yarn/bun/deno → latest — via apply-engine-bumps's resolution) and reuse it for every project. There is no per-package max-wins computation and no range-admission conflict prompt. A project already pinned above the resolved target is surfaced and left higher unless the user opts to converge. Intra-repo misalignment reported by detect-toolchain-surfaces is surfaced and converged to the resolved target (runtime loci only).
  • Step 8 — override registry consultation is SKIPPED. Package-name overrides (Storybook-style families) have no meaning for runtime/PM surfaces. OVERRIDE_RUN / OVERRIDE_SKIP are empty; every eligible runtime surface is in the generic apply set.
  • Step 10.3 — apply. The per-project apply invokes apply-engine-bumps (capability engine-update-apply) instead of apply-npm-updates. No ncu is invoked at engines level. The orchestrator passes the project's inventory + the resolved per-engine targets (with confirmed: true — the user already gated at Step 9, and any ambiguous loci were resolved by the command/orchestrator before apply) and folds the returned { resolvedTargets, applied, skipped, droppedHashes, failure? } fragment into the project's summary entry. support and unknownSurfaces loci are never touched; publishable-lib engines.<engine> support ranges are preserved across every project.
  • Step 6.5 / 7.D — deep mode. When mode === "deep", Step 6.5 invokes parallel-research-workflow with level=engines (research targets engine release notes, deduplicated once per engine/version) and Step 7.D surfaces the resulting dossier.md — including the presence of its ## Breaking changes & migration + ## Changelogs sections — by path + bounded digest (never verbatim bodies). No ## PR plan / partition-breaking-changes applies (Step 7.D point 3 runs only for level === "major"): an engine bump is a single coordinated co-upgrade (Node + its PM, moved together), so the PR-partition is meaningless — one bucket. Isolation, when chosen (Step 9.5), wraps the whole engine bump as one workspace per project.

Everything else (the project picker, the gate's apply-all/pick-subset/cancel shape, stop-on-fail, the summary skeleton, the hard rules, registry read-only) is reused verbatim. Where the steps below describe ncu/package mechanics, read them through this section's overrides when level === "engines".

Registry contract (read-only excerpt)

This skill reads the user-scoped Commander registry. The full contract is in commander:add. Relevant invariants repeated here so this file is self-contained — no shared sidecar yet (extraction deferred until the third commander consumer requires it).

Path

<HOME>/.claude/commander/projects.json<HOME> resolves to $HOME on POSIX and %USERPROFILE% on Windows.

Lazy create — read MUST NOT touch disk

A missing file MUST be treated as an empty registry. The skill MUST NOT create the directory or the file. The on-disk state is byte-identical before and after every run (verifiable via shasum).

Schema (v2)

{
    "version": 2,
    "projects": {
        "<name>": {
            "name": "...",
            "path": "...",
            "keywords": ["..."],
            "description": "...",
            "createdAt": "<ISO-8601 UTC>",
            "updatedAt": "<ISO-8601 UTC>",
            "repoType": "single-repo | monorepo | multi-monorepo",
            "specialRules": ["..."],
            "monorepoRoot": "..."
        }
    }
}

repoType, specialRules, and monorepoRoot MAY be absent on legacy v1 records. The skill consumes only name and path for routing — repoType is informational.

Version gate

  • version <= 2 → read normally.
  • version > 2 → abort with unsupported registry version: <n> and exit non-zero. Do NOT touch the file.

Step 1 — Validate inputs

Apply the input validation listed above. If validation fails, abort with the exact error string and perform no scan or apply.

Step 2 — Resolve projects from the registry

  1. Resolve REGISTRY_PATH = <HOME>/.claude/commander/projects.json.
  2. Probe with Bash test -f "<REGISTRY_PATH>".
    • If missing: print exactly No projects registered. Use /commander:add to register one. and exit 0. Do NOT create the directory or the file.
  3. Read the file and JSON-parse the contents.
    • On parse failure: print registry file is not valid JSON, exit non-zero, do NOT touch the file.
  4. Inspect version:
    • If version > 2: print unsupported registry version: <n>, exit non-zero, do NOT touch the file.
  5. Inspect projects:
    • If absent, null, or an empty object: print No projects registered. Use /commander:add to register one. and exit 0.
  6. Iterate projects in JSON insertion order. Build the candidate list RESOLVED = [{ name, path, repoType? }, ...] preserving that order.

2.1 Apply projectsFilter (when provided)

  • Compute MATCHED = RESOLVED ∩ projectsFilter by name.
  • For each name in projectsFilter not present in RESOLVED, print one line: Filter name not found: <name>.
  • Replace RESOLVED ← MATCHED.
  • If RESOLVED is empty after the filter: print No projects matched the filter. and exit 0.

2.2 Detect missing-path drift

For each record retained, run Bash test -d "<record.path>". If exit is non-zero:

  • Append { name, path } to a pathMissing[] buffer.
  • Drop the record from RESOLVED.

The skill SHALL NOT abort on missing paths. The aggregated summary lists every dropped record under Skipped (path missing) (<N>):.

2.3 Pass-through legacy v1 records

Records lacking repoType are kept as-is. Do NOT synthesize the field, do NOT abort, do NOT mutate the registry.

2.4 Empty-after-resolution exit

If RESOLVED is empty after Step 2.2 (every retained record has a missing path), print:

  • No projects registered. Use /commander:add to register one. — when the registry was empty initially (the empty-registry / empty-projects-object branches in Step 2 already exited there; this branch is effectively dead unless Step 2.2 emptied a non-empty list).
  • Otherwise, print No selectable projects after path-drift filtering. followed by every pathMissing[] entry as a - <name> — <path> bullet, and exit 0.

Step 3 — Project subset selection (skipped when projectsFilter is set)

When projectsFilter is provided, skip this step entirely — the resolved set is already final.

When projectsFilter is unset and RESOLVED is non-empty, the interface depends on N = RESOLVED.lengthAskUserQuestion caps a question at 4 options, so the project options + all only fit when N ≤ 3:

  • N ≤ 3 — raise exactly one AskUserQuestion call configured as:
    • multiSelect: true
    • One option per project, label = <name> — <path>, description = a short hint (e.g., repoType: <value> when present, else legacy v1 record).
    • A final all option, label = All registered projects (<N>).
  • N ≥ 4 — SHALL NOT attempt the AskUserQuestion (its option count would exceed the tool cap and the call fails with invalid parameters). Instead, use a free-form selection message: print the selectable projects as a numbered <name> — <path> list and ask for a comma-separated list of names, or all. Unknown names → print the valid list and re-prompt. Empty response → treat as zero selection.

Selection handling (both interfaces)

  • Selecting all is equivalent to selecting every individual project.
  • Selecting zero options → print No projects selected. Cancelled. and exit 0 with no scan or apply.
  • Otherwise, set RESOLVED ← <selected subset> (preserving registry insertion order, NOT the order the user clicked).

Step 4 — Parallel scan dispatch

level=engines: substitute detect-toolchain-surfaces for experiments:scan-npm-updates in the agent prompt below (the agent returns an EngineSurfaceInventory JSON, or {"_error": …} on abort). The CWD config (4.1), the JSON parse + project-tag plumbing (4.2), and the per-project map (4.3) are unchanged — an EngineSurfaceInventory is tagged and stored exactly like a ScanResult. See "Level-conditional routing: level=engines".

For the resolved project set, send a single message containing N Agent tool-uses (one per project). Each agent call:

  • No model override — the agent inherits the session model. Do NOT force a latency-optimized tier: the agent executes the full scan-npm-updates skill (not a JSON echo); a weaker tier returned a fabricated empty ScanResult in dry-run 2026-07-12, silently dropping every update for a project.

  • subagent_type: "general-purpose".

  • description: short, e.g. commander scan: <name>.

  • prompt (verbatim, substituting <level> and <path>):

    You are scanning a single project for npm dependency updates as part of a cross-project Commander run.
    
    PROJECT_PATH: <absolute path from the resolved record>
    
    Tasks:
    
    1. Change working directory to PROJECT_PATH.
    2. Invoke the `experiments:scan-npm-updates` skill with `level: <level>`.
    3. Return the resulting `ScanResult` JSON verbatim.
    
    CRITICAL OUTPUT FORMAT: your entire response MUST be a single JSON object (the `ScanResult`). No markdown fences (no ```json). No prose before or after. No explanations. The first character of your response MUST be `{` and the last character MUST be `}`. Nothing else.
    
    If the skill aborts on a precondition error (no package manager detected, no manifest, etc.), your entire response MUST be a single JSON object of the shape:
    
    {"_error": "<the precondition error string>"}
    
    Do NOT attempt to recover. Do NOT modify any file. Do NOT run any package-manager command outside what `scan-npm-updates` itself runs.

4.1 Per-agent CWD

The Agent tool-call MUST configure the agent's working directory at <record.path> so scan-npm-updates detects the project's local package manager (pnpm/npm/yarn/bun/deno) without drift.

4.2 Parse responses

Collect each agent's response and parse it as JSON:

  • If parse succeeds and the JSON has shape { packageManager, repoType, updates, warnings }: this is a ScanResult. Tag with the originating project's name/path.
    • Empty-scan cross-check (level ∈ {patch, minor, major} only; not engines): when updates is empty, cross-check with one direct read-only ncu invocation (--jsonUpgraded, no --upgrade, no file writes) in that project before accepting the empty result. On mismatch, re-dispatch that project's scan agent once; if it still mismatches, mark the project scan-failed with reason scan disagreed with ncu cross-check. An empty result the cross-check confirms is accepted normally.
  • If parse succeeds and the JSON has shape { "_error": "<string>" }: mark the project as scan-failed, store the error string for the summary, and exclude the project from aggregation and apply.
  • If parse fails (non-JSON or invalid JSON): same as _error above, with a synthesized error string Agent response was not valid JSON.

4.3 Build the per-project map

Combine successful results into ScanResultByProject = { [projectName]: ScanResult }. The scanFailed[] buffer holds { name, error } for every project that failed.

The skill SHALL continue processing other projects' results normally; a failure in one project never blocks the rest.

Step 5 — Cross-project aggregation

Aggregate ScanResultByProject into a CrossProjectPlan:

interface CrossProjectPlan {
    packages: Array<{
        name: string;
        occurrences: Array<{
            projectName: string;
            currentVersion: string;
            targetVersion: string;
            location: string;
            sourceFile: string;
            skippedByReleaseAge?: boolean;
        }>;
        proposedTarget: string; // see Step 6
        conflict: boolean; // see Step 6
    }>;
    warnings: string[]; // per-project warnings, prefixed with `<projectName>: `
    scanFailed: Array<{ name: string; error: string }>;
    pathMissing: Array<{ name: string; path: string }>;
}

Aggregation rules:

  1. Group every update across every project by package name (case-sensitive npm name).
  2. Preserve insertion order: packages appear in the order of their first occurrence across the iteration of projects (same as registry insertion order).
  3. Each occurrence carries the full per-project context (projectName, currentVersion, targetVersion, location, sourceFile, skippedByReleaseAge).
  4. Concatenate warnings[] across every project's ScanResult, prefixing each with <projectName>:.

Step 6 — Version alignment

For each aggregated package, compute:

  • proposedTarget = max(occurrences[].targetVersion) (semver max). Strip leading ^/~/= for comparison; preserve the prefix of the highest-versioned occurrence on output.
  • conflict = true when at least one occurrence's declared range (currentVersion interpreted as a range) does NOT admit proposedTarget. Range admission: standard semver satisfies(proposedTarget, currentVersion).

If any package.conflict === true, raise exactly one AskUserQuestion (regardless of how many packages conflict):

  • Question copy (verbatim, substitute the package list):

    Cross-project version conflict for: <comma-separated conflicting package names>.
    At least one project's declared range does not admit the proposed maximum target.
    How should the run resolve every conflicting package?
  • multiSelect: false

  • Options:

    • use-max-where-possible — Apply proposedTarget only to occurrences whose range admits it; non-admitting occurrences keep their per-project targetVersion.
    • per-project — Every occurrence retains its per-project targetVersion; no max-alignment for the conflicting packages.
    • skip-package — Drop every conflicting package from the run entirely (their occurrences are removed from the plan).

The chosen policy applies to every conflicting package in the run. Do NOT prompt per-package.

Materialize the post-policy plan

Walk every package, applying the chosen policy to the conflicting subset:

  • use-max-where-possible: per occurrence, set effectiveTarget = proposedTarget when admissible, else effectiveTarget = occurrence.targetVersion.
  • per-project: per occurrence, effectiveTarget = occurrence.targetVersion.
  • skip-package: drop the package; record names under Skipped by conflict policy for the summary.

Non-conflicting packages always set effectiveTarget = proposedTarget for every occurrence (range admission already proven).

Step 6.5 — Cross-project research (deep mode only)

Fires only when mode === "deep". Inserted between Step 6 (version alignment) and Step 7 (dossier gate rendering). The shallow path SHALL skip this entire section.

This step composes three already-shipped pieces: experiments:group-packages-for-research, experiments:parallel-research-workflow (in cross-project mode), and — through the workflow's subagents — the fetch-changelog plugin executable. The orchestrator SHALL NOT advance the workflow's phases on its behalf; the workflow owns its own phase machine (0 → init → 1 → 2 → 3 → 4, through dossier synthesis).

6.5.1 Build the deduplicated package set

Convert the post-policy CrossProjectPlan.packages (output of Step 6) into a ScanResult.updates[]-compatible array — one record per unique package, NOT one per occurrence:

const dedupedUpdates = postPolicyPlan.packages.map((pkg) => ({
    name: pkg.name,
    targetVersion: pkg.effectiveTarget, // post-policy resolution from Step 6
    currentVersion: mostCommonCurrentVersion(pkg.occurrences), // by occurrence count; ties broken by first-occurrence order
    location: "cross-project", // synthetic; cross-project has no single location
    sourceFile: "cross-project", // synthetic; cross-project has no single source file
}));

Sort the deduplicated set alphabetically by name (stable) — matches the Step 7 ordering shipped for the shallow rendering.

The deduplication is package-level. A package that appears in N projects produces exactly one updates[] record, not N. This is the deduplication contract MON-199 mandates: research keys off the unique package set, not off the (package, project) matrix.

Packages dropped by Step 6's skip-package conflict policy are NOT included in dedupedUpdates (they were already removed from postPolicyPlan.packages).

6.5.2 Group the deduplicated set

Invoke experiments:group-packages-for-research with { updates: dedupedUpdates } (and maxPerGroup only if the caller overrode it). Capture:

  • groups: Array<{ groupId, bucketKey, packages: [...] }> — input to the workflow.
  • warnings: string[] — append each entry to the orchestrator's running warnings[] list for the Step 11 summary.

6.5.3 Synthesize a cross-project scanResult

parallel-research-workflow requires a scanResult input shaped like a ScanResult. Build one from the cross-project plan:

const crossProjectScan = {
    packageManager: unionPackageManager(allProjects), // see below
    repoType: "workspace", // cross-project is workspace-shaped by construction
    updates: dedupedUpdates,
    warnings: orchestratorWarnings, // the running list at this point
};

unionPackageManager rule: when every project in the resolved set shares the same package manager (every ScanResult.packageManager value identical across ScanResultByProject), use that value verbatim. Otherwise, use the literal string "mixed".

When the result is "mixed", append the warning Mixed package managers across selected projects: <comma-separated unique pm list, alphabetical>. to the orchestrator's running warnings[] list (surfaces in Step 11 and is persisted in crossProjectScan.warnings).

The workflow does NOT consume scanResult.packageManager for routing in cross-project mode — this field is informational only. It propagates into <plan-dir>/scan-by-project.json (which the orchestrator writes — see 6.5.5) for user inspection.

6.5.4 Invoke the workflow

Call experiments:parallel-research-workflow with:

{
    groups: groups,                                  // from Step 6.5.2
    level: skillInput.level,                         // e.g. "patch"
    scanResult: crossProjectScan,                    // from Step 6.5.3
    mode: "cross-project",                           // mandatory for cross-project research contract
    slugOverride: `commander-deep-${level}`,         // e.g. "commander-deep-patch" — plan-dir slug
}

Capture the absolute <plan-dir> path the workflow returns. Inside this single invocation the workflow runs (the orchestrator does not advance any phase on the workflow's behalf):

  1. Phase 0 — stale-plan cleanup (pattern ^[a-z0-9-]+-(patch|minor|major|engines)-\d+(-\d+)?$ matches cross-project plan-dirs, e.g. commander-deep-patch-1715693231).
  2. Phase init — plan-dir creation under ~/.claude/experiments/plans/commander-deep-<level>-<unix-ts>[-N]/.
  3. Phase 1 — batched parallel changelog fetch via the fetch-changelog executable (sequential batches of maxConcurrent, parallel within a batch; hard-wall fallback prompt on dispatch denial).
  4. Phase 2 — parallel research with the cross-project subagent prompt template (universal findings only, no codebase cross-reference).
  5. Phase 3 — mandatory integrity gate (retry-failed / continue-without / abort if any group is non-healthy).
  6. Phase 4 — dossier synthesis: the workflow's named synthesizer teammate writes <plan-dir>/dossier.md with the cross-project template (chronology assembled by script) and the two-layer compliance check runs before the dossier is surfaced.

6.5.5 Persist per-project scans alongside the cross-project plan

After Step 6.5.4 returns (i.e. dossier.md exists at <plan-dir>/dossier.md), the orchestrator SHALL write two additional artifacts under the plan-dir (the workflow's contract delegates these two files to the cross-project caller):

  • <plan-dir>/scan-by-project.json — JSON object mapping projectName → the verbatim per-project ScanResult from Step 4 (ScanResultByProject).
  • <plan-dir>/cross-project-plan.json — JSON object capturing the post-Step-6 CrossProjectPlan (deduplicated package list with per-occurrence projection — projectName, currentVersion, targetVersion, location, sourceFile, plus proposedTarget, effectiveTarget per occurrence, and the resolved conflict-policy outcome).

Both files are pretty-printed (2-space indent). The workflow itself does NOT require these files for dossier synthesis (it consumes groups[] and the synthesized scanResult directly through its inputs); they exist for the user's post-hoc inspection and as the data source for dossier.md's affects projects: rendering plus the chronology script's cross-project representative versions. The orchestrator MAY write them before invoking the workflow (Step 6.5.4) or after — implementations SHALL write them by end of Step 6.5 regardless. Writing before the workflow runs is preferred because the workflow's phase 4 synthesis reads <plan-dir>/cross-project-plan.json for the bump-set table's projects (locations) cell and the chronology headers. When aligning versions deterministically, the orchestrator MAY use the plugin script node ${CLAUDE_PLUGIN_ROOT}/scripts/semver-max-wins.mjs --scan-by-project <plan-dir>/scan-by-project.json (max-wins effectiveTarget + most-common representative currentVersion per package).

6.5.6 Workflow early-exit handling

The workflow can return one of three abort signals. The orchestrator SHALL handle each before advancing to Step 7:

  • Phase 0 cancel (Cancelled by stale-cleanup): print exactly Cancelled. No files modified. and exit 0. Steps 7–11 SHALL NOT execute. No plan-dir is created for this run (phase 0's cancel short-circuits before plan-dir creation).
  • Phase 1 hard-wall abort: surface the workflow's abort message verbatim. Skip Steps 7–11 (no override prompts, no gate, no apply, no Step 10c cleanup invocation). The plan-dir IS preserved on disk per the workflow's contract; the orchestrator SHALL NOT re-invoke the workflow for cleanup on this path.
  • Phase 3 integrity-gate abort: same as Phase 1 hard-wall — surface message verbatim, skip Steps 7–11, plan-dir preserved on disk.

For Phase 1 degrade-to-direct-synthesis (a non-abort outcome of the hard-wall prompt), the workflow proceeds to phase 4 and emits dossier.md with the degraded banner (research consolidated from the changelog cache). The orchestrator continues normally to Step 7 — the degraded path is NOT an early exit.

Step 7 — Render the cross-project bump set / dossier gate

Rendering branches on mode. Shallow mode generates the bump-set table inline from CrossProjectPlan. Deep mode references the workflow-produced dossier.md by path plus a bounded digest and appends the orchestrator-owned drift sections — it does NOT ingest the full dossier.

7.S — Shallow mode (mode === "shallow" or absent)

Render a single markdown table:

| package | proposed target | projects       | locations                  |
| ------- | --------------- | -------------- | -------------------------- |
| lodash  | ^4.17.22        | proj-A, proj-B | root, workspace:@scope/foo |
| ...     | ...             | ...            | ...                        |
  • Sort rows alphabetically by name (stable).
  • proposed target reflects the post-policy effectiveTarget. When use-max-where-possible produced a split, render the per-project values with a slash separator (e.g., ^4.17.22 / ~4.17.21 (proj-B)) and append a footnote * per-project under conflict policy.
  • projects lists project names where the package will be applied (deduplicated, comma-separated, registry insertion order).
  • locations lists the unique location strings across those projects (deduplicated, comma-separated).
  • Append Warnings: heading with each warning as a - bullet, when warnings[] is non-empty.
  • Append Skipped (scan-failed) (<N>): heading listing project names + error, when scanFailed[] is non-empty.
  • Append Skipped (path missing) (<N>): heading listing <name> — <path> bullets, when pathMissing[] is non-empty.

7.D — Deep mode (mode === "deep"): dossier gate rendering

When the workflow returned successfully in Step 6.5 (i.e. <plan-dir>/dossier.md exists at the plan-dir root):

  1. Reference <plan-dir>/dossier.md by absolute path (so the user can open it) and surface a bounded digest of the dossier — NOT its full content. The digest comprises:

    • the Cross-project bump set table (a bounded structured table; the workflow produced it from <plan-dir>/cross-project-plan.json — the orchestrator SHALL NOT regenerate it inline),
    • the improvement / workaround bullet titles with their affects projects: tags,
    • the ## Skipped or unavailable entries,
    • section presence counts (e.g. Breaking changes & migration: 4 items; Changelogs: 12 package blocks), plus any residual violations escalated by the workflow's two-layer compliance check.

    The digest SHALL NOT include the ## Changelogs bodies or any full research content; the main conversation SHALL NOT ingest the full dossier (main-window context diet). The dossier's five H2 sections (in order: Improvements (universal — applicability checked per project at apply time), Workarounds resolved, Skipped or unavailable, Cross-project bump set, Changelogs; ## Breaking changes & migration first at level ∈ {major, engines}) live on disk for the user to open.

  2. Append the orchestrator-owned drift sections after the digest, in this exact order. Each section is omitted when its count is zero:

    • **Warnings:** heading with each warning as a - bullet, when the orchestrator's warnings[] list (the running list across Step 5, Step 6.5.2 grouping-skill warnings, Step 6.5.3 mixed-pm warning, and any later source) is non-empty.
    • **Skipped (scan-failed) (<N>):** heading with <name>: <error> bullets, when scanFailed[] is non-empty.
    • **Skipped (path missing) (<N>):** heading with <name> — <path> bullets, when pathMissing[] is non-empty.

    These three sections are orchestrator-owned (they originate at Steps 2.2, 4.2, 5, and 6.5.3) — the workflow does NOT know about per-project scan failures or path-missing drift, so it cannot emit them in dossier.md. The orchestrator MUST append them at Step 7 rendering time.

  3. (level=major only) Append the ## PR plan. When level === "major", invoke the partition-breaking-changes skill and append its ## PR plan section (ordered buckets + count-by-policy summary; the section name ## PR plan is a retained legacy name — see the deep-update artifact glossary carve-outs) after the drift sections. Build its inputs from already-available data: bumpSet = the rows of the dossier's ## Cross-project bump set; breakingFindings = the per-package items under the dossier's ## Breaking changes & migration; depGraph = a per-project peerDependencies + import-site read (reuse ScanResultByProject); overrideFamilies = the shipped registry families. The ## PR plan is advisory cross-project — v1 isolation is one worktree per project (Step 9.5), NOT per bucket. For level ∈ {patch, minor, engines} this step SHALL NOT run (no ## PR plan is appended — output unchanged).

7.1 Empty-plan early exit

7.1.S — Shallow mode

If the post-policy plan has no apply-able packages (every package was scan-failed, path-missing, or skip-package-dropped):

  • Print any warnings and the literal line No <level> updates available across selected projects.
  • Exit 0 with no apply, no install, no override execution.

7.1.D — Deep mode

If the workflow's dossier.md reports zero bumps (the Cross-project bump set table has no data rows) AND zero improvements (Improvements (...) section body is the _no improvements identified_ sentinel) AND zero workarounds (Workarounds resolved section body is the _no workarounds resolved_ sentinel):

  • Print any orchestrator warnings (per the rules in Step 7.D, point 2 above).
  • Print exactly No <level> updates available across selected projects.
  • Exit 0 without invoking Step 8, Step 9, Step 10a/10b/10c, or Step 11.

The plan-dir is preserved on disk; the workflow's end-of-flow cleanup runs separately when the next deep-mode invocation hits phase 0 stale-cleanup (>10 days). The orchestrator SHALL NOT delete the plan-dir on the empty-plan exit path.

Step 8 — Override registry consultation

level=engines: this entire step is SKIPPED (package-name overrides have no meaning for runtime/PM surfaces). Treat OVERRIDE_RUN and OVERRIDE_SKIP as empty and proceed to Step 9. See "Level-conditional routing: level=engines".

Mode-independent. Step 8 runs identically in both shallow and deep modes — same registry path default, same first-win matching, same run-override / skip-matched / force-generic prompt, same OVERRIDE_RUN / OVERRIDE_SKIP / GENERIC partitioning. This is Decision 5 in design.md: cross-project deep mode IS consulted for overrides (explicit divergence from single-project npm-update-deep-patch, which deliberately skips overrides). Rationale: in cross-project context, Storybook-style families spanning multiple projects need the same coordinated handling shallow already provides; degrading to "run shallow first, then deep" would defeat the one-command UX.

Resolve overrides using the apply-npm-updates override-resolution procedure (R1–R3) for registry load, first-win matching, and {version} resolution — the shared procedure, NOT an inline copy of the algorithm. The cross-project prompt (8.4) and the cross-project resolution scope stay owned by this skill.

8.1 Load + match + resolve (procedure R1–R3)

Invoke the procedure with the registry path from overrideRegistryPath (default claude-plugins/experiments/skills/scan-npm-updates/data/pkg-upgrade-overrides.yaml) and the resolution source set to the cross-project aggregated proposedTarget set (NOT per-project):

  • R1 (load) — on a missing/unparseable registry the procedure prints Override registry unavailable: <reason>. Proceeding without overrides. and treats it as empty. Do NOT abort.
  • R2 (first-win glob match) — over the post-policy plan's package names. Build MATCHED_BY_ENTRY = { entry.id → [packages bound to this entry] }.
  • R3 (resolve + interpolate) — resolve {version} against the cross-project proposedTarget set: target-of:<name> → the proposedTarget of the package whose name == <name> in the cross-project plan (prefix-stripped); max-target-of:<glob> → the max semver across proposedTarget of packages whose name matches <glob> (prefix-stripped); latest → the literal latest; with fallbackVersionSource fallback. {version} resolution SHALL run against the cross-project aggregate, never per-project. On an unresolvable entry the procedure warns, drops the entry, and its matched packages rejoin the generic flow. Otherwise the resolved version is interpolated into command.

8.4 Prompt once per matched entry across the run

For each remaining entry, raise exactly one AskUserQuestion:

  • Question copy (verbatim, substitute fields):

    Override detected for {entry.id}. {entry.notes}
    Matched packages (across all projects): {comma-separated names}.
    Affected projects: {comma-separated project names where any matched package occurs}.
    Suggested command: {interpolated command}.
    {entry.reference ? "Reference: <url>" : ""}
    How do you want to handle this family across every affected project?
  • multiSelect: false

  • Options:

    • run-override — Execute the command once per affected project; skip generic ncu bump for these packages.
    • skip-matched — Leave these packages untouched in every project; do not run the override and do not bump generically.
    • force-generic — Ignore the override and bump these packages with the generic ncu flow in every affected project.

Record the chosen action per entry into OVERRIDE_ACTIONS: Map<entry.id, "run-override"|"skip-matched"|"force-generic"> along with the interpolated command.

8.5 Partition for apply

Compute three disjoint subsets per package:

  • OVERRIDE_RUN — packages bound to a run-override entry. The override command runs once per project that has at least one matched occurrence.
  • OVERRIDE_SKIP — packages bound to a skip-matched entry. Excluded from everything.
  • GENERIC — packages not bound to any entry, plus packages bound to a force-generic entry.

If every package in the post-policy plan is in OVERRIDE_SKIP and OVERRIDE_RUN is empty, print All accepted updates were skipped by override policy. Nothing to apply. and exit 0 without touching files. (ACCEPTED is defined in Step 9; this short-circuit happens before the user confirmation gate.)

Step 9 — User confirmation gate

Raise exactly one AskUserQuestion. The option set depends on mode.

9.S — Shallow-mode options (three)

  • Question copy: Apply <level> updates across <N> project(s)?
  • multiSelect: false
  • Options (in this exact order):
    • apply-all — Proceed with the entire (post-policy, post-override) plan.
    • pick-subset — Accept a free-form package-name list to exclude before apply.
    • cancel — Exit without modifying any file.

9.D — Deep-mode options (four)

  • Question copy: Apply <level> updates across <N> project(s)? (same as shallow)
  • multiSelect: false
  • Options (in this exact order):
    • apply-all — Proceed with the entire (post-policy, post-override) plan, INCLUDING the post-bumps per-project changeset gate round (Step 10b).
    • apply-bumps-only — Apply bumps + overrides + installs sequentially per project (Step 10a), but SKIP the changeset gate round (Step 10b) entirely. The Step 11 summary's Applied improvements section is omitted (zero items). All run-override decisions resolved in Step 8 still execute on this path because they were resolved before the gate.
    • pick-subset — Accept a free-form selection of the items to APPLY, combining improvement-bullet titles AND package names. Substring match (case-insensitive) for improvements; exact match for bumps. Selected improvements are the only bullets in scope for the changeset gate round (Step 10b); selected packages are the only bumps applied in Step 10a; unlisted items are skipped (9.2.D).
    • cancel — Exit without modifying any file. In deep mode the plan-dir IS preserved on disk and the orchestrator invokes Step 10c (end-of-flow cleanup) before exiting; in shallow mode there is no plan-dir.

9.1 apply-all

Let ACCEPTED = post-policy ∖ OVERRIDE_SKIP. Proceed to Step 10 (shallow) or Step 10a (deep).

9.2 pick-subset

9.2.S — Shallow pick-subset (package names only)

  1. Compute VALID_NAMES = unique names in the post-policy plan (post Step 6 conflict policy), then remove names in OVERRIDE_SKIP (post Step 8.5) — the resulting set is what the user can validly exclude.

  2. Ask the user (free-form message, no AskUserQuestion):

    Enter package names to exclude (comma-separated or one per line). Empty response means exclude none.
    Valid names: {comma-separated VALID_NAMES}
  3. Parse the response by splitting on commas and newlines, trimming whitespace, removing empty tokens. Result: EXCLUDED.

  4. If EXCLUDED === [] → treat as apply-all.

  5. Validate every name in EXCLUDED is in VALID_NAMES. On any invalid:

    • Print Unknown package name(s): {invalid names}. Valid names: {VALID_NAMES}.
    • Re-prompt step 9.2.S.2.
  6. Let ACCEPTED = post-policy \ {names in EXCLUDED} \ {names in OVERRIDE_SKIP} (set difference: drop both excluded and override-skipped packages — OVERRIDE_SKIP MUST NOT be re-included by pick-subset). Let SKIPPED_BY_USER = EXCLUDED.

  7. If ACCEPTED is empty after exclusion → print All updates excluded; nothing to apply. and exit 0 without touching files.

  8. Otherwise proceed to Step 10.

9.2.D — Deep pick-subset (package names AND improvement titles)

Free-form selection over both improvement bullets and package bump names (mirrors single-project npm-update-deep-patch.md Step 6c).

  1. Compute VALID_BUMP_NAMES = unique names in the post-policy plan (post Step 6 conflict policy), then remove names in OVERRIDE_SKIP — same as shallow VALID_NAMES.

  2. Compute VALID_IMPROVEMENT_TITLES = the leading title text of each - bullet under the ## Improvements (universal — applicability checked per project at apply time) heading in the workflow's dossier.md (a bounded titles-only read — no research bodies). The title is the prefix before the (group: ...; affects projects: ...) parenthetical — typically formatted as {package}: {opportunity description} or [{priority}] {package} — {opportunity}.

  3. Ask the user (free-form message, no AskUserQuestion):

    Enter the IDs to apply (comma-separated or one per line). Use plan-line excerpts for improvements
    (case-insensitive substring match), package names for bumps. Empty response cancels.
    Improvements: {comma-separated VALID_IMPROVEMENT_TITLES}
    Bumps: {comma-separated VALID_BUMP_NAMES}
  4. Parse the response: split on commas and newlines, trim whitespace, drop empty tokens. Result: SELECTIONS.

  5. Empty SELECTIONS → equivalent to cancel (Step 9.3). Print Cancelled. No files modified. and proceed to Step 10c cleanup + Step 11 summary (with the cancel section).

  6. For each token in SELECTIONS, classify it as:

    • An improvement if it matches an entry in VALID_IMPROVEMENT_TITLES via case-insensitive substring (the token is a substring of a valid title).
    • A bump if it matches an entry in VALID_BUMP_NAMES exactly (case-sensitive).
    • Unknown if it matches neither.

    Classification MAY be delegated to the deterministic plugin script node ${CLAUDE_PLUGIN_ROOT}/scripts/validate-subset.mjs (input { selection, bumpNames, improvementTitles }; output { bumpMatches, improvementMatches, unmatched } — matched tokens are the items to APPLY) — same semantics, no prose drift.

  7. On any unknown tokens:

    • Print Unknown selection(s): {invalid items}. Valid improvements: {VALID_IMPROVEMENT_TITLES}. Valid bumps: {VALID_BUMP_NAMES}.
    • Re-prompt step 9.2.D.3.
  8. Compute:

    • ACCEPTED_BUMPS = post-policy ∖ OVERRIDE_SKIP, restricted to names in the bump-classified selections.
    • ACCEPTED_IMPROVEMENTS = improvement bullets whose title text matches at least one improvement-classified selection (case-insensitive substring).
    • SKIPPED_BY_USER = VALID_BUMP_NAMES \ ACCEPTED_BUMPS (package names the user chose to exclude).
    • SKIPPED_IMPROVEMENTS_BY_USER = VALID_IMPROVEMENT_TITLES \ ACCEPTED_IMPROVEMENTS (improvement bullets the user chose to exclude — appear in Step 11 Skipped improvements with (excluded via pick-subset)).
  9. If both ACCEPTED_BUMPS is empty AND ACCEPTED_IMPROVEMENTS is empty → treat as cancel (Step 9.3). Print Cancelled. No files modified. and proceed to Step 10c + Step 11.

  10. Otherwise proceed to Step 10a with ACCEPTED = ACCEPTED_BUMPS (Step 10a filters by name) and Step 10b with ACCEPTED_IMPROVEMENTS as the in-scope bullets only.

9.3 cancel

Print exactly:

Cancelled. No files modified.

In shallow mode: exit 0 without touching files. Do NOT run any apply, install, or override command.

In deep mode: the plan-dir exists (Step 6.5 created it). Do NOT run any apply, install, or override command, but DO invoke Step 10c (end-of-flow cleanup) before rendering the Step 11 summary. The summary's H1 SHALL be the deep H1 (## commander-update-deep-<level> summary) and the summary SHALL contain a single body line Cancelled. No files modified. plus the always-rendered Suggested next steps section.

Step 9.5 — Optional isolation gate (default none, both modes)

After the Step 9 gate resolves to an apply path (apply-all / apply-bumps-only / pick-subset with a non-empty accepted set) and before Step 10, raise exactly one AskUserQuestion offering branch/worktree isolation. On Step 9 cancel, this step SHALL NOT run.

  • Question copy: Isolate these updates before applying?
  • multiSelect: false
  • Options (in this exact order):
    • none — Apply each project in its current working tree (default; no VCS action — byte-equivalent to pre-isolation behavior).
    • worktree — For each resolved project, create a branch + worktree via update-isolation (worktrunk-preferred) and apply there; the project's current checkout stays untouched.
    • branch — For each resolved project, create a branch in place via update-isolation and apply on it.

Build ISOLATION_BY_PROJECT (consumed by Step 10.2/10.3):

  • none → for every project, workdir = <record.path>, no VCS action.
  • worktree / branch → for each resolved project, invoke the update-isolation skill once with { projectPath: <record.path>, branchName: "deps/<level>-<YYYY-MM-DD>", strategy: <worktree → "auto"; branch → "branch"> }; record the returned workdir and installAlreadyRan. v1 cross-project isolation is one worktree per project — the deep-major ## PR plan buckets stay advisory; per-(project, bucket) worktrees are deferred.

update-isolation creates a branch/worktree only — it never commits, pushes, or opens a PR. On any update-isolation failure it degrades to none for that project (apply in place) with a surfaced note, never aborting the run.

Step 10 — Sequential apply (one project at a time, stop-on-fail)

The apply step splits by mode.

  • Shallow mode (mode === "shallow" or absent): a single per-project bumps loop with no changeset gate round and no end-of-flow cleanup invocation. The existing 10.1–10.6 sub-steps apply unchanged. The orchestrator returns after Step 10.6 (or on stop-on-fail) and renders Step 11.

  • Deep mode (mode === "deep"): split into three phases:

    1. Step 10a — Bumps loop (identical to shallow Step 10.1–10.6, except failure handling: a per-project failure pauses the run at the per-project failure gate — see 10.6.D).
    2. Step 10b — Per-project changeset gate round for improvements (conditional — see 10b's gating below).
    3. Step 10c — End-of-flow cleanup invocation (runs on every deep path except workflow-abort paths).

    A Step 10a failure removes the failed and unattempted (pending) projects from Step 10b; projects that DID apply bumps successfully still get their changeset gate round (see 10b's gating). Step 10c always runs (the plan-dir exists and the user deserves a cleanup decision).

Step 10a — Bumps loop (both modes; renamed from "Step 10" for shallow)

Iterate the resolved project set in registry insertion order (already preserved through Steps 2–9). For each project:

10.1 Compute the per-project subset

Collect occurrences in ACCEPTED whose projectName matches this project. Apply:

  • The chosen conflict policy (Step 6) — drop occurrences for skip-package-dropped packages; preserve per-project effectiveTarget under per-project; honor partition under use-max-where-possible.
  • The override partition (Step 8.5) — drop occurrences in OVERRIDE_SKIP; route OVERRIDE_RUN packages to the apply spec's overrideCommands; route everything else (GENERIC) to the apply spec's manifestBumps / catalogEdits (built in Step 10.3).
  • The user exclusion (Step 9.2) — already excluded from ACCEPTED.

If the per-project subset is empty (no generic occurrences AND no override entries touch this project), skip apply AND install for this project. Continue to the next.

10.2 Set the working directory

Resolve this project's apply directory from ISOLATION_BY_PROJECT (Step 9.5): WORKDIR = ISOLATION_BY_PROJECT[project].workdir — which is <record.path> under none, or the isolation branch/worktree path otherwise. For every Bash invocation in the apply for this project, prepend cd "<WORKDIR>" && (or use absolute paths for ncu's --packageFile). The skill SHALL NOT mutate the user's shell state across iterations.

10.3 Build the per-project apply spec and invoke apply-npm-updates

level=engines: invoke apply-engine-bumps (capability engine-update-apply) for this project instead of apply-npm-updates. Pass { cwd: WORKDIR, inventory: <this project's EngineSurfaceInventory from Step 4>, resolvedTargets: <the per-engine targets resolved once in Step 6>, ambiguousResolutions: <any ambiguous loci the user resolved before the gate>, confirmed: true }. No ncu, no manifestBumps/catalogEdits/overrideCommands spec. Fold the returned { resolvedTargets, applied, skipped, droppedHashes, failure? } fragment into this project's summary entry; on a non-null failure apply Step 10.4/10.6 stop-on-fail exactly as below (the failure.step is resolve/write rather than ncu/catalog/override/install — surface its detail in the cross-project abort copy). The rest of 10.1–10.6 (subset, workdir, stop-on-fail) is unchanged. See "Level-conditional routing: level=engines".

The apply-npm-updates skill is the single source of truth for the per-project mechanical apply (generic ncu package.json bumps, catalog source edits — pnpm-workspace.yaml for pnpm, the root package.json for Bun — override commands, single install). The orchestrator builds the resolved spec for this project and invokes the skill once; it SHALL NOT restate the ncu / catalog / install recipe inline.

Build the spec from this project's subset (Step 10.1):

  • packageManager = this project's ScanResult.packageManager. cwd = WORKDIR (Step 10.2 — <record.path> under none, else the isolation branch/worktree). target = the orchestrator's target input (passed unchanged — the target → ncuTarget mapping, major→latest, and the exact-pin --removeRange write are owned by apply-npm-updates, the single source of truth). cooldown = the value scan-npm-updates resolved for this project (omit for pnpm). (This step runs only for target ∈ {patch, minor, major}; target=engines routes to apply-engine-bumps per the note above and never reaches this spec.)
  • manifestBumps — one element per distinct GENERIC package.json sourceFile: { sourceFile, names: <GENERIC names for this file, space-separated>, includeFilter }. Set includeFilter: true when any of: the user picked pick-subset and excluded ≥1 package for this project; any update for the file was removed by OVERRIDE_RUN/OVERRIDE_SKIP; or the conflict policy is use-max-where-possible and ncu's full set ≠ this project's effective subset. Otherwise false (ncu's own set equals the target set for this file). Additionally, when target is major (it maps to ncu --target latest), includeFilter SHALL ALWAYS be true for every element regardless of the above — the per-project names list is authoritative, preventing over-bumping dependencies that scan-npm-updates deliberately excluded. (apply-npm-updates also forces the filter for latest-mapped targets; this explicit set is defense-in-depth and keeps the spec readable. The patch/minor branch is unchanged.)
  • catalogEdits — one element per GENERIC occurrence whose location is catalog:default / catalog:<name> (pnpm pnpm-workspace.yaml or Bun root package.json): { name, targetVersion: <effectiveTarget>, catalogSource: <the scan record's catalogSource> }. Threading catalogSource lets apply-npm-updates target the exact source node; omitting it falls back to the legacy pnpm default.
  • overrideCommands — the OVERRIDE_RUN entries that touch this project, as { id, command: <interpolated command> }, in declaration order (run once per affected project).
  • skipInstalltrue when every accepted package for this project went through run-override AND no generic ncu bump ran AND no catalog edit happened for this project (every override handles its own install); also true when Step 9.5's update-isolation reported installAlreadyRan for this project's worktree (a worktrunk hook already installed); otherwise false.

Invoke apply-npm-updates once with this spec and cwd: <record.path>, passing runDir: <plan-dir> in deep mode (shallow mode omits it; the skill logs to a temporary path). The skill redirects ncu / install / override stdout/stderr to an on-disk run log — one-line digests to the conversation, a bounded tail (≤ ~40 lines) on failure only, never verbatim streaming — and returns { appliedGeneric, appliedOverrides, installRan, logPath, failure }. Fold the returned fragment into this project's entry of the cross-project summary (Step 11).

10.4 On structured failure — format the cross-project failure copy

If apply-npm-updates returns a non-null failure, print the orchestrator-owned cross-project failure copy for the failing step (the skill never prints this copy — it only surfaces the bounded log tail), then apply the mode's failure handling (Step 10.6). In deep mode, replace each template's closing line Stopping the run. Subsequent projects not attempted. with the per-project failure gate (10.6.D) — whether subsequent projects are attempted is the user's

Truncated - read the full file at https://github.com/pabloimrik17/monolab/blob/0145297bd1f6b1dc2833d8e357e17b4d46f9351b/claude-plugins/experiments/skills/commander-update-orchestrator/SKILL.md.

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/pabloimrik17-monolab-commander-update-orchestrator/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

pabloimrik17-monolab-commander-update-orchestrator.ocm.jsonjson
{
  "ocm": "1",
  "id": "pabloimrik17-monolab-commander-update-orchestrator",
  "kind": "skill",
  "name": "commander-update-orchestrator",
  "description": "Use when a Commander update command (`/experiments:commander-update-{patch,minor,major,engines}` and their deep variants) needs to apply npm dependency or toolchain-engine updates across every project registered in the user-scoped Commander registry. Owns the cross-project pipeline — list+filter projects, parallel scan dispatch, deduplicate updates, version-align (max-wins with per-project fallback; engine-version alignment at `level=engines`), dossier gate rendering (path + bounded digest in deep mode), sequential apply with stop-on-fail, per-project changeset gate with an apply teammate (deep mode), aggregated summary. Read-only against the registry; dependency-level writes go to each project's own manifests via `ncu --upgrade` + one `<pm> install` (output to on-disk logs, digest to the conversation), engines-level writes via `apply-engine-bumps` (runtime surfaces, no ncu). Never commits/pushes/opens PRs autonomously.",
  "publisher": "pabloimrik17",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "coding"
    ],
    "tags": [
      "skill-md",
      "github"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Use when a Commander update command (`/experiments:commander-update-{patch,minor,major,engines}` and their deep variants) needs to apply npm dependency or toolchain-engine updates across every project registered in the user-scoped Commander registry. Owns the cross-project pipeline — list+filter projects, parallel scan dispatch, deduplicate updates, version-align (max-wins with per-project fallback; engine-version alignment at `level=engines`), dossier gate rendering (path + bounded digest in deep mode), sequential apply with stop-on-fail, per-project changeset gate with an apply teammate (deep mode), aggregated summary. Read-only against the registry; dependency-level writes go to each project's own manifests via `ncu --upgrade` + one `<pm> install` (output to on-disk logs, digest to the conversation), engines-level writes via `apply-engine-bumps` (runtime surfaces, no ncu). Never commits/pushes/opens PRs autonomously."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "github",
      "repository": "https://github.com/pabloimrik17/monolab",
      "path": "claude-plugins/experiments/skills/commander-update-orchestrator/SKILL.md",
      "ref": "0145297bd1f6b1dc2833d8e357e17b4d46f9351b",
      "url": "https://github.com/pabloimrik17/monolab/blob/0145297bd1f6b1dc2833d8e357e17b4d46f9351b/claude-plugins/experiments/skills/commander-update-orchestrator/SKILL.md",
      "key": "pabloimrik17/monolab/claude-plugins/experiments/skills/commander-update-orchestrator/SKILL.md"
    }
  },
  "instructions": "# commander-update-orchestrator\n\nCross-project npm-update orchestration. Parameterized by `level` / `target`, so every `commander:update-*` command (and the deep variants) drops into the same plumbing without re-implementing fan-out / fan-in.\n\n## When to use\n\n- Invoked by `/experiments:commander-update-patch` with `level=patch`, `target=patch` (shallow mode, default).\n- Invoked by `/experiments:commander-update-deep-patch` with `level=patch`, `target=patch`, `mode=deep` (deep mode, MON-199).\n- `commander-update-{minor,major,engines}` and `commander-update-deep-{minor,major,engines}` wire the m",
  "cost": {
    "context_tokens": 21607
  }
}

Fetch it by URL: GET /api/v1/registry/pabloimrik17-monolab-commander-update-orchestrator/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.