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.27.0",
"version": "0.27.1",
"description": "Claude Code operations toolkit. Seven skills: observability (read locally captured telemetry — OTEL store, collector, hook-event JSONL, ccusage — 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 — 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 — 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 — 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
39 changes: 39 additions & 0 deletions plugins/claude-ops/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,45 @@
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.27.1]

### Changed

- **`observability` marks the Token / cost dollar column as a list-rate estimate.** ccusage prices
tokens at public per-token list rates while subscription usage is plan-priced, so the USD figures
are an estimate, not a bill; [costs](https://code.claude.com/docs/en/costs.md) (verified
2026-08-04) documents the same list-rate caveat for Claude Code's own locally computed figures.

### Fixed

- **`changelog` `status` no longer goes silent on the first release outside the CC 2.1 series.**
The applied-versions scan grepped git log with patterns pinned to `v2\.1\.`, so a 2.2.x/3.x
release would return nothing without erroring. The scan now matches any
`CC v<major>.<minor>.<patch>` (`-E --grep="CC v[0-9]+\.[0-9]+\.[0-9]+"`), semantics otherwise
unchanged; a skill-wide sweep confirmed no other file carries the series pin — remaining
`v2.1.x` literals are illustrative examples.
- **`changelog` fetch steps target the raw-markdown channel (`docs/en/changelog.md`), not the
rendered HTML page.** The `.md` sibling is the smaller, chrome-free channel — 514,578 B against
the rendered page's 2,696,671 B (~5x), measured 2026-08-04 — and both carry the same 355
releases. It buys no extra version depth: WebFetch truncates **both** channels identically, to
the same 32 most-recent versions with a `[Content truncated due to length...]` marker, because
its budget applies after HTML-to-markdown conversion. Reaching a deep version needs a
range-scoped fetch or a direct `curl`, on either channel. Every fetch-source reference in the
skill now points at the `.md` URL.
- **`lanes` no longer skips a lane whose effort is `ultracode`.** The launcher validated
`lanes[].effort` against `low|medium|high|xhigh|max`, so `ultracode` — a documented
`claude --effort` value since CC 2.1.203 (verified 2026-08-04 against
[model-config](https://code.claude.com/docs/en/model-config#adjust-effort-level)) — made the
lane silently unlaunchable. The valid set now includes it, gated on the installed
`claude --version` meeting that floor: below it the CLI rejects the value outright (`Unknown
--effort value 'ultracode'`) and starts the session at the default effort, so the launcher skips
the lane rather than launching it at an unintended effort. That check runs in the shared
launch-input preflight, which `restart` already performs BEFORE stopping — so a lane the gate
refuses keeps running rather than being taken down and left down. The whole run shares one
`claude --version` probe, and `--dry-run` keeps working with no CLI installed (the exemption
`require_claude` documents): with no binary to probe, the preview reports the gate unevaluated
instead of refusing a lane a real run may well launch.

## [0.27.0]

### Added
Expand Down
4 changes: 2 additions & 2 deletions plugins/claude-ops/skills/changelog/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Distinct from:
Three ways to provide changelog content (priority order):

1. **User pastes text** — skill parses inline changelog from conversation context
2. **Specific version** — `/changelog apply v2.1.152` fetches that version from `code.claude.com/docs/en/changelog`
2. **Specific version** — `/changelog apply v2.1.152` fetches that version from `code.claude.com/docs/en/changelog.md`
3. **Auto-detect latest** — `/changelog apply` (no version) automatically fetches changelog, identifies latest version, and proceeds

## Version awareness
Expand Down Expand Up @@ -165,7 +165,7 @@ If user approves:

The three read-only actions stop short of any edit — **full steps in [context/read-actions.md](context/read-actions.md)**:

- **`fetch`** — WebFetch + display a version (or latest, or a `v.X..v.Y` range) of `code.claude.com/docs/en/changelog`. No edits
- **`fetch`** — WebFetch + display a version (or latest, or a `v.X..v.Y` range) of `code.claude.com/docs/en/changelog.md` (raw markdown — the smaller, chrome-free channel; WebFetch truncates it and the rendered page alike, so a deep version needs a range-scoped fetch or `curl`). No edits
- **`diff`** — dry run of `apply`: Phase 0 (ingest) + Phase 1 (explore) + Phase 2 (research), stops before interview. Emits the triage table only. Answers "is this release worth an `apply`?"
- **`status`** — applied versions (`git log --grep`), open routine-pipeline issues (`gh issue list`), current `claude --version`, and the gap if installed > last-applied

Expand Down
12 changes: 9 additions & 3 deletions plugins/claude-ops/skills/changelog/context/read-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ The three read-only actions (`fetch`, `diff`, `status`). SKILL.md keeps the acti

Read-only. Fetch and display changelog content.

WebFetch truncates this document — the raw-markdown and rendered-HTML channels alike — to roughly
the 32 most recent releases, emitting a `[Content truncated due to length...]` marker. A version
older than that window will not be in the response no matter which channel is used, so fetch it
with a range- or anchor-scoped request, or `curl` the `.md` and slice locally. Never report a
version "absent from the changelog" on a truncated fetch.

**With version arg** (`/changelog fetch v2.1.152`):

1. WebFetch `https://code.claude.com/docs/en/changelog`
1. WebFetch `https://code.claude.com/docs/en/changelog.md`
2. Extract section matching version
3. Display formatted

Expand All @@ -34,10 +40,10 @@ Useful for: "should I bother running apply for this release?"

Show changelog integration status:

1. **Applied versions** — scan git log for commits mentioning "CC v2.1." or "Claude Code v2.1." or "changelog":
1. **Applied versions** — scan git log for commits mentioning "CC v<x.y.z>" or "Claude Code v<x.y.z>" or "changelog":

```bash
git log --oneline --all --grep="CC v2\.1\." --grep="Claude Code v2\.1\." -20
git log --oneline --all -E --grep="CC v[0-9]+\.[0-9]+\.[0-9]+" --grep="Claude Code v[0-9]+\.[0-9]+\.[0-9]+" -20
```

2. **Open issues** — if the consumer repo files CC-release tracking issues, count the pending
Expand Down
2 changes: 1 addition & 1 deletion plugins/claude-ops/skills/lanes/context/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ A missing config exits `4`; malformed JSON or a config with no lanes exits `3`.
| `lanes[].name` | yes | The lane's session name — the `--name` value the launcher gives the background session, and the key `status`/`stop` match on. Keep distinct from ad-hoc session names. |
| `lanes[].prompt` | yes | Path to the lane's canonical prompt file. Relative → resolved against `prompt_dir`; absolute → used as-is. The file's full contents seed the session (positional prompt). A missing or empty file skips that lane with an error. |
| `lanes[].model` | no | Passed as `claude --model`. An alias (`opus`, `sonnet`, `fable`) or a full model id. Omit to inherit the machine default. |
| `lanes[].effort` | no | Passed as `claude --effort`. One of `low`, `medium`, `high`, `xhigh`, `max` (validated; a bad value skips the lane). Omit to inherit the default. |
| `lanes[].effort` | no | Passed as `claude --effort`. One of `low`, `medium`, `high`, `xhigh`, `max`, `ultracode` (validated; a bad value skips the lane). `ultracode` [requires Claude Code v2.1.203 or later](https://code.claude.com/docs/en/model-config#adjust-effort-level); below that floor the CLI rejects the value outright (`Unknown --effort value 'ultracode'`) and starts the session at the default effort, so the launcher checks the installed `claude --version` and skips the lane rather than launching it at an unintended effort. `restart` makes that check before stopping, so a refused lane keeps running. Omit to inherit the default. |
| `lanes[].settings` | no | A JSON **object** passed inline as `claude --settings` — a session-only override that never persists. The motivating use is opting a lane into the `autonomy` plugin's lane-stop gate via a `pluginConfigs` override (example above; the plugin id is marketplace-qualified, `<plugin>@<marketplace>`, for however the plugin was installed). A non-object value skips the lane with an error. A gate request (`lane_stop_gate_enabled: true` under an `autonomy` key) additionally triggers launch-time ARMING (#1784): the launcher runs autonomy's `hooks/lane-stop-gate-arm.sh` and injects a random `lane_stop_gate_arm_id` into the launched settings — the trusted per-session channel the gate actually honors (it ignores the bare env mirror a repo `env` block could forge). A gate-requesting lane that cannot be armed (autonomy missing/pre-0.12.0, arming error, managed-settings veto) is skipped with an error rather than launched silently ungated. |

Lane names are free-form (`work`, `work-2`, `babysit`, `decide`, …); nothing is
Expand Down
59 changes: 57 additions & 2 deletions plugins/claude-ops/skills/lanes/scripts/lane-launcher.sh
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@
# prompt required; path to the canonical prompt file (absolute, or
# relative to prompt_dir).
# model optional; passed as --model.
# effort optional; passed as --effort (low|medium|high|xhigh|max).
# effort optional; passed as --effort (low|medium|high|xhigh|max|ultracode).
# ultracode additionally requires the installed CLI to meet
# ULTRACODE_MIN_VERSION; a lane below it is skipped, not launched.
# settings optional; a JSON OBJECT passed inline as --settings for that
# session only (e.g. a pluginConfigs override opting the lane into
# the autonomy plugin's lane-stop gate). Non-object values are
Expand Down Expand Up @@ -119,7 +121,44 @@

set -uo pipefail

VALID_EFFORTS="low medium high xhigh max"
VALID_EFFORTS="low medium high xhigh max ultracode"
Comment thread
kyle-sexton marked this conversation as resolved.

# `ultracode` is the one effort upstream gates on a CLI version, so it is the one
# the static allowlist cannot settle on its own. Below the floor the CLI rejects the
# value outright (`Unknown --effort value 'ultracode'`) and starts the session at the
# default effort, so the launcher skips the lane rather than launching it at an
# unintended effort — restart preflights this BEFORE stopping, keeping a healthy lane
# up. https://code.claude.com/docs/en/model-config#adjust-effort-level
ULTRACODE_MIN_VERSION="2.1.203"

# Compared component by component in awk — deliberately NOT `sort -V`, which is a
# GNU-only construct this repo's portability lane rejects. Missing components
# compare as 0, so "2.2" reads as 2.2.0.
version_at_least() { # <candidate> <minimum>
[[ "$1" =~ ^[0-9]+(\.[0-9]+)*$ ]] || return 1
awk -F. -v a="$1" -v b="$2" 'BEGIN {
na = split(a, x, "."); nb = split(b, y, ".")
n = (na > nb ? na : nb)
for (i = 1; i <= n; i++) {
av = (i <= na ? x[i] + 0 : 0); bv = (i <= nb ? y[i] + 0 : 0)
if (av > bv) exit 0
if (av < bv) exit 1
}
exit 0
}' 2>/dev/null
}

# Memoized: every lane in a run shares one `claude --version` probe. Callers read
# CLI_VERSION_CACHE directly — a `$(cli_version)` substitution would populate the
# cache inside a subshell and throw it away, re-probing once per lane.
CLI_VERSION_CACHE=""
cli_version() {
[[ -n "$CLI_VERSION_CACHE" ]] && return 0
CLI_VERSION_CACHE="$(claude --version 2>/dev/null | tr -d '\r' |
grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)"
[[ -n "$CLI_VERSION_CACHE" ]] || CLI_VERSION_CACHE="unknown"
return 0
}

ACTION="start"
CONFIG=""
Expand Down Expand Up @@ -649,6 +688,22 @@ validate_launch_inputs() {
err "lane '$name': invalid effort '$effort' (want: $VALID_EFFORTS) — skipped"
return 1
fi
if [[ "$effort" == "ultracode" ]]; then
# The same exemption require_claude documents: a dry run must preview with no
# CLI installed. With no binary to probe the gate cannot be evaluated, so the
# preview says so rather than refusing a lane a real run may well launch. Keyed
# on the binary's absence, not on an "unknown" version — a CLI whose --version
# is unparsable is installed, and stays refused.
if ((DRY_RUN)) && ! command -v claude >/dev/null 2>&1; then
info " lane '$name': effort 'ultracode' version gate not evaluated (no claude CLI to probe)"
else
cli_version
if ! version_at_least "$CLI_VERSION_CACHE" "$ULTRACODE_MIN_VERSION"; then
err "lane '$name': effort 'ultracode' needs Claude Code >= $ULTRACODE_MIN_VERSION (installed: $CLI_VERSION_CACHE) — skipped"
return 1
fi
fi
fi
# `settings` must be a JSON object — the launcher passes it verbatim to
# `claude --settings`, and a string/array/scalar would make the whole session
# fail to launch with an opaque CLI error instead of a per-lane skip here.
Expand Down
63 changes: 62 additions & 1 deletion plugins/claude-ops/skills/lanes/scripts/lane-launcher.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ if [[ "\$1" == "stop" ]]; then exit "\${STUB_CLAUDE_STOP_RC:-0}"; fi
# A test can force a failed \`claude plugin marketplace update\` via
# STUB_CLAUDE_UPDATE_RC to exercise the refresh-failure abort path.
if [[ "\$1" == "plugin" ]]; then exit "\${STUB_CLAUDE_UPDATE_RC:-0}"; fi
# STUB_CLAUDE_VERSION drives the ultracode version gate; the default clears it.
if [[ "\$1" == "--version" ]]; then
printf '%s (Claude Code)\n' "\${STUB_CLAUDE_VERSION:-2.1.220}"
exit 0
fi
STUB
REAL_GIT="$(command -v git)"
cat >"$STUB_BIN/git" <<STUB
Expand Down Expand Up @@ -371,7 +376,8 @@ cat >"$TMP/badprompt.json" <<'JSON'
"lanes": [
{ "name": "gone", "prompt": "missing.md" },
{ "name": "blank", "prompt": "blank.md" },
{ "name": "baddy", "prompt": "work.md", "effort": "turbo" }
{ "name": "baddy", "prompt": "work.md", "effort": "turbo" },
{ "name": "ultra", "prompt": "work.md", "effort": "ultracode" }
] }
JSON
: >"$REPO/.work/blank.md"
Expand All @@ -380,6 +386,61 @@ assert_contains "missing prompt file skipped" "$out" "prompt file not found"
assert_contains "empty prompt file skipped" "$out" "prompt file is empty"
assert_contains "invalid effort skipped" "$out" "invalid effort 'turbo'"
assert_not_contains "no launch for bad lanes" "$out" "claude --bg -n baddy"
assert_contains "ultracode effort accepted" "$out" "claude --bg -n ultra"
assert_contains "ultracode passed through as --effort" "$out" "claude --bg -n ultra --effort ultracode"

# ============================================================================
# ultracode version gate — below the floor the lane is skipped, and a restart
# preflights it BEFORE stopping so a healthy running lane stays up
# ============================================================================
cat >"$TMP/ultra.json" <<'JSON'
{ "prompt_dir": ".work",
"lanes": [ { "name": "work", "prompt": "work.md", "effort": "ultracode" } ] }
JSON
out="$(STUB_CLAUDE_VERSION=2.1.202 run_launcher start --repo "$REPO" --config "$TMP/ultra.json" --agents-json "$AGENTS_EMPTY" --dry-run 2>&1)"
assert_contains "ultracode below floor skipped" "$out" "needs Claude Code >= 2.1.203"
assert_contains "skip message reports installed version" "$out" "(installed: 2.1.202)"
assert_not_contains "no launch below the floor" "$out" "claude --bg -n work"

out="$(STUB_CLAUDE_VERSION=2.1.203 run_launcher start --repo "$REPO" --config "$TMP/ultra.json" --agents-json "$AGENTS_EMPTY" --dry-run 2>&1)"
assert_contains "ultracode at the floor launches" "$out" "claude --bg -n work"

# The running lane must survive a restart the version gate refuses. NOT --dry-run:
# `run` short-circuits under it, so the stop could never reach the stub and the
# assertion below would hold no matter when the launcher stopped the lane.
: >"$CLAUDE_LOG"
out="$(STUB_CLAUDE_VERSION=2.1.202 run_launcher restart --repo "$REPO" --config "$TMP/ultra.json" --agents-json "$AGENTS_RUNNING" 2>&1)"
assert_contains "restart refused below the floor" "$out" "needs Claude Code >= 2.1.203"
assert_not_contains "healthy lane not stopped by a refused restart" "$(cat "$CLAUDE_LOG")" "stop sid-work-1"

# Every lane in a run shares ONE `claude --version` probe. Callers read the cache
# global directly; a `$(cli_version)` substitution would fill it in a subshell and
# re-probe once per lane, so this counts the probes rather than trusting the shape.
cat >"$TMP/ultra3.json" <<'JSON'
{ "prompt_dir": ".work",
"lanes": [ { "name": "u1", "prompt": "work.md", "effort": "ultracode" },
{ "name": "u2", "prompt": "work.md", "effort": "ultracode" },
{ "name": "u3", "prompt": "work.md", "effort": "ultracode" } ] }
JSON
: >"$CLAUDE_LOG"
out="$(run_launcher start --repo "$REPO" --config "$TMP/ultra3.json" --agents-json "$AGENTS_EMPTY" --dry-run 2>&1)"
assert_contains "every ultracode lane launches" "$out" "claude --bg -n u3 --effort ultracode"
assert_eq "version probe memoized across lanes" 1 "$(grep -c -- '--version' "$CLAUDE_LOG")"

# A dry run must preview with no `claude` installed — the exemption require_claude
# documents. The ultracode gate has no binary to probe there, so it reports the gate
# unevaluated and still previews the lane instead of refusing it.
NOCLAUDE_BIN="$TMP/bin-noclaude"
mkdir -p "$NOCLAUDE_BIN"
cp "$STUB_BIN/git" "$NOCLAUDE_BIN/git"
NOCLAUDE_PATH="$NOCLAUDE_BIN:$(dirname "$(command -v jq)"):/usr/bin:/bin"
assert_eq "no-claude PATH really carries no claude" "" "$(PATH="$NOCLAUDE_PATH" bash -c 'command -v claude' || true)"
out="$(
export PATH="$NOCLAUDE_PATH"
run_launcher start --repo "$REPO" --config "$TMP/ultra.json" --agents-json "$AGENTS_EMPTY" --dry-run 2>&1
)"
assert_contains "no-CLI dry run reports the gate unevaluated" "$out" "version gate not evaluated"
assert_contains "no-CLI dry run still previews the lane" "$out" "claude --bg -n work --effort ultracode"

# ============================================================================
# Medium 1 — an option must not swallow the next flag as its value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ Repo: <slug> · Branch: <name>

5-hour blocks (current + prior 3): <inline summary>

Cost USD is a list-rate estimate, not a bill: ccusage prices tokens at public per-token list
rates, while subscription usage is plan-priced.

## Cache health (last <window>)

| Model | Cache read | Cache creation | Read : create |
Expand Down Expand Up @@ -115,6 +118,9 @@ Top recurring (same `<bin>:<sha16>` ≥ 3×):
publishes no threshold, so any HIGH/MEDIUM cutoff would be invented here rather than sourced. It
is also the one section sourced from the OTEL store rather than ccusage, which is why it sits
apart from Token / cost instead of adding columns to it
- **The Token / cost caveat line is fixed copy** — Claude Code documents the same list-rate
limitation for its own locally computed dollar figures
(<https://code.claude.com/docs/en/costs.md>, verified 2026-08-04)

## Severity coloring (terminal)

Expand Down