Skip to content

feat(skills/pr-quality): post-PR coordinator + GitHub Action - #94

Merged
bdchatham merged 2 commits into
mainfrom
feat/pr-quality-skill-issue-88
Jun 1, 2026
Merged

feat(skills/pr-quality): post-PR coordinator + GitHub Action#94
bdchatham merged 2 commits into
mainfrom
feat/pr-quality-skill-issue-88

Conversation

@bdchatham

@bdchatham bdchatham commented May 31, 2026

Copy link
Copy Markdown
Collaborator

Closes #88.

Summary

Two-mode PR convention coordinator. No CI infrastructure. Agent-invoked pre-PR (against the staged diff before gh pr create; findings surface inline for revision) and user-invocable post-PR (/pr-quality <PR>; posts a fresh comment with findings). Suggestive only — never gates merge. Silence is the success state.

Proactive trigger lives in CLAUDE.md + AGENTS.md working-agreement references (same pattern as /brevity layer 2 in PR #92).

This closes Cycle 3 of the 3-cycle goal: #86 brevity → #87 GTM → #88 this.

Pivot story

The original v1 shipped a GitHub Actions workflow + 8 bash scripts + claude-code-action invocation + state directory + concurrency group + cost guardrail + self-consistency + anchored marker + hash dedupe + 5-finding cap. It failed-closed on its own introduction PR because the Claude Code App wasn't installed on the repo — inverting the suggestive-only intent.

Pivoted on Brandon's "targeted and effective, no bloat" framing. Coral panel review (PE + reviewer + PM) iterated to a substantially tighter v1:

Was Now Why
.github/workflows/pr-quality.yml Deleted CI infrastructure isn't Tide's pattern. Agent-invoked is.
9 orchestration scripts 0 Claude orchestrates via Agent + Bash tools natively
judge-mechanical.sh (dispatcher) scan-yaml-cpu.sh + scan-harbor-ghcr.sh (single-purpose each) Mechanical predicates earn keep (Claude shouldn't redo each invocation); split for atomicity
claude-code-action@v1 No runtime Claude Code IS the runner
Self-consistency n=3 Single-shot Defer; un-defer on first real false-positive
5-finding cap + severity rank Uncapped Defer; un-defer on first PR producing >7 findings
Anchored marker + hash dedupe + PATCH lifecycle Post fresh comment Defer; un-defer on first comment-spam complaint
State directory + audit log None No state worth persisting; closer to thin orchestrator than stateful procedure
Cost guardrail $1.00 None Claude Code session budget handles
Concurrency group / cancel-in-progress None No concurrent runs in a session
Fork-PR guard None Irrelevant without workflow
skip-pr-quality label None Local invocation — user just doesn't invoke
references/guardrails.md (85 lines) Merged into SKILL.md After CI cuts, remaining content fit in SKILL guardrails stanza
references/judge-prompt-template.md Inlined into rule-registry.md After self-consistency cut, the structured-output contract collapses
5 judge files (per rule) 3 LLM judge files only Mechanical specs live in rule-registry.md row + script

Net: 24 files / 1322 lines → 8 files / 393 lines. Each file earns its keep on a "Claude shouldn't redo this in reasoning" or "single source of truth" basis. Zero overlap with sibling files. Zero CI ceremony.

Final tree

.claude/skills/pr-quality/
├── SKILL.md                                  (88 — procedure for both modes)
├── references/
│   ├── rule-registry.md                       (87 — v1 contract + mechanical specs + un-defer triggers)
│   ├── format-spec.md                         (41 — post-PR comment shape)
│   └── judges/
│       ├── narration_comments.md              (33 — LLM prompt + few-shot)
│       ├── temporary_migration_notes.md       (38)
│       └── authoritative_voice.md             (33)
├── scripts/
│   ├── scan-yaml-cpu.sh                       (39 — stateful awk for resources.limits.cpu)
│   └── scan-harbor-ghcr.sh                    (34 — unified-diff state machine)
└── evals/evals.json                           (happy + halt + pressure)

v1 rules (locked, in rule-registry.md)

Mechanical

  • no_cpu_limits — YAML scan for cpu: set inside any limits: block
  • harbor_ecr_convention — diff-line grep for ghcr.io in clusters/harbor/**

LLM-judged

  • narration_comments — function-doc-style only (*.go/*.py/*.ts)
  • temporary_migration_notes — durable docs only (CLAUDE.md, AGENTS.md, README.md, docs/**)
  • authoritative_voice — skill content only (.claude/skills/**/*.md)

Skill dispatch

  • Verbosity → /brevity via subagent-loads-target-skill. Verbosity judge detects the trigger; /brevity owns the standard. No re-implementation of brevity rules inside pr-quality.

Composition with /brevity

Verbosity dimension dispatches /brevity via subagent-loads-target-skill (same pattern as /coral, /council). The verbosity judge is intentionally not a re-implementation of brevity's rules — pr-quality detects the trigger that brevity matters here, brevity owns the standard. PE flagged the coupling risk in scope review; the boundary is preserved.

Working-agreement wiring (same-PR per layer-2 pattern)

CLAUDE.md ## Working Agreements gains:

PR-quality discipline: Before invoking gh pr create, apply /pr-quality (.claude/skills/pr-quality/) to the staged diff + planned body. Findings surface inline for revision. Post-PR: invoke /pr-quality <PR> to post a fresh comment with findings. (Brevity runs during authoring; pr-quality runs on the final diff — they don't chain.)

AGENTS.md ## Working Agreement gains a parallel "Pre-PR review" paragraph after the existing "Output discipline" entry.

Deferred mechanisms — un-defer triggers documented in rule-registry.md

Mechanism Un-defer trigger
Self-consistency (n=3 sampling) First real false-positive
5-finding cap + severity rank First PR producing >7 findings (wallpaper)
Anchored marker + hash dedupe First comment-spam complaint
Cost ceiling per PR First session budget overrun

Coral cross-review on the trim plan

  • product-engineer: CLEAR — trim is sound; 8→0+2 scripts is the right floor; composition with /brevity is clean as long as the verbosity judge stays a trigger detector, not a rule re-implementation (boundary preserved in rule-registry.md + judge file).
  • reviewer: CLEAR with 3 small adjustments (catalog description updated, format-spec separation kept as single source of truth, SKILL.md procedure rewritten to 2-script shape) — all applied.
  • product-manager: NEW pushing harder — cut cap + dedupe + lifecycle script + pre-PR mode. All cuts applied EXCEPT pre-PR mode per Brandon's explicit override (he's the customer; he'll fire it).

Test plan

  • Smoke test on scan-yaml-cpu.sh against multi-container fixture (1 violation under limits:, 1 non-violation under requests:): correctly emits 1 finding.
  • After merge: invoke /pr-quality against a sample real PR; verify the 5 judges fire per scope filter, output renders per format-spec, no comment on zero findings.
  • After merge: invoke /pr-quality 94 (this PR) post-merge to validate the post-PR mode end-to-end.
  • Verify CLAUDE.md + AGENTS.md wiring produces proactive /pr-quality invocation by sub-agents drafting PR bodies (council/coral sessions).

Goal completion

Cycle Issue PR Status
1 #86 brevity #89 ✅ merged
1.5 brevity wiring #92 ✅ merged
2 #87 GTM specialist #93 ✅ merged
3 #88 pr-quality #94 (this) awaiting review

Goal closes the moment #94 merges. Standing by.

🤖 Generated with Claude Code

Closes #88.

Adds .claude/skills/pr-quality/ — a procedural coordinator skill that runs
on pull_request: opened/synchronize/reopened, dispatches a fixed v1 judge
set in parallel, and posts a single anchored PR comment with up to 5
findings ranked by severity. Suggestive only — never gates merge. Silence
is the success state on clean PRs.

## v1 scope (locked by coral scope-cut)

Dimensions: 2 (verbosity dispatches /brevity; convention adherence with
5 specific memory-encoded rules). Deferred dimensions (docs completeness,
reference drift, commit message hygiene) carry un-defer triggers in
references/rule-registry.md.

Rules:
- no_cpu_limits (mechanical YAML scan, awk indent + parent-block aware)
- harbor_ecr_convention (mechanical diff-state-machine grep)
- narration_comments (LLM-judged, function-doc style only, n=3 SC)
- temporary_migration_notes (LLM-judged, durable docs only)
- authoritative_voice (LLM-judged, .claude/skills/**/*.md only)
- /brevity dispatch on PR body via skill-loaded subagent

## Architecture

GitHub Action only (no local-invocable surface in v1). Workflow at
.github/workflows/pr-quality.yml with:
- Per-PR concurrency group + cancel-in-progress
- Fork-PR guard (forks have read-only token; explicit skip rather than
  pull_request_target footgun)
- ANTHROPIC_API_KEY pre-check (env-scoped, no template leak)
- Artifact upload of full state for debugging

Composition: subagent loads target skill (same pattern as /coral, /council).
No skill-to-skill registry invented.

## Comment model

Anchored marker `<!-- tide-pr-quality | sha=<SHA> | findings-hash=<H> -->`.
- No prior + findings count == 0 → no comment posted (no thumbs-up)
- No prior + findings > 0 → create new
- Prior + hash matches → no-op (no churn on identical re-runs)
- Prior + hash differs → PATCH in place
- Prior + findings count == 0 → DELETE prior (clean PR after fixes)

## Cross-review

product-engineer + reviewer + product-manager cleared after 2 review cycles.

Round 1 HOLDs addressed:
- Missing judge scripts (judge-mechanical.sh, judge-llm.sh,
  judge-skill-dispatch.sh) — written
- JSON contamination via undisciplined stdout — every judge writes JSON to
  stdout only, logs to stderr via log() { ... >&2; }
- Fork-PR write-permission reality — workflow guard added
- SKILL.md step 6 vs format-spec rendering mismatch — SKILL.md defers to
  format-spec.md as single source of truth

Round 2 HOLDs addressed:
- no_cpu_limits awk was structurally broken on stdin/FILENAME — rewrote
  with proper file-arg + indent + parent-block tracking; PyYAML branch
  rejected per PE for multi-container miscite + early-return bugs (YAGNI)
- Workflow secret-leak via template interpolation — switched to env: +
  ${ANTHROPIC_API_KEY:-} guard
- judge-llm.sh --arg ARG_MAX risk — switched to --rawfile streaming

## Follow-ups (tracked in rule-registry.md un-defer triggers)

- Documentation completeness dimension — un-defer on first missing-doc
  reviewer flag
- Reference drift dimension — un-defer on first stale wikilink causing
  confusion
- Commit message hygiene dimension — un-defer on first non-CC commit on
  main
- Acceptance criterion deferred: dry-run on last 10 PRs (do post-merge)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@cursor

cursor Bot commented May 31, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Adds documentation, shell scanners, and agent workflow guidance only—no runtime code paths, secrets, or merge enforcement in this diff.

Overview
Introduces the /pr-quality Claude skill (.claude/skills/pr-quality/) as a pre-PR and on-demand post-PR coordinator: it runs a locked v1 rule set against the staged diff or gh pr diff, surfaces findings inline before gh pr create, or posts a single fresh PR comment when invoked as /pr-quality <PR>. The skill is suggestive only (no merge gating) and stays out of CI—triggers are wired through CLAUDE.md / AGENTS.md and the skills catalog, same pattern as /brevity.

v1 checks: mechanical scan-yaml-cpu.sh (cpu under limits:) and scan-harbor-ghcr.sh (ghcr.io in clusters/harbor/** diffs); LLM judges for narration comments, temporary migration notes in durable docs, and authoritative voice in skill markdown; verbosity via subagent dispatch to /brevity (rules not duplicated in pr-quality). rule-registry.md, format-spec.md, and evals/evals.json document scope, comment shape, halt/refusal behavior, and pressure tests (no runtime rule expansion, no blocking merge).

Docs: README catalog entry for pr-quality/; new Pre-PR review / PR-quality discipline bullets in AGENTS.md and CLAUDE.md (run after brevity on the final diff; post-PR is explicit user invocation).

Reviewed by Cursor Bugbot for commit 3fea141. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread .claude/skills/pr-quality/scripts/fetch-context.sh Outdated
if (current_file ~ /^clusters\/harbor\// && /ghcr\.io/) print current_file ":" line_num
next
}
/^[- ]/ { line_num++ }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diff line counter inflated by counting removed lines

Medium Severity

The harbor_ecr_convention awk diff-state-machine increments line_num for both removed (-) and context ( ) lines via /^[- ]/. Removed lines don't exist in the new file, so they shouldn't increment the new-file line counter. Any findings appearing after a deletion in the same hunk will report an inflated line number, causing the span field to point at the wrong line.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c77d88e. Configure here.

Comment thread .github/workflows/pr-quality.yml Outdated
Comment thread .claude/skills/pr-quality/scripts/fetch-context.sh Outdated
Comment thread .claude/skills/pr-quality/SKILL.md Outdated
Comment thread .claude/skills/pr-quality/scripts/judge-llm.sh Outdated
…greement-wired

Pivot per Brandon's "targeted and effective, no bloat" framing.

The original v1 shipped a GitHub Actions workflow + 8 bash scripts +
claude-code-action invocation + state directory + concurrency group + cost
guardrail + self-consistency + anchored marker + hash dedupe + 5-finding cap.
The action failed-closed on its own introduction PR because the Claude Code
App wasn't installed on the repo — the inverse of the suggestive-only intent.

Pivot:

- Drop ALL CI infrastructure
- Agent-invoked + user-invocable, no claude-code-action
- Working-agreement reference in CLAUDE.md + AGENTS.md (same pattern as
  /brevity layer 2; PR #92)

## Final shape (8 files, 393 lines)

  .claude/skills/pr-quality/
  ├── SKILL.md                                 (88 lines)
  ├── references/
  │   ├── rule-registry.md                     (87 — locked v1 set + 2
  │   │                                          mechanical specs inline +
  │   │                                          deferred mechanisms with
  │   │                                          un-defer triggers)
  │   ├── format-spec.md                       (41)
  │   └── judges/
  │       ├── narration_comments.md            (33)
  │       ├── temporary_migration_notes.md     (38)
  │       └── authoritative_voice.md           (33)
  ├── scripts/
  │   ├── scan-yaml-cpu.sh                     (39 — stateful awk, indent
  │   │                                          + parent-block tracking,
  │   │                                          multi-container aware)
  │   └── scan-harbor-ghcr.sh                  (34 — unified-diff state
  │                                              machine)
  └── evals/evals.json                         (happy + halt + pressure)

## Two invocation modes

**Pre-PR (agent-fired)**: before `gh pr create`, agent runs /pr-quality
against the staged diff + planned body. Findings surface inline for
revision. No comment posted.

**Post-PR (user-invocable)**: /pr-quality <PR> reads the existing PR via
gh, dispatches the v1 judges, posts a fresh comment with findings. No
comment on zero findings.

## Composition with /brevity

Verbosity dimension dispatches /brevity via subagent-loads-target-skill
(same pattern as /coral, /council). The verbosity judge in
references/judges/ is intentionally NOT a re-implementation of brevity's
rules — pr-quality detects the trigger, brevity owns the standard. PE
flagged the coupling risk in scope review; the boundary is preserved.

## Deletions (was CI ceremony)

- .github/workflows/pr-quality.yml
- 9 scripts (check-optout, check-pr-size, fetch-context, dispatch-judges,
  aggregate, render-comment, post-or-update, judge-llm,
  judge-skill-dispatch — plus the now-split judge-mechanical)
- scripts/README.md
- references/guardrails.md (CI-specific content; what remained fits in
  SKILL.md guardrails stanza)
- references/judge-prompt-template.md (without self-consistency, the
  structured-output contract collapses to 5 fields — inlined into rule-
  registry's per-judge schema)
- references/judges/no_cpu_limits.md, harbor_ecr_convention.md
  (mechanical specs are in the script + 1 row in rule-registry)
- state/.gitkeep (no state worth persisting; closer to a thin
  orchestrator than a stateful procedure)

## Working-agreement wiring (same-PR per layer-2 pattern)

CLAUDE.md ## Working Agreements gains:

  - **PR-quality discipline:** Before invoking `gh pr create`, apply
    /pr-quality to the staged diff + planned body. Findings surface
    inline for revision. Post-PR: invoke /pr-quality <PR> to post a
    fresh comment with findings. (Brevity runs during authoring;
    pr-quality runs on the final diff — they don't chain.)

AGENTS.md ## Working Agreement gains a parallel "Pre-PR review"
paragraph after the existing "Output discipline" entry.

## Deferred mechanisms (un-defer triggers in rule-registry.md)

- Self-consistency (n=3 sampling) → first real false-positive
- 5-finding cap + severity rank → first PR producing >7 findings
- Anchored marker + hash dedupe → first comment-spam complaint
- Cost ceiling per PR → first session budget overrun

Coral panel reviewed the trim plan: PE CLEAR, reviewer CLEAR (3 small
adjustments incorporated), PM NEW with harder cuts (all incorporated;
Brandon explicitly retained pre-PR mode as the customer override).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@bdchatham
bdchatham merged commit 5b2a9d1 into main Jun 1, 2026
1 check passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3fea141. Configure here.

if (in_harbor && /ghcr\.io/) print current_file ":" line_num
next
}
/^[- ]/ && !/^---/ { line_num++ }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed diff lines incorrectly increment new-file line counter

High Severity

The character class /^[- ]/ matches both removed lines (-) and context lines ( ), but line_num tracks position in the new file (initialized from the +c side of @@ -a,b +c,d @@). Removed lines exist only in the old file and must not increment the new-file counter. Any hunk containing deletions before a ghcr.io addition will report an inflated line number, off by the count of preceding removed lines. The script's own header comment (lines 10–11) confirms the intent is to count only added and context lines. The pattern needs to match only context lines starting with a space, not the - prefix.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3fea141. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Post-PR convention + quality coordinator that suggests improvements

1 participant