Skip to content
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.42.4",
"version": "0.42.5",
"description": "Claude Code operations toolkit. Twelve 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 \u2014 a skill is invisible when its description is dropped by Claude Code's skill-listing context budget, which sheds descriptions lowest-score-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: 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 the fan-out layer, which covers a load-labelled no-op spawn baseline, every hook that will fire bucketed per-tool-call versus per-turn with its invocation shape, the configured statusline, subagent concurrency and spawn-depth ceilings against documented defaults, whether running sessions predate the settings file they are judged by, and orphan attribution by parent liveness rather than age, plus on Windows a kernel-object census (Token objects against uptime, paged pool) that names a host-level leak beneath all four suspects; read against a bundled known-performance-issues reference that also records the causes tested and cleared; separates the four documented suspects of accumulated state, version regression, component bloat, and per-spawn fan-out cost, and routes remediation out; reports, never mutates, and never executes a discovered hook or statusline command), audit-native-overlap (map native Claude Code surfaces \u2014 built-in CLI commands, bundled skills, plugin-backed built-ins, session-provided skills \u2014 against the current repo's plugin skills and agents, so a custom component never silently duplicates what Claude Code itself ships; bare invocation is a read-only overlap report carrying the extraction's integrity floors and a shared-listing-budget exposure section, verdicts are human-gated in a committed store rendered into a generated registry whose every row carries an observable recheck trigger, and only an explicit apply step bakes presence-gated native references into descriptions and Boundary sections), 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
94 changes: 94 additions & 0 deletions plugins/claude-ops/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,100 @@
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.42.5]

### Added

