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.11",
"version": "0.31.12",
"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
11 changes: 11 additions & 0 deletions plugins/claude-ops/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
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.12]

### Fixed

- **`morning-brief` Queues section no longer hardcodes melodic-software queue labels
on live runs (#610).** Default labels remain the melodic-software taxonomy, but
live queries now filter to labels that exist in the target repo (like the
telemetry-issue path, degrading to "no queue labels found" when none match).
Pass `--queue-labels` to pin a custom comma-separated set and `--decision-label`
to pin the parked-decision label.

## [0.31.11]

### Fixed
Expand Down
13 changes: 10 additions & 3 deletions plugins/claude-ops/skills/morning-brief/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: "Prints the operator's read-only morning view for the current GitHub repo in one pass — open counts per queue label (needs-triage / ready / needs-decision / needs-human), the gh-native merge-ready PR list, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness (last-cycle age + flags). Use when: 'morning brief', 'morning view', 'ops dashboard', 'what needs attention', 'daily standup view', 'operator morning pass', 'queues and merge-ready'. Read-only and gh-based — never mutates issues, PRs, labels, or comments."
argument-hint: "[--repo owner/name] [--telemetry-issue N] [--stale-hours N] — read-only; omit to view the current repo"
argument-hint: "[--repo owner/name] [--telemetry-issue N] [--queue-labels A,B,C] [--decision-label L] [--stale-hours N] — read-only; omit to view the current repo"
user-invocable: true
disable-model-invocation: false
shell: bash
Expand Down Expand Up @@ -43,9 +43,9 @@ re-query the sections by hand.

| Section | Source | Notes |
|---|---|---|
| Queues | `gh issue list --label <queue>` counts | Labels: `priority: needs-triage`, `status: ready`, `status: needs-decision`, `needs-human` |
| Queues | `gh issue list --label <queue>` counts | Defaults to melodic-software queue labels; live runs filter to labels that exist in the repo (pass `--queue-labels` to pin a custom set) |
| Merge-ready PRs | `gh pr list` filtered to non-draft + `mergeStateStatus=CLEAN` | A light glance signal; `reviewDecision` shown but not required (repos without required review leave it empty) |
| Parked decisions | open `status: needs-decision` issues | Surfaces each one's RECOMMENDED line — the uppercase marker wins over an incidental lowercase mention; a case-insensitive fallback catches lowercase markers |
| Parked decisions | open issues with the decision label (default `status: needs-decision`) | Surfaces each one's RECOMMENDED line — the uppercase marker wins over an incidental lowercase mention; a case-insensitive fallback catches lowercase markers; pass `--decision-label` to pin |
| Lane telemetry | the loop-lane telemetry issue's per-lane comments | Each lane's `last-cycle` age (marked `STALE` past `--stale-hours`, default 6) and any `flags:` |
| Stranded findings | merged PRs whose unresolved review threads were **created after the merge** | One line per PR at its worst severity, with a finding count; window is `--stranded-days`, default 3 |

Expand All @@ -60,6 +60,13 @@ The telemetry issue is auto-discovered by title; pass `--telemetry-issue N` to p
When no such issue exists (e.g. a consuming repo without loop-lane telemetry), that
section reports "no telemetry issue found" and the rest of the brief still renders.

Queue labels default to the melodic-software taxonomy and are filtered to labels
that actually exist in the target repo on live runs, so a consuming repo with a
different scheme does not show misleading `0`/`?` rows. Pass `--queue-labels` to
pin a custom comma-separated set; pass `--decision-label` to pin the parked-decision
label. When none of the configured queue labels exist, the Queues section reports
"no queue labels found" and the rest of the brief still renders.

## Cross-references

- `/source-control:babysit-prs` — the **authoritative** PR merge gate and readiness
Expand Down
80 changes: 80 additions & 0 deletions plugins/claude-ops/skills/morning-brief/morning-brief.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ assert_exit() { if [[ "$2" == "$3" ]]; then pass "$1"; else fail "$1" "exit $2"

# --- Fixtures -----------------------------------------------------------------
NOW="2026-07-20T08:00Z"
FIXTURE_REPO="example/test-repo"

cat >"$TMP/counts.json" <<'EOF'
{"priority: needs-triage": 28, "status: ready": 44, "status: needs-decision": 3, "needs-human": 7}
Expand Down Expand Up @@ -224,6 +225,19 @@ cat >"$TMP/telemetry-no-lanes.json" <<'EOF'
]
EOF

# A repo whose label taxonomy does not include the melodic-software defaults.
cat >"$TMP/repo-labels-alternate.json" <<'EOF'
["bug", "enhancement", "status: blocked"]
EOF

# A repo that carries only a subset of the defaults (ready + needs-human).
cat >"$TMP/repo-labels-partial.json" <<'EOF'
["status: ready", "needs-human", "documentation"]
EOF

# A repo with no labels at all (successful empty lookup, not a failed one).
printf '[]\n' >"$TMP/repo-labels-empty.json"

# --- Full render --------------------------------------------------------------
OUT="$(bash "$BRIEF" --now "$NOW" --stale-hours 6 \
--counts-json "$TMP/counts.json" \
Expand Down Expand Up @@ -306,6 +320,72 @@ OUT2="$(bash "$BRIEF" --now "$NOW" \
--telemetry-json "$TMP/no-telemetry.json" 2>&1)"
assert_contains "missing telemetry degrades gracefully" "$OUT2" "no telemetry issue found"

# --- Graceful: queue labels absent in consuming repo -------------------------
OUT_NO_QL="$(bash "$BRIEF" --now "$NOW" \
Comment thread
kyle-sexton marked this conversation as resolved.
--repo "$FIXTURE_REPO" \
--repo-labels-json "$TMP/repo-labels-alternate.json" \
Comment thread
kyle-sexton marked this conversation as resolved.
--counts-json "$TMP/empty.json" \
--pr-json "$TMP/empty.json" \
--decisions-json "$TMP/empty.json" \
--telemetry-json "$TMP/empty.json" \
--merged-json "$TMP/merged-clean.json" 2>&1)"
assert_contains "no matching queue labels degrades gracefully" "$OUT_NO_QL" "no queue labels found in this repo"
assert_contains "degradation names the defaults" "$OUT_NO_QL" "pass --queue-labels to customize"
QUEUE_ONLY="$(printf '%s\n' "$OUT_NO_QL" | awk '/^== Queues/,/^== /{if (!/^== / || /^== Queues/) print}')"
assert_not_contains "absent defaults are not rendered as 0 count rows" "$QUEUE_ONLY" $'priority: needs-triage 0'
assert_not_contains "absent defaults are not rendered as ? count rows" "$QUEUE_ONLY" $'priority: needs-triage ?'

