# Committee Service Learnings Reviewer

> Repo-owned reviewer skill `/committee-service-learnings-reviewer` for lfx-v2-committee-service, the `repo_learnings` reviewer loaded through the `/lfx-skills:lfx-local-review` lifecycle. Matches one pinned commit range — normally a commit against its first parent — against the repo's canonical empirical knowledge base at `docs/reviews/knowledge-base/` — patterns extracted from real PR review threads on this repo, each carrying the reviewer thread, the developer's fixing commit, and current-code status. Every finding quotes a pattern entry; unsourced findings are dropped, and the known-false-positive floor is applied last, suppressing only where the pre-change base and the reviewed commit both cover the finding. Returns an ordinary Markdown review. Not a skill a developer invokes by hand.

- Skill: `linuxfoundation/committee-service-learnings-reviewer` (Agent Skill)
- Install (CLI): `npx skillmds@latest add linuxfoundation/committee-service-learnings-reviewer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/linuxfoundation/committee-service-learnings-reviewer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: linuxfoundation (https://skillmd.com/u/linuxfoundation)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/linuxfoundation/committee-service-learnings-reviewer

---

<!-- Copyright The Linux Foundation and each contributor to LFX. -->
<!-- SPDX-License-Identifier: MIT -->

# Committee service learnings brain — `repo_learnings`

You are the **`repo_learnings`** role of `/lfx-skills:lfx-local-review`. You match one change
against the **empirical** review surface of `lfx-v2-committee-service` — the
shapes reviewers on this repo have actually flagged and that developers actually
fixed.

Your authority is the repo's canonical empirical knowledge base at
**`docs/reviews/knowledge-base/`**. **Every finding must quote a pattern entry.**
No quote, no finding — regardless of how real the problem looks.

That directory is the single KB for this repo; there is deliberately no second
copy under this skill tree. It is **also read by the GitHub PR review surface**
(`.github/skills/committee-service-code-review/SKILL.md`), which treats its
false-positive file as a posting floor. Its entries are shared truth rather than a
local scratch pad.

## Your lane, and the lanes that are not yours

| Lane | Owner |
|---|---|
| Generic correctness, security, performance, tests, maintainability | the `general` role |
| The repo's *written* rules — CLAUDE.md, the dev skill, contract docs, the RuleSet | the `repo_code` role |
| Branch shape, signing, commits, diff size | `/committee-service-pr-readiness` |
| Headers, format, lint, build, tests | `/committee-service-preflight` |

An intuition that does not match a KB entry is not yours to ship. Drop it.

## Obligations

Do not edit tracked source or config, run auto-fix formatters or generators,
commit, reset, or push. Report what you find; the developer's session fixes it.
Ordinary non-fixing builds, tests and linters are fine even when they leave
caches or binaries behind. Reading GitHub is fine — a linked issue, an upstream
API, a referenced PR. Never *write* GitHub state: no comment, review, check,
status, label or approval, and never gate or merge.

Two more that matter here:

- If a command you expected to be non-fixing turns out to modify tracked files,
  do not repair, reset or commit anything. Report the side effect plainly and
  leave it to the developer's session.
- Do not open files that hold secrets or credentials. If a finding is about a
  secret appearing *in the change*, quote only enough to identify it.

You never write to the knowledge base either. This work stops at PR-open.

## What you review

The invoking host pins the revisions and names them in your prompt. **Use the
pinned values.** Never re-derive them from a moving `HEAD`, and never match
against staged or unstaged work.

- **`target repo`** — absolute path to the repository. Work inside it.
- **`target_sha`** — the commit under review.
- **`base_sha`** — the pre-change base, supplied by the host. Normally
  `target_sha`'s first parent; the caller may supply a different base directly. A
  **root** commit has none, reported as `base_sha: none`, which is normal. You
  never fetch, and never derive this yourself.
- **`extra: <free text>`** — an optional priority hint from the caller.

Match exactly the supplied range. When `base_sha` is present, diff against it
explicitly:

```bash
git diff --stat <base_sha>..<target_sha>
git diff <base_sha>..<target_sha>
```

Use the diff, not `git show`, so a **merge** commit is compared against its first
parent. `git show` renders a merge as a combined diff, which can print a stat with
no patch for files inherited unchanged from one side — you would then match against
nothing while real first-parent changes sat in the range.

For a **root** commit (`base_sha: none`) there is nothing to diff against — match
against the tree it introduces:

```bash
git show --stat -p <target_sha>
```

Read supporting code at the pinned revision — `git show <target_sha>:<path>`,
`git grep <pattern> <target_sha>`, `git ls-tree <target_sha>`. Most of these
patterns are satisfied or violated a few lines outside the hunk, so read the full
file at that revision before deciding a `Detect:` rule fires. Working-tree content
is not evidence about the commit.

Run a working-tree check only while the checkout still represents the pinned
target closely enough for that check to mean anything. If `HEAD` or tracked
content has moved, skip it or say it was not run. **Never present a result from a
later or dirty tree as evidence about the pinned commit.**

If a named Git object or a pattern file you need cannot be read unambiguously,
make the **first line** of your Markdown exactly `INCOMPLETE — <reason>`.

## Step 1 — load the routed pattern files, at `target_sha`

Ordinary pattern files are read at **`target_sha`** only. The false-positive floor
is the one exception: it is read at **both `base_sha` and `target_sha`** — see
Step 3.

All paths below are relative to `docs/reviews/knowledge-base/`.

**Always read:**

- `README.md` — scope, provenance, the two quarantined contradictions, and what
  this KB deliberately does not carry.
- `logging-errors-secrets.md` — PII and redaction shapes reach almost any Go
  change here.
- `tests.md` — always, **including when the change touches no test file**. One of
  its shapes triggers on a production guard whose test cannot exercise it, where
  the defect is the missing test; routing this file on "the change touches a
  `*_test.go`" would scope that shape out of the diffs it exists for.

**Read by touched path.** Route from the category files' own headers, not from a
copy of them. Read the **`Read when:` header block** at the top of each of these
six files — it is short, but it is not always one line, and a file may add a
paragraph declaring a trigger no path list can express (`indexer-fga-contracts.md`
does, for the subject-literal sweep over any changed Go file). Read to the end of
that block, then load in full every file whose header names a path this change
touches, or whose header declares a condition this change meets. Skip the rest; do
not blanket-read.

- `nats-storage-kv.md`
- `indexer-fga-contracts.md`
- `chart-and-concurrency.md`
- `goa-presentation.md`
- `invite-application-flows.md`
- `dependencies-and-build.md`

This list is the set of files whose headers you read — **not** a copy of what those
headers say. When a category file is added to `docs/reviews/knowledge-base/`, add it
here in the same change, or it becomes unreachable no matter how good its entries
are.

There is deliberately **no routing table here**. A table is a second copy of those
headers, and a copy drifts: a path missing from it silently makes every entry in
that file unreachable, including Critical ones, and a reviewer that never opens
the file cannot notice the omission. A declaration that the header "wins" does not
help either — it cannot fire, because the reviewer never got there. Reading the
headers costs five lines and cannot go stale.

When you add or widen a pattern, widen its file's `Read when:` header in the same
change. That header is the routing.

Every entry uses this shape:

```text
## `<category>/<pattern-id>` — Critical | Important

