feat(claude-ops): add morning-brief operator dashboard skill#569
Conversation
Collapse the daily hand-run operator morning queries into one read-only, gh-based view for the current repo: open counts per queue label, the gh-native merge-ready PR list (non-draft + mergeStateStatus=CLEAN), parked status: needs-decision issues with their RECOMMENDED lines (uppercase marker preferred, case-insensitive fallback), and loop-lane telemetry freshness (per-lane last-cycle age, STALE past --stale-hours, plus any flags). Owner/repo is derived from `gh repo view`, never hardcoded. The telemetry issue is auto-discovered by title and degrades to "no telemetry issue found" where absent, so the tool is reusable across repos. Merge-ready is a light gh-native glance that points to /source-control:babysit-prs as the authoritative gate rather than shelling into that plugin's engine (which would break the plugin-cache boundary for installed consumers). Ships scripts/morning-brief.sh plus a fixture-driven morning-brief.test.sh (29 cases, network-free), bumps claude-ops 0.13.1 -> 0.14.0, and updates the CHANGELOG, README skills table, plugin description, and marketplace tags. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 4m 21s —— View job PR Review: feat(claude-ops): add morning-brief operator dashboard skill
|
`generate-catalog.mjs --check` (plugin-gate) flagged the root README catalog block as stale after the claude-ops plugin.json description bumped from five to six skills. Regenerated via the sanctioned generator rather than hand-edited. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 56s —— View job PR Review: feat(claude-ops): add morning-brief operator dashboard skill
SummaryThis is a clean, well-scoped addition. The implementation is read-only, repo-agnostic, plugin-form safe, and the fixture-driven test design is particularly elegant (allows network-free CI without any mocking infrastructure). The cross-plugin boundary is handled correctly — slash-command cross-reference to Four findings below, all minor; none are blocking. Findings1.
|
--stale-hours and --rec-maxlen accepted non-numeric values, which crashed cryptically under `set -u` in the later arithmetic (STALE_HOURS * 3600 / rec-length compare) instead of failing cleanly. Add an integer guard after each flag's require_value that exits 3 with a specific message, and cover the error path with tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-green review pass (work lane)Bot review's finding #1 (missing numeric validation on 🤖 Generated with Claude Code |
|
Claude finished @kyle-sexton's task in 2m 45s —— View job PR Review: feat(claude-ops): add morning-brief operator dashboard skill
This is a post-fix pass covering the Numeric validation fix (the key change)The guard is placed inline during argument parsing — correct and cleaner than the post-parse block suggested in the prior review, since it fails fast at the point of bad input rather than after all flags are processed.
[[ "$2" =~ ^[0-9]+$ ]] || {
printf 'morning-brief: --stale-hours requires a non-negative integer\n' >&2
exit 3
}The regex Test additions (cases 30–33)
ERR_STALE="$(bash "$BRIEF" --stale-hours abc 2>&1)"
assert_exit "non-numeric --stale-hours exits 3" 3 "$?"
assert_contains "non-numeric --stale-hours clear message" "$ERR_STALE" "--stale-hours requires a non-negative integer"
ERR_MAXLEN="$(bash "$BRIEF" --rec-maxlen 12x 2>&1)"
assert_exit "non-numeric --rec-maxlen exits 3" 3 "$?"
assert_contains "non-numeric --rec-maxlen clear message" "$ERR_MAXLEN" "--rec-maxlen requires a non-negative integer"The pattern is correct: Nothing new to flagThe three open items from the first two review passes (deferred to #579) are minor maintenance nits — fragile Metadata is consistent: This looks good to merge. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6fceb0ed96
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
morning-brief.sh used GNU-only `date -d` at three sites (--now, telemetry last-cycle, and the header render). On BSD/macOS date, where Claude Code commonly runs, `-d` is unsupported: --now exited 3 and every last-cycle value rendered "unparsable timestamp", so no lane got a staleness verdict. Add to_epoch/from_epoch helpers that try GNU (`-d`) first, then BSD (`-j -f` for parsing, `-r` for epoch render). TZ=UTC on the BSD parse forces the timezone-naive strptime result to UTC, matching how GNU reads the Z-suffixed stamps. Only time-bearing formats are tried so a malformed stamp stays unparsable instead of prefix-matching to a wrong midnight. Addresses codex P2. 33/33 tests pass on GNU and under a BSD-date stub. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 54s —— View job PR Review: feat(claude-ops): add morning-brief operator dashboard skill
This is a targeted review of the BSD date fix (
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 68225a9f13
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
--stale-hours and --rec-maxlen passed the ^[0-9]+$ guard but were stored raw, so leading-zero values were later misread as octal: 08 threw "value too great for base" in the ((delta > STALE_HOURS * 3600)) staleness test (silently suppressing the STALE verdict), and 010 truncated recommendations to 8 chars instead of 10. Normalize each value to decimal once at parse time with the 10# base prefix (the repo's established octal-guard convention), so every downstream use is safe. Add tests asserting 08 still flags STALE with no arithmetic error and 010 truncates at 10 chars. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 15s —— View job PR Review: feat(claude-ops): add morning-brief operator dashboard skill
This is a targeted review of Base-10 normalization fix — overall assessmentThe fix is correct, minimal, and well-placed. STALE_HOURS="$((10#$2))"
# ...
REC_MAXLEN="$((10#$2))"The
Default values ( Test additions — coverage is sound
Both failure modes are covered with the right fixtures and assertions. Nothing new to flagThe one remaining nit from the prior review pass (stale skip-guard comment at L17–22 still says "BSD cannot run the staleness path" after This is ready to merge. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bd653cba98
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…pping The test guard required GNU `date -d`, so it skipped entirely on macOS and never exercised to_epoch/from_epoch's BSD `date -j -f` / `-r` fallback on any runner. Relax the guard to run whenever either date dialect is present (so a macOS run drives the real BSD branch), and add a sub-test that forces the BSD branch on a GNU host via a `date` test double rejecting `-d` — proving the branch is reached and wired (return handling, TZ, from_epoch's -r render, and the parseable-vs-unparseable decision). Negative control: breaking the BSD parse fails exactly the four new cases and nothing else. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 41s —— View job PR Review: feat(claude-ops): add morning-brief operator dashboard skill
This is a targeted review of Skip guard relaxationhave_gnu_date() { date -u -d "2026-01-01T00:00Z" +%s >/dev/null 2>&1; }
have_bsd_date() { date -u -j -f "%Y-%m-%dT%H:%MZ" "2026-01-01T00:00Z" +%s >/dev/null 2>&1; }
if ! have_gnu_date && ! have_bsd_date; then
echo "SKIP: no supported date dialect (need GNU 'date -d' or BSD 'date -j -f')" >&2
exit 0
fiCorrect. The probe calls match exactly what BSD date test double
The stub's argument parsing is correct. Tracing through the key call
For Known limitation (documented in the comment): the stub ignores which format string is passed to Four new assertions
All four cover the right failure modes for the BSD path. ✓ The What looks good
SummaryAll five commits ( |
The BSD-path test comment used "unparseable"; the typos gate (and the script's own output) uses "unparsable". Aligns the word so the hygiene lane passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 35s —— View job PR Review: feat(claude-ops): add morning-brief operator dashboard skill
This is a targeted review of Typos fix — correct and minimal-# unparseable decision on the `07:XX` fixture.
+# unparsable decision on the `07:XX` fixture.The change is comment-only, in the BSD test double's scope note. Overall PR statusAll five commits are clean. A summary of what each addressed:
Three minor items remain deferred to #579 (none blocking):
This is ready to merge. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1fcf9ffd3b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… position The tier-2 lowercase fallback matched the first `recommended` anywhere, so an issue whose body says "... is not recommended" before its real lowercase "recommended: ..." marker rendered the rejected option as the recommendation — the same false positive tier 1 exists to avoid. Anchor the fallback to marker position (line start, after optional list/blockquote/bold decoration), and add a discriminating fixture (#105) proving the marker beats an earlier "not recommended" line. 42/42 pass, shellcheck + typos clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 10s —— View job PR Review: feat(claude-ops): add morning-brief operator dashboard skill
This is a targeted review of RECOMMENDED anchor fix — overall assessmentThe fix is correct, minimal, and addresses exactly the failure mode the Codex reviewer described. Before[[ -n "$rec" ]] || rec="$(grep -iam1 'recommended' <<<"$combined")"Unanchored — After[[ -n "$rec" ]] || rec="$(grep -iam1 -E '^[[:space:]]*([0-9]+[.)][[:space:]]*)?[-*>|#[:space:]]*\**[[:space:]]*recommended' <<<"$combined")"Anchored to line start via Regex correctness tracingFor the fixture body:
Checked additional edge cases the character class might interact with:
The Test fixture and assertions{"number": 105, "title": "lowercase after rejection", "url": "http://x/i/105",
"body": "The bare-clone hub is not recommended here.\nrecommended: keep the sibling worktree layout.",
"comments": []}
assert_contains "decision #105 lowercase marker beats earlier 'not recommended'" "$OUT" "keep the sibling worktree layout"
assert_not_contains "decision #105 ignores 'not recommended' rejection" "$OUT" "bare-clone hub is not recommended"The fixture is a direct instantiation of the Codex failure scenario: the rejection phrase appears first, the real marker second. Both assertions are discriminating — the positive assertion confirms the correct line was extracted; the negative confirms the rejection line was not selected. Together they would catch a regression to the pre-fix unanchored grep. ✓ The complement with the existing #100 fixture (tier-1 uppercase) is now complete: both tiers have an "X is not recommended" / "RECOMMENDED/recommended: Y" discrimination case proving each tier anchors to marker position independently. What looks correct in this commit
SummaryAll six prior Codex/bot findings (five from the review rounds, one from the final Codex pass) have been addressed in this PR. The three minor items deferred to #579 remain correctly deferred:
This is ready to merge. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c88224f1f7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The prior fix anchored only the lowercase tier; the uppercase tier still matched any RECOMMENDED, so "Option A is NOT RECOMMENDED because..." before the real marker rendered the rejected option. Rather than anchor to line start (which drops the legitimate mid-line "After review, RECOMMENDED:" marker in fixture #103), gate BOTH tiers on a labeled marker — RECOMMENDED followed, past optional bold/space, by a ':'/'-'/em-dash separator. Negated prose ("NOT RECOMMENDED because", "not recommended for") is followed by a word, not a separator, so it no longer wins. Added discriminating fixture #106; both tiers' anchors proven load-bearing by negative control. 44/44 pass, shellcheck + typos clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 11s —— View job PR Review: feat(claude-ops): add morning-brief operator dashboard skill
This is a targeted review of Labeled-marker fix — overall assessmentThe fix is correct and the approach is sound. Before (
After (current marker_re='RECOMMENDED[[:space:]]*\**[[:space:]]*[-:—]'
rec="$(grep -am1 -E "$marker_re" <<<"$combined")"
[[ -n "$rec" ]] || rec="$(grep -iam1 -E "$marker_re" <<<"$combined")"Both tiers are now symmetric, using the same pattern. The separator constraint ( Regression check on prior fixtures:
Test additions — coverage is correct
{"number": 106, "title": "uppercase after rejection", "url": "http://x/i/106",
"body": "Option A is NOT RECOMMENDED because of drift.\nRECOMMENDED: choose the managed sync path.",
"comments": []}
assert_contains "decision #106 uppercase marker beats earlier 'NOT RECOMMENDED'" "$OUT" "choose the managed sync path"
assert_not_contains "decision #106 ignores 'NOT RECOMMENDED' rejection" "$OUT" "because of drift"This is the canonical discriminating case for the finding: rejection phrase ( The complement set is now: One minor residual note (not blocking)The separator set includes What looks correct
SummaryAll six Codex/bot findings across the PR have been addressed. The three minor items deferred to #579 remain correctly deferred:
This is ready to merge. |
…0; harden usage() (#844) ## Summary Closes the three non-blocking test-coverage/maintenance nits deferred from the claude[bot] review on PR #569 (the `morning-brief` dashboard skill). ## Fix - Added a regression test for the `any=0` telemetry path: a non-empty telemetry-comments array where none carry a `lane:` field (only scope-note comments) — previously only the `null`/no-issue-found case and the populated-with-lanes case were covered. - Added a regression test for `--rec-maxlen 0` (full, untruncated RECOMMENDED preview) — previously the `0 = full` semantics were only exercised implicitly via the `!= "0"` source check, with no assertion that a long recommendation tail survives untruncated. - Hardened `usage()`'s header self-extraction: replaced the hardcoded `sed -n '2,35p'` line range with a sentinel-based `awk` extraction (shebang line skipped, then every comment line printed up to the first non-comment/blank line). The header comment can now grow or shrink without silently truncating or over-running `--help` output. - Bumped `plugins/claude-ops/.claude-plugin/plugin.json` to `0.17.2` (patch — test/maintenance only, no behavior change) and added a matching `CHANGELOG.md` entry. ## Verification Ran the plugin's own test suite locally (bash + jq, both GNU-date and BSD-date-stub paths): ``` $ bash plugins/claude-ops/skills/morning-brief/morning-brief.test.sh ... PASS: [28] rec-maxlen 0 keeps the full tail untruncated PASS: [29] rec-maxlen 0 never inserts an ellipsis PASS: [30] telemetry with no lane-tagged comments reports none found ... morning-brief.test.sh: all 47 cases passed ``` All 47 cases pass (44 pre-existing + 3 new: rec-maxlen=0 truncation-skip, rec-maxlen=0 no-ellipsis, and the any=0 no-lane-comments case). Also manually diffed `--help` output before/after the `usage()` change — byte-identical. Closes #579 ## Related - #579 (this issue) - #569 (origin PR — the `morning-brief` dashboard skill; claude[bot] review surfaced these as deferred, non-blocking nits) ## Note on serialization PR #841 (`fix/812-jq-guard`) is currently open and also touches `plugins/claude-ops/` (a different skill, `lanes`). Opening this as **draft** with `do-not-merge` per the repo's serialization convention for concurrent `claude-ops` changes; this PR's version bump (`0.17.1` → `0.17.2`) may need to be re-based once #841 lands. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

Summary
The 30-60 minute operator morning pass starts with the same hand-run
ghqueries every day. This adds amorning-briefskill to theclaude-opsplugin that collapses them into a single read-only,gh-based picture for the current repo: open counts per queue label, the merge-ready PR list, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness. The pass now begins from a complete 5-second view instead of ad-hoc lookups — and the same view is the natural seed for the WP2-telemetry-era dashboard.Fix
New sibling skill
plugins/claude-ops/skills/morning-brief/(an observability/operational-visibility concern matching this plugin's existing shape, chosen over a barebin/script):scripts/morning-brief.sh— self-contained, read-only,gh-based. Never mutates labels, comments, issues, or PRs. Owner/repo is derived fromgh repo view(never hardcoded). Sections:priority: needs-triage,status: ready,status: needs-decision,needs-human(label spellings verified againstgh label list).gh pr listfiltered to non-draft +mergeStateStatus=CLEAN,reviewDecisionshown but not required (this repo leaves it empty — no required-review rule). This is a lighter, gh-native glance; it points at/source-control:babysit-prsas the authoritative merge gate rather than shelling into that plugin'spr_queue_snapshot.pyengine. That shell-out would cross the plugin-cache boundary (an installedclaude-opsconsumer cannot reach another plugin's scripts, and source-control may not even be installed) — the repo has zero runtime cross-plugin script invocations, and slash-command cross-reference is its established boundary-safe reuse pattern.status: needs-decisionissues with each one's RECOMMENDED line. Two-tier extraction: the deliberate uppercaseRECOMMENDEDmarker wins over an incidental lowercase mention (e.g. "not recommended"), with a case-insensitive fallback per the process note that a case-sensitive-only scan produced false negatives. Long lines truncate to a scannable preview (--rec-maxlen, default 240; the issue URL carries the full text).last-cycleage (markedSTALEpast--stale-hours, default 6) and anyflags:. The issue is auto-discovered by title (--telemetry-issueto pin) and degrades to "no telemetry issue found" where absent, so the tool is reusable in repos without loop-lane telemetry.morning-brief.test.sh— 29 fixture-driven, network-free cases (each data source injected via a--*-jsonfixture flag, clock pinned with--now); SKIPs whenjq/GNU-dateare absent, per the plugin-test runner contract.SKILL.md— thin instructions that invoke the script and cross-reference/source-control:babysit-prs(authoritative merge gate) and/observability(local telemetry stores).claude-ops0.13.1→0.14.0(minor, new skill) with a matching CHANGELOG entry; README skills table, plugin description ("Five → Six skills"), and marketplace tags updated for accuracy.Verification
All run locally against this repo's own configs — actual output, not asserted.
Test suite (
bash plugins/claude-ops/skills/morning-brief/morning-brief.test.sh):Cases cover: queue-count rendering; merge-ready filtering (keeps CLEAN non-drafts, drops draft + BLOCKED, shows/normalizes
reviewDecision); two-tier RECOMMENDED extraction (uppercase wins over "not recommended", lowercase fallback, no-marker message, comment-body scan); preview truncation; telemetry staleness (fresh vsSTALEvs unparsable stamp vs missing issue), flag surfacing, non-lane-comment skipping; and argument handling (--help=0, unknown flag=3, missing fixture=3).Linters (repo-pinned configs):
shellcheck --rcfile .shellcheckrcon both.shfiles — clean.markdownlint-cli2 --config .markdownlint-cli2.jsoncon SKILL.md + CHANGELOG.md + README.md —Summary: 0 error(s).typos --config _typos.tomlon the plugin + marketplace.json — 0 findings.editorconfig-checker --config .editorconfig-checker.jsonon every changed file — exit 0.plugin.jsonandmarketplace.jsonre-validated as JSON after edits.Live output (
bash .../morning-brief.sh, against this real repo):(Merge-ready was empty at capture time — 3 BLOCKED, 1 UNSTABLE — demonstrating the graceful "none clean" path; the
19:XXlane demonstrates graceful handling of a malformed stamp already present in the telemetry issue.)Closes #507
Related
🤖 Generated with Claude Code