feat(claude-ops): add plugins skill for plugin-fleet sync/audit/converge#248
Conversation
Locked contract for /claude-ops:plugins (sync/audit/converge): interview Brief, design early-exit resolution, and the stress-tested six-phase implementation plan. Phase 1 carries the USER-RESERVED go/no-go on whether `plugin update -s project` writes committed settings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…phase 1) Empirically resolve the plan's three unknowns before authoring the plugins skill: `plugin update -s project` does not write committed settings (hash-verified in a real dual-scope repo), `/reload-plugins` has no `--force` flag, and `userConfig` has no `enum` support. Snapshot the live installed_plugins.json / known_marketplaces.json shapes into scrubbed fixtures for phase 2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…n (phase 2) Read-only JSON state report over CC's internal plugin-state files: catalog completeness, install completeness, and scope divergence per marketplace, with --marketplace/--all and a dynamic default-marketplace resolution that never hardcodes a marketplace name. Ships with an 11-case TDD suite covering divergence, missing-install, missing-enabled vs explicit opt-out, marketplace-absent, malformed-shape fail-loud, the native-Windows-path-vs-Git-Bash CRITICAL case, and jq-missing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ivergence fleet-state.sh's divergences flagged every multi-scope install record as a conflict, but a project pin and a user pin at the identical version isn't one — it's a normal, non-actionable state. A raw record count inflates the report's "N behind, run converge" line with entries that need no action (2 of 31 on this machine). Add versionsMatch so the report layer can filter to the actionable subset (29 here) instead of the raw count. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… docs (phase 3) SKILL.md action router (sync default / audit / converge) plus four context files: the sync algorithm (marketplace refresh, in-repo update, user-scope sweep, install-new per policy, enabledPlugins completeness), the converge scope-consolidation flow with autonomous-session abort, verified scope/version/reload semantics, and known failure modes. Also fixes fleet-state.sh's missing_from_install, which didn't yet exclude ids explicitly opted out (enabledPlugins: false) before ever being installed — found while grounding sync.md's "Missing" definition in the script's actual behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6 cases covering routing (bare invocation -> sync), audit's read-only guarantee, install_new=ask batched-prompt behavior, converge's autonomous-session abort, the divergence report's bulk-collapse vs per-row-conflict shape, and the never-hand-edit-internal-state anti-pattern. Passes check-skill.sh with 0 errors/0 warnings. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
version, description, CHANGELOG, README skills table + configuration entry for the new plugins skill and its install_new userConfig option. Regenerates the repo-root README's plugin catalog block, which went stale against the updated claude-ops description. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…KILL.md
SKILL.md pointed at ${CLAUDE_PLUGIN_ROOT}/scripts/fleet-state.sh; the
script actually lives at skills/plugins/scripts/fleet-state.sh relative
to the plugin root. Caught by a real end-to-end /claude-ops:plugins
audit run against a --plugin-dir dev-loop session (phase 6).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…phase 6) Found via real end-to-end execution against the actual skill (the repo's --plugin-dir dev-loop convention, not a simulation): - fleet-state.sh's currentProject stayed null in a real headless session because CLAUDE_PROJECT_DIR isn't reliably exported there. Falls back to the cwd's git toplevel now (CLAUDE_PROJECT_DIR stays authoritative when set), with a regression test for the fallback path that failed against the unpatched script and passes now. - Documents that `claude plugin update` rejects a bare plugin name even when unambiguous -- always pass the fully-qualified id. - Removes a hardcoded marketplace name that leaked into gotchas.md's worked example, caught by this repo's own plugin-contract validator. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… boundary audit is live-verified end-to-end; sync's Step 2 (in-repo update) and converge were never exercised against real divergence this session. Caught by advisor review before declaring the plan complete. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… plugins skill /verification:confirm's fresh-context verifier found that converge.md and SKILL.md described an enable-state mismatch (true in one scope, false in another) as detected-but-unresolved, when fleet-state.sh never exposes each scope's raw enabledPlugins map and genuinely cannot detect it. Reword both docs to state the real blind spot, and swap evals.json's eval 5 off a scenario the model could only pass by echoing a fact handed to it in the prompt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every other *.test.sh in this repo is 100755; these two new scripts carry a shebang but were committed at 100644. CI's exec-bit lane (scripts/aggregate-hygiene-results.sh via the shared exec-bit action) would have flagged this. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mis-attribution tripped the typos hygiene lane; reworded to wrong attribution. Caught by locally mirroring CI's hygiene lane before PR creation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 96981c38bc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…e PR run CI's machine-specific-paths lane flagged a hardcoded D:\repos\...\medley path in PLAN.md's Phase 1 open questions; genericized to a placeholder. CI's plugin-gate lane failed a test that passed locally: the "jq missing" case ran the bare script against default file paths instead of routing through run_state's fixture builder like every other case. On this dev machine a real Claude Code install satisfies those defaults so the script reached its jq check; on a clean CI runner the file-not-found check fires first and the case never exercises what it claims to test. Fixed by building a real fixture dir and only stripping jq from PATH, isolating jq-absence as the sole variable. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 114e31845b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
command -v jq only reports the first PATH match. This machine has a single jq, so stripping its directory worked locally, but the CI runner's ubuntu-24.04 image resolves jq from a second PATH directory too — the "jq missing" case silently ran with jq still present and exercised the wrong branch. Walk all of PATH and drop every directory that actually contains an executable jq. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f403c5207a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Claude finished @kyle-sexton's task in 4m 27s —— View job Code Review — feat(claude-ops): plugins skill
Overall the design is solid — That said, I found two confirmed bugs (one severe, one latent on certain CI configurations) and three correctness/documentation gaps that will cause silent misbehavior in production use. Details below, most-severe first. 🔴 Bug —
|
Directory-exclusion PATH filtering (drop every dir containing jq) is not universally safe: on this dev machine jq lives in its own directory, so excluding it is harmless, but a GitHub Actions ubuntu runner colocates jq with bash and coreutils in /usr/bin, so excluding jq's directory there also removed bash, breaking the case with "env: 'bash': No such file or directory" before the script ever reached its jq check. Verified empirically that a chmod-000 decoy jq earlier on PATH doesn't help either — command -v skips it and still finds the real jq further down PATH. Fixed by building an isolated PATH containing copies (never symlinks — those need elevation on Windows) of only the tools fleet-state.sh needs before its jq check: dirname, and bash itself to launch the interpreter, plus any colocated *.dll bash.exe needs on Windows/MSYS (a no-op on Linux). This no longer depends on jq's location at all. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 41s —— View job Re-review after
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88bd1ecda7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…missing_from_enabled false positive
Two real bugs found by Codex's PR review, both verified by direct
reproduction before fixing:
- emit_marketplace ran a marketplace's catalog JSON through the fatal
require_json helper, which exit-2's the whole process. A malformed
(not merely absent) catalog for one marketplace during --all silently
killed the entire sweep before any {marketplaces: ...} report was
emitted for any marketplace, not just the broken one. Fixed to match
the sibling error branches: report the error inline and return 1.
- missing_from_enabled compared every project/local install machine-wide
against only the current PROJECT_ROOT's settings files. A plugin
already enabled in a different repo's own .claude/settings.json (which
this script never reads) showed up as a false-positive
missing_from_enabled, which sync's Step 5 could act on from the wrong
repo. Fixed to only consider ids this invocation can actually verify:
user scope (global) and the current repo's project/local scope;
other-repo project/local installs are excluded, not asserted missing.
Both regression-tested (fixture-reproduced red, fixed, green): 17 cases,
0 failed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…repo risk, stale --force claim, unrendered install_new
Four more findings from Codex's PR review, each independently verified
against current docs, actual CLI behavior, or the script's own output
before fixing:
- sync.md Step 2 gated in-repo update on CLAUDE_PROJECT_DIR being set
directly, bypassing fleet-state.sh's own git-toplevel fallback — the
exact case that fallback exists for. It also pre-filtered on
divergences[] with versionsMatch:false, but divergences[] only holds
ids with more than one scope record, so a single-scope project/local
install (the common case) or a multi-scope install stale in every
scope alike never appeared there and was silently never updated,
defeating the skill's own documented "primary value path." Fixed to
always call fleet-state.sh and unconditionally update every
currentProject:true entry, mirroring Step 3's own pattern.
- converge.md's proposed -s project/-s local commands carried no path
targeting, but the CLI always operates on the current directory's
settings — a divergence row can legitimately belong to a different
repo than the one converge is running from. Fixed to require an
explicit cd into the row's own projectPath before every project/local
command.
- "/reload-plugins has no --force flag" was wrong: current docs
(code.claude.com/docs/en/discover-plugins) document --force
(Claude Code >=2.1.163) for the case where an updated MCP-server
plugin's non-deferred tools would otherwise invalidate the prompt
cache. Fixed everywhere this claim appeared (SKILL.md, sync.md,
scope-semantics.md, evals.json) to recommend --force only when that
specific condition applies, not as a blanket default.
- install_new's userConfig value was never actually rendered anywhere
in this skill's content via ${user_config.install_new} — Claude Code
only text-substitutes a configured value where that literal
placeholder appears, so the model had no way to read the configured
policy at runtime despite sync.md's Step 4 branching on it. Fixed by
rendering the value in SKILL.md's userConfig section and pointing
sync.md's Step 4 at that render instead of the option's static prose.
The marketplace-CLI-syntax finding from the same review pass was
verified INCORRECT (claude plugin marketplace update <name> is
documented CLI syntax at plugin-marketplaces.md's "non-interactive
subcommands for scripting" section) and needs no fix, replied to on
GitHub with the citation instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 706633bba5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ssing_from_enabled Codex's PR review (on the previous fix push) found that Step 5 treated a plugin with no explicit enabledPlugins entry as a completeness gap needing claude plugin enable, even when the marketplace entry declares defaultEnabled:false — a publisher's deliberate opt-in-required default (plugins-reference.md: takes precedence over plugin.json's own field, "the user turns it on with claude plugin enable"). No entry anywhere is the intended state for those ids, not something sync should auto-flip. Fixed by reading defaultEnabled per catalog plugin from the same marketplace.json fleet-state.sh already parses for the catalog list, and excluding those ids from missing_from_enabled. Documented the one residual gap this doesn't cover (a plugin.json-only defaultEnabled with no mirrored marketplace-entry override — fleet-state.sh never reads an installed plugin's own manifest). Regression-tested: confirmed red before the fix, green after. 18 cases, 0 failed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…onsolidation strategy Codex's PR review found that Step 2 chose "uninstall project/local, fall through to user scope" whenever a user-scope entry existed at all, with no check that user scope actually held the newer version. divergences[] only proves scopes disagree, not which one is ahead — a repo pinning project:0.9.0 against a stale user:0.8.0 would have its project pin uninstalled in favor of the older user record, regressing the effective loaded version. That's the opposite of what converge exists to do, and the one action that can touch committed settings is exactly where a silent regression is worst. Fixed: Step 2 now compares scopes[].version (fleet-state.sh already emits it per scope) to find the actual highest version first, then picks the strategy from that — user scope only wins when it's genuinely the newest; otherwise every lagging scope, user included, updates to match the highest version present. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0d6f1f73f5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Re-review after
|
| Finding | Status |
|---|---|
🔴 require_json/exit 2 corrupts --all output |
✅ Fixed e5f1b37 |
| 🔴 jq-missing test strips bash from PATH | ✅ Fixed 88bd1ec |
🟡 missing_from_enabled spans all projects |
✅ Fixed e5f1b37 |
🟡 sync.md Step 2 gates on CLAUDE_PROJECT_DIR env var |
✅ Fixed 706633b |
🟡 sync.md Step 2 divergence gate misses single-scope installs |
✅ Fixed 706633b |
🟡 converge.md missing cwd context for cross-project commands |
✅ Fixed 706633b |
⚪ /reload-plugins --force claim unverified |
✅ Fixed 706633b |
🟡 defaultEnabled: false plugins auto-enabled by Step 5 |
✅ Fixed 348dd52 |
🟡 converge could regress to stale user pin |
✅ Fixed 0d6f1f7 |
🟡 ascii_downcase unconditional in currentProject filter |
Open |
🟡 sync.md Step 5 iterates all scopes (including other-repo) |
Open |
Summary
Two 🟡 findings remain. Neither is a merge blocker at the severity of the prior two 🔴s — the case-sensitivity issue only manifests on POSIX systems with case-differing sibling directories (genuinely unusual), and the Step 5 scope-restriction gap is a doc-level fix for the scan already correctly scoping the missing_from_enabled list. Both are real and worth fixing before this skill sees broad use, but the core script logic and the previously-breaking test paths are all solid.
…risons Codex's PR review found that currentProject and default-marketplace resolution both applied ascii_downcase to path comparisons unconditionally, mirroring hook::normalize_path's Windows-only case fold everywhere else calls it. hook::normalize_path itself is already platform-conditional (case-fold only on msys/cygwin/win32; pass through unchanged on POSIX, where filesystems are case-sensitive), but these two inline jq comparisons reimplemented the fold without the platform check. On a case-sensitive host (e.g. GitHub's ubuntu CI runner), two genuinely different sibling repos differing only by case (/work/repo vs /work/Repo) would compare equal, and since sync now unconditionally updates every currentProject:true entry, a false match could steer a project-scope mutation at the wrong repo. Fixed by computing the same $OSTYPE-based case-insensitive flag hook::normalize_path uses and gating both comparisons on it instead of folding case unconditionally. Regression-tested: same fixture run twice, once forcing a POSIX OSTYPE (must not match) and once with this host's real OSTYPE (must still match — no regression to the existing Windows behavior). Confirmed red against the unpatched script before fixing. 19 cases, 0 failed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Codex's PR review found that even after the missing_from_enabled fix excluded other-repo project/local installs from the missing set, an id that legitimately IS missing_from_enabled (e.g. a genuine user-scope gap) could still carry an unrelated other-repo project/local install record in installed[]. Step 5's per-scope loop iterated every scope with an install record for that id, including that unread other-repo record, and would run claude plugin enable -s project|local for it — risking a mutation at the current repo or an unread repo, the same wrong-repo class of bug already fixed in converge.md and missing_from_enabled itself. Fixed: Step 5's loop is now explicitly restricted to user scope, or project/local scope with currentProject:true — the exact same restriction already applied to missing_from_enabled's computation, extended to the downstream action that consumes it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1de4658fe0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ministic The "no regression" half of the case-sensitivity test asserted the differently-cased fixture would still match under "this host's real OSTYPE," which only holds on the case-insensitive Windows dev machine this was authored on — CI's ubuntu runner has a genuinely case-sensitive OSTYPE, so the fix (working correctly) produced false there too, and the test's own hardcoded "true" expectation was wrong for that platform. Fixed by forcing both halves of the test explicitly (OSTYPE=linux-gnu / OSTYPE=msys) instead of relying on either host's ambient value, so the assertions hold identically everywhere. 19 cases, 0 failed, verified on this machine. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6d751174e0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Claude finished @kyle-sexton's task in 5m 12s —— View job Re-review after
|
| Finding | Status |
|---|---|
🔴 require_json/exit 2 corrupts --all output |
✅ Fixed e5f1b37 |
| 🔴 jq-missing test strips bash from PATH | ✅ Fixed 88bd1ec |
🟡 missing_from_enabled spans all projects |
✅ Fixed e5f1b37 |
🟡 sync.md Step 2 gates on CLAUDE_PROJECT_DIR env var |
✅ Fixed 706633b |
🟡 sync.md Step 2 divergence gate misses single-scope installs |
✅ Fixed 706633b |
🟡 converge.md missing cwd context for cross-project commands |
✅ Fixed 706633b |
⚪ /reload-plugins --force claim unverified |
✅ Fixed 706633b |
🟡 defaultEnabled: false plugins auto-enabled by Step 5 |
✅ Fixed 348dd52 |
🟡 converge could regress to stale user pin |
✅ Fixed 0d6f1f7 |
🟡 ascii_downcase unconditional in currentProject filter |
✅ Fixed 6b42033 |
🟡 sync.md Step 5 iterates all scopes (including other-repo) |
✅ Fixed 1de4658 |
| 🟡 Case-fold test fails on case-sensitive CI host | ✅ Fixed 6d75117 |
🟡 missing_from_install uses all-scope ids, misses user-scope gap |
Open (from 1de4658 Codex, unanswered) |
| 🟡 Per-plugin shape drift not caught by shape check | Open (from HEAD Codex) |
🟡 --marketplace with no name infinite-loops |
Open (from HEAD Codex) |
Summary
6d75117 correctly closes the case-fold test OS-awareness finding with a clean explicit-OSTYPE approach. All prior 🔴 and 🟡 findings from the full review cycle are resolved. The remaining three are genuine but none rises to the severity of the earlier blockers — the infinite-loop is only reachable on malformed manual invocation, the shape-drift check is a defensive-robustness gap, and the missing_from_install semantic question is a design clarification. The core script logic, test suite (17/17 cases), and skill documents are solid.
| Branch
) ## Summary Closes the three deferred, independently-corroborated (Codex + claude[bot]) review findings from PR #248's review cycle against `plugins/claude-ops/skills/plugins/scripts/fleet-state.sh`. Each was verified VALID but deferred out of #248 to bound an already-long (6+ round) fix/review loop. All fixed here with TDD regression tests (red-then-green). Version bumped `0.15.4` → `0.16.0` (minor: adds a new consumable output field). ## Fix **1 — user-scope install completeness (additive, no behavior change to the existing field).** `fleet-state.sh` now emits a new field `missing_from_user_install` = catalog minus *user-scope*-installed minus opted-out, alongside the untouched all-scope `missing_from_install`. A plugin installed only at `project`/`local` scope was present all-scope (absent from `missing_from_install`) and so was never offered a `user`-scope install by `sync` Step 4 — silently breaking the "usable from any directory" guarantee for it. `sync.md` Step 4 now keys its user-scope install offer off the new field. The existing `missing_from_install` field and its all-scope semantics are unchanged. Chosen approach: the additive option from the issue (new field), not the redefinition option — no behavior change to any existing consumer. Beyond the literal instruction: `SKILL.md`'s report template ("Action needed") reference was also switched to `missing_from_user_install`, so the report and Step 4 name the same signal (safe — `missing_from_user_install` ⊇ `missing_from_install` always, since user-installed ⊆ all-installed). `scope-semantics.md`'s reference was deliberately left on `missing_from_install` — it documents the preserved all-scope field's rename-coverage semantics. **2 — per-plugin shape validation.** The shape check validated `.plugins` is an object but not that each entry is an array. A drifted non-array entry passed the check, then failed inside the installed-flatten `jq` pipeline in a command substitution; with `set -uo pipefail` but no `set -e` the failure was swallowed and the script exited **0** with `installed: []` instead of failing loud per its stated design. The check now also asserts `(.plugins | to_entries | all(.value | type == "array"))` and exits 2 on drift. **3 — `--marketplace` with no name.** `fleet-state.sh --marketplace` (no following arg) set `TARGET=""`, then `shift 2` failed silently (only one positional param left, no `set -e`), leaving `$1` unchanged so the arg loop re-read `--marketplace` forever; the post-loop `-z "$TARGET"` guard was never reached. The empty check now runs inside the `--marketplace` branch before `shift`, exiting 2. The now-dead post-loop guard was removed. ## Verification TDD: three regression cases were added and run **against the unfixed script first** to prove each bug, then the fixes were applied. Before (unfixed script — the three new cases red, proving each bug): ``` FAIL: user-scope: project-only + never-installed both surface, user-scope install excluded FAIL: user-scope: opt-out excluded from missing_from_user_install too FAIL: non-array plugin entry: exit 2 detail: expected exit 2, got 0 # #2: swallowed failure — exited 0 with installed:[] FAIL: --marketplace no arg: exit 2, not an infinite loop detail: expected exit 2, got 124 # #3: 124 = timeout killed the infinite loop FAIL: --marketplace no arg: actionable error ``` After (fixed script — full suite green): ``` PASS: --marketplace no arg: exit 2, not an infinite loop PASS: --marketplace no arg: actionable error 23 cases, 0 failed ``` `shellcheck` (both scripts) and `markdownlint-cli2` (all changed markdown) report clean: ``` SHELLCHECK CLEAN Summary: 0 error(s) ``` Closes #254 ## Related - Closes #254 — the tracking issue enumerating all three findings. - Follow-up from PR #248 (`feat/claude-ops-plugins-skill`), the origin review cycle where these three were verified VALID and deliberately deferred. - #797 — follow-up: `FLEET_STATE_HOOK_UTILS`'s test-only override isn't gated to test contexts at runtime. Pre-existing (unchanged by this PR), out of scope here — filed post-green per the lane's review-deferral rule. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
No linked issue.
Summary
/claude-ops:plugins— an action-routed skill (syncdefault /auditread-only /convergeexplicit scope consolidation) that brings a machine's Claude Code plugin fleet current: marketplace refresh, updates to plugins that actually load (including in-repo project/local-scope installs), new-catalog-plugin install per aninstall_newpolicy, and scope-divergence detection/reporting — never silently fixing drift.fleet-state.sh, a read-only, TDD-built state-inspection script (14 tests / 29 assertions) that reconcilesinstalled_plugins.json/known_marketplaces.jsonagainst a project's cwd, handling Windows-vs-Git-Bash path normalization and separating benign same-version multi-scope installs from actionable version divergence.claude-opsbumped 0.8.0 → 0.9.0; newinstall_newuserConfig option.Verification
/verification:confirmran end-to-end: Stage 1 (bash + markdown ecosystems, fullvalidate-plugins.sh,check-skill.sh, script test suite) all green; Stage 2 outcome verdict from a fresh-context (non-authoring) verifier agent: CONFIRMED, all 6 plan-phase acceptance criteria hold.fleet-state.shonly exposes the merged effective value, not each scope's raw map) is fixed in this PR — seecontext/converge.md/SKILL.md/evals.json.docs/topics/plugin-fleet-sync-skill/PLAN.md's Phase 6 evidence):auditis live-verified end-to-end against this repo and a second real repo.sync's in-repo update step (Step 2, the plan's own "primary value path") andconvergewere never exercised against real divergence this session — both would require mutating a real repo outside this one, which needs an explicit decision at that time, not a unilateral one here.Plan
Full phase-by-phase plan, evidence, and acceptance criteria:
docs/topics/plugin-fleet-sync-skill/PLAN.md.Related
None — self-contained plan/design/implementation cycle for a new skill, not tied to an existing issue, ADR, or decision-log entry.