OUT_PARTIAL_QL="$(bash "$BRIEF" --now "$NOW" \
--repo-labels-json "$TMP/repo-labels-partial.json" \
--counts-json "$TMP/counts.json" \
--pr-json "$TMP/empty.json" \
--decisions-json "$TMP/empty.json" \
--telemetry-json "$TMP/empty.json" \
--merged-json "$TMP/merged-clean.json" 2>&1)"
assert_contains "partial taxonomy shows only labels that exist" "$OUT_PARTIAL_QL" "status: ready"
assert_contains "partial taxonomy keeps another existing label" "$OUT_PARTIAL_QL" "needs-human"
assert_not_contains "partial taxonomy drops absent default labels" "$OUT_PARTIAL_QL" "priority: needs-triage"

OUT_CUSTOM_QL="$(bash "$BRIEF" --now "$NOW" \
--repo "$FIXTURE_REPO" \
--queue-labels "bug, enhancement" \
--repo-labels-json "$TMP/repo-labels-alternate.json" \
--counts-json "$TMP/empty.json" \
--pr-json "$TMP/empty.json" \
--decisions-json "$TMP/empty.json" \
--telemetry-json "$TMP/empty.json" \
--merged-json "$TMP/merged-clean.json" 2>&1)"
assert_contains "pinned queue labels are honored" "$OUT_CUSTOM_QL" "bug"
assert_contains "pinned queue labels include the second entry" "$OUT_CUSTOM_QL" "enhancement"

