Skip to main content

council-patterns

Canonical reference for yellow-council reviewer contracts, CLI invocation, redaction, and output-parsing conventions. Use when authoring or modifying claude-reviewer, gemini-reviewer, opencode-reviewer, or the /council command.

설치로 이동

소스 정보

저장소
KingInYellows/yellow-plugins
최근 소스 활동
2026년 9월 10일 01:11
감지된 SKILL.md 언어
영어
스타
0
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
2 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
council-patterns
description
Canonical reference for yellow-council reviewer contracts, CLI invocation, redaction, and output-parsing conventions. Use when authoring or modifying claude-reviewer, gemini-reviewer, opencode-reviewer, or the /council command.
user-invocable
false
# council-patterns Skill ## What It Does Single source of truth for yellow-council reviewer surfaces. Defines: - Per-mode pack templates (plan / review / debug / question) - Reviewer output schema (verdict / confidence / findings / summary) - 11-pattern credential redaction awk block - Injection fence format - `timeout` invocation pattern with exit code handling - Path validation rules - Slug derivation algorithm with collision handling - Diff truncation algorithm for `review` mode - UNKNOWN verdict fallback semantics - Atomic file write convention (Write tool direct, brainstorm-orchestrator pattern) Reviewer agents (`claude-reviewer.md`, `gemini-reviewer.md`, `opencode-reviewer.md`) and the `/council` orchestrator command read this skill at agent spawn time via `skills:` frontmatter preload. `claude-reviewer` is the in-process slot — no `Bash`, no CLI to wrap. See "Claude slot" under Reviewer-Specific CLI Flag Pattern below for what that makes N/A. What it DOES share with the other three: the Layer-2 6-key return contract, the verdict enum and UNKNOWN fallback, the findings cap, the injection fence format, and the redaction pattern list. ## When to Use - Authoring `claude-reviewer.md`, `gemini-reviewer.md`, or `opencode-reviewer.md` - Authoring `commands/council/council.md` - Modifying any of the above — keep contracts in sync via this single source ## Usage ### Per-Mode Pack Templates All four modes share a structural envelope. Only the `## Task` block differs. The `{{REVIEWER_NAME}}` slot is the only per-reviewer variable; templates are otherwise identical across all four reviewers. (`claude-reviewer`'s spawn prompt carries one additional line — the orchestrator-minted fenced-output path — because it has no `Bash` and cannot mint one itself. That line is appended by `council.md`, not part of the pack template.) ```text You are {{REVIEWER_NAME}}, a code reviewer performing an INDEPENDENT analysis. Do not reference what other reviewers might say. Only report findings you can cite with a file:line reference. Do not write any files; analyze only. ## Task: {{MODE}} {{MODE_SPECIFIC_CONTEXT}} ## Required Output Format Verdict: APPROVE | REVISE | REJECT Confidence: HIGH | MEDIUM | LOW Findings: - [P1|P2|P3] file:line — <80-char summary> Evidence: "<exact quoted line from file>" [repeat per finding; if none: write "Findings: none"] Summary: <2-3 sentences in your own words> ## Rules - P1 = security/correctness blocker; P2 = quality issue; P3 = style/nit - Cite file paths relative to repository root - If a finding has no quotable line (e.g., "missing function"), write `Evidence: N/A — <reason>` - The `Verdict:` line is required and must appear exactly as shown ``` Per-mode `{{MODE_SPECIFIC_CONTEXT}}` block: | Mode | Context block contents | |------|------------------------| | `plan` | `### Planning Document` + fenced full content + `### Repo Conventions` + truncated CLAUDE.md (capped at 4K chars) | | `review` | `### Diff (HEAD vs <BASE_REF>)` + fenced `git diff` output (truncated per algorithm below) + `### Changed Files` + truncated content of each (4K chars per file) | | `debug` | `### Symptom` + user-supplied text + `### Cited Files` + content of each `--paths` file (4K chars per file, max 3 files) + `### Recent History` + `git log -10 --oneline -- <paths>` | | `question` | `### Question` + user-supplied text + (optional) `### Referenced Files` + content of each `--paths` file (4K chars per file, max 3 files) + `### Repo Conventions` + truncated CLAUDE.md (4K chars) | ### Reviewer Output Schema Two distinct layers, easy to conflate: **Layer 1 — CLI output → reviewer agent (capitalized `Verdict:` format).** The external CLI's response to the pack uses the capitalized format the pack template above demands (`Verdict:` / `Confidence:` / `Findings:` / `Summary:`). Each reviewer AGENT parses that CLI output with these regexes: ```bash VERDICT=$(grep -m1 '^Verdict: ' "$OUTPUT_FILE" | sed 's/^Verdict: //') CONFIDENCE=$(grep -m1 '^Confidence: ' "$OUTPUT_FILE" | sed 's/^Confidence: //') SUMMARY=$(awk '/^Summary: / { sub(/^Summary: /, ""); print; exit }' "$OUTPUT_FILE") # Findings: extract block between "Findings:" and "Summary:" lines FINDINGS=$(awk '/^Findings:/ { capture=1; next } /^Summary: / { capture=0 } capture' "$OUTPUT_FILE") ``` **Layer 2 — reviewer agent → council (lowercase 6-key contract).** After parsing, redacting, and fencing, the agent's own Task-tool return carries the structured 6-key contract that `parse_reviewer_return` in `council.md` (the authoritative definition site) extracts uniformly for all four reviewers: `verdict=` / `confidence=` / `summary=` / `fenced_output_path=` plus the `findings_block_begin`...`findings_block_end` sentinel pair — lowercase `key=` lines, first occurrence wins (`grep -m1`). The capitalized Layer-1 lines never reach council.md directly. (Codex differs only at Layer 1 — its CLI emits strict-mode JSON parsed with `jq` per yellow-codex's `codex-patterns` skill; its Layer-2 return is identical.) `claude-reviewer` also returns `summary=` and its findings block **empty** by contract. The three CLI reviewers run the redaction inside their own agent before returning, so their prose is sanitized by the time the orchestrator sees it; the in-process slot has no `Bash` and cannot, and anything it returned would enter orchestrator context raw, where no later pass can retract it. It writes its prose only into its fenced file, and `council.md` reads the summary and findings back out of that file **after** redacting it, using the Layer-1 regexes above. Verdict and confidence are still returned directly — both are constrained to a fixed enum on arrival and carry no free text. `claude-reviewer` has **no Layer 1 at all** — there is no external CLI whose output it parses. It implements Layer 2 directly, and writes the capitalized `Verdict:`/`Confidence:`/`Findings:`/`Summary:` shape only into its fenced output file, so the report's raw-output appendix reads identically across all four reviewers. This is the one contract asymmetry worth stating twice: the pack it receives still contains the `## Required Output Format` block demanding capitalized keys, and an in-process reviewer that obeys that block instead of the Layer-2 contract returns nothing `parse_reviewer_return` can match — its slot is then silently recorded as `ERROR` on every run. If the CLI output's `Verdict:` line is absent, the reviewer agent must: 1. Set `VERDICT=UNKNOWN`, `CONFIDENCE=LOW` 2. Use the first 2K chars of the raw output as `SUMMARY` (truncated at word boundary) 3. Set `FINDINGS=` (empty — cannot extract structured findings without a parseable verdict) 4. Surface a one-line warning to council.md: `"[<reviewer>] Warning: no Verdict: line found in output — marked UNKNOWN"` UNKNOWN verdicts are excluded from the synthesis Headline majority computation but are included in the Disagreement section so the user sees the prose. ### 11-Pattern Credential Redaction Apply this awk block to all reviewer output BEFORE injection fencing and BEFORE writing to `docs/council/<file>.md`: ```awk function strip_deco(s, prev, guard, limit) { # Strip to a FIXPOINT rather than in one fixed pass. Decoration nests in # arbitrary order and depth: a blockquote inside a list item # ("- > <header>"), a combined diff with one prefix character per parent # ("++"/"--"), a numbered excerpt wrapping either. A single ordered pass # removes whichever layer it happens to reach first and leaves the rest, so # the marker never normalises, the anchored classifier fails, and the block # drops to the bounded path where a narrowly wrapped body leaks. # # Repeating until nothing changes removes every layer regardless of order # or count. The bound is derived from the INPUT LENGTH, not a constant: an # iteration only continues after removing at least one character, so # length(s)+2 iterations always reach the fixpoint. A CONSTANT ceiling (the # original 8, then 64) is a real limit on a nesting depth the attacker # chooses -- 100 leading "+" exhausted the 64-ceiling with prefixes still # attached, the anchored classifier below then failed, and the block leaked # on the bounded path. # # Reaching `limit` is therefore impossible while every substitution above # shrinks s; it can only mean a later edit added one that rewrites without # shrinking. That is a bug, not deep nesting, so record it and let the # caller fail CLOSED (treat the line as a real key) instead of falling # through to the bounded path. No test exercises this arm today -- it exists # so a future edit degrades safely rather than silently leaking. # A "+" run is consumed whole below, and a "-" run longer than a delimiter # collapses to five in one pass, so both flood cases are linear (a 100,000 # dash prefix went from 19 seconds under gawk to 20 milliseconds). An # earlier revision bounded the dash case with a flat length cap that failed # CLOSED, but keying "this is a real key" off LENGTH ALONE meant any long # line that merely MENTIONED a marker was promoted to a real key and # swallowed the report through EOF; collapsing the run keeps the per-line # classification exactly as it was. guard = 0 limit = length(s) + 2 do { prev = s sub(/^[[:space:]]*([>|][[:space:]]*)*/, "", s) sub(/^([-*+]|[0-9]+[.)])[[:space:]]+/, "", s) sub(/^[0-9]+[[:space:]]*\|[[:space:]]*/, "", s) # A "+" run can never be part of a PEM delimiter, so take the whole run in # one pass. Only the dash case below needs character-at-a-time care. sub(/^\+\+*/, "", s) # Never strip a leading dash off a line that is ALREADY a valid PEM # delimiter: that corrupts "-----BEGIN" into "----BEGIN" and breaks every # anchored test downstream. # A dash run longer than a delimiter can never BE one, so collapse it # to five in one pass: a flood of 100,000 dashes cost one pass per # character (quadratic, about nine seconds) and could stall the # council. Five is exactly what the per-character step below would # leave before reaching a marker, so classification is unchanged. if (s ~ /^------/) sub(/^--*/, "-----", s) if (s !~ /^-----BEGIN/ && s !~ /^-----END/) sub(/^[-+]/, "", s) sub(/^[[:space:]]+/, "", s) } while (s != prev && ++guard < limit) deco_exhausted = (s != prev) sub(/[[:space:]]+$/, "", s) return s } function cred_hit(re, minlen, s) { # mawk (the default /usr/bin/awk on Debian/Ubuntu) does not support # interval expressions ({n,}/{n}) — it matches them literally, so a # `{20,}`-gated credential regex silently stops matching real secrets on # a mawk host. match()+RLENGTH (POSIX, mawk-safe) reproduces the same # trigger condition without interval syntax: `+` greedily consumes the # run after the literal prefix, RLENGTH is prefix-plus-run length, so # RLENGTH >= prefixlen+N is equivalent to {N,} / {N} for detection # purposes (we only ever discard the matched text, never reuse it, so # {N} exact and {N,} at-least are interchangeable here). # match() returns only the LEFTMOST occurrence. When a short placeholder # sharing the same literal prefix appears before a real token on the same # line ("example sk-ant-xxx ... sk-ant-<real>"), the leftmost RLENGTH falls # under minlen and the line — real token included — is emitted unredacted. # Walk every start position instead of testing only the first, advancing by # ONE character rather than past the whole match: a longer occurrence can # begin inside a shorter one ("sk-sk-ant-<real>"), and skipping RLENGTH # would step over it. s = $0 while (match(s, re)) { if (RLENGTH >= minlen) return 1 s = substr(s, RSTART + 1) } return 0 } function is_base64_line(s, minlen) { if (s !~ /^[A-Za-z0-9+\/=]+$/) return 0 return length(s) >= minlen } # Narrow-wrapped key body. A real key whose BEGIN shared its line with prose # runs under the bounded stray window, and a decoy END inside a real key # hands the rest of the body to the re-arm window; both used the 20-char # floor below, so a body wrapped narrower than that released redaction and # printed the tail. A body line of 12 to 19 characters counts as key-shaped # only when it carries BOTH a digit, "+", "/" or "=" AND a character outside # the hex alphabet, the same exclusion the 20-char branch applies: base64 # key material has both in nearly every slice that wide, an English word or # identifier has no digit, and a short git SHA or hash fragment has no # non-hex letter. So a short list after a quoted marker still counts as # stray and cannot swallow the report. Bodies wrapped under 12 characters, # and the rare slice with no digit or with hex characters only, remain a # documented residual. function is_narrow_key_line(s) { if (!is_base64_line(s, 12) || length(s) >= 20) return 0 return s ~ /[0-9+\/=]/ && s ~ /[G-Zg-z+\/=]/ } # The narrow rule plus the width chain, shared by the two sites that decide # whether a line inside a bounded window is key material: the re-arm test # after a decoy END and the stray-counter test. One helper so a future # tweak cannot land at one site and not its sibling, which is how the # 20-char floor survived at the re-arm test after it was fixed below. # pem_key_len is the width of the last key-shaped line in the current # block; a pure base64 line of exactly that width is body even when the # slice carries no digit (the fixed PKCS#8 DER prefix yields such slices). function is_narrow_key_run(s) { if (is_narrow_key_line(s)) return 1 # A digit-free slice continues the body only at the established width and # only when it does not read as a plain word: one optional capital then # lowercase ("Recommendation", "consideration"). Base64 of random bytes # mixes case on nearly every line (about 1 slice in 4000 at width 12 reads # as a word, and one such line only counts as stray, it does not release # the window), while a run of equal-length words after a quoted marker or # a genuine END no longer extends the window toward the verdict. return pem_key_len > 0 && is_base64_line(s, 12) && length(s) == pem_key_len && s !~ /^[A-Z]?[a-z]+$/ } { line = $0 # OpenAI / Anthropic / Google / GitHub / AWS / Bearer / Authorization if (cred_hit("sk-proj-[A-Za-z0-9_-]+", 28)) line = "--- redacted credential at line " NR " ---" else if (cred_hit("sk-ant-[A-Za-z0-9_-]+", 27)) line = "--- redacted credential at line " NR " ---" else if (cred_hit("sk-[A-Za-z0-9]+", 23)) line = "--- redacted credential at line " NR " ---" else if (cred_hit("AIza[0-9A-Za-z_-]+", 39)) line = "--- redacted credential at line " NR " ---" else if (cred_hit("gh[pous]_[A-Za-z0-9]+", 40)) line = "--- redacted credential at line " NR " ---" else if (cred_hit("github_pat_[A-Za-z0-9_]+", 51)) line = "--- redacted credential at line " NR " ---" else if (cred_hit("AKIA[0-9A-Z]+", 20)) line = "--- redacted credential at line " NR " ---"
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기