agentsclimarketplace

Affinage

Skill paulnsorensen/easy-cheese/skills/affinage

Cheese your code πŸ§€ β€” high-quality results as easy as cheese. A portable, harness-agnostic Agent Skills toolkit.

Install
npx -y skills add paulnsorensen/easy-cheese --skill affinage

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 12 stars12 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Triage a PR's review comments and failing CI (plus merge conflicts) through the /age lens, deciding which claims are worth acting on. Use when the user says "respond to PR comments", "handle review feedback", "affinage the PR", "/affinage <pr>", "fix the failing build", "resolve the conflicts and respond". Do NOT use for a bare diff with no PR (route to /age).

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

30.8 KB, as published. Nobody here has run it

/affinage

Use this skill when the user wants to act on external claims about a PR β€” review comments from humans or bots, plus failing CI checks and merge conflicts β€” and wants those claims graded through the same lens /age uses for fresh review, then handed to /cure for application.

/affinage always refines the claims that already exist on the PR (comments, CI failures, conflicts). Whether it also generates fresh /age findings depends on how it was reached:

  • Standalone β€” the user typed /affinage <pr> directly, with no upstream handoff_context. The PR diff has not been reviewed in this session, so /affinage runs /age over it and folds the findings into the same report (unless --no-age).
  • Chained β€” reached from /cook or /cure with a handoff_context. /age already ran in that chain, so /affinage skips the fresh pass to avoid double-grading and only refines existing claims.

See ## Fresh-window review for the detection rule and ## Merge-conflict resolution for the conflict path.

Inputs

/affinage [<pr-ref>] [--auto --stake <floor>] [--plate] [--safe] [--open-pr] [--hard] [--full] [--include-outdated]

<pr-ref> accepts a PR number, a full GitHub PR URL, or nothing (auto-detect via gh pr view --json number on the current branch).

Flags:

  • --auto --stake <floor> β€” autonomous mode. <floor> is blocker, high, medium+, or all (same semantics as /cure). Skips the selection gate, dispatches /cure --auto --stake <floor>, posts all replies without prompting.
  • --safe β€” also gate the cure-selection and merge-conflict-resolution steps (which otherwise run autonomously by default). Reply posting is gated by default regardless of this flag β€” only --auto posts without prompting. Use --safe when you also want to choose before anything is fixed or a conflict is resolved.
  • --open-pr β€” allow affinage's terminal /plate to open a new PR when none exists (otherwise plate only updates the already-open one).
  • --plate β€” one-shot publish combo, equivalent to --auto --stake medium+ --open-pr: autonomously triage, cure the recommended floor (medium-and-above plus cheap contained-fix lows), post every reply, then plate. Because it carries --auto, replies post without prompting. An explicit --stake <floor> overrides the medium+ default.
  • --hard β€” propagated metacognitive-gate flag. /affinage does not fire the gate; passes --hard forward to its terminal /plate at publication.
  • --full β€” un-collapses ## Low when β‰₯10 low-severity findings exist (mirrors /age --full).
  • --include-outdated β€” include outdated review threads. Default skips them.
  • --no-age β€” skip the standalone fresh /age pass. No effect when chained (the pass is already skipped). Use when you only want to triage existing comments, CI failures, and conflicts.

Portability reference: ../cheese/references/harness-portability.md. It covers helper resolution, sub-agent dispatch, GitHub operations, and handoff transitions; prefer the bundled or repo-local helper first, and treat ${CLAUDE_SKILL_DIR} as optional host-provided fallback. The handoff blocks below are the portable contract; slash commands are host renderings, not the control model.

