Harvest Review Lessons (infrahub-skills variant)
SkillDev toolsTurns review feedback on an opsmill/infrahub-skills pull request into durable rules, in this repository's own guidance layer. Reports first; edits only with approval. Follow the workflow in the body, the description does not summarize it. TRIGGER when: the user wants to turn PR review feedback on this repository into durable rules; capture recurring reviewer comments as contributor documentation; or check whether review lessons are reflected in the rules, guides, and graders. DO NOT TRIGGER when: the lesson is about Infrahub the product rather than this repository (use `infrahub-reporting-issues`); the friction happened live in a session rather than in a review thread (use `infrahub-reporting-skill-gaps`); or you only need to reply to or resolve review threads (normal `gh` flow).
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Harvest Review Lessons (infrahub-skills variant) skill
What this skill tells your AI
The instructions your AI receives, as published by opsmill/infrahub-skills in .claude/skills/harvesting-skill-review/SKILL.md and read by ahel’s review.
Not the upstream skill. This started as
harvesting-reviewinopsmill/infrahuband diverged far enough to warrant its own name. Do not sync edits between them in either direction; apply a change to whichever repository it was reasoned about.What differs here:
- Two routing tracks, not one. A lesson about a shipped skill lands in
skills/<skill>/rules/and cannot land as prose alone — it pulls in a grader check, aneval.yamltask, four fixtures, andsync-evals.py. A lesson about contributing lands indev/guidelines/ordev/and is cheap. Upstream has only the cheap track.- An extra verdict: covered in prose but nothing can fail. This repository's characteristic gap, and the fix is a test rather than better wording.
- Three GitHub endpoints, not one. Review feedback here lives in review summaries and PR discussion; inline comment volume is routinely zero.
- Different destinations.
dev/guidelines/,dev/guides/,dev/knowledges/,AGENTS.md,skills/*/rules/,graders/,eval.yaml.
What this does
Reviewers repeat themselves. The same missing grader, the same unexecuted
snippet, the same rule that never got linked from SKILL.md recur PR after PR
because each lesson lived in a thread and died there. This skill reads a PR's
review comments, keeps the ones that generalize into a rule a future author
should follow, investigates each against the actual code, checks whether it is
already codified, and proposes the smallest edit to the right home.
It also prunes. Every run adds; nothing else stops this repository's guidance layer from growing forever. §5 sweeps the destinations for rot the repo already knows how to name, and folds the fix into the same PR.
Refine, don't accrete — this outranks every other rule here
A harvest that only adds has made the repo worse, however good each
individual rule is. AGENTS.md is loaded into every session in this
repository, and dev/guidelines/ loads on every matching file. Both are a tax
every contributor's agent pays. Nothing else in this skill removes a line, so
it has to be you, on every lesson you apply:
- Measure before appending.
wc -lthe target.SKILL.mdfiles stay under 500 lines (dev/knowledges/skill-writing-guide.md). A rule file covers one concern; a second concern is a second file, not a longer one. - Cut what the new rule supersedes. Rewrite the section around the new
rule instead of bolting it on the end. This is also step 6 of
dev/guides/adding-a-rule.md: sweepskills/,graders/, andeval.yamlfor the claim the new rule contradicts. - Report added/removed line counts. Zero deletions means you accreted. Say so plainly rather than presenting it as a win.
- Raise the bar as the file grows. "True but rarely needed" loses to keeping the doc readable.
Write every edit in the house style: dev/knowledges/skill-writing-guide.md.
Rule first, plain words, the why before the imperative, compliant and
non-compliant examples in two separate fences.
Where lessons go
This repository has two destination classes, and they cost very different amounts. Decide which track a lesson is on before choosing a file.
Track A — the skills we ship
The lesson is something an Infrahub user's agent should do differently. It
belongs in skills/<skill>/rules/, and prose alone is not a landing:
A new rule ships with its eval coverage in the same change. —
dev/guidelines/rule-equals-test.md
That means a check function in graders/<skill>/lib.py registered in CHECKS,
an eval.yaml task that fails with the instruction's Read the skill at ...
line commented out, a task grader run against four fixtures, and
python scripts/sync-evals.py. Budget for it before proposing the rule, and
say in the report that this is the expensive track. Full procedure:
dev/guides/adding-a-rule.md.
Track B — how we contribute
The lesson is about working on this repository. Cheap by comparison.
| Destination | What lives there | Bar |
|---|---|---|
dev/guidelines/*.md | Terse constraints, loaded automatically when a matching file is touched | Highest. Edit an existing rule before adding a file; a new file means a new paths: glob that fires for every future edit under it. |
dev/guides/** | How to do X — task procedures and their checklists | When the lesson belongs in a step of a recurring task. |
dev/knowledges/** | How the repo works and why — reference, not do/don't | When the lesson is an explanation a future contributor needs. |
AGENTS.md | Repo-wide facts, navigation, boundaries | For what to do / where things live, not craft detail. It loads every session, so the bar is high and the entry is short. |
dev/README.md | The index for dev/ | Only when a new destination file needs a route. |
AGENTS.md and dev/README.md are routers. Put the rule in its topical home
and the trigger in the router; never move a rule into AGENTS.md because
AGENTS.md is read more often.
Workflow
1. Gather scope
Resolve the argument to a PR: a number, a branch name
(gh pr view <branch>), or nothing, in which case use the current branch's
PR. If none exists, ask.
Three endpoints, not one. /comments returns only
diff-anchored comments, so a reviewer who writes the lesson
in a "Request changes" summary — or in plain PR discussion —
is invisible to a run that reads that endpoint alone.
gh pr view <n> --json title,body,headRefName,commits
# inline, diff-anchored
gh api repos/opsmill/infrahub-skills/pulls/<n>/comments --paginate \
-q '.[] | "--- \(.user.login) on \(.path):\(.line // .original_line)\n\(.body)\n"'
# review summaries (approve / request-changes bodies)
gh api repos/opsmill/infrahub-skills/pulls/<n>/reviews --paginate \
-q '.[] | select(.body != "") | "--- \(.user.login) [\(.state)]\n\(.body)\n"'
# general PR discussion
gh api repos/opsmill/infrahub-skills/issues/<n>/comments --paginate \
-q '.[] | "--- \(.user.login)\n\(.body)\n"'
Read resolved and unresolved threads. A resolved thread whose lesson never reached a rule or a grader is exactly the gap this skill exists to catch. Prioritise human reviewers but do not filter by author: a bot comment goes through the same step-3 investigation and is kept when the code confirms it. When a bot-sourced lesson survives, flag its origin. Skim the addressing commit messages too; they often state the lesson more crisply than the thread.
2. Extract and abstract
Abstract each comment to the underlying rule. Promote the rule, not the reviewer's wording. Keep a candidate only if it passes all three:
- Generalizes beyond these lines.
- Actionable as an imperative a future author follows.
- Would prevent a repeat comment.
A fourth check bounds the other end: the rule must be specific to this repository or a non-obvious gotcha. Universal hygiene every competent author already applies is not worth the load cost.
Set aside anything obviously PR-local now. Borderline cases are not judged yet; the investigation is what tells you whether they generalize.
3. Investigate each candidate — before any verdict
No verdict, no home, no edit until a candidate clears this step. It is the one most easily skipped.
a. Reconstruct before → after, then verify. Read the code the reviewer saw and the correction that actually landed. The correction is the ground truth; the comment only prompted it. A thread with no landed change is unresolved or a no-op: flag it, do not invent a fix. Then check the reviewer is technically right, and grep for the suggested alternative elsewhere in the repo to confirm it is an established drop-in.
b. Interpret the intent. Separate a preference for new work from a repo-wide migration; directional guidance from an actionable request; and the precise boundary of what the rule covers.
c. Size the naive reading. If acting on the literal comment implies rewriting every skill or regrading every task, you have probably mis-read it. Name the over-scoped reading you are ruling out; that record stops the next person over-scoping too.
d. Derive the rule and its root cause. Write the one-sentence imperative, scoped exactly as the investigation showed. Then name why an agent would have produced the rejected shape: a missing convention, a reflexive idiom, a copied template, a guide that still says the old thing. In this repository the root cause is very often a stale claim somewhere else in the tree — check before concluding, because the fix is then a sweep, not a new rule.
The investigation can also demote a candidate. Record the reason. Judge "generalizes" by whether the underlying idiom recurs, not by the size of the local fix.
Never demote on the assumption a linter already enforces it. A reviewer having to raise it is evidence the tooling did not catch it.
4. Check existing coverage, then route
Grep both layers. In this repository a rule can be codified in prose, in a test, or in neither, and the three need different fixes:
grep -rin "<keyword>" dev/guidelines/ dev/ AGENTS.md skills/*/rules/
grep -rin "<keyword>" graders/ eval.yaml
A grep hit is not coverage until you read it. Open the match, confirm it
states this rule and not an adjacent one, and cite the exact file:line.
Verdicts:
- Missing — written nowhere. Propose the smallest addition in the most-specific home, on the track §"Where lessons go" assigns. If the only reason to write it down is a defect in the code today, it is not documentation: phrase it as a forward-looking convention or file an issue instead.
- Covered in prose but nothing can fail — the rule is written in a
SKILL.mdor a rule file, and no grader asserts it. This is the characteristic failure of this repository, and it is a finding, not a relief: the constraint survives only until the next refactor of the prose. The fix is a test, not better wording. Route todev/guides/adding-a-rule.md§2 and §5, and verify the new check against four fixtures including the violating near-miss. - Covered but ineffective — the rule is written, a grader may even exist,
yet a reviewer still had to flag it. Diagnose why it did not land:
- too abstract — states the principle, not the concrete case. The signal: the reviewer had to write the correct shape themselves. Add the compliant/non-compliant pair, in two separate fences.
- not discoverable — the rule is in the right file, but nothing pulled
that file into context. Two coordinated edits, not a move: fix the
load-trigger (the
SKILL.mdlink at the workflow step that needs it, or the rule'spaths:glob, or theAGENTS.md/dev/README.mdentry) and strengthen the rule in place. A rule reachable only from_sections.mdis read after the mistake. - mis-homed — genuinely in the wrong file. Move it, then fix the load-trigger so the new home is reachable.
- stale or contradicted — another layer still says the old thing, so
authors discount it. Sweep per
dev/guides/adding-a-rule.md§6.
- Not applicable — coincidental match, or the comment was a question. Demote with the reason.
Scope is not an alibi. "Out of scope for this PR" is a reason not to change code now. It never exempts the guidance from being made concrete.
Routing rule of thumb: most-specific existing home wins; edit before create;
strengthen before duplicate; fix the load-trigger before relocating;
dev/guidelines/ only for a constraint worth loading on every matching file.
Confirm the target file exists before routing a lesson to it.
5. Sweep for rot
Cheap, and it is what keeps "harvested" from meaning "bloated". Run it every time.
a. Staleness grep. Scope it to the prose that states rules, and match the
shape a citation actually takes — a bare #\d+ also matches hex colours and
legitimate upstream issue references, and this repository's own PR numbers are
three digits, not five:
SCOPE=(dev/guidelines dev/guides dev/knowledges AGENTS.md
skills/*/*.md skills/*/rules/ .claude/skills/)
EXCL=(--exclude=examples.md --exclude='*reference.md'
--exclude-dir=harvesting-skill-review)
grep -rnoE "${EXCL[@]}" '\(#[0-9]{2,4}\)|PR #[0-9]+' "${SCOPE[@]}"
grep -rniE "${EXCL[@]}" \
'(currently (broken|unfixed)|not yet fixed|known gap|for now)' "${SCOPE[@]}"
The exclusions are part of the command, not an instruction to remember:
examples.md and *reference.md carry literals — colours, IDs, sample
payloads — that look like citations and are not, and this skill's own
directory is skipped because the patterns above appear in it verbatim. Everything else that states a
rule is in scope, including each skill's SKILL.md and its prose files.
A hit is a candidate, not a defect. Read it before touching it. A reference
to an opsmill/infrahub issue is documenting upstream behaviour and stays; a
reference to a merged PR in this repository, made to justify a rule that now
stands on its own, is the rot. If the grep returns only the first kind, the
sweep is clean — say so and move on. Drop a stale citation and keep the
behaviour description it was attached to. Check a defect note against the
current code: delete it if fixed, reframe it as a convention if not.
b. Broken-route check. This repository routes by file path, so a moved file silently breaks every pointer to it:
uv run invoke lint
python scripts/check-cli-invocations.py
c. Supersession. When this run's lesson generalizes something an earlier run wrote narrowly, broaden the earlier entry in place rather than leaving both.
d. Fix every confirmed defect now. A punch list is not pruning. A hit you read and kept — an upstream reference doing real work — is resolved, not outstanding; say so and move on. The only entries that may stay genuinely unresolved are calls for the user.
6. Report
Present the findings and stop. Do not edit yet. These files shape every contributor's agent.
7. Apply (opt-in)
Ask: all, cherry-pick, or none. Then edit, following §4 routing and §5 pruning, and apply Refine, don't accrete — measure, cut what is superseded, report the line counts.
Reviewer quotes, PR numbers, and the investigation trail belong in the report and the commit message, never in the file text.
A Track A lesson is not done when the rule file is written. Finish the
checklist in dev/guides/adding-a-rule.md, then:
uv run invoke lint
uv run invoke test
python scripts/sync-evals.py # if eval.yaml changed; commit the JSON too
skillgrade --smoke # optional, slow, needs an API key
Never resolve review threads. Reply if useful; resolution is the reviewer's call.
Report format
## Review-Lessons Report — PR #<n>
### Scope
<!-- PR, branch, threads read (resolved + unresolved). After applying: lines
added/removed per file, and what was cut. Zero deletions is a finding. -->
### Covered in prose, nothing can fail
<!-- The repository's characteristic gap, so it leads the report. For each:
lesson, source, the exact file:line that states it, and the check +
eval task that would make it enforceable. Omit the section when empty. -->
### Existing coverage to strengthen
<!-- Written, yet a reviewer still had to raise it. Lesson, source, the exact
file:line confirmed to be the same rule, why it didn't land (too abstract
/ not discoverable / mis-homed / stale), and how to make the existing
coverage land. Not a duplicate rule. -->
### New rules to add
<!-- For each: the one-sentence imperative; source (reviewer + quote, and the
addressing commit); the investigation trail (before → after, the claim
verified in code, intent, the over-scoped reading ruled out); the root
cause; Track A or B and the exact file; and the text to add, already in
house style. For Track A, list the grader and eval work it pulls in. -->
### Not lessons
<!-- PR-local fixes, and candidates the investigation demoted. Say why,
briefly. Promoting every comment to a rule is as useless as missing the
real ones. -->
### Pruned or consolidated
<!-- Debt from earlier runs, already fixed in this PR's diff. For each:
file:line, what was there, why it changed. Label a genuine user call
"Needs a decision". Say "none found" when the sweep is clean. -->
Signals
- GitHub stars
- 27
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
harvesting-skill-review- Source
- github.com/opsmill/infrahub-skills