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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/claude-ops/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "claude-ops",
"version": "0.35.4",
"version": "0.36.0",
"description": "Claude Code operations toolkit. Eleven skills: audit-skill-visibility (audit whether each installed skill is actually VISIBLE to the model, and diagnose why most of a fleet never gets used — a skill is invisible when its description is dropped by Claude Code's skill-listing context budget, which drops descriptions least-invoked-first so an unused skill loses the keywords that would let it be matched, from skills genuinely not wanted, from skills the run cannot observe at all; computes whether the listing overflows from documented settings, and withholds every cold verdict the data cannot support rather than reporting absence of data as absence of use), inventory (read-only enumeration of the complete invocable surface \u2014 every built-in CLI command with aliases and hidden/gated status, every bundled skill, and every component of every installed plugin across all marketplaces; reads the shipped binary because upstream publishes no built-in command list, and carries an integrity verdict so a drifted build reports counts as floors rather than silently short totals), audit-install-state (read-only audit of the machine-scope ~/.claude installation directory and ~/.claude.json \u2014 full inventory split into an authored surface and rolled-up bulk trees, product-managed retention vs genuinely unmanaged state, filename-scheme resolution before any process-liveness check, and deliberate/mid-experiment detection; reports, never deletes), audit-performance (read-only slowness-diagnostic capture run at the moment the machine or a session feels slow \u2014 CLI version, retention-sweep health including the silent unparsable-settings pause, a timed census walk of the install tree as a sweep-cost proxy, active-session and plugin-fleet counts, a process census, and a bundled known-performance-issues reference; separates the three documented suspects \u2014 accumulated state, version regression, component bloat \u2014 and routes remediation out; reports, never mutates), observability (read locally captured telemetry \u2014 OTEL store, collector, hook-event JSONL, ccusage \u2014 with trend reports and store pruning), known-issues (search known Claude product GitHub bugs, check service health, maintain a persistent tracked-issue registry), changelog (ingest Claude Code changelog entries and integrate them into the current repo), plugins (bring a machine's plugin fleet current on demand \u2014 marketplace refresh, effective-scope updates including in-repo project/local installs, new-plugin install per policy, scope-divergence detection and explicit convergence), morning-brief (read-only gh-based operator morning view \u2014 queue-label counts, merge-ready PRs, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness), lanes (start/restart/stop/status loop lanes as named background Claude Code sessions seeded from canonical prompt files, with per-lane model/effort, a repo-pull + marketplace-refresh launch step, and a consume-restarts action \u2014 an OS-schedulable reader that relaunches stopped lanes whose telemetry carries a restart_request), and a re-runnable setup action that settles where the known-issues registry lives. Plus a family of eight advisory *-audit hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures, and unsurfaced hook failures \u2014 the last also warns the user via systemMessage, since a hook that fails to launch enforces nothing and Claude Code surfaces the failure to nobody) that emit the shared hook-telemetry envelope, and a reference sink that maps envelopes into the hook-events.jsonl the observability skill reads.",
"author": {
"name": "Melodic Software",
Expand Down
58 changes: 58 additions & 0 deletions plugins/claude-ops/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,64 @@
All notable changes to the `claude-ops` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.36.0]

### Added