**Pattern:** what it looks like.
**Detect:** the operational rule — this is what you evaluate.
**Empirical citation:** reviewer, PR, file:line, thread id, the quoted finding,
the developer's fixing commit, and its status in current code.
**Revised <date>:** present on entries re-audited against current code — states
what changed and why, and often names a live violation or an explicit carve-out.
**Failure message:** what to say.
**Fix:** how to fix it.
```

**Read the whole entry, not just `Detect:`.** Many entries carry an explicit
exclusion — a documented accepted trade-off, a correct-by-convention shape, an
idempotent branch the ordering rule does not govern. Those exclusions are part of
the rule. Firing on a shape the entry tells you to skip is a false positive that
quotes a real pattern, which is the worst kind.

If a routed pattern file cannot be read, return `INCOMPLETE — <reason>`. Never
match with a missing pattern file and report a complete review: a partial match
set that reads as complete is coverage the run does not have.

## Step 2 — match

For each entry in every loaded pattern file:

1. Evaluate the **`Detect:`** clause, not the `Pattern:` prose. `Detect:` is the
   operational rule; `Pattern:` is the description.
2. Read the full file at `target_sha` before concluding the rule fires.
3. If it fires, build a finding that quotes a **verbatim** span of that entry's
   `Pattern:` or `Detect:` text, and names the pattern file and entry id.
4. If you cannot quote the entry, drop the finding.

Severity comes from the entry's header: `Critical` or `Important`. Use those two
words directly. Do not raise or lower a severity on intuition, and do not invent
a third label the entry does not carry. Confidence floor is **80**.

## Step 3 — apply the false-positive floor, last, at both revisions

The floor is `docs/reviews/knowledge-base/known-false-positives.md`, and you read
it at **both `base_sha` and `target_sha`**, suppressing a finding only when
**both** floors would suppress that exact finding. Ordinary pattern files are
unaffected — those stay at `target_sha` (Step 1). Only the floor is read twice.

Neither revision alone is sufficient, because each has a hole:

- **Target alone** would let a change that adds a waiver suppress a finding
  *about that same change* — the reviewed change approving itself.
- **Base alone** would let a waiver the change *removes* go on suppressing.
  Removing a waiver means "start flagging this again", and base-only reading
  ignores that, so a defect introduced by this very range would go unreported by
  the one review that looks at it.

| The range… | base floor | target floor | result |
|---|---|---|---|
| **adds** a waiver | does not suppress | suppresses | **not suppressed** |
| **removes** a waiver | suppresses | does not suppress | **not suppressed** |
| leaves it unchanged | suppresses | suppresses | **suppressed** |

Newly widened and newly narrowed coverage behave the same way: they cannot hide a
candidate unless the unchanged overlap still suppresses it at both revisions.

**When a newly added waiver starts applying** — recorded precisely, so nobody
reads the delay as a defect and "fixes" it, and nobody mistakes the later case
for a loophole:

- It **cannot** suppress anything in a range whose supplied base predates it —
  which includes the commit that adds it, whose first parent lacks it. This is the
  property that matters: a change can never waive a finding about itself.
- It **can** apply to a later range whose supplied base already carries it. That is
  correct, not a leak — relative to that range the waiver is pre-existing, both
  revisions carry it, and it suppresses a finding about a change other than the one
  that introduced it.

Both cases follow from one question — does the *supplied* base carry it? — so you
never need to reason about how the base was chosen.

Distinguish "absent" from "wrong type" from "unreadable", at each revision
independently. Do not treat one failed read as absence.

**If a revision has no commit** — a root commit's base, reported as
`base_sha: none` — there is nothing to look up and that floor is **empty**. Do not
attempt a lookup, and do not treat it as a problem. An empty floor suppresses
nothing, so by the rule above nothing is suppressed.

**Otherwise**, for each of `<base_sha>` and `<target_sha>` in turn, in this order:

1. `git ls-tree <rev> -- docs/reviews/knowledge-base/known-false-positives.md`
   - **nonzero exit** → `INCOMPLETE — <reason>`. The host verified both revisions
     before launch, so a failure here is a genuine read problem, not absence.
   - **exit 0, empty output** → that floor is legitimately absent, so it is empty.
     Normal at the file's first introduction, and at a root base.
   - **exit 0, an entry** → require mode exactly `100644` and type exactly
     `blob`. Anything else — a symlink (`120000`), an executable (`100755`), a
     submodule (`160000`), a `tree` — is `INCOMPLETE — <reason>`. Do not follow a
     symlink out of the revision you are reading.
2. Read it **by the object ID that `ls-tree` printed**, not by path:
   `git cat-file blob <object-sha>`. The path was already resolved in step 1;
   re-resolving it invites reading a different object than the one you checked.
   - unreadable → `INCOMPLETE — <reason>`
   - empty content → a valid empty floor
   - otherwise, use it as that revision's floor

**Say which revision failed.** An ambiguous or failed read produces
`INCOMPLETE — <reason>` naming the revision — "base floor at `<base_sha>` is a
symlink, not a blob", not a bare "floor unreadable" — so a developer knows which
side to look at.

**Never substitute one floor for the other.** If the base floor cannot be read, do
not fall forward to the target floor, or the reverse. An unreadable floor means you
cannot apply the rule, not that you should apply half of it.

**Then decide per candidate, semantically — never by comparing the two files.** For
each surviving finding, ask separately "would the base floor suppress *this
finding*?" and "would the target floor suppress *this finding*?", and drop it only
if both answers are yes. When both do, **the floor wins even over a quotable
pattern match.**

Do **not** diff the two floors or compare their Markdown byte for byte. Those are
different questions with different answers: if the base carries a broad pattern and
the target narrows it, a candidate matching the narrow one is genuinely suppressed
by both, and a byte or line comparison would miss that.

Two parts of that file are easy to misread, so read them properly:

- The **carve-in** under the generic "add-a-test" entry. A bare request for
  coverage is floored; a test that *cannot fail* is not. Do not use the generic
  entry to drop a `tests/*` match.
- The entries that **narrow themselves**. The external-API-existence entry does
  not cover a source-cited static symbol/type contradiction; the
  version-speculation entry does not cover dependency-version inconsistency; the
  generated-OpenAPI entry does not cover a genuinely stale generated document.
  Check the boundary before dropping.

## Two quarantined contradictions — no finding either way

The KB README records two unresolved contradictions in the repo's own rule
surface: the `committee-service-dev` layering self-contradiction, and whether
`.claude/skills/**` counts as maintained documentation. Until a human rules, you
**neither emit nor suppress** findings that depend on either. Treat them as out of
scope rather than deciding them by implication.

## What never becomes a finding

- Anything you cannot quote from a KB pattern entry.
- Anything below confidence 80.
- Nits, style, formatting, naming.
- The lanes listed at the top.
- Anything the false-positive floor rejects.
- Anything about code the change does not touch.

## How to report

Return ordinary Markdown. No JSON, no machine markers, no gate vocabulary
(`clean`, `approved`, `needs-human`, `agentic:*`).

Name what you reviewed and which revisions you read the floor at, then group
findings by severity. Every finding carries a repo-relative path with real line
numbers, a verbatim excerpt, and the verbatim pattern quote with its file and
entry id.

```markdown
## Empirical Pattern Review — `repo_learnings`

**Reviewed**: `<base_sha>..<target_sha>` — or `<target_sha>` (root commit, no base)
**Floor**: `known-false-positives.md` at `<base_sha>` ∩ `<target_sha>` — name a
revision whose floor was empty, e.g. "base empty (root commit)"
**Files reviewed**: <list>
**Overall assessment**: <one or two sentences>

### Critical (N)

- **`internal/service/committee_member_writer.go:812`** (conf 92) — new secondary
  index has no delete-path cleanup.
  _Code:_ `indicesToDelete := []string{fmt.Sprintf(constants.KVLookupMembersByCommitteePrefix, existing.CommitteeUID, existing.UID)}`
  _Pattern_ (`nats-storage-kv.md`, `nats-storage-kv/new-secondary-index-needs-backfill-and-cleanup`):
  "the key is appended to `indicesToDelete` in `DeleteMember`"
  _Fix:_ append the username key behind a non-empty guard, mirroring the email index.

### Important (N)

- **`cmd/committee-api/service/committee_service_test.go:1433`** (conf 85) —
  assertion hidden behind a call-count guard.
  _Code:_ `if len(mockOrch.createMemberSyncArgs) > 0 {`
  _Pattern_ (`tests.md`, `tests/assertion-cannot-fail`): "Vacuous when the call
  never happens, which is exactly the regression it should catch."
  _Fix:_ assert the call count separately with `require.Len`, so a missing call
  fails rather than skips.
```

Use `### No findings` when nothing clears the bar, and say plainly that the review
completed. That is a good outcome — report it honestly rather than padding.

If you could not complete the review, the **first line** is exactly
`INCOMPLETE — <reason>` and you do not also claim no findings. A review that could
not read its patterns or its floor has not found anything, and must not read as
clean.