OUT_NO_DEC="$(bash "$BRIEF" --now "$NOW" \
--repo "$FIXTURE_REPO" \
--repo-labels-json "$TMP/repo-labels-alternate.json" \
--counts-json "$TMP/counts.json" \
--pr-json "$TMP/empty.json" \
--telemetry-json "$TMP/empty.json" \
--merged-json "$TMP/merged-clean.json" 2>&1)"
assert_contains "missing decision label degrades gracefully" "$OUT_NO_DEC" "decision label not found in this repo"
Comment thread
kyle-sexton marked this conversation as resolved.
Comment thread
kyle-sexton marked this conversation as resolved.

OUT_EMPTY_LABELS="$(bash "$BRIEF" --now "$NOW" \
--repo "$FIXTURE_REPO" \
--repo-labels-json "$TMP/repo-labels-empty.json" \
--counts-json "$TMP/empty.json" \
--pr-json "$TMP/empty.json" \
--decisions-json "$TMP/empty.json" \
--telemetry-json "$TMP/empty.json" \
--merged-json "$TMP/merged-clean.json" 2>&1)"
assert_contains "empty label inventory degrades queue section" "$OUT_EMPTY_LABELS" "no queue labels found in this repo"

OUT_EMPTY_LABELS_DEC="$(bash "$BRIEF" --now "$NOW" \
--repo "$FIXTURE_REPO" \
--repo-labels-json "$TMP/repo-labels-empty.json" \
--counts-json "$TMP/empty.json" \
--pr-json "$TMP/empty.json" \
--telemetry-json "$TMP/empty.json" \
--merged-json "$TMP/merged-clean.json" 2>&1)"
assert_contains "empty label inventory degrades decision section" "$OUT_EMPTY_LABELS_DEC" "decision label not found in this repo"

# --- Graceful: empty queues / no parked decisions ----------------------------
OUT3="$(bash "$BRIEF" --now "$NOW" \
--counts-json "$TMP/counts.json" \
Expand Down
141 changes: 132 additions & 9 deletions plugins/claude-ops/skills/morning-brief/scripts/morning-brief.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
# morning-brief.sh live view of the current repo
# morning-brief.sh --repo owner/name target a specific repo
# morning-brief.sh --telemetry-issue N pin the lane-telemetry issue
# morning-brief.sh --queue-labels A,B,C pin the queue-label set (comma-separated)
# morning-brief.sh --decision-label L pin the parked-decision label
# morning-brief.sh --stale-hours N age past which a lane is STALE (default 6)
# morning-brief.sh --stranded-days N age window for stranded review findings (default 3)
# morning-brief.sh --rec-maxlen N truncate RECOMMENDED previews (default 240; 0 = full)
Expand All @@ -26,6 +28,7 @@
# Fixture flags (skip the network; used by the test suite and for reuse):
# --now ISO fixed clock for deterministic staleness
# --counts-json FILE label->count object, e.g. {"status: ready":4}
# --repo-labels-json FILE array of label names (or {name} objects) for existence checks
# --pr-json FILE array as emitted by `gh pr list --json ...`
# --decisions-json FILE array of {number,title,url,body,comments:[{body}]}
# --telemetry-json FILE array of {body} (the telemetry issue's comments)
Expand All @@ -37,17 +40,22 @@

set -uo pipefail