Flow

  1. Resolve PR. From <pr-ref> or gh pr view --json number on the current branch. Resolve <owner>/<repo> from the git remote.

  2. Fetch PR status. Call python3 skills/affinage/scripts/affinage.pyz pr-status <pr>. The script returns JSON with build status, per-check failure summaries (last ~10 lines of failed logs + parsed failed-test names), and merge state. Map the exit code:

    • Exit 0 β€” proceed with grading.
    • Exit 3 (logs-expired) β€” the build is failing but every failing check's log was unfetchable (typically expired GitHub Actions logs past the retention window), so there is nothing to ground a CI finding on. Write status: halt: pr-status-logs-expired and stop with the hint: "CI is failing but the logs have expired β€” rerun the failed jobs (gh run rerun <run-id> --failed, where <run-id> is the /actions/runs/<id>/ segment of the failing check's url, or read it from gh pr checks) and re-invoke /affinage."
    • Any other non-zero (1 PR/gh API error, 2 missing gh binary) β€” write status: halt: pr-status-unavailable and stop.
    • Merge conflicts. If merge.mergeable is CONFLICTING or merge.state is DIRTY, the PR has unresolved conflicts. Resolve them before grading β€” see ## Merge-conflict resolution.
  3. Fresh-window review. If this is a standalone run and --no-age was not passed: compute files_changed/insertions/deletions from the PR diff stat, grep it for age_route.OVERRIDE_FLAGS risk flags, and call age_route.route(files_changed=..., insertions=..., deletions=..., risk_flags=..., entry="affinage", comments=<unresolved-thread-count>, ci_class=<"failing"|"red"|"flaky"|None from pr-status>) β€” the same router /age itself calls, but sized with affinage's comment count and CI failure class so a heavily-commented or red-CI PR gets the bigger fan-out even on a small diff. If the host only ships the bundle, echo '{"files_changed": <n>, "insertions": <n>, "deletions": <n>, "risk_flags": [...], "entry": "affinage", "comments": <n>, "ci_class": <"failing"|"red"|"flaky"|null>}' | python3 skills/affinage/scripts/affinage.pyz age-route is the fallback (JSON on stdin, route JSON on stdout). Pass the returned n/lenses/effort into the /age dispatch (so /age uses affinage's sizing rather than recomputing from entry="age" defaults) and treat each finding as an additional input. See ## Fresh-window review.

  4. Fetch comments.

    • Inline threads: gh api repos/<owner>/<repo>/pulls/<pr>/comments. This REST endpoint returns individual review comments without thread-level resolution state, so the skill cannot filter on isResolved from this surface; it skips comments whose position is null (the diff has moved past the anchored line) unless --include-outdated.
    • Review bodies: gh api repos/<owner>/<repo>/pulls/<pr>/reviews. Filter to non-empty bodies. Dedupe against inline comments via pull_request_review_id.
  5. Skip already-replied threads. A thread whose most recent comment is from the resolved GitHub handle (see Β§Rules) has already been responded to; skip it. The same resolved handle is rendered in the reply footer as agent on behalf of <handle>.

  6. Grade through the age lens. For each input (comment, CI/build failure, OR fresh /age finding):

    • Classify dimension from the code + claim (or check type + failure summary, for CI items). See ../age/references/dimensions.md for the dimension rubric.
    • Build failures count, not just test failures. A failing check is a finding whether the failure is a compile error, a lint/type-check failure, or a failing test β€” grade the build.status: failing checks from affinage.pyz pr-status and route them to /cure exactly like test failures. Tag CI-sourced items [from-check:<job>].
    • Fresh /age findings (standalone runs) arrive already dimension-classified and severity-scored; fold them into the buckets below tagged [from-age:<dimension>]. Dedupe against comment-sourced items echoing the same defect β€” keep the comment-sourced one (it carries a reviewer to reply to).
    • Compute severity from base + location + compounding modifiers (same rubric as /age).
    • Ignore reviewer-asserted urgency for severity computation. Surface CHANGES_REQUESTED as metadata (reviewer-asserted: line) but do not let it modify computed severity.
    • Bucket into:
      • Standard severity sections (## Blocker / ## High / ## Medium / ## Low) when the claim is grounded in the diff and its fix is contained (fix-cost-now: contained β€” roughly a few lines or a localized refactor). Every such item still maps to a dimension and carries a [<dimension>:<severity>] tag β€” a style or quality nit maps to deslop (e.g. [deslop:low]). The new rule is to route these grounded, contained-fix nits to /cure (usually as Low) instead of ## Reviewer-rejected, keeping the [from-comment:<id>] tag so /cure's reply still reaches the reviewer; a valid cheap nit is cheaper to fix than to argue, so do not push back on it.
      • ## Needs-investigation when the claim is plausible but requires evidence outside the diff (e.g., downstream caller in another repo).
      • ## Reviewer-rejected only when the claim is wrong or ungrounded (the code is already correct, the reviewer misread it, or there is no real improvement) OR is valid but a lot of follow-up work (fix-cost-now: moderate/sprawling or fix-cost-later: structural β€” a refactor or scope expansion beyond this PR). Reject the wrong ones; defer the expensive ones.
  7. Write report to .cheese/affinage/pr-<n>.md with the four-line handoff slug at the top, then the age-format body plus the two extra sections. See ## Output below.

  8. Act or ask β€” per Β§Handoff.

  9. Draft non-cure replies, then gate before posting (runs whenever grading produced these items, with or without /cure). Never post blind β€” posting requires the reply-approval gate (Β§Handoff) by default, or --auto. Draft each reply, then post approved ones via python3 skills/affinage/scripts/affinage.pyz post-reply:

    • Reviewer-rejected items β†’ the pre-drafted push-back text from the affinage report.
    • Needs-investigation items β†’ do NOT post a bare acknowledgement. The reply must (a) name the specific evidence that would settle the claim β€” the regression test, throwaway prototype, or out-of-diff file to read β€” and (b) state that a follow-up will report the result. Before posting, offer to run that investigation now: a regression test via /pasteurize, or explore the out-of-diff evidence via /briesearch. If run, post a reply carrying the actual outcome; if the user declines, post the explicit "Needs <named test/exploration> to confirm β€” will follow up with the result." note β€” never a blind "investigating".
    • CI-sourced findings (from-check:<job> tag) and fresh-review findings (from-age:<dimension> tag) β†’ no reply (no reviewer to notify).
  10. Post-cure reply posting (only when /cure ran). The chained /cure applies its fixes and runs its /age --scope loop but returns without plating (it owns no publication in the /affinage chain). When /cure returns, read .cheese/cure/pr-<n>.md's ### Applied / ### Deferred sections and post per-finding replies via python3 skills/affinage/scripts/affinage.pyz post-reply:

    • Applied (with from-comment:<id> tag) β†’ "Fixed β€” <applied summary>."
    • Deferred (with from-comment:<id> tag) β†’ "Attempted fix reverted β€” <reason>."
  11. Plate β€” the final writes above (steps 9–10) must land before publication. Once every approved reply is posted, and only when the cure applied β‰₯1 fix (there is something to publish), dispatch terminal /plate [--open-pr] [--hard] [--safe] to commit and publish cure's fixes to the PR. /affinage owns this dispatch; publication lands after all replies. After publication lands, run the Β§ Post-PR learnings write-back (../cure/SKILL.md Β§ Handoff) β€” affinage owns terminal /plate, so it owns the write-back the chained /cure suppressed. Skip plate and write-back when no fix was applied β€” there is nothing to publish.

Fresh-window review

Detection: standalone = no upstream handoff_context; chained = a handoff_context with source_skill: /cook or /cure. Behaviour:

  • Standalone (and --no-age not passed): compute the entry="affinage" router call (Flow step 3) and run /age <pr-ref> over the PR diff, passing the router's n/lenses/effort so /age doesn't recompute a smaller entry="age" sizing from the diff alone. Fold each returned finding into the affinage report's severity sections tagged [from-age:<dimension>]. They flow to /cure with every other selected finding and get no GitHub reply β€” there is no reviewer to notify, same as [from-check:…] items.
  • Chained, or --no-age: skip the pass. Re-running /age on an already-reviewed diff double-grades.

Run the fresh /age before grading external claims so a comment that merely echoes an /age finding can be deduped. To keep the parent context lean, run the pass under the same sub-agent gate as grading (## Sub-agent context gate).

Merge-conflict resolution

When affinage.pyz pr-status reports merge.mergeable: CONFLICTING or merge.state: DIRTY, the PR cannot merge until conflicts are resolved. /affinage does not resolve conflicts by hand β€” it routes to /melt, which runs the structural cascade (mergiraf β†’ rerere β†’ kdiff3).

  1. Materialise the conflicts locally: gh pr checkout <pr>, then git merge origin/<base>. (gh pr checkout neither opens nor updates the PR, so it does not breach the no-/gh rule.)
  2. Hand off to /melt. It first checks for squash-merge residue and stops with remedies if found β€” surface those verbatim and do not auto-apply.
  3. After /melt resolves cleanly, the resolution commit is owned by /melt / /cure. /plate owns the verified commit and existing-PR update transaction.
  • Default and --auto mode: run the checkout + /melt automatically before dispatching /cure, then re-run affinage.pyz pr-status to confirm mergeable cleared. If /melt cannot resolve (manual kdiff3 needed, or squash residue), write status: halt: merge-conflicts-need-human and stop.
  • --safe mode: gate the checkout + /melt behind the handoff prompt β€” offer "Resolve merge conflicts" alongside the cure-selection options.

Sub-agent context gate

/affinage keeps dialogue, selection, approval state, and reply posting in the parent context. When the parent context would balloon, resolve a fresh read-only reviewer through the shared agent resolver. A compatible reviewer is preferred; a general worker qualifies only with prompt-only no-write enforcement and degraded: true:

  • Total input count (comments + CI failures) exceeds 10.
  • Diff exceeds ~25 KB.
  • Threads span more than 5 files.

The sub-agent returns a digest: graded findings table with dimension, severity, confidence, grounded-evidence cite, and pre-drafted push-back text for any Reviewer-rejected items. The parent owns the report write, selection gate, /cure dispatch, and reply posting.

Digest size, parent-vs-sub-agent split, and harness-agnostic sub-agent selection live in ../age/references/sub-agent-gate.md.

Preferred tools and fallbacks

Call source-code search and read backends directly according to the shared code-intelligence-routing.md contract. Beyond source-code routing there are affinage-specific tools:

NeedPreferFallback
PR status (build + merge)skills/affinage/scripts/affinage.pyz pr-statusmanual gh pr checks + gh pr view
GitHub fetchgh apinone (skill halts)
Reply postingskills/affinage/scripts/affinage.pyz post-replynone β€” direct gh api calls bypass the agent on behalf of <handle> attribution
Diff inspectiondeltagit diff --unified=3

Output

Write to .cheese/affinage/pr-<n>.md with the four-line handoff slug at the top, then the age-style body with two extra sections:

status: ok | halt: <one-line reason>
next: cure | done
artifact: <path-to-prior-cure-or-press-report-if-any>
<one-line orientation: what the PR does and what was graded>

# Affinage Report β€” PR #<n>

## Orientation
<one or two factual sentences about the PR and what was graded>

## 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
- **[from-comment:<id>] [security:blocker]** alice on `src/auth.ts:42` β€” token parsed without validation.
  - location: contract Β· fix-cost-now: contained Β· fix-cost-later: structural Β· confidence: certain
  - reviewer-asserted: changes-requested
  - recommendation: validate `authorization` header; reject with 401 on missing.
- **[from-check:test-suite] [correctness:blocker]** CI job `test-suite` β€” 3 tests failing in `tests/auth.test.ts`.
  - location: contract Β· fix-cost-now: contained Β· fix-cost-later: structural Β· confidence: certain
  - recommendation: re-run after fixing the missing null check.
- **[from-check:build] [correctness:blocker]** CI job `build` β€” `tsc` fails: `src/auth.ts:42: 'token' is possibly undefined`.
  - location: contract Β· fix-cost-now: contained Β· fix-cost-later: structural Β· confidence: certain
  - recommendation: narrow `token` before use; build is red until this compiles.
- **[from-age:efficiency] [efficiency:high]** fresh review β€” `src/api/users.ts:88` re-fetches the user inside the loop body.
  - location: hot path Β· fix-cost-now: contained Β· fix-cost-later: contained Β· confidence: speculating
  - recommendation: hoist the fetch above the loop.

## High
... (same shape)

## Medium
... (same shape)

## Low
- **[from-comment:<id>] [deslop:low]** copilot on `src/utils/format.ts:18` β€” rename `data` to `lineItems` for clarity.
  - location: class Β· fix-cost-now: contained Β· fix-cost-later: contained Β· confidence: certain
  - recommendation: rename `data` β†’ `lineItems`. Valid cheap nit β€” fixed via `/cure`, not pushed back.
... (same shape; collapsible per --full rules)

## Needs-investigation
- **[from-comment:<id>]** bob on `src/api/users.ts:108` β€” "might break analytics pipeline."
  - reason: claim plausible but pipeline lives in a different repo; diff cannot confirm.
  - suggested action: human reads `analytics-svc/consumers/users.ts`.

## Reviewer-rejected
- **[from-comment:<id>]** copilot on `src/auth.ts:30` β€” "missing `await`; this promise is unhandled."
  - reason: wrong β€” `parseToken` is synchronous (returns `string`, not a `Promise`, see `src/auth.ts:12`); there is nothing to await.
  - draft reply: "`parseToken` is synchronous here (returns `string`, `src/auth.ts:12`), so there's no promise to await. Leaving as-is."
- **[from-comment:<id>]** dana on `src/api/users.ts:60` β€” "extract this into a generic repository layer."
  - reason: valid but large β€” fix-cost-now: sprawling (6 files across 2 slices); scope expansion beyond this PR.
  - draft reply: "Agreed this would be cleaner, but it's a cross-slice refactor beyond this PR's scope β€” filing a follow-up rather than growing this change."

## Confidence
<certain | speculating | don't know> β€” <one-line justification>

## Next step
Auto-fixing the recommended set via `/cure`; drafted replies are held for the reply-approval gate before posting (`--auto` posts directly). Replies post before terminal `/plate` publishes cure's fixes. On a reason to ask / `--safe`, the cure-selection prompt renders inline β€” pick findings to cure or `none` to stop.

Empty severity sections are omitted entirely. ## Needs-investigation and ## Reviewer-rejected are omitted when no items land there.

Per-finding confidence: uses the voice-kernel scale (../age/references/voice.md Β§ Reasoning posture): certain β€” the defect is verified by direct evidence (diff/code read, command output); speculating β€” inferred from indirect signal. A don't know grading never ships as a severity row β€” route it to ## Needs-investigation.

status: ok when grading completed; status: halt: <reason> when gh or pr-status.py failed in a way that blocks honest grading. next: is set per Β§Handoff β€” cure when β‰₯1 finding meets the medium+ floor; done otherwise.

Handoff

Pipeline: culture β†’ mold β†’ cook β†’ press β†’ age β†’ cure β†’ plate Β· /affinage is parallel to /age and feeds /cure.

After the report lands, affinage acts by default and asks only on a genuine reason (a sprawling/structural fix in the recommended set, conflicting findings) or under --safe (Flow step 8). What it acts on depends on whether any severity-section finding exists.

When at least one severity-section finding exists (any severity, including Low) β€” compute the recommended composite (all-medium, cheap). With no reason to ask and no --safe: announce the one-line selection, dispatch /cure (below), then render the reply-approval gate before posting the non-cure replies (Flow step 9) and the cure-dependent replies (step 10) β€” never post blind. --auto posts without the gate (Β§Auto mode). On a reason to ask or --safe: render the cure-selection table inline (per ../cure/references/selection.md) and ask via ../cheese/references/handoff-gate.md, pre-selecting the recommended composite and flagging heavy rows. Lead with the recommended composite, then present the four severity-floor options below it, in the same most-inclusive-to-least order, so the gate is predictable across every run:

  • The five severity-floor options (recommended all-medium, cheap, then all, all-medium, all-high, all-blocker) are exactly age's β€” see ../age/SKILL.md Β§ Selection gate for their labels and semantics.

Then offer the non-floor options last:

  • Pick findings to fix β€” free-text reply using /age//cure verbs (1,3,5, all-blocker, all-medium, all-high, cheap, all, none, skip N).
  • Resolve merge conflicts (offered only when the PR has conflicts) β€” checkout + /melt per ## Merge-conflict resolution, then re-render this gate.
  • Stop β€” leave the report for later β€” equivalent to none.

The "present all four severity options on every run, empty-set-resolves-to-none" rule is age's β€” see ../age/SKILL.md Β§ Selection gate.

When no severity-section finding exists but Reviewer-rejected or Needs-investigation has items β€” /cure has nothing to act on, so skip it. Posting is gated by default; render the reply-approval gate (below) and post nothing until the user chooses. Only --auto posts without this gate.

Reply-approval gate β€” the single gate both Handoff branches use before any post-reply call:

  • Post pushbacks only (recommended) β€” post Reviewer-rejected drafts; hold Needs-investigation items for investigation.
  • Investigate now, then post β€” for each Needs-investigation item, run the follow-up investigation (/pasteurize for a regression test, /briesearch to explore the out-of-diff evidence), then post a reply carrying the actual result.
  • Post all β€” post every drafted push-back and the explicit Needs-investigation follow-up notes (naming the needed evidence) without running the investigation first.
  • Skip posting β€” leave the report for later; post nothing.
  • Per-finding β€” free-text pick of which drafts to post or investigate.

After the selection, post the approved replies via Flow step 9. Then β€” only when the cure applied β‰₯1 fix β€” dispatch terminal /plate (Flow step 11) so publication follows the replies; when no cure ran or no fix applied, there is nothing to plate. Exit with status: ok / next: done β€” see ## Auto mode Β§ "no findings meet the floor" for the auto path.

Slug next: values. Write next: cure when at least one finding meets the medium+ floor (medium-or-above, or a cheap contained-fix low). Write next: done when no severity-section finding exists or all meeting items are empty-selection after floor resolution.

On a non-empty cure selection (auto-selected by default or chosen at the gate), immediately dispatch /cure <slug> [--safe] [--open-pr] [--hard] with locked context:

handoff_context:
  source_skill: /affinage
  source_report: .cheese/affinage/pr-<n>.md
  selection: "<verb or explicit ids>"
  resolved_ids: [<expanded ids>]

/cure re-confirms cited ids and goes straight to apply. Because the handoff carries source_skill: /affinage, /cure applies its fixes and runs its /age --scope loop but suppresses its own terminal /plate and returns β€” affinage owns publication. Propagate --safe, --open-pr, and --hard to /cure when in scope. On return, /affinage posts every reply (Flow steps 9–10) β€” its terminal final writes β€” and only then dispatches terminal /plate [--open-pr] [--hard] [--safe] (Flow step 11), so publication lands after every reply. Skip the plate dispatch when the cure applied no fix.

Auto mode

When invoked with --auto --stake <floor> (or --plate, which enters this mode with --stake medium+ --open-pr):

  • Skip the selection gate.
  • If the PR has merge conflicts, resolve them via /melt first (see ## Merge-conflict resolution). If /melt cannot resolve, halt with status: halt: merge-conflicts-need-human before any /cure dispatch.
  • If standalone (and --no-age not passed), run the fresh /age pass so [from-age:…] findings join the floor-based auto-selection.
  • Auto-select every finding (comment-sourced, CI-sourced, OR fresh-/age-sourced) that meets the floor β€” severity at or above the floor, plus cheap contained-fix lows when the floor is medium+ (same floor semantics as /cure).
  • Dispatch /cure --auto --stake <floor>.
  • After /cure --auto and its downstream /age --scope --auto chain settle, post replies for the originally graded items only. Do NOT re-grade for findings discovered by /age --scope.
  • Reviewer-rejected items: post the pre-drafted push-back.
  • Needs-investigation items: post the explicit follow-up note naming the evidence that would settle the claim ("Needs <named test/prototype> to confirm β€” will follow up with the result."). Auto mode does not pause to run the spike; it posts the honest follow-up note, never a blind acknowledgement.
  • After the cure chain settles and all replies are posted (previous two bullets), /affinage dispatches terminal /plate --open-pr [--hard] to publish cure's fixes β€” the final writes precede publication. /cure suppresses its own terminal /plate for the /affinage chain (keyed on source_skill: /affinage). Skip the dispatch when no fix was applied.

The whole cure chain (cure β†’ /age --scope --auto β†’ up to the two-cure-pass cap) must run in the parent affinage context so the post-cure reply step still has the original graded findings (slug, ids, from-comment:<id> tags, drafted push-back text) in memory. Spawning the cure chain in a sub-agent breaks reply posting β€” do not.

If no findings meet the floor, skip the /cure dispatch, post replies for Reviewer-rejected + Needs-investigation items only, and exit with status: ok / next: done.

--hard mode

/affinage passes --hard to its own terminal /plate (dispatched after replies), which fires /hard-cheese after verifying the final artifact state. /cure does not dispatch plate in the /affinage chain, so the gate fires once β€” at affinage's publication boundary.

Rules

  • Grading is code-grounded, not reviewer-asserted. CHANGES_REQUESTED is metadata, not a severity bump.
  • Prefer fixing over pushing back. A valid, grounded nit whose fix is contained (fix-cost-now: contained β€” a few lines or a localized refactor) goes to /cure as a Low finding tagged [from-comment:<id>]; do not draft a push-back for it. Reserve ## Reviewer-rejected for claims that are wrong/ungrounded or whose fix is a lot of work (moderate/sprawling/structural). See ../age/references/voice.md.
  • Never auto-apply fixes from /affinage itself. Code fixes go through /cure; merge conflicts go through /melt.
  • Never post a GitHub reply without approval. Reply posting is gated by default (Β§Handoff reply-approval gate); only --auto posts autonomously. A Needs-investigation reply must name the follow-up test or exploration and offer to run it (/pasteurize or /briesearch) before posting β€” never a blind acknowledgement.
  • Fresh /age runs only on standalone invocations (no upstream handoff_context) and only when --no-age is absent. Chained runs never re-review the diff.
  • Merge conflicts are resolved through /melt, not by hand. gh pr checkout to materialise conflicts is allowed β€” it neither opens nor updates the PR. After resolution, affinage's terminal /plate owns the verified commit and push.
  • /affinage owns terminal publication. The chained /cure suppresses its own /plate (keyed on source_skill: /affinage); after a clean cure, /affinage posts every reply and then dispatches /plate to update the existing PR β€” or, with --open-pr, apply the explicit-choice and review-shape policy before publishing a new PR. Replies always precede plate: no final write lands after publication.
  • Every posted reply ends with the literal agent on behalf of <handle> attribution via skills/affinage/scripts/affinage.pyz post-reply, where <handle> is resolved from RESPOND_GH_HANDLE β†’ gh api user --jq .login β†’ git config user.name. Never call gh api directly to post.
  • Idempotent re-runs: skip threads where the latest comment is from the resolved handle. The REST /comments endpoint does not expose thread resolution, so honest idempotency relies on the latest-comment-from-self heuristic; switch to GraphQL reviewThreads if cross-session resolution-state visibility becomes required.
  • CI-sourced findings get no reply (no reviewer to notify).
  • Apply the shared voice kernel (../age/references/voice.md): name confidence as certain | speculating | don't know; agree when no findings warrant grading.

References

  • skills/age/SKILL.md β€” review pipeline, dimensions, sub-agent gate, report shape.
  • ../age/references/dimensions.md β€” per-dimension rubrics and severity computation.
  • skills/cure/SKILL.md β€” apply pipeline, --auto --stake floors, handoff context shape.
  • ../cure/references/selection.md β€” selection verbs and composition.
  • skills/melt/SKILL.md β€” merge-conflict resolution cascade (mergiraf β†’ rerere β†’ kdiff3).
  • ../cheese/references/handoff-gate.md β€” gate primitives.
  • python3 skills/affinage/scripts/affinage.pyz post-reply β€” reply posting with agent on behalf of <handle> attribution.
  • python3 skills/affinage/scripts/affinage.pyz pr-status β€” PR status fetcher.

Agent resolution

Resolve each dispatch through ../cheese/references/agent-resolution.md.

WorkPreferred typesPermissions/isolationMinimum powerEffortFallback
Triage review claims and CI evidencereviewerread-only, fresh-contextpowerfulhighcompatible reviewer, then general

The canonical affinage report carries the shared agent_resolution block.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.