Skip to content
Merged
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
127 changes: 127 additions & 0 deletions plugins/claude-ops/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,133 @@
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]

Remediates the `claude-ops:plugins` post-use audit of the `sync` action (#3112). Every claim about
CLI behaviour added or changed below was verified on **Claude Code 2.1.240**.

### Added

- **Catalog-version pre-filter for the Step 3 user-scope sweep.** `marketplace.json` entries carry
no version, which is why Step 3 previously called `claude plugin update` for *every* user-scope
install and let the CLI no-op. Each plugin's version does exist in the marketplace checkout, at
`<installLocation>/<entry.source>/.claude-plugin/plugin.json`, readable with no network call and
no CLI invocation. `fleet-state.sh` now exposes it as `catalog_versions`, and a new
`--ids update-candidates-user` selector withholds only ids it positively proved already sit at
the catalog version. On the authoring machine's own already-current fleet this takes the sweep
from 66 `claude plugin update` calls to **0**.

**The selector fails open by construction, and that is the dominant path, not an edge case.** An
id whose catalog version cannot be read — an object-valued `source`, an unmaterialized plugin
directory, a manifest with no `version`, unparsable JSON — is emitted as a candidate, exactly as
if no pre-filter existed. Measured across the nine marketplaces registered on the authoring
machine: the version resolves for **every** entry of five (`melodic-software` 70/70, plus four
single-plugin marketplaces), **partially** for two (`claude-plugins-official` 13/53,
`dotnet-agent-skills` 1/15), and for **none** of two (`anthropic-agent-skills` 0/5, `caveman`
0/1). So a marketplace the pre-filter cannot narrow at all is an ordinary outcome, not a
malfunction. `fleet-state.test.sh` proves the degradation as an equality: with no catalog version
readable, `update-candidates-user` output is byte-identical to `installed-user`.

**The manifest it reads must sit inside the marketplace checkout, and that is enforced
physically.** A catalog entry's `source` is third-party content, and the only unsafe direction
this pre-filter has is *withholding* an update — a foreign manifest that happens to carry the
installed version string would suppress a real update. A lexical `../` refusal is not sufficient,
because a symlink inside the checkout pointing outside it is reached by an ordinary `./name`
source that no string inspection can see. So the resolved manifest path is required to sit under
the resolved checkout root, with symlinks followed. `fleet-state.test.sh` covers both: a real
symlink escape (created with `MSYS=winsymlinks:nativestrict` so Git Bash emits a link rather than
silently deep-copying, and skipped where the platform yields no real symlink) is refused, while an
in-checkout directory of the same shape still resolves.
- **`project_root`** (top level) — the resolved project root, or `null`. Closes the F1 silent no-op:
`currentProject` is a tri-state whose `null` collapses "no project context resolved at all"
together with "this is a user-scope record", so a run from `$HOME` and a run inside a repo with
no in-repo installs produced an identical downstream signal and an identical report.
- **`user_scope_orphans`** (top level) plus an `--ids user-scope-orphans` selector — ids holding a
project/local record and no user-scope record. Structurally invisible before: `divergences[]`
discards any id with fewer than two records, and `missing_from_user_install` excludes ids that
are installed somewhere, so nothing in the output named them.
- **`projectPathPresent`** on every project/local `installed[]` record and every
`divergences[].scopes[]` entry — advisory only, never a filter.
- Five eval cases covering the silent-failure paths the suite never reached (skipped in-repo step,
`sync all` marketplace coverage, absent-`projectPath` handling, pre-filter fail-open, and the
version-capture divergence branch).

### Fixed

- **Step 2 no longer skips silently.** It now branches on `project_root` and the report carries a
fixed `In-repo:` row in all three states, including `skipped — no project context resolved`.
- **`sync all` no longer sweeps one marketplace while reporting as though it covered every one.**
Steps 2–5 are the per-marketplace loop body and every `--ids` call carries `--marketplace`.
- **Divergences are no longer routed to a `converge` command that cannot run.** Records whose
`projectPath` is not present get their own report section, outside the actionable Divergences
count, and `converge` emits them as *blocked* rather than as runnable commands.
- **`pluginConfigs` scope claim corrected.** It is read from user settings, `--settings`, and
managed settings only — project and local entries are ignored (since v2.1.207) — while
`enabledPlugins`, read by this same skill, still honors them. The old text said "some
`pluginConfigs` scope", which invited setting `install_new` in a repo where it does nothing.
- **`/reload-plugins --force` guidance restated as the docs' two-step.** The trigger is prompt-cache
invalidation; the MCP-server case is the common cause, not the only one.
- **Divergence count split** into run-caused versus pre-existing, so the report stops presenting
skew the sweep itself just created as discovered drift.
- **`sync` now reports when it updated `claude-ops` itself**, naming that the algorithm which ran is
the pre-update one.
- **TOCTOU wording matches the implementation**: the re-read boundary is the step, a loop body is
deliberately snapshot-driven, and the inert "outcome didn't match the snapshot" detector is
replaced with the one signal that is actually distinguishable.
- **`install_new: all` recurrence** and **unset-`userConfig` install notices** now have report slots
instead of living only in prose.
- **`setup`: the headless `--config` route no longer prescribes an uninstall/reinstall cycle.**
Rerunning the install writes the option against an already-installed plugin — it prints
`already installed` and still writes the value (verified on Claude Code 2.1.240, for a
non-sensitive option at `user` scope; a `sensitive` option and `project`/`local` scope were not
covered, and the wording says so). The old cycle was unnecessary and actively destructive:
uninstalling drops the whole stored `pluginConfigs` entry, resetting all fifteen options to
their manifest defaults (the previous text miscounted them as fourteen, and the
`*_audit_enabled` toggles as seven rather than eight).

It also separates the two claims a reader conflates: the **write** lands, but the **running
session's** behavior does not change — `${user_config.*}` is injected at skill load and each
hook's `CLAUDE_PLUGIN_OPTION_*` comes from an environment fixed at session start, so a
same-session `check` still reports the OLD value and reading that as a failed write is wrong.
Verify in a fresh session.

Wording matches the fleet-wide correction landed in #3115 verbatim apart from this plugin's own
option list, so the copies stay identical rather than drifting into a claude-ops variant. This
closes a live contradiction on `main`: #3115 regenerated this plugin's README with the corrected
guidance while `skills/setup/SKILL.md` still prescribed the destructive cycle, a gap that PR's
own reviewer flagged and could not fix because the file sits in this change set's fence.

### Changed

- The `versionsMatch` filter rule now has one origin (`context/scope-semantics.md`); `SKILL.md`,
`gotchas.md`, and `converge.md` point at it instead of restating it a fourth time.
- New gotchas: a `projectPath` outliving its directory, a spoke file never receiving
`${user_config.*}` substitution, and `sync` updating its own plugin mid-run.
- `SKILL.md`'s "index, not a substitute" rule now names its two deliberate exceptions and why they
must live in the hub.
- Version capture now instructs retaining the pre-sweep snapshot for the whole run — it is the sole
source of every `<old>`.

### Deferred (audit findings deliberately not closed in this release)

- **`--run-log` written by `fleet-state.sh`** (audit remediation 19, F12's most ambitious tier).
The script's own header advertises it as read-only, and the auditor's correction notes the
tension. F12's cheapest tier — retain the pre-sweep snapshot — is implemented instead. A durable
log, if wanted, belongs in a sibling script that owns it rather than in the read-only inspector.
- **A fourth `install_new` value with declined-install memory** (remediation 20, F9's durable fix).
Needs persistent state the skill does not have today; the recurrence clause narrates the surprise
rather than removing it.
- **F14 (description trigger-phrase trim).** The repo's own `check-changed-skills.sh` gate enforces
trigger-keyword preservation against `HEAD` and fails a dropped phrase as an auto-invocation
regression. The finding is cosmetic (the skill sets `disable-model-invocation: true`, so the
phrases only serve as `/`-menu help) and is not worth fighting a validator for.
- **A `--selfcheck` that reports whether the rendered `install_new` value is the literal placeholder
token** (F8's most ambitious tier). F8 graded the *record*, not the claim — the defect was a stamp
with no recheck trigger, and that is fixed. Turning the prose stamp into a runtime observation is
a separate enhancement.
- **An upstream issue for the absent record-reaping verb** (remediation 21) — not a change to this
repository.

## [0.35.4]

### Fixed
Expand Down
Loading