# --- Queue labels (verified against `gh label list`) --------------------------
QUEUE_LABELS=(
# --- Queue labels (melodic-software defaults; overridable / filtered live) ------
DEFAULT_QUEUE_LABELS=(
"priority: needs-triage"
"status: ready"
"status: needs-decision"
"needs-human"
)
DECISION_LABEL="status: needs-decision"
DEFAULT_DECISION_LABEL="status: needs-decision"
QUEUE_LABELS=()
DECISION_LABEL=""
QUEUE_LABELS_ARG=""
DECISION_LABEL_ARG=""

REPO=""
TELEMETRY_ISSUE=""
REPO_LABELS_JSON=""
STALE_HOURS="6"
REC_MAXLEN="240"
NOW_ISO=""
Expand Down Expand Up @@ -110,6 +118,22 @@ while (($# > 0)); do
TELEMETRY_ISSUE="$2"
shift 2
;;
--queue-labels)
require_value "$1" "${2:-}"
QUEUE_LABELS_ARG="$2"
shift 2
;;
--decision-label)
require_value "$1" "${2:-}"
DECISION_LABEL_ARG="$2"
shift 2
;;
--repo-labels-json)
require_value "$1" "${2:-}"
require_file "$1" "$2"
REPO_LABELS_JSON="$2"
shift 2
;;
--stale-hours)
require_value "$1" "${2:-}"
require_uint "$1" "$2"
Expand Down Expand Up @@ -182,8 +206,23 @@ have jq || {

# gh is only needed for live sources; a fully fixtured run (tests) must not
# require it. Demand it only when at least one section will hit the network.
ANY_LIVE=0
[[ -z "$COUNTS_JSON" || -z "$PR_JSON" || -z "$DECISIONS_JSON" || -z "$TELEMETRY_JSON" ]] && ANY_LIVE=1
NEEDS_LIVE_COUNTS=0
NEEDS_LIVE_PRS=0
NEEDS_LIVE_DECISIONS=0
NEEDS_LIVE_TELEMETRY=0
[[ -z "$COUNTS_JSON" ]] && NEEDS_LIVE_COUNTS=1
[[ -z "$PR_JSON" ]] && NEEDS_LIVE_PRS=1
[[ -z "$DECISIONS_JSON" ]] && NEEDS_LIVE_DECISIONS=1
[[ -z "$TELEMETRY_JSON" ]] && NEEDS_LIVE_TELEMETRY=1
# Parked-decisions can short-circuit on a fixture label inventory when the
# decision label is absent — no gh issue list needed for that probe.
if ((NEEDS_LIVE_DECISIONS)) && [[ -n "$REPO_LABELS_JSON" ]]; then
_probe_decision_label="${DECISION_LABEL_ARG:-$DEFAULT_DECISION_LABEL}"
if _probe_repo_labels="$(fetch_repo_label_names)"; then
Comment thread
kyle-sexton marked this conversation as resolved.
label_exists_in_repo "$_probe_decision_label" "$_probe_repo_labels" || NEEDS_LIVE_DECISIONS=0
fi
fi
Comment thread
kyle-sexton marked this conversation as resolved.
ANY_LIVE=$((NEEDS_LIVE_COUNTS || NEEDS_LIVE_PRS || NEEDS_LIVE_DECISIONS || NEEDS_LIVE_TELEMETRY))
if ((ANY_LIVE)) && ! have gh; then
printf 'morning-brief: gh required for live queries (pass fixtures to run offline)\n' >&2
exit 4
Expand Down Expand Up @@ -271,17 +310,91 @@ fmt_age() {
fi
}

# --- Queue label resolution ---------------------------------------------------
# Defaults match melodic-software's taxonomy. Live runs filter to labels that
# actually exist in the target repo so a consuming repo with a different scheme
# does not render misleading 0/? rows. Pass --queue-labels / --decision-label to
# pin a custom set (same spirit as --telemetry-issue).
resolve_queue_labels() {
QUEUE_LABELS=()
if [[ -n "$QUEUE_LABELS_ARG" ]]; then
local part
IFS=',' read -ra _parts <<<"$QUEUE_LABELS_ARG"
for part in "${_parts[@]}"; do
part="${part#"${part%%[![:space:]]*}"}"
part="${part%"${part##*[![:space:]]}"}"
[[ -n "$part" ]] && QUEUE_LABELS+=("$part")
done
else
QUEUE_LABELS=("${DEFAULT_QUEUE_LABELS[@]}")
fi
}

resolve_decision_label() {
if [[ -n "$DECISION_LABEL_ARG" ]]; then
DECISION_LABEL="$DECISION_LABEL_ARG"
else
DECISION_LABEL="$DEFAULT_DECISION_LABEL"
fi
}

fetch_repo_label_names() {
local raw=""
if [[ -n "$REPO_LABELS_JSON" ]]; then
jq -e 'type == "array"' "$REPO_LABELS_JSON" >/dev/null 2>&1 || return 1
raw="$(jq -r '.[] | if type == "string" then . else .name end' "$REPO_LABELS_JSON" 2>/dev/null)" || return 1
elif [[ -n "$REPO" ]]; then
raw="$(gh label list "${REPO_ARGS[@]}" --limit 500 --json name -q '.[].name' 2>/dev/null | tr -d '\r')" || return 1
else
return 1
fi
jq -R -s '
split("\n")
| map(select(length > 0))
| unique
' <<<"$raw"
}

label_exists_in_repo() {
local label="$1" names="$2"
jq -e --arg l "$label" 'index($l) != null' <<<"$names" >/dev/null 2>&1
}

resolve_queue_labels
resolve_decision_label

# =============================================================================
# Section 1 — queue label counts
# =============================================================================
print_queues() {
echo "== Queues (open issues per label) =="
local counts=""
local counts="" repo_labels="" labels_to_show=() label n labels_available=0
if [[ -n "$COUNTS_JSON" ]]; then
counts="$(cat "$COUNTS_JSON")"
fi
local label n
for label in "${QUEUE_LABELS[@]}"; do
if [[ -n "$REPO_LABELS_JSON" || ( -z "$counts" && -n "$REPO" ) ]]; then
if repo_labels="$(fetch_repo_label_names)"; then
labels_available=1
fi
fi
if ((labels_available)); then
for label in "${QUEUE_LABELS[@]}"; do
label_exists_in_repo "$label" "$repo_labels" && labels_to_show+=("$label")
done
if ((${#labels_to_show[@]} == 0)); then
echo " no queue labels found in this repo (nothing to report)"
if [[ -z "$QUEUE_LABELS_ARG" ]]; then
echo " defaults: ${DEFAULT_QUEUE_LABELS[*]} — pass --queue-labels to customize"
else
echo " pinned: ${QUEUE_LABELS[*]}"
fi
echo
return
fi
else
labels_to_show=("${QUEUE_LABELS[@]}")
fi
for label in "${labels_to_show[@]}"; do
if [[ -n "$counts" ]]; then
n="$(jq -r --arg l "$label" '.[$l] // 0' <<<"$counts" 2>/dev/null)"
else
Expand Down Expand Up @@ -332,7 +445,17 @@ print_merge_ready() {
# =============================================================================
print_decisions() {
echo "== Parked decisions (${DECISION_LABEL}) with RECOMMENDED lines =="
local decisions
local decisions repo_labels="" labels_available=0
if [[ -z "$DECISIONS_JSON" && ( -n "$REPO" || -n "$REPO_LABELS_JSON" ) ]]; then
if repo_labels="$(fetch_repo_label_names)"; then
labels_available=1
fi
if ((labels_available)) && ! label_exists_in_repo "$DECISION_LABEL" "$repo_labels"; then
echo " (decision label not found in this repo — pass --decision-label to customize)"
echo
return
fi
fi
if [[ -n "$DECISIONS_JSON" ]]; then
decisions="$(cat "$DECISIONS_JSON")"
else
Expand Down