- **`fleet-state.sh --ids <selector> --from <report.json>` projects a selector from a
report the script already emitted.** `sync` re-reads the full JSON report before
each mutating step, and every selector is derivable from it, so the separate
live `--ids` process was paying a second process to re-parse
`installed_plugins.json`, re-walk the catalog manifests, and re-run `realpath`
in order to recompute a block the caller was already holding. `--from` reads no
Claude Code state file at all and runs the SAME jq projection the live mode
runs, so the CR-free, TAB-separated output contract is unchanged, which is the
reason the selector exists instead of a hand-written `jq` at each call site.
Refused with exit 2 and an EMPTY stdout, so a `< <(…)` consumer can never read
an error as a plugin id: `--from` with `--all`, `--from` without `--ids`, a
missing or malformed file, an `--all` envelope (valid JSON that every selector
projects to nothing, refused by name rather than silently returning an empty
list), a `--marketplace <name>` disagreeing with the report's own
`marketplace.name`, and a report that carries the baseline `.marketplace.name`
and `.installed` fields but not the field the CHOSEN selector reads. That last
one is per-selector: each selector declares the fields its branch of the
projection program consumes (only `update-candidates-user` reads
`catalog_versions`), and a missing or wrong-typed one is exit 2 naming the file
and the field, because `{"marketplace":{"name":"m"},"installed":[]}` otherwise
evaluated the absent array with `[]?`, emitted nothing, and exited 0 — a
silently-empty id list read as "nothing to do". A field that is present but
empty still exits 0 with empty output, so the exit status is a usable
discriminator. `--marketplace` under `--from` is an optional consistency check,
never a second read. New regression cases cover per-selector equality with the
live projection, each rejection's exit code and empty stdout, the incomplete
and present-but-empty reports, and a projection run with every state file
absent. (#3728)
- **`sync` writes a per-run journal.** At run start it creates
`${CLAUDE_PLUGIN_DATA}/plugins-sync/runs/<UTC timestamp>.XXXXXX/` with
`mktemp -d` (atomically, so two sessions starting in the same UTC second cannot
share a directory and interleave their snapshots and logs), saves the pre-sweep,
mid-sweep, and post-sweep `fleet-state.sh` reports there, and appends every
mutating CLI call and its output to `journal.log`. Step 6 reads the
`<old>`/`<new>` pairs and the three `divergences[]` snapshots out of those files instead
of out of conversation, so a sweep of several dozen mutations is no longer one
context compaction away from being unable to emit its own report, and `converge`
or a later audit gets a real before-state. This does not reverse the deferred
`--run-log` finding, it honors its reasoning: the journal is agent-executed shell
around calls the algorithm already makes, and `fleet-state.sh` stays the
read-only inspector its own header advertises. The path is substituted in
`SKILL.md` because `${CLAUDE_PLUGIN_DATA}` resolves in skill content and not in a
`context/*.md` spoke, which is read raw. (#3728)
- **New spoke `context/sync-install-enable.md` carrying sync Steps 4 and 5.**
Roughly a hundred lines of install policy branches, the `--setting-sources`
caveat, the reinstall-recurrence caveat, the normalizer contract,
`defaultEnabled` precedence, and the project-scope enable-gap suppression
ordering used to load on every run, and are unreachable when
`missing_from_user_install` and `missing_from_enabled` are both empty, the
common case on a current fleet. `sync.md` keeps a pointer with the read
condition, the same progressive-disclosure pattern the hub already uses for
`converge.md` and `scope-semantics.md`. The Report template and the
`install_new` render stay in `SKILL.md`, which documents them as deliberate
hub-only exceptions. The spoke loads on the FRESH pre-Step-4 re-read's arrays,
not on Step 1's older report: another session can uninstall a plugin or change
enable state in between, and a gate keyed on the stale report would skip the
live pre-install and pre-enable reads the spoke mandates and leave the new gap
unresolved. Step 4 reuses that re-read; Step 5 still takes its own, because
Step 4 mutates in between. (#3728)

### Changed

- **`sync.md`'s Step 2 and Step 3 loops project their id lists with `--from`**
against the report each step already read, rather than launching a second
`fleet-state.sh`. Each step now shows the redirect that creates its saved
report, and projects to a file whose exit status is checked before the loop:
a `--from` rejection is exit 2 with empty stdout, which a `while read` consumer
cannot tell apart from an empty id list, so the check is what keeps a failed
projection from being reported as "nothing to do". The mandate to take ids from
the script and never from a hand-written `jq` is unchanged, and so is every
selector's output. Cross-file references in `converge.md`, `gotchas.md`, and
`scope-semantics.md` that named `sync.md` Step 4 or Step 5 now point at the new
spoke. (#3728)
- **Every `tee`-journaled mutating call captures `rc=${PIPESTATUS[0]}`.** A
pipeline's own `$?` is `tee`'s status, and `tee` succeeds whenever it can write
the log, so the documented snippet reported success for a `claude plugin update`
that failed and never emitted the "Action needed" row the failure earned. The
capture must be the statement immediately after the pipeline, since any
intervening command clobbers `PIPESTATUS`. `sync.md`'s "Run journal" section
carries the one canonical shape; `sync-install-enable.md` points at it for its
install and enable calls rather than restating it. (#3728)
- **`audit` uses a throwaway `mktemp -d` scratch directory for its reports.**
`audit` runs the same algorithm, whose steps project with `--from` against a
saved report, so forbidding it from saving reports at all left it choosing
between violating its read-only contract and taking exit 2 from `--from` and
omitting its predictions. It now writes to a scratch directory under
`${TMPDIR:-${TEMP:-.}}` (never a hardcoded `/tmp`, which is an MSYS mount alias
on Windows) and removes it when the run ends, so one algorithm serves both
actions and only `sync` writes the durable journal. (#3728)

## [0.42.4]

### Changed
Expand Down
22 changes: 22 additions & 0 deletions plugins/claude-ops/skills/plugins/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ files directly, and never write them:
```bash
"${CLAUDE_PLUGIN_ROOT}"/skills/plugins/scripts/fleet-state.sh [--marketplace <name> | --all]
"${CLAUDE_PLUGIN_ROOT}"/skills/plugins/scripts/fleet-state.sh [--marketplace <name>] --ids <selector>
"${CLAUDE_PLUGIN_ROOT}"/skills/plugins/scripts/fleet-state.sh --ids <selector> --from <report.json>
```

The second form emits the plain id list a mutating step loops, instead of the JSON report. One
Expand All @@ -94,6 +95,20 @@ whenever a step needs ids; never hand-write a `jq` extraction over the JSON, whi
trailing `\r` on Windows and silently corrupts every id but the last (see
[context/gotchas.md](context/gotchas.md)).

The third form projects that same id list from a report already on disk rather than recomputing the
fleet, and is the form `sync`'s steps use: each step re-reads the full report anyway, and every
selector is derivable from it. Same script, same projection, so the `\r` protection is unchanged.

`sync` writes its run journal under this plugin's per-machine data directory. The path is
substituted here because `${CLAUDE_PLUGIN_DATA}` resolves in skill content and **not** in a
`context/*.md` spoke, which is read raw:

```bash
journal_root="${CLAUDE_PLUGIN_DATA}/plugins-sync/runs"
```

See [context/sync.md](context/sync.md)'s "Run journal" section for what goes in it.

After Step 4 installs anything, reorder user-scope `enabledPlugins` with the bundled writer.
Never hand-edit `~/.claude/settings.json`:

Expand All @@ -119,6 +134,12 @@ contents (`installed_plugins.json`, `known_marketplaces.json`, committed setting
an `audit` run, modulo any concurrent session or background `autoUpdate` sweep. Note that caveat in
the report rather than asserting byte-identical files.

`audit` runs the same steps, which project their id lists with `--from` against a saved report, so
it does write those reports — to a throwaway `mktemp -d` scratch directory it deletes when the run
ends, never to the durable run journal under this plugin's data directory. That keeps one algorithm
for both actions while leaving nothing behind, which is what "mutates nothing" means here. See
[context/sync.md](context/sync.md)'s "Run journal" section.

Because `audit` issues no `marketplace update`, its Step 3 prediction is computed against an
**unrefreshed** catalog and is therefore a lower bound on what `sync` would update. Report it as one,
carrying the catalog's `lastUpdated`. See [context/sync.md](context/sync.md) Step 3. An `audit`
Expand Down Expand Up @@ -286,6 +307,7 @@ default when that render is still the placeholder token, not on the option's nam
| File | Load when |
|---|---|
| [context/sync.md](context/sync.md) | Running `sync` or `audit`; it is the step sequence both actions execute. |
| [context/sync-install-enable.md](context/sync-install-enable.md) | Sync Steps 4 and 5, and only when the fresh pre-Step-4 re-read (not Step 1's report) has a non-empty `missing_from_user_install` or `missing_from_enabled`, or its Step 1 refresh failed. Both arrays are empty on a current fleet. |
| [context/converge.md](context/converge.md) | Running `converge`, the only action that may rewrite a committed settings file. |
| [context/scope-semantics.md](context/scope-semantics.md) | A scope, version, or reload claim needs its verified source before you act on it. |
| [context/gotchas.md](context/gotchas.md) | A run failed in a way the steps do not explain, or a safeguard looks removable. |
2 changes: 1 addition & 1 deletion plugins/claude-ops/skills/plugins/context/converge.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Converge — explicit scope consolidation

`converge` is the **only** action that can rewrite a committed `.claude/settings.json`, and only
after an explicit per-plugin confirm — [sync.md](sync.md) Step 5 keeps it that way by reporting a
after an explicit per-plugin confirm — [sync-install-enable.md](sync-install-enable.md) Step 5 keeps it that way by reporting a
`project`-scope enable gap instead of filling it. It never runs implicitly from `sync`: that report
only names the `converge` command, and the user runs it explicitly.

Expand Down
4 changes: 2 additions & 2 deletions plugins/claude-ops/skills/plugins/context/gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ Claude Code substitutes `userConfig` values when it renders the **skill**. A con
`${user_config.install_new}` in a spoke and it arrives as that literal token, with **no error and no
warning**; the value simply never appears, and a step branching on it branches on a placeholder.

This is why `SKILL.md` holds the `install_new` render and `sync.md` Step 4 branches on *that* line
rather than on its own prose. Verified empirically: `context/sync.md` on disk shows the raw
This is why `SKILL.md` holds the `install_new` render and `sync-install-enable.md` Step 4 branches on *that* line
rather than on its own prose. Verified empirically: `context/sync-install-enable.md` on disk shows the raw
`${user_config.install_new}` token in the same session where `SKILL.md`'s render shows the
configured value. Nothing enforces this — a future spoke that inlines such a token fails silently,
so it is a review-time rule, not a checkable one.
Expand Down
4 changes: 2 additions & 2 deletions plugins/claude-ops/skills/plugins/context/scope-semantics.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Two consequences:
`enabledPlugins` entry still dirties the tracked file, with a diff that changes no behavior: an
empty map plus a key reorder. Expect it; it is not evidence an entry was removed.
[converge.md](converge.md) Step 5 classifies it.
- **`sync`** — this is why [sync.md](sync.md) Step 5 enables automatically only at `user` and `local`
- **`sync`** — this is why [sync-install-enable.md](sync-install-enable.md) Step 5 enables automatically only at `user` and `local`
scope and reports a `project`-scope gap instead of filling it. `sync` has no autonomous-session
abort, so it has no safe moment to write a committed file; after that restriction, no `sync` path
writes one.
Expand Down Expand Up @@ -213,7 +213,7 @@ Two consequences this skill must not get wrong:
indication the configured value was discarded. Never advise setting it at project or local scope.
- A `--setting-sources` invocation that omits `user` drops user settings from that three-source read
list, so a headless `sync` launched that way silently loses `install_new` the same way. See
[sync.md](sync.md) Step 4 — the fallback is correct, the silence is not.
[sync-install-enable.md](sync-install-enable.md) Step 4 — the fallback is correct, the silence is not.

## `userConfig` has no `enum` field

Expand Down
Loading
Loading