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.31.10",
"version": "0.31.11",
"description": "Claude Code operations toolkit. Ten skills: 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 seven advisory *-audit telemetry-emitter hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures) 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
17 changes: 17 additions & 0 deletions plugins/claude-ops/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@
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.31.11]

### Fixed

- **`skills/plugins` `sync` wrote a committed settings file, breaking the skill's own invariant.**
SKILL.md states that `converge` is the one action that may touch a committed
`.claude/settings.json`, and only behind a per-plugin confirm. Step 5 issued
`claude plugin enable <id> -s project` for any `currentProject: true` completeness gap, and that
call writes exactly that file (verified on Claude Code 2.1.228 in 0.31.8). 0.31.8 documented the
exposure and asked the report to name it; this removes it. Step 5 now enables automatically only
at `user` and `local` scope — neither is team-shared state, since `local` writes the gitignored
`.claude/settings.local.json` — and reports a `project`-scope gap as an "Action needed" row
carrying the runnable `cd`-into-its-own-`projectPath` command instead of filling it. Confirming
was rejected as the fix: `converge` can afford a confirm because it aborts in an autonomous
session, while `sync` is the headless maintenance action with no such abort, so there may be no
human to answer. No `sync` path writes a committed settings file after this change.

## [0.31.10]

### Fixed
Expand Down
27 changes: 20 additions & 7 deletions plugins/claude-ops/skills/plugins/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ plugin names). This skill compares the *local, already-installed* state (`instal
per-scope `enabledPlugins`) against the *local* marketplace catalog — a different axis (install/scope
completeness, not settings-vs-upstream drift).

**Never silently fixes drift it finds.** `sync` mutates only via the documented CLI actions below;
`sync` can also write a committed `.claude/settings.json` when Step 5 issues `enable -s project`
(see [context/scope-semantics.md](context/scope-semantics.md)); `converge` is the action that
consolidates cross-scope divergence and can touch committed settings after an explicit per-plugin
confirm.
**Never silently fixes drift it finds.** `sync` mutates only via the documented CLI actions below,
and never writes a committed `.claude/settings.json`: its Step 5 enables automatically only at
`user` and `local` scope, and reports a `project`-scope gap rather than filling it, because `sync`
has no autonomous-session abort behind which a confirm would mean anything. `converge` is the one
action that can touch committed settings, and only after an explicit per-plugin confirm. See
[context/scope-semantics.md](context/scope-semantics.md) for which CLI calls write that file.

## Action Router

Expand Down Expand Up @@ -104,10 +105,22 @@ Installed: <N> new catalog plugin(s) — <id>@<marketplace> (only when N > 0; pe
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)
Action needed: <bulleted list — missing_from_user_install, missing_from_enabled, CLI failures,
unknown/orphaned plugins> (omit section entirely when empty)
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)
```

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:

```text
- project-scope enable gap: (cd "<projectPath>" && claude plugin enable <id>@<marketplace> -s project)
— 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.

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
Expand Down
9 changes: 4 additions & 5 deletions plugins/claude-ops/skills/plugins/context/converge.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# Converge — explicit scope consolidation

`converge` is the action that consolidates cross-scope divergence and can rewrite a committed
`.claude/settings.json` after explicit per-plugin confirm. It never runs implicitly from `sync` —
`sync` can also write committed settings when Step 5 issues `enable -s project` (see
[scope-semantics.md](scope-semantics.md)), but `sync`'s report only names the `converge` command for
divergence; the user runs `converge` explicitly.
`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
`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.

## Autonomous-session abort (run this check FIRST, before any preview work)

Expand Down
7 changes: 4 additions & 3 deletions plugins/claude-ops/skills/plugins/context/scope-semantics.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,10 @@ 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`** — Step 5 issues `enable <id> -s <that scope>`, so a `project`-scope completeness gap
makes the default, non-destructive action write a team-shared tracked file. `sync` surfaces no
settings diff today; [sync.md](sync.md) Step 5 flags the exposure.
- **`sync`** — this is why [sync.md](sync.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.

## Project scope: the CLI keys on the cwd, `fleet-state.sh` matches on the checkout root

Expand Down
60 changes: 44 additions & 16 deletions plugins/claude-ops/skills/plugins/context/sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,31 +163,59 @@ default recorded in the marketplace entry; a plugin whose `defaultEnabled: false
own `plugin.json`, with no mirrored marketplace-entry override, is a known residual gap (`fleet-state.sh`
reads the marketplace's catalog file, never each installed plugin's own manifest).

For each remaining id, and for each *verifiable* scope where that id has an install record (from
Consider each remaining id in each *verifiable* scope where it has an install record (from
`installed[]`) but no raw entry in that scope's own `enabledPlugins` map — **`user` scope, or
`project`/`local` scope with `currentProject: true`, never a `project`/`local` record for a different
repo** (same restriction as `missing_from_enabled` itself, for the same reason: this invocation never
reads another repo's settings files, so it cannot know whether that record is genuinely unmentioned
there or already has its own entry — running `enable -s project|local` for it would risk mutating the
current repo or an unread repo instead):

```bash
claude plugin enable <id> -s <that scope>
```
there or already has its own entry — acting on it would risk mutating the current repo or an unread
repo instead).

**`sync` never writes a committed settings file — the scope decides whether this step acts or
reports.** SKILL.md's scope section makes `converge` the one action that may touch a committed
`.claude/settings.json`, and only behind its confirm gate. `enable <id> -s project` writes exactly
that file (verified on Claude Code 2.1.228 — see [scope-semantics.md](scope-semantics.md)), so this
step must not issue it. Confirming instead of skipping is not an option: `converge` can afford a
confirm because it *aborts* in an autonomous session, while `sync` is the on-demand and headless
maintenance action with no such abort, so there may be no human to answer.

- **`user` and `local` — enable automatically.** Neither is team-shared state: `user` writes
machine-scope `~/.claude/settings.json`, and `local` writes the gitignored
`.claude/settings.local.json`.

```bash
claude plugin enable <id> -s user # or -s local
```

- **`project` — never enable; report it, but only when the report would be runnable.** Emit an
"Action needed" row per SKILL.md's Report section carrying the exact command, so the user can run
it deliberately and review the resulting diff:

```bash
(cd "<that record's projectPath>" && claude plugin enable <id>@<marketplace> -s project)
Comment thread
kyle-sexton marked this conversation as resolved.
Comment thread
kyle-sexton marked this conversation as resolved.
```

The `cd`-into-its-own-`projectPath` form is required for the reason
[converge.md](converge.md) Step 2 gives — `-s project` has no path flag and always acts on the
current directory — and the id stays fully qualified per [gotchas.md](gotchas.md).

**Order matters — suppress this row for any id the `user`/`local` branch just enabled.** An id
with no `enabledPlugins` entry anywhere but install records at *both* `user` and `project` scope
produces two rows in one run. The `user` row enables first, and `enable -s project` gates on the
**merged effective** value, not that scope's raw map (see
[scope-semantics.md](scope-semantics.md)), so the reported command would then fail with
`Plugin "<id>" is already enabled at project scope` — a report that hands the user a command
guaranteed to error. Emit the `project` row only for an id this step did **not** enable at `user`
or `local` scope; in practice that means an id whose only verifiable record is the project one.
Skipping is correct rather than merely convenient: after the `user` enable the plugin already
loads in that project by scope precedence, so nothing is broken — only the team-shared *declaration*
is absent, and that is a deliberate choice for the user to make, not drift for `sync` to report as
actionable.

Never touches an id that has an explicit entry anywhere (true — already enabled, nothing to do; or
false — deliberate opt-out, never flipped). This step only fills a genuine gap: installed but never
recorded either way.

**Known exposure — `-s project` here dirties a tracked file, and `sync` surfaces no diff.**
`enable <id> -s project` writes the project's committed `.claude/settings.json` (verified on Claude
Code 2.1.228; see [scope-semantics.md](scope-semantics.md)), so this step can leave a team-shared
tracked file modified without the report ever mentioning it — the failure class
[converge.md](converge.md) Step 5 exists to prevent, in the default action. `-s user` and `-s local`
are unaffected: local scope writes the gitignored `.claude/settings.local.json`. Until this step
gains converge's diff-surfacing, name any `-s project` enable in the report so the user knows to
check `git status`.

## Step 6 — Report

Emit the report per SKILL.md's "Report" section, filling each updated plugin's `<old> → <new>` from
Expand Down
Loading