affinage
Triage a PR's review comments, CI failures, and merge conflicts through the /age lens. Use when the user asks to address PR feedback, fix CI, or resolve conflicts. Do not use for a diff without a PR. Use /age instead.
Install
npx skills add https://github.com/paulnsorensen/easy-cheese/tree/main/skills/affinage
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install paulnsorensen-easy-cheese@llmmart
git clone https://github.com/paulnsorensen/easy-cheese.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole paulnsorensen/easy-cheese collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
/affinage
Act on existing claims about a PR.
Claims can come from reviewers, CI checks, or merge conflicts.
Grade each claim through the /age lens.
Send accepted claims to /cure.
/affinage always grades claims that exist on the PR.
Its entry path controls whether it also finds new /age findings:
- Standalone — The user starts
/affinage <pr>withouthandoff_context. Run/ageon the PR diff unless the user passes--no-age. Add the new findings to the same report. - Chained —
/cookor/curesupplieshandoff_context. Skip the fresh review because/agealready ran in this chain.
See ## Fresh review for the entry rule.
See ## Merge-conflict resolution for the conflict path.
Phase entry
Run python3 skills/affinage/scripts/affinage.pyz wheypoint-resolve --ref <slug>.
authoritative uses the record; its working_context is the first batched tilth_read.
not-found proceeds cold; legacy shows its source and slug, then proceeds.
gated, ambiguous, and error stop and show the payload.
Show advisory stale-commit and grounded-path-missing findings.
Inputs
/affinage [<pr-ref>] [--auto --stake <floor>] [--plate] [--safe] [--open-pr] [--hard] [--full] [--include-outdated]
<pr-ref> accepts a PR number, a PR#<n> reference, or a full GitHub PR URL.
Extract the integer before you call pr-status.
The command accepts only the integer.
If no reference exists, run gh pr view --json number on the current branch.
Flags:
--auto --stake <floor>— Run without selection prompts.<floor>acceptsblocker,high,medium+, orall. Bare--autouses themedium+default floor. Use the same floor rules as/cure. Send/cure --auto --stake <floor>. Post replies without prompts. Seereferences/auto-mode.md.--safe— Add gates before cure selection and conflict resolution. This flag does not remove the default reply gate.--open-pr— Let terminal/plateopen a new PR when no PR exists. Without this flag,/plateonly updates an open PR.--plate— Run--auto --stake medium+ --open-pr. Grade the claims. Cure the selected findings. Post the replies. Then run/plate. An explicit--stake <floor>replacesmedium+.--hard— Pass the metacognitive gate flag to terminal/plate.--full— Show all low findings when at least 10 low findings exist.--include-outdated— Include outdated review threads.--no-age— Skip the fresh review in standalone mode. This flag has no effect in chained mode.
Read ../cheese/references/harness-portability.md for portability rules.
It covers helper resolution, agent dispatch, GitHub operations, and handoff transitions.
Use the bundle or repository helper first.
Do not use the ${CLAUDE_SKILL_DIR} environment variable in an invocation path.
The handoff blocks below define the portable contract.
The rule "slash commands are host renderings, not the control model" applies here.
Flow
Read references/flow-details.md for exact commands, exit codes, and grading reasons.
- Resolve PR. Use
<pr-ref>orgh pr view --json number. Normalize aPR#<n>reference or a PR URL to its integer. Resolve<owner>/<repo>from the Git remote. - Fetch PR status. Run
python3 skills/affinage/scripts/affinage.pyz pr-status <pr>. Exit 3 stops withstatus: halt: pr-status-logs-expired. Any other nonzero exit stops withstatus: halt: pr-status-unavailable. Route a conflicting or dirty merge state to## Merge-conflict resolution. - Run fresh review. Run this step only in standalone mode without
--no-age. Build contextual review input and call the bundledage-routecommand. Include the comment count and CI failure class. Run/agewith the complete subject plan and its evidence. Tag each new finding with[from-age:<dimension>]. - Fetch comments. Fetch inline threads from
pulls/<pr>/comments. Skip comments withposition: nullunless the user passes--include-outdated. Fetch review bodies frompulls/<pr>/reviews. Keep each nonempty body. Remove duplicates bypull_request_review_id. - Skip answered threads. Skip a thread when the resolved GitHub handle wrote its latest comment.
Render the footer as
agent on behalf of <handle>. - Grade claims. Classify each claim by the
/agedimension and severity rules. Do not increase severity because a reviewer selectedCHANGES_REQUESTED. Put contained fixes in severity sections. Put claims that need outside evidence in## Needs-investigation. Put wrong, unsupported, or large claims in## Reviewer-rejected. - Write report. Write
.cheese/affinage/pr-<n>.md. Start with the four-line handoff slug. Add the/agereport body and two affinage sections. See## Output. - Act or ask. Follow
## Handoff. - Draft non-cure replies. Draft replies for rejected and investigation claims. Do not reply to CI or fresh-review findings.
- Draft cure replies. Run this step only after
/cure. Read### Appliedand### Deferredfrom.cheese/cure/pr-<n>.md. DraftFixed — <applied summary>.for applied comment findings. DraftAttempted fix reverted — <reason>.for deferred comment findings. - Post replies. Show one reply gate for every drafted reply.
Skip the gate only when
--autois active. Post approved replies withpython3 skills/affinage/scripts/affinage.pyz post-reply. - Publish. Run this step only after all approved replies post.
Publish when
/cureapplies at least one fix. Also publish when/meltresolved a merge conflict. Send terminal/plate [--open-pr] [--hard] [--safe]. Then run the post-PR learning write-back from../cure/SKILL.md. Skip publication and write-back when the working tree has no change.
Fresh review
Standalone mode calls the router with entry="affinage".
Pass the PR reference and the router values to /age.
This prevents /age from calculating a smaller route with entry="age".
Add each result to its severity section with [from-age:<dimension>].
Send these findings to /cure like other findings.
Do not post GitHub replies for these findings.
Run the fresh review before you grade external claims. This order lets you remove duplicate findings. Use the same agent gate as the grading step.
Merge-conflict resolution
When pr-status reports conflicts, send the PR to /melt.
/melt uses mergiraf, rerere, and kdiff3.
Do not resolve conflicts by hand.
Default and --auto modes run checkout and /melt before /cure.
--safe requires the handoff gate first.
If /melt fails, write status: halt: merge-conflicts-need-human and stop.
/melt leaves the resolution uncommitted.
Terminal /plate commits and pushes that resolution.
See references/merge-conflict.md.
Sub-agent context gate
Keep dialogue, selection, approval state, and reply posting in the parent context.
Use a fresh read-only reviewer when any limit below is true:
- More than 10 inputs exist.
- The diff exceeds approximately 25 KB.
- Threads cover more than 5 files.
Resolve the reviewer through the shared agent resolver.
Use a general worker only with degraded: true.
The reviewer returns a compact digest of graded findings.
Each finding includes its dimension, severity, confidence, evidence, and draft push-back.
The parent writes the report, controls selection, calls /cure, and posts replies.
See ../age/references/sub-agent-gate.md for digest limits.
Preferred tools and fallbacks
Use code-intelligence-routing.md for source code operations.
Use these affinage tools:
| Need | Prefer | Fallback |
|---|---|---|
| PR status | python3 skills/affinage/scripts/affinage.pyz pr-status |
gh pr checks and gh pr view |
| GitHub fetch | gh api |
none; stop the skill |
| Reply posting | python3 skills/affinage/scripts/affinage.pyz post-reply |
none; direct gh api calls omit attribution |
Output
Write the report to .cheese/affinage/pr-<n>.md.
Start with the four-line handoff slug.
Then add the /age report body and the ## PR status section.
Use the severity sections from /age.
Add the ## Needs-investigation and ## Reviewer-rejected sections from /affinage.
See references/report-template.md.
status: <canonical status field>
next: cure | done
artifact: <path-to-prior-cure-or-press-report-if-any>
<one-line orientation: what the PR does and what was graded>
Use the canonical status: grammar from the handback contract.
Only next: and the extra keyed lines are phase-specific.
Omit empty sections.
Use status: ok after grading completes.
Use status: halt: <reason> when gh or pr-status fails.
Set next: by the rules in ## Handoff.
Handoff
Pipeline: culture → mold → cook → press → age → cure → plate.
/affinage runs parallel to /age and sends findings to /cure.
By default, affinage acts without a prompt.
Ask only when the selected fix is large, findings conflict, or --safe is active.
- Severity findings exist. Calculate the recommended
all-medium, cheapselection. Without an ask reason, announce the selection. Then send/curethe lockedhandoff_context. Then show one reply gate for every drafted reply. That gate covers the applied, deferred, push-back, and investigation replies. With an ask reason, show the cure selection gate instead. Use the shared gate in../cheese/references/handoff-gate.md. Preselect the composite and identify large rows.--autoskips both gates. - Only rejected or investigation claims exist. Do not call
/cure. Show the reply gate. Then wait for a selection.--autoskips this gate.
Post approved replies after the selection.
Run terminal /plate [--open-pr] [--hard] [--safe] only when the working tree has a change.
A resolved merge conflict is such a change.
Set status: ok / next: done when no action remains.
Set next: cure when at least one finding meets the medium+ floor.
Set next: done when no severity finding exists.
Also set next: done when the selected findings are empty.
Auto mode
Skip the selection gate.
Resolve merge conflicts through /melt first.
Stop with status: halt: merge-conflicts-need-human when conflicts remain.
Run the fresh review in standalone mode.
Select each finding that meets <floor>.
--plate uses --stake medium+ --open-pr.
Send /cure --auto --stake <floor>.
Post replies for the original graded claims after the cure chain stops.
Then run terminal /plate --open-pr [--hard] after every reply posts.
Skip /plate when the working tree has no change.
If no finding meets the floor, skip /cure.
Post only rejection and investigation replies.
Exit with status: ok / next: done.
See references/auto-mode.md.
--hard mode
Pass --hard to terminal /plate.
/plate runs /hard-cheese after it verifies the final artifact.
/cure does not call /plate in this chain.
The gate therefore runs once at the publication boundary.
Rules
- Ground each grade in code evidence.
- Prefer a contained fix to push-back.
- Send a valid, contained quality fix to
/cureasLow. - Reserve
## Reviewer-rejectedfor wrong, unsupported, or large claims. - Never apply code fixes in affinage.
- Send code fixes to
/cureand merge conflicts to/melt. - Never post a reply without approval, unless
--autois active. - Post replies only through
python3 skills/affinage/scripts/affinage.pyz post-reply. - End every reply with
agent on behalf of <handle>. - Resolve
<handle>fromRESPOND_GH_HANDLE,gh api user --jq .login, orgit config user.name. - Skip a thread when the resolved handle wrote its latest comment.
- Use GraphQL
reviewThreadsonly when cross-session resolution state is necessary. - Apply the voice rules in
../age/references/voice.md. - Use
certain,speculating, ordon't knowfor confidence. - State that no findings exist when no claim needs grading.
References
Use these affinage references:
references/flow-details.mdreferences/merge-conflict.mdreferences/report-template.mdreferences/handoff-templates.mdreferences/auto-mode.md
Use ../age/references/sub-agent-gate.md for the shared agent gate.
See the generated command inventory in references/commands.md.
Agent resolution
Resolve each dispatch through ../cheese/references/agent-resolution.md.
| Work | Preferred types | Permissions/isolation | Minimum power | Effort | Fallback |
|---|---|---|---|---|---|
| Triage review claims and CI evidence | reviewer | read-only, fresh context | powerful | high | compatible reviewer, then general |
The canonical affinage report includes the shared agent_resolution block.
Files (easy-cheese)
-
references
-
auto-mode.md 1.7 KB
# Auto mode — complete process Use this process for `/affinage --auto --stake <floor>`. Also use it for `--plate`, which adds `--stake medium+ --open-pr`. `SKILL.md` defines the decisions. This file defines each step. - Skip the selection gate. - Send merge conflicts to `/melt` before `/cure`. - Stop with `status: halt: merge-conflicts-need-human` when `/melt` cannot resolve them. - Run the fresh review in standalone mode unless the user passes `--no-age`. - Add `[from-age:…]` findings to the automatic selection. - Select every finding that meets the floor. - Include cheap, contained low findings when the floor is `medium+`. - Send `/cure --auto --stake <floor>`. - Wait for `/cure` and its `/age --scope --auto` chain to stop. - Post replies only for the original graded claims. - Do not grade findings from `/age --scope` again. - Post the prepared push-back for each `Reviewer-rejected` claim. - Post a specific follow-up note for each `Needs-investigation` claim. - Name the test, prototype, or evidence that can confirm the claim. - Use `Needs <evidence> to confirm — will follow up with the result.` - Do not run the investigation in auto mode. - Post all replies before terminal `/plate` publishes fixes. - Send terminal `/plate --open-pr [--hard]` only when the working tree has a change. - Treat a merge conflict that `/melt` resolved as such a change. Keep the complete cure chain in the parent affinage context. The parent must retain finding slugs, comment identifiers, tags, and reply text. A child agent cannot post these replies reliably. If no finding meets the floor, skip `/cure`. Post only rejection and investigation replies. Exit with `status: ok / next: done`. -
commands.md 937 B
# `/affinage` bundle commands `scripts/render_generated_regions.py` generates this file from the static `COMMANDS` manifest in `src/easy_cheese/skills/affinage/commands.py`. Do not edit this file. Run each command as `python3 skills/affinage/scripts/affinage.pyz <command> [args...]`. Each command returns an integer exit status. Pass `--help` to a command for its arguments and output format. Keep worked examples in the skill instructions. | Command | Purpose | Subcommands | | --- | --- | --- | | `age-route` | Size an /age review into single-pass or fan-out lanes (JSON in, JSON out) | | | `post-reply` | Post a PR reply carrying the mandatory agent attribution | | | `pr-status` | Fetch a PR's build and merge status for grading | | | `review-surface` | Score the reviewable git surface that routing sizes against (JSON out) | | | `wheypoint-resolve` | Resolve a phase slug through the shared Wheypoint kernel (JSON out) | | -
flow-details.md 4 KB
# Flow command and reason details Use this file for `## Flow` steps 2, 3, 6, and 9. It gives exact commands, exit codes, and grading rules. ## Step 2 — Fetch PR status Run `python3 skills/affinage/scripts/affinage.pyz pr-status <pr>`. The command returns JSON with build status, failed check summaries, failed test names, and merge state. Each failed check summary includes approximately 10 final log lines. - **Exit 3** means `logs-expired`. CI fails, but no failed check has usable logs. Write `status: halt: pr-status-logs-expired` and stop. Tell the user to rerun failed jobs with `gh run rerun <run-id> --failed`. Read `<run-id>` from the `/actions/runs/<id>/` URL segment or `gh pr checks`. Then tell the user to run `/affinage` again. - **Any other nonzero exit** means the PR or GitHub status is unavailable. Exit 1 identifies a PR or API error. Exit 2 identifies a missing `gh` binary. Write `status: halt: pr-status-unavailable` and stop. ## Step 3 — Fresh review Score the PR diff with the `review-surface` command. Run `python3 skills/affinage/scripts/affinage.pyz review-surface --repo . <base>...HEAD`. Use the complete PR range against its base branch. After checkout, use `origin/<base>...HEAD`. Do not use the bare `HEAD` default because it scores only uncommitted changes. Build the evidence-bearing review context described in `../../age/references/fan-out.md`. Include all changed paths, even when their workload weight is zero. Use `entry="affinage"`, `comments=<unresolved-thread-count>`, and `ci_class=<"passing"|"failing"|"red"|"flaky"|null>` alongside `context`. The router preserves comment-count and CI workload escalation. Use normal review effort unless the user explicitly selects quick or deep. Run `python3 skills/affinage/scripts/affinage.pyz age-route <request.json>`. The command reads the contextual request and emits a deterministic subject plan. Pass the complete plan and its evidence to `/age`, not the old dimension-lens tuple. Then treat each `/age` finding as an additional claim. ## Step 6 — Grading rules - Grade every failed check, including build, compile, lint, type, and test failures. - Send failed checks to `/cure` like test failures. - Tag each CI finding with `[from-check:<job>]`. - Keep the existing dimension and severity for each fresh review finding. - Tag each fresh review finding with `[from-age:<dimension>]`. - Remove a duplicate fresh review finding when a reviewer reports the same defect. - Keep the reviewer claim because it requires a reply. - Record `CHANGES_REQUESTED` as `reviewer-asserted:` metadata. - Do not use reviewer urgency to calculate severity. Use these report sections: - Put grounded claims with contained fixes in `## Blocker`, `## High`, `## Medium`, or `## Low`. Add a `[<dimension>:<severity>]` tag to each claim. Map style and quality claims to `deslop`. Add a `source: from-comment:<id>` line so `/cure` can reply. Prefer a cheap fix to push-back. - Put plausible claims that need outside evidence in `## Needs-investigation`. - Put wrong or unsupported claims in `## Reviewer-rejected`. - Also put valid but large claims in `## Reviewer-rejected`. Large claims have `fix-cost-now: moderate` or `sprawling`. Structural later work is also large. Reject a wrong claim. Defer a large claim. ## Step 9 — Reply rules Post approved replies with `python3 skills/affinage/scripts/affinage.pyz post-reply`. Do not post with `gh api` because it omits the required attribution. - Post the prepared push-back for `Reviewer-rejected` claims. - Do not post a general acknowledgement for `Needs-investigation` claims. - Name the exact evidence that can confirm each investigation claim. - State that a follow-up will report the result. - Offer to run the investigation before you post. - Use `/pasteurize` for a regression test. - Use `/briesearch` for evidence outside the diff. - Post the actual result when the user accepts the investigation. - Post the explicit follow-up note when the user declines. - Do not reply to `[from-check:<job>]` or `[from-age:<dimension>]` findings. -
handoff-templates.md 2.3 KB
# Handoff gate templates Read this when rendering either handoff gate from `SKILL.md`. Use the exact option text below. ## Cure selection gate Show the recommended composite first. Then show all five floor options in this order: 1. `all-medium, cheap` 2. `all` 3. `all-medium` 4. `all-high` 5. `all-blocker` Use the labels and rules from [`../../age/references/handoff-detail.md`](../../age/references/handoff-detail.md). Then show these options: - **Pick findings to fix** — Accept `1,3,5`, floor names, `cheap`, `all`, `none`, or `skip N`. - **Resolve merge conflicts** — Show this option only when the PR has conflicts. Check out the PR. Run `/melt`. Then show the gate again. - **Stop — leave the report for later** — Treat this option as `none`. Show all five floor options on every run. Treat an empty selection as `none`. ## Reply approval gate Show one batch gate before every `post-reply` call. Show the gate after `/cure` returns. Include every drafted reply in the gate. A drafted reply is an applied reply, a deferred reply, a push-back draft, or an investigation note. Read the applied and deferred replies from `.cheese/cure/pr-<n>.md`. - **Post push-back drafts only** *(recommended)* — Post rejected drafts and hold every other reply. - **Investigate now, then post** — Investigate each claim and post its actual result. Use `/pasteurize` for a regression test. Use `/briesearch` for evidence outside the diff. - **Post all** — Post every applied reply, every deferred reply, and every push-back draft. Also post each specific investigation note without prior investigation. - **Skip posting** — Post nothing and leave the report for later. - **Per-finding** — Let the user select any drafted reply to post. Let the user also select a claim to investigate. ## Cure dispatch context When the selection is not empty, send `/cure <slug> [--safe] [--open-pr] [--hard]` immediately. Include this locked context: ```yaml handoff_context: source_skill: /affinage source_report: .cheese/affinage/pr-<n>.md selection: "<verb or explicit ids>" resolved_ids: [<expanded ids>] ``` `/cure` confirms each selected identifier and applies the fixes. It runs its `/age --scope` loop but does not run terminal `/plate`. Affinage owns publication because `source_skill` is `/affinage`. Pass `--safe`, `--open-pr`, and `--hard` to `/cure` when they apply. -
merge-conflict.md 1.1 KB
# Merge conflict resolution A PR cannot merge when `pr-status` reports `merge.mergeable: CONFLICTING` or `merge.state: DIRTY`. Send conflicts to `/melt`. Do not resolve them by hand. `/melt` runs mergiraf, rerere, and kdiff3. 1. Run `gh pr checkout <pr>`. 2. Run `git merge origin/<base>` to create the local conflicts. 3. Send the conflicts to `/melt`. 4. Show any squash residue remedy from `/melt` without changes. 5. Keep the resolved merge in the local working tree. 6. Let `/plate` own the resolution commit and the PR update. In default and `--auto` modes, run checkout and `/melt` before `/cure`. `/melt` leaves the resolution uncommitted. Treat a resolved merge as a publishable change. If `/melt` cannot resolve the conflicts, write `status: halt: merge-conflicts-need-human` and stop. Run terminal `/plate` after every approved reply posts. Then run `python3 skills/affinage/scripts/affinage.pyz pr-status` again to confirm that the conflicts are gone. In `--safe` mode, require approval before checkout and `/melt`. Include `Resolve merge conflicts` in the cure selection options. -
report-template.md 4.4 KB
# Affinage report template Use this example for `.cheese/affinage/pr-<n>.md`. Keep the four-line handoff slug from `SKILL.md` before these sections. Downstream skills parse that slug. Each severity bullet uses the shared finding grammar. The bullet holds one `[<dimension>:<severity>]` tag, then the location in backticks, then the summary. `/cure` parses that grammar with `findings`. Put the provenance tag on an indented `source:` line under the bullet. The `## Needs-investigation` and `## Reviewer-rejected` sections do not use this grammar. `/cure` does not parse those two sections. ```markdown # Affinage Report — PR #<n> ## Orientation <one or two facts about the PR and the graded claims> ## PR status - Build: passing | failing (N jobs) - Merge: clean | conflicts (resolved via /melt | needs human) - Comments: K unresolved (M skipped as outdated) - Fresh review: ran /age (N findings) | skipped (chained) | skipped (--no-age) ## Blocker - **[security:blocker]** `src/auth.ts:42` — The code parses a token without validation. - source: from-comment:<id> · author: alice - location: contract · fix-cost-now: contained · fix-cost-later: structural · confidence: certain - reviewer-asserted: changes-requested - recommendation: Validate `authorization`. Return 401 when the header is absent. - **[correctness:blocker]** `tests/auth.test.ts` — Three tests fail in CI job `test-suite`. - source: from-check:test-suite - location: contract · fix-cost-now: contained · fix-cost-later: structural · confidence: certain - recommendation: Add the absent null check. Then run the tests again. - **[correctness:blocker]** `src/auth.ts:42` — `tsc` reports `'token' is possibly undefined` in CI job `build`. - source: from-check:build - location: contract · fix-cost-now: contained · fix-cost-later: structural · confidence: certain - recommendation: Narrow `token` before use. The build cannot pass until this compiles. - **[efficiency:high]** `src/api/users.ts:88` — The fresh review found a user fetch in each loop iteration on the hot path. - source: from-age:efficiency - location: module · fix-cost-now: contained · fix-cost-later: contained · confidence: speculating - recommendation: Move the fetch before the loop. ## High ... (use the same format) ## Medium ... (use the same format) ## Low - **[deslop:low]** `src/utils/format.ts:18` — The name `data` does not state what the value holds. - source: from-comment:<id> · author: copilot - location: class · fix-cost-now: contained · fix-cost-later: contained · confidence: certain - recommendation: Rename `data` to `lineItems`. Send this cheap fix to `/cure`. ... (use the same format; collapse it by the --full rules) ## Needs-investigation - **[from-comment:<id>]** bob on `src/api/users.ts:108` — "This might break the analytics pipeline." - reason: The claim is plausible. The pipeline is in another repository. - suggested action: Read `analytics-svc/consumers/users.ts`. ## Reviewer-rejected - **[from-comment:<id>]** copilot on `src/auth.ts:30` — "This needs `await`." - reason: `parseToken` returns `string`, not `Promise`. See `src/auth.ts:12`. - draft reply: "`parseToken` returns `string` here. No promise exists, so I will keep the current code." - **[from-comment:<id>]** dana on `src/api/users.ts:60` — "Extract a generic repository layer." - reason: The change needs six files in two slices. It exceeds this PR scope. - draft reply: "This cross-slice refactor exceeds this PR scope. I will record it as follow-up work." ## Confidence <certain | speculating | don't know> — <one reason for the confidence> ## Next step Send the recommended findings to `/cure`. Hold prepared replies for the reply approval gate. Post directly only in `--auto` mode. Post replies before terminal `/plate` publishes fixes. Show the cure selection gate when `--safe` is active or an ask reason exists. ``` Omit empty severity sections. Omit `## Needs-investigation` and `## Reviewer-rejected` when they are empty. Use the confidence scale from `../../age/references/voice.md`. Use `certain` when direct evidence confirms the defect. Use `speculating` when indirect evidence supports the defect. Put a `don't know` claim in `## Needs-investigation`, not a severity section. Use only `class`, `module`, `cross-module`, or `contract` for `location:`. See [`../../age/references/dimensions.md`](../../age/references/dimensions.md).
-
-
scripts
-
affinage.pyz 2 MB · in bundle
-
-
SKILL.md 13.2 KB
--- name: affinage description: > Triage a PR's review comments, CI failures, and merge conflicts through the /age lens. Use when the user asks to address PR feedback, fix CI, or resolve conflicts. Do not use for a diff without a PR. Use /age instead. license: MIT metadata: {dispatches-agents: true} --- # /affinage Act on existing claims about a PR. Claims can come from reviewers, CI checks, or merge conflicts. Grade each claim through the `/age` lens. Send accepted claims to `/cure`. `/affinage` always grades claims that exist on the PR. Its entry path controls whether it also finds new `/age` findings: - **Standalone** — The user starts `/affinage <pr>` without `handoff_context`. Run `/age` on the PR diff unless the user passes `--no-age`. Add the new findings to the same report. - **Chained** — `/cook` or `/cure` supplies `handoff_context`. Skip the fresh review because `/age` already ran in this chain. See `## Fresh review` for the entry rule. See `## Merge-conflict resolution` for the conflict path. ## Phase entry Run `python3 skills/affinage/scripts/affinage.pyz wheypoint-resolve --ref <slug>`. `authoritative` uses the record; its `working_context` is the first batched `tilth_read`. `not-found` proceeds cold; `legacy` shows its source and slug, then proceeds. `gated`, `ambiguous`, and `error` stop and show the payload. Show advisory `stale-commit` and `grounded-path-missing` findings. ## Inputs ```text /affinage [<pr-ref>] [--auto --stake <floor>] [--plate] [--safe] [--open-pr] [--hard] [--full] [--include-outdated] ``` `<pr-ref>` accepts a PR number, a `PR#<n>` reference, or a full GitHub PR URL. Extract the integer before you call `pr-status`. The command accepts only the integer. If no reference exists, run `gh pr view --json number` on the current branch. Flags: - `--auto --stake <floor>` — Run without selection prompts. `<floor>` accepts `blocker`, `high`, `medium+`, or `all`. Bare `--auto` uses the `medium+` default floor. Use the same floor rules as `/cure`. Send `/cure --auto --stake <floor>`. Post replies without prompts. See `references/auto-mode.md`. - `--safe` — Add gates before cure selection and conflict resolution. This flag does not remove the default reply gate. - `--open-pr` — Let terminal `/plate` open a new PR when no PR exists. Without this flag, `/plate` only updates an open PR. - `--plate` — Run `--auto --stake medium+ --open-pr`. Grade the claims. Cure the selected findings. Post the replies. Then run `/plate`. An explicit `--stake <floor>` replaces `medium+`. - `--hard` — Pass the metacognitive gate flag to terminal `/plate`. - `--full` — Show all low findings when at least 10 low findings exist. - `--include-outdated` — Include outdated review threads. - `--no-age` — Skip the fresh review in standalone mode. This flag has no effect in chained mode. Read [`../cheese/references/harness-portability.md`](../cheese/references/harness-portability.md) for portability rules. It covers helper resolution, agent dispatch, GitHub operations, and handoff transitions. Use the bundle or repository helper first. Do not use the `${CLAUDE_SKILL_DIR}` environment variable in an invocation path. The handoff blocks below define the portable contract. The rule "slash commands are host renderings, not the control model" applies here. ## Flow Read `references/flow-details.md` for exact commands, exit codes, and grading reasons. 1. **Resolve PR.** Use `<pr-ref>` or `gh pr view --json number`. Normalize a `PR#<n>` reference or a PR URL to its integer. Resolve `<owner>/<repo>` from the Git remote. 2. **Fetch PR status.** Run `python3 skills/affinage/scripts/affinage.pyz pr-status <pr>`. Exit 3 stops with `status: halt: pr-status-logs-expired`. Any other nonzero exit stops with `status: halt: pr-status-unavailable`. Route a conflicting or dirty merge state to `## Merge-conflict resolution`. 3. **Run fresh review.** Run this step only in standalone mode without `--no-age`. Build contextual review input and call the bundled `age-route` command. Include the comment count and CI failure class. Run `/age` with the complete subject plan and its evidence. Tag each new finding with `[from-age:<dimension>]`. 4. **Fetch comments.** Fetch inline threads from `pulls/<pr>/comments`. Skip comments with `position: null` unless the user passes `--include-outdated`. Fetch review bodies from `pulls/<pr>/reviews`. Keep each nonempty body. Remove duplicates by `pull_request_review_id`. 5. **Skip answered threads.** Skip a thread when the resolved GitHub handle wrote its latest comment. Render the footer as `agent on behalf of <handle>`. 6. **Grade claims.** Classify each claim by the `/age` dimension and severity rules. Do not increase severity because a reviewer selected `CHANGES_REQUESTED`. Put contained fixes in severity sections. Put claims that need outside evidence in `## Needs-investigation`. Put wrong, unsupported, or large claims in `## Reviewer-rejected`. 7. **Write report.** Write `.cheese/affinage/pr-<n>.md`. Start with the four-line handoff slug. Add the `/age` report body and two affinage sections. See `## Output`. 8. **Act or ask.** Follow `## Handoff`. 9. **Draft non-cure replies.** Draft replies for rejected and investigation claims. Do not reply to CI or fresh-review findings. 10. **Draft cure replies.** Run this step only after `/cure`. Read `### Applied` and `### Deferred` from `.cheese/cure/pr-<n>.md`. Draft `Fixed — <applied summary>.` for applied comment findings. Draft `Attempted fix reverted — <reason>.` for deferred comment findings. 11. **Post replies.** Show one reply gate for every drafted reply. Skip the gate only when `--auto` is active. Post approved replies with `python3 skills/affinage/scripts/affinage.pyz post-reply`. 12. **Publish.** Run this step only after all approved replies post. Publish when `/cure` applies at least one fix. Also publish when `/melt` resolved a merge conflict. Send terminal `/plate [--open-pr] [--hard] [--safe]`. Then run the post-PR learning write-back from `../cure/SKILL.md`. Skip publication and write-back when the working tree has no change. ## Fresh review Standalone mode calls the router with `entry="affinage"`. Pass the PR reference and the router values to `/age`. This prevents `/age` from calculating a smaller route with `entry="age"`. Add each result to its severity section with `[from-age:<dimension>]`. Send these findings to `/cure` like other findings. Do not post GitHub replies for these findings. Run the fresh review before you grade external claims. This order lets you remove duplicate findings. Use the same agent gate as the grading step. ## Merge-conflict resolution When `pr-status` reports conflicts, send the PR to `/melt`. `/melt` uses mergiraf, rerere, and kdiff3. Do not resolve conflicts by hand. Default and `--auto` modes run checkout and `/melt` before `/cure`. `--safe` requires the handoff gate first. If `/melt` fails, write `status: halt: merge-conflicts-need-human` and stop. `/melt` leaves the resolution uncommitted. Terminal `/plate` commits and pushes that resolution. See `references/merge-conflict.md`. ## Sub-agent context gate Keep dialogue, selection, approval state, and reply posting in the parent context. Use a fresh read-only `reviewer` when any limit below is true: - More than 10 inputs exist. - The diff exceeds approximately 25 KB. - Threads cover more than 5 files. Resolve the reviewer through the shared agent resolver. Use a general worker only with `degraded: true`. The reviewer returns a compact digest of graded findings. Each finding includes its dimension, severity, confidence, evidence, and draft push-back. The parent writes the report, controls selection, calls `/cure`, and posts replies. See `../age/references/sub-agent-gate.md` for digest limits. ## Preferred tools and fallbacks Use [`code-intelligence-routing.md`](../cheese/references/code-intelligence-routing.md) for source code operations. Use these affinage tools: | Need | Prefer | Fallback | | --- | --- | --- | | PR status | `python3 skills/affinage/scripts/affinage.pyz pr-status` | `gh pr checks` and `gh pr view` | | GitHub fetch | `gh api` | none; stop the skill | | Reply posting | `python3 skills/affinage/scripts/affinage.pyz post-reply` | none; direct `gh api` calls omit attribution | ## Output Write the report to `.cheese/affinage/pr-<n>.md`. Start with the four-line handoff slug. Then add the `/age` report body and the `## PR status` section. Use the severity sections from `/age`. Add the `## Needs-investigation` and `## Reviewer-rejected` sections from `/affinage`. See `references/report-template.md`. ```markdown status: <canonical status field> next: cure | done artifact: <path-to-prior-cure-or-press-report-if-any> <one-line orientation: what the PR does and what was graded> ``` Use the canonical `status:` grammar from the [handback contract](../cheese/references/handback-contract.md). Only `next:` and the extra keyed lines are phase-specific. Omit empty sections. Use `status: ok` after grading completes. Use `status: halt: <reason>` when `gh` or `pr-status` fails. Set `next:` by the rules in `## Handoff`. ## Handoff **Pipeline:** culture → mold → cook → press → age → cure → plate. `/affinage` runs parallel to `/age` and sends findings to `/cure`. By default, affinage acts without a prompt. Ask only when the selected fix is large, findings conflict, or `--safe` is active. - **Severity findings exist.** Calculate the recommended `all-medium, cheap` selection. Without an ask reason, announce the selection. Then send `/cure` the locked `handoff_context`. Then show one reply gate for every drafted reply. That gate covers the applied, deferred, push-back, and investigation replies. With an ask reason, show the cure selection gate instead. Use the shared gate in [`../cheese/references/handoff-gate.md`](../cheese/references/handoff-gate.md). Preselect the composite and identify large rows. `--auto` skips both gates. - **Only rejected or investigation claims exist.** Do not call `/cure`. Show the reply gate. Then wait for a selection. `--auto` skips this gate. Post approved replies after the selection. Run terminal `/plate [--open-pr] [--hard] [--safe]` only when the working tree has a change. A resolved merge conflict is such a change. Set `status: ok / next: done` when no action remains. Set `next: cure` when at least one finding meets the `medium+` floor. Set `next: done` when no severity finding exists. Also set `next: done` when the selected findings are empty. ## Auto mode Skip the selection gate. Resolve merge conflicts through `/melt` first. Stop with `status: halt: merge-conflicts-need-human` when conflicts remain. Run the fresh review in standalone mode. Select each finding that meets `<floor>`. `--plate` uses `--stake medium+ --open-pr`. Send `/cure --auto --stake <floor>`. Post replies for the original graded claims after the cure chain stops. Then run terminal `/plate --open-pr [--hard]` after every reply posts. Skip `/plate` when the working tree has no change. If no finding meets the floor, skip `/cure`. Post only rejection and investigation replies. Exit with `status: ok / next: done`. See `references/auto-mode.md`. ## --hard mode Pass `--hard` to terminal `/plate`. `/plate` runs `/hard-cheese` after it verifies the final artifact. `/cure` does not call `/plate` in this chain. The gate therefore runs once at the publication boundary. ## Rules - Ground each grade in code evidence. - Prefer a contained fix to push-back. - Send a valid, contained quality fix to `/cure` as `Low`. - Reserve `## Reviewer-rejected` for wrong, unsupported, or large claims. - Never apply code fixes in affinage. - Send code fixes to `/cure` and merge conflicts to `/melt`. - Never post a reply without approval, unless `--auto` is active. - Post replies only through `python3 skills/affinage/scripts/affinage.pyz post-reply`. - End every reply with `agent on behalf of <handle>`. - Resolve `<handle>` from `RESPOND_GH_HANDLE`, `gh api user --jq .login`, or `git config user.name`. - Skip a thread when the resolved handle wrote its latest comment. - Use GraphQL `reviewThreads` only when cross-session resolution state is necessary. - Apply the voice rules in `../age/references/voice.md`. - Use `certain`, `speculating`, or `don't know` for confidence. - State that no findings exist when no claim needs grading. ## References Use these affinage references: - `references/flow-details.md` - `references/merge-conflict.md` - `references/report-template.md` - `references/handoff-templates.md` - `references/auto-mode.md` Use `../age/references/sub-agent-gate.md` for the shared agent gate. See the generated command inventory in [`references/commands.md`](references/commands.md). ## Agent resolution Resolve each dispatch through [`../cheese/references/agent-resolution.md`](../cheese/references/agent-resolution.md). | Work | Preferred types | Permissions/isolation | Minimum power | Effort | Fallback | | --- | --- | --- | --- | --- | --- | | Triage review claims and CI evidence | reviewer | read-only, fresh context | powerful | high | compatible reviewer, then general | The canonical affinage report includes the shared `agent_resolution` block.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.