- **`plugins`: four sync-enabling `fleet-state.sh` capabilities (#3112).** `project_root` as a
top-level field on every successful marketplace block (string when project context resolved,
null otherwise, including each `--all` block); `projectPathExists` on every `installed[]` record
and every `divergences[].scopes[]` entry (true/false from a directory test; null for user-scope
records with no `projectPath`); a standalone `--marketplaces` flag emitting
`known_marketplaces.json` names one per line, CR-free (a usage error combined with
`--marketplace`/`--all`/`--ids`); and an `--ids stale-user` selector emitting the user-scope ids
not confirmed current with the local marketplace checkout's per-plugin manifest version —
fail-open (an unresolvable catalog version keeps the id in the list), validated on the audited
CC 2.1.240 run at exactly 48/61 predicted updates with 0 false positives/negatives.
- **`plugins`: the sync report's always-present `In-repo:` row (#3112).** Step 2 (the
self-described primary value path) branches on `project_root` for the zero-record case, so a run
where the step never applied ("no project context") is visibly distinct from one that ran and
found no in-repo installs — previously the two were indistinguishable silence.
- **`plugins`: a conditional self-update `Note:` row (#3112).** When the sweep updates
`claude-ops@<marketplace>` itself, the report states the run executed the pre-update algorithm —
the rendered skill content is the pre-update version, and the report no longer implies otherwise.
- **`plugins`: a durable version-capture ledger (#3112).** `sync` now mandates a `mktemp` ledger
with one `<id>\t<old>\t<new>` record appended per mutating call as it runs; Step 6 composes the
report from the ledger, never from conversational memory (on the audited 67-call run the old→new
data had survived only in transcript). Session scratch, never a committed artifact.

### Changed

- **`plugins`: `sync all` now iterates Steps 2–5 per marketplace (#3112).** Names come from
`fleet-state.sh --marketplaces` (never a hand-written `jq -r 'keys[]' | while read`, per the
CRLF gotcha), each step passing `--marketplace <name> --ids <selector>` since `--ids` refuses
`--all`. Previously only Step 1 looped, so an `all` sweep silently covered one marketplace's
installs (observed: 66 ids swept against 72 machine-wide across 9 marketplaces, CC 2.1.240).
- **`plugins`: Step 3 sweeps `--ids stale-user` instead of `--ids installed-user` (#3112).** The
local-manifest comparison pre-filters already-current plugins, so the audited sweep issues 48
update calls instead of 61 and a routine already-current sync issues 0; the stale "fleet-state.sh
has no per-plugin catalog version" rationale is corrected where the docs repeated it. Step 2's
just-call-update posture is unchanged (its records are project/local-scope; `stale-user` is
user-scope only).
- **`plugins`: the `versionsMatch` filter rule now has a single normative home (#3112).**
scope-semantics.md's "Divergence is not automatically actionable" section is the one normative
statement; SKILL.md, converge.md, and gotchas.md now point at it instead of hand-maintaining
restatements across four files.

### Fixed

- **`plugins`: no more converge/enable commands into directories that no longer exist (#3112).**
Every doc site constructing `(cd "<projectPath>" && claude plugin …)` now gates on the record's
`projectPathExists`; a `false` record routes to a named "Action needed" category — **orphaned
install record** — as report-only, since no CLI verb reaps such a record (observed on CC 2.1.240;
`prune -s project` has the same no-path-flag limitation). A divergence whose only lagging scope
rows are orphaned is reported as non-convergeable instead of previewing commands that can only
fail.
- **`plugins`: SKILL.md's `pluginConfigs` scope claim corrected (#3112).** The value is substituted
only from a `pluginConfigs` map Claude Code actually reads — since CC v2.1.207 that is user scope
(`~/.claude.json` / the `--settings` file) and managed settings; project- and local-scope
`pluginConfigs` are ignored, unlike `enabledPlugins`, which still honors project/local scope.

## [0.35.4]

### Fixed
Expand Down
60 changes: 44 additions & 16 deletions plugins/claude-ops/skills/plugins/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,8 @@ trailing `\r` on Windows and silently corrupts every id but the last (see
[context/gotchas.md](context/gotchas.md)).

Read [context/scope-semantics.md](context/scope-semantics.md) before interpreting its output — in
particular, `divergences[].versionsMatch` separates a benign same-version multi-scope install
(normal, no action) from a real version skew (the actionable "run converge" signal); a raw
divergence-record count conflates the two and overstates the report.
particular its "Divergence is not automatically actionable" section, the normative statement of the
`versionsMatch` filter rule that every divergence count this skill reports must apply.

## Action: audit

Expand All @@ -107,15 +106,31 @@ action.

```text
Marketplace: <name> — <current | needs update> (autoUpdate: <on|off — suggest enabling if off>)
In-repo: <N> updated | none — project context <root>, no in-repo installs | none — no project
context (Step 2 did not apply)
Updated: <N> plugin(s) — <id>@<marketplace>: <old> → <new> (only when N > 0)
Installed: <N> new catalog plugin(s) — <id>@<marketplace> (only when N > 0; per install_new policy)
Note: claude-ops updated mid-run (<old> → <new>); this run executed the <old> algorithm
(only when the sweep updated claude-ops@<marketplace> itself — see context/sync.md)
Divergences: <N> project-scope install(s) behind user scope → run `/claude-ops:plugins converge`
(N = actionable only — versionsMatch:false; same-version multi-scope installs are not counted
or listed here)
(N = the actionable subset per scope-semantics.md's versionsMatch filter rule)
Action needed: <bulleted list — missing_from_user_install, missing_from_enabled, project-scope
enable gaps, CLI failures, unknown/orphaned plugins> (omit section entirely when empty)
enable gaps, orphaned install records, CLI failures, unknown plugins> (omit section entirely
when empty)
```

The `In-repo:` row is **always present**, in exactly one of its three states — the deliberate
exception to "only rows needing action." Step 2 of [context/sync.md](context/sync.md) is the
primary value path, and its silent no-op was invisible precisely because the row's absence looked
identical to "ran and found nothing": `fleet-state.sh`'s `project_root` disambiguates the two
zero-record states (null → the step did not apply; non-null → it ran against `<root>` and found no
in-repo installs), and sync.md Step 2 fixes which state maps to which wording.

The `Note:` row appears only when the sweep updated `claude-ops@<marketplace>` itself. The skill
content rendered for this session — the algorithm that actually ran — is the pre-update version,
so the report must never imply the new version's algorithm produced it (see
[context/sync.md](context/sync.md) "Self-update").

A project-scope enable gap is a row `sync` deliberately does not fix — Step 5 enables automatically
only where the write is not team-shared state. Give each one its runnable command rather than a
count, so acting on it is a copy, not a reconstruction:
Expand All @@ -125,18 +140,28 @@ count, so acting on it is a copy, not a reconstruction:
— writes that repo's committed .claude/settings.json; review the diff before committing
```

Only ids that Step 5 did not enable at `user`/`local` scope in this run appear here — for the rest
the command would fail rather than run, and Step 5 explains why.
Emit that command only when the record's `projectPathExists` is `true`. A `false` record's
directory is gone, so the command can only fail — the row moves to the orphaned-install-record
category below instead. Only ids that Step 5 did not enable at `user`/`local` scope in this run
appear here — for the rest the command would fail rather than run, and Step 5 explains why.

An **orphaned install record** — an install record whose recorded `projectPath` no longer exists on
Comment thread
kyle-sexton marked this conversation as resolved.
disk (`projectPathExists: false`) — is its own named "Action needed" category, and always
report-only: any `(cd "<projectPath>" && …)` command constructed against it can only fail, and no
CLI verb reaps such a record (observed on CC 2.1.240; `prune -s project` has the same no-path-flag
limitation as every `-s project` verb), so the record stays until upstream provides a reap path.
Name the id, scope, and dead path per row rather than a bare count.

When running inside a project (`CLAUDE_PROJECT_DIR` set and `fleet-state.sh`'s `installed[]` entries
carry `currentProject: true`), lead the Divergences line with *this* project's actionable count and
fold the rest of the machine into one trailing clause — e.g. `2 behind here → converge; 27 more
elsewhere on this machine`. Per-row detail (naming exact `<old> → <new>` versions per repo) is
reserved for genuine conflicts: an unknown/orphaned plugin id, or a CLI call that failed — never for
the routine bulk case. (Enable-state mismatches — a plugin `true` in one scope's `enabledPlugins`
and `false` in another — are a known blind spot, not a reportable category: `fleet-state.sh` only
exposes the merged effective value, never each scope's raw map, so this skill cannot detect one to
report it. See [context/converge.md](context/converge.md) "V1 scope".)
reserved for genuine conflicts: an unknown plugin id (present in `enabledPlugins` with no matching
catalog entry), or a CLI call that failed — never for the routine bulk case. (Enable-state
mismatches — a plugin `true` in one scope's `enabledPlugins` and `false` in another — are a known
blind spot, not a reportable category: `fleet-state.sh` only exposes the merged effective value,
never each scope's raw map, so this skill cannot detect one to report it. See
[context/converge.md](context/converge.md) "V1 scope".)

Close with reload guidance: recommend bare `/reload-plugins` by default; suggest `--force` only when
an updated/installed component ships an MCP server whose tools aren't deferred — that's the one case
Expand All @@ -159,9 +184,12 @@ value in the report.

**Configured value: `${user_config.install_new}`** — Claude Code text-substitutes a `userConfig`
value into this skill's content before the model sees the rendered skill, but **only when the key is
explicitly set** in some `pluginConfigs` scope; declaring the option in `plugin.json` alone does not
make its value readable here. Crucially, the manifest's `"default": "ask"` is **not** substituted for
an unset key (verified 2026-07-23 against CC 2.1.218: an unset key leaves the placeholder token
explicitly set** in a `pluginConfigs` map Claude Code actually reads: since Claude Code v2.1.207
that is user scope (`~/.claude.json`, or the `--settings` file) and managed settings only —
project- and local-scope `pluginConfigs` are ignored, unlike `enabledPlugins`, which this same
skill reads and which still honors project/local scope. Declaring the option in `plugin.json` alone
does not make its value readable here. Crucially, the manifest's `"default": "ask"` is **not**
substituted for an unset key (verified 2026-07-23 against CC 2.1.218: an unset key leaves the placeholder token
unchanged — the same shape as `${user_config.…}` — while a sibling `${CLAUDE_PLUGIN_ROOT}` substitutes
in the same render). So for the common default-config user — no `pluginConfigs` set anywhere — the
**Configured value** line above still shows that literal placeholder token, not `ask`.
Expand Down
20 changes: 18 additions & 2 deletions plugins/claude-ops/skills/plugins/context/converge.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ treat it as autonomous and abort. Report why, and that `converge` can be re-run
## V1 scope: version divergence only

`converge` resolves entries in `fleet-state.sh`'s `divergences[]` with `versionsMatch: false` —
scopes disagree on version. It does **not** currently resolve, and cannot even detect, an
the actionable subset, per the filter rule stated normatively in
[scope-semantics.md](scope-semantics.md) ("Divergence is not automatically actionable").
It does **not** currently resolve, and cannot even detect, an
enable-state mismatch (a plugin `true` in one scope's `enabledPlugins` and `false` in another) —
that needs comparing each scope's *raw* `enabledPlugins` map, which `fleet-state.sh` doesn't expose
today (only the merged effective value, in `enabled`). This is a genuine blind spot, not a deferred
Expand All @@ -28,7 +30,12 @@ per-scope maps, not something this skill's prompt layer can paper over.
## Step 1 — Detect

Call `fleet-state.sh` (default marketplace, named one, or the current invocation's target) and take
`divergences[]` filtered to `versionsMatch: false`.
`divergences[]` filtered to `versionsMatch: false` (the [scope-semantics.md](scope-semantics.md)
rule above). Then check each row's `scopes[].projectPathExists` before treating it as convergeable:
a divergence whose only *lagging* scope rows carry `projectPathExists: false` cannot be converged —
every command Step 2 could propose for it would `cd` into a directory that no longer exists. Report
such a row as an orphaned install record (SKILL.md's "Action needed" category) and propose no
commands for it.

## Step 2 — Preview per-plugin intent

Expand Down Expand Up @@ -61,6 +68,15 @@ rows a bulk report collapses) — never construct the proposed command as a bare
running the bare form for a row whose `projectPath` isn't the current directory would silently
mutate — or fail against — the wrong repo's settings.

**Before constructing any such command, read that scope row's `projectPathExists`** — carried on
every `divergences[].scopes[]` entry for exactly this decision. `false` → never emit the command:
the recorded directory is gone, so the `cd` can only fail. Treat the record as an **orphaned
install record** instead — no CLI verb reaps an install record whose `projectPath` no longer
exists (observed on CC 2.1.240; `prune -s project` shares the no-path-flag limitation), so it is
report-only under "Action needed" until upstream provides a reap path. When every lagging scope
row of a divergence is orphaned this way, the divergence is not convergeable at all (Step 1):
report it and propose nothing.

Two `git worktree` checkouts of one repository pin independently — verified on Claude Code 2.1.228
by uninstalling one id in a repo's main checkout and observing the worktree's record for the same id
survive untouched. They share one `.git` and one tracked `.claude/settings.json` yet hold separate
Expand Down
11 changes: 6 additions & 5 deletions plugins/claude-ops/skills/plugins/context/gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@ evidence about either read.
## Dual-scope divergence is normal, not a defect

A project pinning an older version at `project` scope while your personal `user` scope has moved on
is expected, common, and not itself something to "fix" silently. `fleet-state.sh`'s `versionsMatch`
field is what separates that benign case from a real, actionable version skew — see
scope-semantics.md. Never report a raw `divergences[].length` count; always filter to
`versionsMatch == false` first, or the report overstates drift with entries that need no action.
is expected, common, and not itself something to "fix" silently. What separates that benign case
from a real, actionable version skew is `fleet-state.sh`'s `versionsMatch` field, and the filter
rule for it is stated normatively in [scope-semantics.md](scope-semantics.md) ("Divergence is not
automatically actionable") — apply it as written there before counting or listing anything; a raw
`divergences[].length` count is exactly the overstatement that rule exists to prevent.

## Internal-schema drift — fail loud, never guess

Expand Down Expand Up @@ -142,7 +143,7 @@ feeds `claude plugin` needs no `jq` of its own at all:
while IFS= read -r id; do
[[ -n "$id" ]] || continue
claude plugin update "$id" -s user
done < <(…/scripts/fleet-state.sh --ids installed-user)
done < <(…/scripts/fleet-state.sh --ids stale-user)
```

For anything `--ids` does not cover: route every `jq` call through the
Expand Down
Loading